1 module nulib.io.stream.file; 2 import nulib.io.stream; 3 import nulib.c.stdio; 4 import nulib.c.string; 5 import nulib.c.errno; 6 import nulib.math : min; 7 import nulib.string; 8 import numem; 9 10 /** 11 Flags 12 */ 13 enum FileModeFlags { 14 read = 1, 15 write = 2, 16 append = 3, 17 18 update = 0x10, 19 binary = 0x20, 20 } 21 22 /** 23 A file stream. 24 */ 25 class FileStream : Stream { 26 private: 27 @nogc @safe: 28 FILE* handle; 29 FileModeFlags flags; 30 size_t fLength; 31 32 void parseFileFlags(immutable(fchar_t)[] flags) nothrow { 33 this.flags = cast(FileModeFlags)0; 34 loop: foreach(i; 0..flags.length) { 35 switch(flags[i]) { 36 case 'r': 37 this.flags |= FileModeFlags.read; 38 break; 39 case 'w': 40 this.flags |= FileModeFlags.write; 41 break; 42 case 'a': 43 this.flags |= FileModeFlags.append; 44 break; 45 case 'b': 46 this.flags |= FileModeFlags.binary; 47 break; 48 case '+': 49 this.flags |= FileModeFlags.update; 50 break; 51 default: break loop; 52 } 53 } 54 } 55 56 bool canUpdate() nothrow { 57 return (flags & FileModeFlags.update) == FileModeFlags.update; 58 } 59 60 void updateLength() nothrow @trusted { 61 if (!handle) 62 return; 63 64 if (canSeek) { 65 this.seek(0, SeekOrigin.end); 66 this.fLength = this.tell(); 67 this.seek(0, SeekOrigin.start); 68 } 69 } 70 71 public: 72 73 /** 74 Whether the stream can be read from. 75 76 Returns: 77 $(D true) if you can read data from the stream, 78 $(D false) otherwise. 79 */ 80 override @property bool canRead() nothrow { 81 if (!handle) 82 return false; 83 84 if (this.canUpdate) 85 return true; 86 87 return (flags & FileModeFlags.read) != 0; 88 } 89 90 /** 91 Whether the stream can be written to. 92 93 Returns: 94 $(D true) if you can write data to the stream, 95 $(D false) otherwise. 96 */ 97 override @property bool canWrite() nothrow { 98 if (!handle) 99 return false; 100 101 if (this.canUpdate) 102 return true; 103 104 return (flags & FileModeFlags.write) != 0; 105 } 106 107 /** 108 Whether the stream can be seeked. 109 110 Returns: 111 $(D true) if you can seek through the stream, 112 $(D false) otherwise. 113 */ 114 override @property bool canSeek() nothrow { 115 if (!handle) 116 return false; 117 118 if (this.canUpdate) 119 return true; 120 121 return (flags & 3) > 0; 122 } 123 124 /** 125 Whether the stream can timeout during operations. 126 127 Returns: 128 $(D true) if the stream may time out during operations, 129 $(D false) otherwise. 130 */ 131 override @property bool canTimeout() nothrow { return false; } 132 133 /** 134 Whether the stream can be flushed to disk. 135 136 Returns: 137 $(D true) if the stream may be flushed, 138 $(D false) otherwise. 139 */ 140 override @property bool canFlush() nothrow { 141 if (!handle) 142 return false; 143 144 return (flags & 3) > 0; 145 } 146 147 /** 148 Length of the stream. 149 150 Returns: 151 Length of the stream, or $(D -1) if the length is unknown. 152 */ 153 override @property ptrdiff_t length() nothrow { 154 if (!handle) 155 return -1; 156 157 return fLength; 158 } 159 160 /** 161 Position in stream 162 163 Returns 164 Position in the stream, or $(D -1) if the position is unknown. 165 */ 166 override @property ptrdiff_t tell() nothrow @trusted { 167 if (!handle) 168 return -1; 169 170 return ftell(handle); 171 } 172 173 /** 174 Timeout in milliseconds before a read operation will fail. 175 176 Returns 177 A timeout in milliseconds, or $(D 0) if there's no timeout. 178 */ 179 override @property int readTimeout() nothrow { return 0; } 180 181 /** 182 Timeout in milliseconds before a write operation will fail. 183 184 Returns 185 A timeout in milliseconds, or $(D 0) if there's no timeout. 186 */ 187 override @property int writeTimeout() nothrow { return 0; } 188 189 // Destructor 190 ~this() nothrow @trusted { 191 this.close(); 192 } 193 194 /** 195 Constructs a file stream. 196 197 Params: 198 path = Path to the file to open 199 mode = Mode flags to open the file with. 200 */ 201 this(string path, string mode) @trusted { 202 auto rpath = path.toSupportedEncoding(); 203 auto rmode = mode.toSupportedEncoding(); 204 205 this.handle = _fopen(rpath.ptr, rmode.ptr); 206 if(!handle) { 207 this.flags = cast(FileModeFlags)0; 208 209 nstring errMsg = strerror(errno); 210 throw nogc_new!NuException(errMsg); 211 } 212 213 this.parseFileFlags(rmode); 214 this.updateLength(); 215 } 216 217 /** 218 Clears all buffers of the stream and causes data to be written to the underlying device. 219 220 Returns: 221 $(D true) if the flush operation succeeded, 222 $(D false) otherwise. 223 */ 224 override bool flush() nothrow @trusted { 225 if (!handle) 226 return false; 227 228 if (!canFlush) 229 return false; 230 231 return fflush(handle) == 0; 232 } 233 234 /** 235 Sets the reading position within the stream 236 237 Returns 238 The new position in the stream, or a $(D StreamError) if the 239 seek operation failed. 240 241 See_Also: 242 $(D StreamError) 243 */ 244 override 245 long seek(ptrdiff_t offset, SeekOrigin origin = SeekOrigin.start) nothrow @trusted { 246 if (!handle) 247 return StreamError.invalidState; 248 249 if (!canSeek()) 250 return StreamError.notSupported; 251 252 int status = fseek(handle, offset, origin); 253 return status == 0 ? tell() : StreamError.outOfRange; 254 } 255 256 /** 257 Closes the stream. 258 */ 259 override 260 void close() nothrow @trusted { 261 if (handle) { 262 cast(void)fclose(handle); 263 this.handle = null; 264 } 265 } 266 267 /** 268 Reads bytes from the specified stream in to the specified buffer 269 270 Notes: 271 The position and length to read is specified by the slice of `buffer`. 272 Use slicing operation to specify a range to read to. 273 274 Returns: 275 The amount of bytes read from the stream, 276 or a $(D StreamError). 277 278 See_Also: 279 $(D StreamError) 280 */ 281 override 282 ptrdiff_t read(ubyte[] buffer) nothrow @trusted { 283 if (!handle) 284 return StreamError.invalidState; 285 286 if (!this.canRead) 287 return StreamError.notSupported; 288 289 size_t rc = fread(cast(void*)buffer.ptr, 1, buffer.length, handle); 290 return rc; 291 } 292 293 /** 294 Writes bytes from the specified buffer in to the stream 295 296 Notes 297 The position and length to write is specified by the slice of `buffer`. 298 Use slicing operation to specify a range to write from. 299 300 Returns: 301 The amount of bytes read from the stream, 302 or a $(D StreamError). 303 304 See_Also: 305 $(D StreamError) 306 */ 307 override 308 ptrdiff_t write(ubyte[] buffer) nothrow @trusted { 309 if (!handle) 310 return StreamError.invalidState; 311 312 if (!this.canWrite) 313 return StreamError.notSupported; 314 315 size_t rc = fwrite(cast(void*)buffer.ptr, 1, buffer.length, handle); 316 return rc; 317 } 318 } 319 320 321 322 // 323 // IMPLEMENTATION DETAILS. 324 // 325 326 private: 327 328 StringImpl!(fchar_t) toSupportedEncoding(string str) @nogc { 329 static if (is (fchar_t == char)) { 330 return StringImpl!(fchar_t)(str); 331 } else { 332 import nulib.text.unicode : decode, encode; 333 import numem.core.memory : nu_dup, nu_resize; 334 335 auto tmp = str.nu_dup(); 336 auto rstr = encode!(StringImpl!(fchar_t))(decode(tmp), false); 337 tmp = tmp.nu_resize(0); 338 return rstr; 339 } 340 } 341 342 343 version (CRuntime_Microsoft) 344 alias _fopen = _wfopen; 345 else 346 alias _fopen = fopen; 347 348 version (CRuntime_Microsoft) 349 alias fchar_t = wchar; 350 else 351 alias fchar_t = char;