1 /** 2 In-memory streams 3 4 Copyright: © 2012-2016 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module vibe.stream.memory; 9 10 import vibe.core.stream; 11 import vibe.utils.array; 12 import vibe.internal.allocator; 13 14 import std.algorithm; 15 import std.array; 16 import std.exception; 17 import std.typecons; 18 19 MemoryOutputStream createMemoryOutputStream(IAllocator alloc = vibeThreadAllocator()) 20 @safe nothrow { 21 return new MemoryOutputStream(alloc, true); 22 } 23 24 /** Creates a new stream with the given data array as its contents. 25 26 Params: 27 data = The data array 28 writable = Flag that controls whether the data array may be changed 29 initial_size = The initial value that size returns - the file can grow up to data.length in size 30 */ 31 MemoryStream createMemoryStream(ubyte[] data, bool writable = true, size_t initial_size = size_t.max) 32 @safe nothrow { 33 return new MemoryStream(data, writable, initial_size, true); 34 } 35 36 37 /** OutputStream that collects the written data in memory and allows to query it 38 as a byte array. 39 */ 40 final class MemoryOutputStream : OutputStream { 41 @safe: 42 43 private { 44 AllocAppender!(ubyte[]) m_destination; 45 } 46 47 deprecated("Use createMemoryOutputStream isntead.") 48 this(IAllocator alloc = vibeThreadAllocator()) 49 { 50 this(alloc, true); 51 } 52 53 /// private 54 this(IAllocator alloc, bool dummy) 55 nothrow { 56 m_destination = AllocAppender!(ubyte[])(alloc); 57 } 58 59 /// An array with all data written to the stream so far. 60 @property ubyte[] data() nothrow { return m_destination.data(); } 61 62 /// Resets the stream to its initial state containing no data. 63 void reset(AppenderResetMode mode = AppenderResetMode.keepData) 64 @system { 65 m_destination.reset(mode); 66 } 67 68 /// Reserves space for data - useful for optimization. 69 void reserve(size_t nbytes) 70 { 71 m_destination.reserve(nbytes); 72 } 73 74 static if (is(typeof(.OutputStream.outputStreamVersion)) && .OutputStream.outputStreamVersion > 1) { 75 override size_t write(scope const(ubyte)[] bytes_, IOMode mode) { return doWrite(bytes_, mode); } 76 } else { 77 override size_t write(in ubyte[] bytes_, IOMode mode) { return doWrite(bytes_, mode); } 78 } 79 80 alias write = OutputStream.write; 81 82 private size_t doWrite(scope const(ubyte)[] bytes, IOMode) 83 { 84 () @trusted { m_destination.put(bytes); } (); 85 return bytes.length; 86 } 87 88 void flush() 89 nothrow { 90 } 91 92 void finalize() 93 nothrow { 94 } 95 } 96 97 mixin validateOutputStream!MemoryOutputStream; 98 99 100 /** 101 Provides a random access stream interface for accessing an array of bytes. 102 */ 103 final class MemoryStream : RandomAccessStream { 104 @safe: 105 106 private { 107 ubyte[] m_data; 108 size_t m_size; 109 bool m_writable; 110 size_t m_ptr = 0; 111 size_t m_peekWindow; 112 } 113 114 deprecated("Use createMemoryStream instead.") 115 this(ubyte[] data, bool writable = true, size_t initial_size = size_t.max) 116 { 117 this(data, writable, initial_size, true); 118 } 119 120 /// private 121 this(ubyte[] data, bool writable, size_t initial_size, bool dummy) 122 nothrow { 123 m_data = data; 124 m_size = min(initial_size, data.length); 125 m_writable = writable; 126 m_peekWindow = m_data.length; 127 } 128 129 /** Controls the maximum size of the array returned by peek(). 130 131 This property is mainly useful for debugging purposes. 132 */ 133 @property void peekWindow(size_t size) { m_peekWindow = size; } 134 135 @property bool empty() { return leastSize() == 0; } 136 @property ulong leastSize() { return m_size - m_ptr; } 137 @property bool dataAvailableForRead() { return leastSize() > 0; } 138 @property ulong size() const nothrow { return m_size; } 139 @property size_t capacity() const nothrow { return m_data.length; } 140 @property bool readable() const nothrow { return true; } 141 @property bool writable() const nothrow { return m_writable; } 142 143 void truncate(ulong size) 144 { 145 enforce(size < m_data.length, "Size limit of memory stream reached."); 146 m_size = cast(size_t)size; 147 } 148 149 void seek(ulong offset) { assert(offset <= m_data.length); m_ptr = cast(size_t)offset; } 150 ulong tell() nothrow { return m_ptr; } 151 const(ubyte)[] peek() { return m_data[m_ptr .. min(m_size, m_ptr+m_peekWindow)]; } 152 153 size_t read(scope ubyte[] dst, IOMode mode) 154 { 155 enforce(mode != IOMode.all || dst.length <= leastSize, "Reading past end of memory stream."); 156 auto len = min(leastSize, dst.length); 157 dst[0 .. len] = m_data[m_ptr .. m_ptr+len]; 158 m_ptr += len; 159 return len; 160 } 161 162 alias read = RandomAccessStream.read; 163 164 static if (is(typeof(.OutputStream.outputStreamVersion)) && .OutputStream.outputStreamVersion > 1) { 165 override size_t write(scope const(ubyte)[] bytes_, IOMode mode) { return doWrite(bytes_, mode); } 166 } else { 167 override size_t write(in ubyte[] bytes_, IOMode mode) { return doWrite(bytes_, mode); } 168 } 169 170 alias write = RandomAccessStream.write; 171 172 private size_t doWrite(scope const(ubyte)[] bytes, IOMode) 173 { 174 assert(writable); 175 enforce(bytes.length <= m_data.length - m_ptr, "Size limit of memory stream reached."); 176 m_data[m_ptr .. m_ptr+bytes.length] = bytes[]; 177 m_ptr += bytes.length; 178 m_size = max(m_size, m_ptr); 179 return bytes.length; 180 } 181 182 void flush() {} 183 void finalize() {} 184 } 185 186 mixin validateRandomAccessStream!MemoryStream;