1 module libd.io.filesystem; 2 3 import libd.io.stream, libd.util.errorhandling, libd.datastructures.smartptr, libd.data.conv, libd.data.format; 4 5 @nogc nothrow: 6 7 enum FileOpenMode 8 { 9 FAILSAFE, 10 createIfNotExists, 11 createAlways, 12 openExisting, 13 openAlways, 14 truncateExisting 15 } 16 17 enum FileUsage 18 { 19 FAILSAFE, 20 read = 1 << 2, 21 write = 1 << 1, 22 readWrite = read | write 23 } 24 25 struct FileStream 26 { 27 static assert(isStream!FileStream); 28 29 @disable this(this){} 30 31 private 32 { 33 FileT _file; 34 FileUsage _usage; 35 } 36 37 @nogc nothrow: 38 39 @("not for public use") 40 this(FileT file, FileUsage usage) 41 { 42 this._file = file; 43 this._usage = usage; 44 } 45 46 ~this() 47 { 48 if(this._file) 49 { 50 version(Windows) 51 { 52 CloseHandle(this._file); 53 this._file = null; 54 } 55 else version(Posix) 56 { 57 import runtime.system.posix; 58 close(this._file); 59 this._file = FileT.min; 60 } 61 } 62 } 63 64 SimpleResult!size_t write(const void[] data) 65 { 66 if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open.")); 67 return fileWriteImpl(this._file, data); 68 } 69 70 SimpleResult!size_t read(scope void[] data) 71 { 72 if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open.")); 73 return fileReadImpl(this._file, data); 74 } 75 76 bool hasData() 77 { 78 return false; // TODO 79 } 80 81 bool isOpen() 82 { 83 version(Windows) return this._file !is null; 84 else return this._file > -1; 85 } 86 87 SimpleResult!size_t getPosition() 88 { 89 if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open.")); 90 return fileGetPositionImpl(this._file); 91 } 92 93 SimpleResult!void setPosition(size_t position) 94 { 95 if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open.")); 96 return fileSetPositionImpl(this._file, position); 97 } 98 99 SimpleResult!size_t getSize() 100 { 101 if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open.")); 102 return fileGetSizeImpl(this._file); 103 } 104 105 bool canPosition() 106 { 107 return true; 108 } 109 110 bool canWrite() 111 { 112 return (this._usage & FileUsage.write) > 0; 113 } 114 115 bool canRead() 116 { 117 return (this._usage & FileUsage.read) > 0; 118 } 119 } 120 121 SimpleResult!(Shared!FileStream) fsOpen(const char[] file, FileOpenMode mode, FileUsage usage) 122 { 123 auto result = fileOpenImpl(file, mode, usage); 124 if(!result.isValid) 125 return typeof(return)(result.error()); 126 return typeof(return)(makeShared(FileStream(result.value, usage))); 127 } 128 129 bool fsExists(const char[] path) 130 { 131 return fsExistsImpl(path); 132 } 133 134 SimpleResult!void fsDelete(const char[] path) 135 { 136 return fsDeleteImpl(path); 137 } 138 139 SimpleResult!(Shared!(Array!ubyte)) fsRead(const char[] file) 140 { 141 auto result = fsOpen(file, FileOpenMode.openExisting, FileUsage.read); 142 auto array = Array!ubyte.init; 143 144 if(!result.isValid) 145 return typeof(return)(result.error); 146 147 auto ptr = result.value; 148 auto stream = ptr.ptrUnsafe; 149 150 array.length = stream.getSize().value; 151 stream.read(array[0..$]); 152 153 return typeof(return)(makeShared(array)); 154 } 155 156 version(Windows) 157 { 158 import runtime.system.windows; 159 alias FileT = HANDLE; 160 161 SimpleResult!FileT fileOpenImpl(const char[] file, FileOpenMode mode, FileUsage usage) 162 { 163 DWORD accessRights; 164 if(usage & FileUsage.read) 165 accessRights |= GENERIC_READ; 166 if(usage & FileUsage.write) 167 accessRights |= GENERIC_WRITE; 168 169 String zeroTerm = file; 170 auto handle = CreateFileA( 171 zeroTerm[0..$].ptr, 172 accessRights, 173 0, 174 null, 175 cast(DWORD)mode, // values match up with win api 176 FILE_ATTRIBUTE_NORMAL, 177 null 178 ); 179 180 if(handle == INVALID_HANDLE_VALUE) 181 { 182 auto error = GetLastError(); 183 bcstring message; 184 185 switch(error) 186 { 187 case ERROR_FILE_EXISTS: message = "Could not open file: file already exists"; break; 188 case ERROR_FILE_NOT_FOUND: message = "Could not open file: file does not exist"; break; 189 default: message = "Could not open file: unknown"; break; 190 } 191 192 return typeof(return)(raise( 193 message, 194 error 195 )); 196 } 197 198 return typeof(return)(handle); 199 } 200 201 SimpleResult!size_t fileWriteImpl(FileT file, const scope void[] data) 202 { 203 uint amountRead; 204 const result = WriteFile( 205 file, 206 data.ptr, 207 cast(uint)data.length, 208 &amountRead, 209 null 210 ); 211 212 if(!result) 213 return typeof(return)(raise("Error? TODO", GetLastError())); 214 215 return typeof(return)(cast(size_t)amountRead); 216 } 217 218 SimpleResult!size_t fileGetSizeImpl(FileT file) 219 { 220 long size; 221 const result = GetFileSizeEx(file, &size); 222 223 if(!result || size < 0) 224 return typeof(return)(raise("TODO", GetLastError())); 225 226 return typeof(return)(cast(size_t)size); 227 } 228 229 SimpleResult!void fileSetPositionImpl(FileT file, size_t position) 230 { 231 assert(position <= long.max); 232 const result = SetFilePointerEx( 233 file, 234 cast(long)position, 235 null, 236 0 // FILE_BEGIN 237 ); 238 239 if(!result) 240 return typeof(return)(raise("TODO", GetLastError())); 241 242 return typeof(return)(); 243 } 244 245 SimpleResult!size_t fileGetPositionImpl(FileT file) 246 { 247 long position; 248 const result = SetFilePointerEx( 249 file, 250 0, 251 &position, 252 1 // FILE_CURRENT 253 ); 254 255 if(!result || position < 0) 256 return typeof(return)(raise("TODO", GetLastError())); 257 258 return typeof(return)(cast(size_t)position); 259 } 260 261 SimpleResult!size_t fileReadImpl(FileT file, scope void[] data) 262 { 263 assert(data.length <= uint.max); 264 265 uint read; 266 const result = ReadFile( 267 file, 268 data.ptr, 269 cast(uint)data.length, 270 &read, 271 null 272 ); 273 274 if(!result) 275 return typeof(return)(raise("TODO", GetLastError())); 276 277 return typeof(return)(cast(size_t)read); 278 } 279 280 bool fsExistsImpl(const char[] path) 281 { 282 String zeroTerm = path; 283 return cast(bool)PathFileExistsA(zeroTerm[0..$].ptr); 284 } 285 286 SimpleResult!void fsDeleteImpl(const char[] path) 287 { 288 String zeroTerm = path; 289 const result = DeleteFileA(zeroTerm[0..$].ptr); 290 291 if(!result) 292 return typeof(return)(raise(GetLastErrorAsString())); 293 294 return typeof(return)(); 295 } 296 } 297 else version(Posix) 298 { 299 import runtime.system.posix; 300 alias FileT = int; 301 302 SimpleResult!FileT fileOpenImpl(const char[] file, FileOpenMode mode, FileUsage usage) 303 { 304 int flags; 305 if(usage == FileUsage.readWrite) 306 flags |= O_RDWR; 307 else if (usage == FileUsage.read) 308 flags |= O_RDONLY; 309 else if(usage == FileUsage.write) 310 flags |= O_WRONLY; 311 else 312 assert(false, "No file usage was specified"); 313 314 final switch(mode) with(FileOpenMode) 315 { 316 case createIfNotExists: flags |= O_CREAT; break; 317 case createAlways: flags |= O_CREAT | O_TRUNC; break; 318 case openExisting: break; 319 case openAlways: flags |= O_CREAT; break; 320 case truncateExisting: flags |= O_TRUNC; break; 321 case FAILSAFE: assert(false, "FAILSAFE was used."); 322 } 323 324 int fd = open(String(file).ptrUnsafe, flags, 0x1B6); 325 if(fd < 0) 326 return typeof(return)(raise("Could not open file at {0} (errno {1})".format(file, g_errno).value, g_errno)); 327 328 return typeof(return)(fd); 329 } 330 331 SimpleResult!size_t fileWriteImpl(FileT file, const scope void[] data) 332 { 333 const wrote = write(file, data.ptr, data.length); 334 if(wrote < 0) 335 return typeof(return)(raise("Could not write to file (errno {0})".format(g_errno).value, g_errno)); 336 337 return typeof(return)(cast(size_t)wrote); 338 } 339 340 SimpleResult!size_t fileGetSizeImpl(FileT file) 341 { 342 stat stats; 343 const result = fstat(file, &stats); 344 if(result < 0) 345 return typeof(return)(raise("fstat failed (errno {0})".format(g_errno).value, g_errno)); 346 347 return typeof(return)(cast(size_t)stats.st_size); 348 } 349 350 SimpleResult!void fileSetPositionImpl(FileT file, size_t position) 351 { 352 assert(position <= long.max); 353 const result = lseek(file, position, SEEK_SET); 354 if(result < 0) 355 return typeof(return)(raise("lseek failed (errno {0})".format(g_errno).value, g_errno)); 356 357 return typeof(return)(); 358 } 359 360 SimpleResult!size_t fileGetPositionImpl(FileT file) 361 { 362 const result = lseek(file, 0, SEEK_CUR); 363 if(result < 0) 364 return typeof(return)(raise("lseek failed (errno {0})".format(g_errno).value, g_errno)); 365 366 return typeof(return)(cast(size_t)result); 367 } 368 369 SimpleResult!size_t fileReadImpl(FileT file, scope void[] data) 370 { 371 const result = read(file, data.ptr, data.length); 372 if(!result) 373 return typeof(return)(raise("read failed (errno {0})".format(g_errno).value, g_errno)); 374 375 return typeof(return)(cast(size_t)result); 376 } 377 378 bool fsExistsImpl(const char[] path) 379 { 380 String zeroTerm = path; 381 return cast(bool)access(zeroTerm[0..$].ptr, F_OK); 382 } 383 384 SimpleResult!void fsDeleteImpl(const char[] path) 385 { 386 String zeroTerm = path; 387 auto result = unlink(zeroTerm.ptrUnsafe); 388 if(result == EISDIR) 389 result = rmdir(zeroTerm.ptrUnsafe); 390 if(result < 0) 391 return typeof(return)(raise("unlink/rmdir failed (errno {0})".format(g_errno).value, g_errno)); 392 return typeof(return)(); 393 } 394 }