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 	size_t write(in ubyte[] bytes, IOMode)
75 	{
76 		() @trusted { m_destination.put(bytes); } ();
77 		return bytes.length;
78 	}
79 
80 	alias write = OutputStream.write;
81 
82 	void flush()
83 	nothrow {
84 	}
85 
86 	void finalize()
87 	nothrow {
88 	}
89 }
90 
91 mixin validateOutputStream!MemoryOutputStream;
92 
93 
94 /**
95 	Provides a random access stream interface for accessing an array of bytes.
96 */
97 final class MemoryStream : RandomAccessStream {
98 @safe:
99 
100 	private {
101 		ubyte[] m_data;
102 		size_t m_size;
103 		bool m_writable;
104 		size_t m_ptr = 0;
105 		size_t m_peekWindow;
106 	}
107 
108 	deprecated("Use createMemoryStream instead.")
109 	this(ubyte[] data, bool writable = true, size_t initial_size = size_t.max)
110 	{
111 		this(data, writable, initial_size, true);
112 	}
113 
114 	/// private
115 	this(ubyte[] data, bool writable, size_t initial_size, bool dummy)
116 	nothrow {
117 		m_data = data;
118 		m_size = min(initial_size, data.length);
119 		m_writable = writable;
120 		m_peekWindow = m_data.length;
121 	}
122 
123 	/** Controls the maximum size of the array returned by peek().
124 
125 		This property is mainly useful for debugging purposes.
126 	*/
127 	@property void peekWindow(size_t size) { m_peekWindow = size; }
128 
129 	@property bool empty() { return leastSize() == 0; }
130 	@property ulong leastSize() { return m_size - m_ptr; }
131 	@property bool dataAvailableForRead() { return leastSize() > 0; }
132 	@property ulong size() const nothrow { return m_size; }
133 	@property size_t capacity() const nothrow { return m_data.length; }
134 	@property bool readable() const nothrow { return true; }
135 	@property bool writable() const nothrow { return m_writable; }
136 
137 	void truncate(ulong size)
138 	{
139 		enforce(size < m_data.length, "Size limit of memory stream reached.");
140 		m_size = cast(size_t)size;
141 	}
142 
143 	void seek(ulong offset) { assert(offset <= m_data.length); m_ptr = cast(size_t)offset; }
144 	ulong tell() nothrow { return m_ptr; }
145 	const(ubyte)[] peek() { return m_data[m_ptr .. min(m_size, m_ptr+m_peekWindow)]; }
146 
147 	size_t read(scope ubyte[] dst, IOMode mode)
148 	{
149 		enforce(mode != IOMode.all || dst.length <= leastSize, "Reading past end of memory stream.");
150 		auto len = min(leastSize, dst.length);
151 		dst[0 .. len] = m_data[m_ptr .. m_ptr+len];
152 		m_ptr += len;
153 		return len;
154 	}
155 
156 	alias read = RandomAccessStream.read;
157 
158 	size_t write(in ubyte[] bytes, IOMode)
159 	{
160 		assert(writable);
161 		enforce(bytes.length <= m_data.length - m_ptr, "Size limit of memory stream reached.");
162 		m_data[m_ptr .. m_ptr+bytes.length] = bytes[];
163 		m_ptr += bytes.length;
164 		m_size = max(m_size, m_ptr);
165 		return bytes.length;
166 	}
167 
168 	alias write = RandomAccessStream.write;
169 
170 	void flush() {}
171 	void finalize() {}
172 }
173 
174 mixin validateRandomAccessStream!MemoryStream;