1 module bio.core.utils.stream;
2 
3 public import undead.stream;
4 import core.stdc.stdio;
5 import core.stdc.errno;
6 import core.stdc.string;
7 import core.sys.posix.sys.select;
8 import std.conv;
9 import std.string : toStringz;
10 
11 version(Posix){
12     private import core.sys.posix.unistd;
13 }
14 
15 version(Windows) {
16     private import std.file;
17     private import std.utf;
18     private import core.stdc.windows.windows;
19     extern (Windows) {
20         DWORD GetFileType(HANDLE hFile);
21     }
22 }
23 
24 FileMode toFileMode(string mode) {
25     FileMode result = FileMode.In;
26     switch (mode) {
27         case "r", "rb":
28             result = FileMode.In; // 1000
29             break;
30         case "r+", "r+b", "rb+":
31             result = FileMode.In | FileMode.Out; // 1100
32         case "w", "wb":
33             result = FileMode.OutNew; // 0110
34             break;
35         case "w+", "w+b", "wb+":
36             result = FileMode.In | FileMode.OutNew; // 1110
37             break;
38         case "a", "ab":
39             result = FileMode.Append; // 0001
40             break;
41         case "a+", "a+b", "ab+":
42             result = FileMode.In | FileMode.Append; // 1001
43             break;
44         default:
45             break;
46     }
47 
48     return result;
49 }
50 
51 final class File: undead.stream.File {
52     this(string filename, string mode="rb") {
53         version (Posix) {
54             // Issue 8528 workaround
55             auto file = fopen(toStringz(filename), toStringz(mode));
56             if (file == null) {
57                 throw new OpenException(cast(string) ("Cannot open or create file '"
58                                                       ~ filename ~ "' : ") ~
59                                         to!string(strerror(errno)));
60             }
61             super(core.stdc.stdio.fileno(file), toFileMode(mode));
62         }
63         version (Windows) {
64             int access, share, createMode;
65             auto mode_flags = toFileMode(mode);
66 
67             share |= FILE_SHARE_READ | FILE_SHARE_WRITE;
68             if (mode_flags & FileMode.In) {
69                 access |= GENERIC_READ;
70                 createMode = OPEN_EXISTING;
71             }
72             if (mode_flags & FileMode.Out) {
73                 access |= GENERIC_WRITE;
74                 createMode = OPEN_ALWAYS;
75             }
76             if ((mode_flags & FileMode.OutNew) == FileMode.OutNew) {
77                 createMode = CREATE_ALWAYS;
78             }
79 
80             auto handle = CreateFileW(std.utf.toUTF16z(filename), access, share,
81                                       null, createMode, 0, null);
82             isopen = handle != INVALID_HANDLE_VALUE;
83             if (!isopen) {
84                 throw new OpenException(cast(string) ("Cannot open or create file '"
85                                                       ~ filename ~ "'"));
86             }
87             super(handle, toFileMode(mode));
88         }
89     }
90 
91     override ulong seek(long offset, SeekPos rel) {
92         assertSeekable();
93         auto hFile = handle();
94         version (Windows) {
95           int hi = cast(int)(offset>>32);
96           uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel);
97           if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0))
98             throw new SeekException("unable to move file pointer");
99           ulong result = (cast(ulong)hi << 32) + low;
100         } else version (Posix) {
101           // Phobos casts offset to int, leading to throwing an exception
102           // on large files
103           auto result = lseek(hFile, cast(off_t)offset, rel);
104           if (result == cast(typeof(result))-1)
105             throw new SeekException("unable to move file pointer");
106         }
107         readEOF = false;
108         return cast(ulong)result;
109       }
110 
111     override size_t readBlock(void* buffer, size_t size) {
112         assertReadable();
113         auto hFile = handle();
114         version (Windows) {
115             auto dwSize = to!DWORD(size);
116             ReadFile(hFile, buffer, dwSize, &dwSize, null);
117             size = dwSize;
118         } else version (Posix) {
119             // http://developerweb.net/viewtopic.php?id=4267
120             fd_set rset;
121             timeval timeout;
122             immutable MAX_IDLE_SECS = 1;
123             while (true) {
124                 auto ret = core.sys.posix.unistd.read(hFile, buffer, size);
125                 if (ret == -1) {
126                     if (errno == EINTR)
127                         continue;
128 
129                     if (errno == EAGAIN || errno == EWOULDBLOCK) {
130                         FD_ZERO(&rset);
131                         FD_SET(hFile, &rset);
132                         timeout.tv_sec = MAX_IDLE_SECS;
133                         timeout.tv_usec = 0;
134                         ret = select(hFile + 1, &rset, null, null, &timeout);
135                         if (ret <= 0) {
136                             size = 0;
137                             throw new ReadException("read timeout");
138                         }
139                     } else {
140                         throw new ReadException(to!string(strerror(errno)));
141                     }
142                 } else {
143                     size = ret;
144                     break;
145                 }
146             }
147         }
148         readEOF = (size == 0);
149         return size;
150     }
151 
152     override size_t writeBlock(const void* buffer, size_t size) {
153       assertWriteable();
154       auto hFile = handle();
155       version (Windows) {
156         auto dwSize = to!DWORD(size);
157         WriteFile(hFile, buffer, dwSize, &dwSize, null);
158         size = dwSize;
159       } else version (Posix) {
160         size = core.sys.posix.unistd.write(hFile, buffer, size);
161         if (size == -1)
162           throw new WriteException(to!string(strerror(errno)));
163       }
164       return size;
165     }
166 }