1 module jaster.stream.core; 2 3 private 4 { 5 import std.experimental.allocator : makeArray, dispose; 6 import std.exception : basicExceptionCtors, enforce; 7 import jaster.stream.util; 8 } 9 10 public import core.time : Duration; 11 12 /++ 13 + The base class for all streams. 14 + 15 + Streams are a common interface for reading/writing a 'stream' of bytes. 16 + 17 + Other classes can be built on top of streams, such as the `BinaryIO` class to provide more 18 + specific functionality to streams. 19 + ++/ 20 abstract class Stream 21 { 22 /// A flag enum specifying what the stream is capable of doing. 23 enum Can 24 { 25 None = 0, 26 27 /// The stream supports writing. 28 Write = 1 << 0, 29 30 /// The stream supports reading. 31 Read = 1 << 1, 32 33 /// The stream supports seeking. 34 Seek = 1 << 2, 35 36 /// The stream supports being able to set timeouts. 37 Timeout = 1 << 3 38 } 39 40 /// Used with `seek`. 41 enum SeekFrom 42 { 43 /// Seek from the start. 44 Start, 45 46 /// Seek from the current position. 47 Current, 48 49 /++ 50 + Seek from the end. 51 + 52 + Implementation_Note: 53 + Some might think that when using `End`, that the amount to seek by means 'How many spaces to go backwards'. 54 + 55 + This however, is not how I feel it should work. So if you want to go backwards from the end, just use a negative offset, 56 + like with the other options. 57 + ++/ 58 End 59 } 60 61 private 62 { 63 Can _can; 64 } 65 66 /// 67 @safe @nogc 68 this(Can can) nothrow pure 69 { 70 this._can = can; 71 } 72 73 // ###################### 74 // # ABSTRACT FUNCTIONS # 75 // ###################### 76 public abstract 77 { 78 /++ 79 + Writes bytes into the stream. 80 + 81 + Params: 82 + data = The data to write. 83 + 84 + Throws: 85 + `StreamCannotWriteException` if this stream does not support writing. 86 + `StreamDisposedException` if this stream has been disposed of. 87 + 88 + Returns: 89 + A slice of `data`, specifying which portion of it has been written to the stream. 90 + ++/ 91 const(ubyte[]) write(scope const ubyte[] data); 92 93 /++ 94 + Reads bytes from the stream into a buffer. 95 + 96 + Params: 97 + buffer = The buffer to read into, the length of the buffer determines how many bytes to read. 98 + 99 + Throws: 100 + `StreamCannotReadException` if this stream does not support reading. 101 + `StreamDisposedException` if this stream has been disposed of. 102 + 103 + Returns: 104 + A slice of `buffer`, specifying which portion of it has been filled with data. 105 + The length of this slice is also how to check how many bytes were read in. 106 + ++/ 107 ubyte[] readToBuffer(scope ref ubyte[] buffer); 108 109 /++ 110 + Disposes of the stream's resources. 111 + 112 + If the stream has already been disposed of, this function does nothing. 113 + 114 + Once disposed of, the stream should cease to function, and should throw `StreamDisposedException`s where appropriate. 115 + ++/ 116 void dispose(); 117 118 /++ 119 + Flushes the stream's data. 120 + 121 + Some streams don't support flushing, so this function will do nothing in those cases. 122 + ++/ 123 void flush(); 124 125 /++ 126 + Seeks somewhere into the stream. 127 + 128 + Throws: 129 + `StreamCannotSeekException` if this stream does not support seeking. 130 + `StreamDisposedException` if this stream has been disposed of. 131 + 132 + Params: 133 + from = The 'origin' from where to seek from. 134 + amount = The amount to seek by. Note that negative numbers can be used to go backwards. 135 + ++/ 136 void seek(SeekFrom from, ptrdiff_t amount); 137 138 /// Returns: If the stream has been disposed of or not. 139 @property 140 bool isDisposed() const; 141 142 /++ 143 + Sets the write timeout for the stream. 144 + 145 + Throws: 146 + `StreamCannotTimeoutException` if the stream does not support timeouts. 147 + `StreamDisposedException` if the stream has been disposed of. 148 + 149 + Params: 150 + dur = The duration to set the timeout for. 151 + ++/ 152 @property 153 void writeTimeout(Duration dur); 154 155 /++ 156 + Gets the write timeout for the stream. 157 + 158 + Throws: 159 + `StreamCannotTimeoutException` if the stream does not support timeouts. 160 + `StreamDisposedException` if the stream has been disposed of. 161 + 162 + Returns: 163 + The duration to set the timeout for. 164 + ++/ 165 @property 166 Duration writeTimeout(); 167 168 /++ 169 + Sets the read timeout for the stream. 170 + 171 + Throws: 172 + `StreamCannotTimeoutException` if the stream does not support timeouts. 173 + `StreamDisposedException` if the stream has been disposed of. 174 + 175 + Params: 176 + dur = The duration to set the timeout for. 177 + ++/ 178 @property 179 void readTimeout(Duration dur); 180 181 /++ 182 + Gets the read timeout for the stream. 183 + 184 + Throws: 185 + `StreamCannotTimeoutException` if the stream does not support timeouts. 186 + `StreamDisposedException` if the stream has been disposed of. 187 + 188 + Returns: 189 + The duration to set the timeout for. 190 + ++/ 191 @property 192 Duration readTimeout(); 193 194 /++ 195 + Sets the length of the stream's data. 196 + 197 + Throws: 198 + `StreamException` if the stream does not support this function. 199 + `StreamDisposedException` if the stream has been disposed of. 200 + 201 + Params: 202 + size = The size to set the stream's length to. 203 + ++/ 204 @property 205 void length(size_t size); 206 207 /++ 208 + Gets the length of the stream's data. 209 + 210 + Throws: 211 + `StreamException` if the stream does not support this function. 212 + `StreamDisposedException` if the stream has been disposed of. 213 + 214 + Returns: 215 + The size to set the stream's length. 216 + ++/ 217 @property 218 size_t length(); 219 220 /++ 221 + Sets the position of the stream's 'cursor'. 222 + 223 + Throws: 224 + See `seek`. 225 + 226 + Params: 227 + pos = The position to use. 228 + ++/ 229 @property 230 void position(size_t pos); 231 232 /++ 233 + Gets the position of the stream's 'cursor'. 234 + 235 + Throws: 236 + See `seek`. 237 + 238 + Returns: 239 + The position to use. 240 + ++/ 241 @property 242 size_t position(); 243 } 244 245 // ##################### 246 // # VIRTUAL FUNCTIONS # 247 // ##################### 248 public 249 { 250 /++ 251 + Creates a GC-allocated buffer with a specified size, and attempts to read in a certain amount of bytes. 252 + 253 + Params: 254 + amount = The amount of bytes to read in. 255 + 256 + Returns: 257 + The portion of the buffer that was filled by `readToBuffer`. 258 + ++/ 259 ubyte[] read(size_t amount) 260 { 261 auto array = new ubyte[amount]; 262 return this.readToBuffer(array); 263 } 264 265 /++ 266 + Copies data $(B from the current position in this stream) to the end of this stream, into 267 + the given stream. $(B The given stream is written to using it's current position as well). 268 + 269 + Params: 270 + to = The `Stream` to copy the data to. 271 + buffer = The buffer used to temporarily store the data as it's being copied over. 272 + This buffer also determines how many bytes are copied at a time. 273 + ++/ 274 void copyTo(Stream to, scope ubyte[] buffer) 275 { 276 // Slight optimisation. Only useful for when the buffer is a small size relative to how much is being copied. 277 // It also depends on the stream itself whether it has any real impact or not. 278 if(this.canSeek && to.canSeek) 279 { 280 auto end = to.position + (this.length - this.position); 281 if(end > to.length) 282 to.length = end; 283 } 284 285 while(true) 286 { 287 auto slice = this.readToBuffer(buffer); 288 if(slice.length == 0) 289 break; 290 291 to.write(slice); 292 } 293 } 294 } 295 296 // ############################## 297 // # FINAL FUNCTIONS/PROPERTIES # 298 // ############################## 299 public final 300 { 301 /// Shortcut for `seek(SeekFrom.Start)` 302 void seekStart(ptrdiff_t amount) 303 { 304 this.seek(SeekFrom.Start, amount); 305 } 306 307 /// Shortcut for `seek(SeekFrom.Current)` 308 void seekCurr(ptrdiff_t amount) 309 { 310 this.seek(SeekFrom.Current, amount); 311 } 312 313 /// Shortcut for `seek(SeekFrom.End)` 314 void seekEnd(ptrdiff_t amount) 315 { 316 this.seek(SeekFrom.End, amount); 317 } 318 319 /++ 320 + A helper function for `copyTo`, that uses a buffer placed on the stack. 321 + 322 + Params: 323 + BufferSize = The size to give the buffer. 324 + to = The stream to copy the data to. 325 + ++/ 326 void copyToStack(size_t BufferSize)(Stream to) 327 if(BufferSize > 0) 328 { 329 ubyte[BufferSize] buffer; 330 this.copyTo(to, buffer[]); 331 } 332 333 /++ 334 + A helper function for `copyTo`, that uses a buffer created from an `std.experimental.allocator` Allocator. 335 + The buffer is of course disposed of afterwards. 336 + 337 + Params: 338 + Alloc = The allocator to use. 339 + to = The stream to copy the data to. 340 + bufferSize = The size to give the buffer. 341 + alloc = [Only for non-static allocator] the allocator instance to use. 342 + ++/ 343 void copyToAlloc(Alloc)(Stream to, size_t bufferSize) 344 if(AllocHelper!Alloc.IsStatic) 345 { 346 assert(bufferSize > 0, "Buffer size cannot be 0"); 347 ubyte[] buffer = Alloc.instance.makeArray!ubyte(bufferSize); 348 scope(exit) Alloc.instance.dispose(buffer); 349 350 this.copyTo(to, buffer); 351 } 352 353 /// ditto. 354 void copyToAlloc(Alloc)(Stream to, size_t bufferSize, auto ref Alloc alloc) 355 if(!AllocHelper!Alloc.IsStatic) 356 { 357 assert(bufferSize > 0, "Buffer size cannot be 0"); 358 ubyte[] buffer = alloc.makeArray!ubyte(bufferSize); 359 scope(exit) alloc.dispose(buffer); 360 361 this.copyTo(to, buffer); 362 } 363 364 /++ 365 + A helper function for `copyTo`, that uses a buffer allocated by the GC. 366 + 367 + Params: 368 + to = The stream to copy the data to. 369 + bufferSize = The size to give the buffer. 370 + ++/ 371 void copyToGC(Stream to, size_t bufferSize) 372 { 373 assert(bufferSize > 0, "Buffer size cannot be 0"); 374 ubyte[] buffer = new ubyte[bufferSize]; 375 376 this.copyTo(to, buffer); 377 } 378 379 /// Returns: What the stream is capable of. 380 @property @safe @nogc 381 Can can() nothrow const 382 { 383 return this._can; 384 } 385 386 /// Returns: Whether the stream can write. 387 @property @safe @nogc 388 bool canWrite() nothrow const 389 { 390 return (this.can & Can.Write) > 0; 391 } 392 393 /// Returns: Whether the stream can Read. 394 @property @safe @nogc 395 bool canRead() nothrow const 396 { 397 return (this.can & Can.Read) > 0; 398 } 399 400 /// Returns: Whether the stream can Seek. 401 @property @safe @nogc 402 bool canSeek() nothrow const 403 { 404 return (this.can & Can.Seek) > 0; 405 } 406 407 /// Returns: Whether the stream can Timeout. 408 @property @safe @nogc 409 bool canTimeout() nothrow const 410 { 411 return (this.can & Can.Timeout) > 0; 412 } 413 } 414 } 415 416 /++ 417 + Throws an exception if the given stream does not support writing/reading/seeking/timeouts. 418 + ++/ 419 void enforceCanWrite(const Stream stream) 420 { 421 enforce!StreamCannotWriteException(stream.canWrite); 422 } 423 424 /// ditto 425 void enforceCanRead(const Stream stream) 426 { 427 enforce!StreamCannotReadException(stream.canRead); 428 } 429 430 /// ditto 431 void enforceCanSeek(const Stream stream) 432 { 433 enforce!StreamCannotSeekException(stream.canSeek); 434 } 435 436 /// ditto 437 void enforceCanTimeout(const Stream stream) 438 { 439 enforce!StreamCannotTimeoutException(stream.canTimeout); 440 } 441 442 /// ditto 443 void enforceNotDisposed(const Stream stream) 444 { 445 enforce!StreamDisposedException(!stream.isDisposed); 446 } 447 448 /// Base stream exception class. 449 class StreamException : Exception 450 { 451 mixin basicExceptionCtors; 452 } 453 454 /// Thrown if the stream doesn't support writing. 455 class StreamCannotWriteException : StreamException 456 { 457 mixin basicExceptionCtors; 458 459 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) 460 { 461 super("This stream does not support writing/is read only.", file, line, next); 462 } 463 } 464 465 /// ditto 466 class StreamCannotReadException : StreamException 467 { 468 mixin basicExceptionCtors; 469 470 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) 471 { 472 super("This stream does not support reading.", file, line, next); 473 } 474 } 475 476 /// ditto 477 class StreamCannotSeekException : StreamException 478 { 479 mixin basicExceptionCtors; 480 481 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) 482 { 483 super("This stream does not support seeking.", file, line, next); 484 } 485 } 486 487 /// ditto 488 class StreamCannotTimeoutException : StreamException 489 { 490 mixin basicExceptionCtors; 491 492 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) 493 { 494 super("This stream does not support timing out.", file, line, next); 495 } 496 } 497 498 /// ditto 499 class StreamDisposedException : StreamException 500 { 501 mixin basicExceptionCtors; 502 503 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) 504 { 505 super("This stream has been disposed.", file, line, next); 506 } 507 }