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;