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 }