1 /// Basic file stream implementation inheriting `IStream`.
2 module tern.stream.file_stream;
3 
4 public import tern.stream.impl;
5 import tern.typecons;
6 import tern.serialization;
7 import tern.traits;
8 import tern.memory;
9 import tern.digest.mira;
10 import std.stdio;
11 
12 public enum Mode
13 {
14     Read,
15     Write,
16     ReadWrite,
17     Append,
18     ReadAppend,
19 }
20 
21 public class FileStream : IStream
22 {
23 protected:
24 final:
25     File file;
26 
27 public:
28     Endianness endianness;
29 
30     this(string path, Mode mode = Mode.ReadWrite, Endianness endianness = Endianness.Native)
31     {
32         if (mode == Mode.Read)
33             this.file = File(path, "r");
34         else if (mode == Mode.Write)
35             this.file = File(path, "w");
36         else if (mode == Mode.ReadWrite)
37             this.file = File(path, "r+");
38         else if (mode == Mode.Append)
39             this.file = File(path, "a");
40         else if (mode == Mode.ReadAppend)
41             this.file = File(path, "a+");
42         this.endianness = endianness;
43     }
44 
45     this(File file, Endianness endianness = Endianness.Native)
46     {
47         this.file = file;
48         this.endianness = endianness;
49     }
50 
51     @property size_t position()
52     {
53         return file.tell();
54     }
55 
56     @property size_t position(size_t val)
57     {
58         file.seek(val, SEEK_SET);
59         return file.tell();
60     }
61 
62     size_t size()
63     {
64         return file.size();
65     }
66 
67     bool mayRead(T)()
68     {
69         return position + T.sizeof < size;
70     }
71 
72     bool mayRead(size_t size = 1)
73     {
74         return position + size < this.size;
75     }
76 
77     void step(T)()
78     {
79         file.seek(T.sizeof, SEEK_CUR);
80     }
81 
82     void seek(Seek SEEK = Seek.Start)(size_t offset)
83     {
84         if (SEEK == Seek.Current)
85             file.seek(offset, SEEK_CUR);
86         else if (SEEK == Seek.Start)
87             file.seek(offset, SEEK_SET);
88         else
89             file.seek(offset, SEEK_END);
90     }
91 
92     ubyte[] readAllBytes()
93     {
94         ubyte[] buff = new ubyte[size];
95         file.rawRead(buff);
96         return buff;
97     }
98 
99     immutable(CHAR)[] readAllText(CHAR = char)()
100     {
101         return cast(immutable(CHAR)[])readAllBytes;
102     }
103 
104     T read(T)()
105     {
106         ubyte[T.sizeof] buff;
107         file.rawRead(buff);
108         return (cast(ubyte[])buff).deserialize!T;
109     }
110 
111     T[] read(T)(size_t count)
112     {
113         ubyte[] buff = new ubyte[T.sizeof * count + size_t.sizeof];
114         file.rawRead(buff);
115         return (cast(ubyte[])buff).deserialize!(T[]);
116     }
117         
118     T[] readRaw(T)(size_t count)
119     {
120         ubyte[] buff = new ubyte[T.sizeof * count];
121         file.rawRead(buff);
122         ubyte[] ret = new ubyte[(T.sizeof * count) + size_t.sizeof];
123         ret[size_t.sizeof..$] = buff;
124         ret[0..size_t.sizeof] = (cast(ubyte*)&count)[0..size_t.sizeof];
125         return (cast(ubyte[])ret).deserialize!(T[]);
126     }
127 
128     T read(T)()
129         if (isDynamicArray!T)
130     {
131         return read!T(read7EncodedInt());
132     }
133 
134     T peek(T)()
135     {
136         size_t position = file.tell();
137         scope(exit) file.seek(position);
138         ubyte[T.sizeof] buff;
139         file.rawRead(buff);
140         return (cast(ubyte[])buff).deserialize!T;
141     }
142 
143     T[] peek(T)(size_t count)
144     {
145         size_t position = file.tell();
146         scope(exit) file.seek(position);
147         ubyte[] buff = new ubyte[T.sizeof * count + size_t.sizeof];
148         file.rawRead(buff);
149         return (cast(ubyte[])buff).deserialize!(T[]);
150     }
151         
152     T[] peekRaw(T)(size_t count)
153     {
154         size_t position = file.tell();
155         scope(exit) file.seek(position);
156         ubyte[] buff = new ubyte[T.sizeof * count];
157         file.rawRead(buff);
158         ubyte[] ret = new ubyte[(T.sizeof * count) + size_t.sizeof];
159         ret[size_t.sizeof..$] = buff;
160         ret[0..size_t.sizeof] = (cast(ubyte*)&count)[0..size_t.sizeof];
161         return (cast(ubyte[])ret).deserialize!(T[]);
162     }
163 
164     T read(T)()
165         if (isDynamicArray!T)
166     {
167         size_t position = file.tell();
168         scope(exit) file.seek(position);
169         return read!T(read7EncodedInt());
170     }
171 
172     void write(bool RAW = false, T)(T val)
173         if (!isArray!T)
174     {
175         file.rawWrite(val.serialize!RAW);
176     }
177 
178     void write(T, bool PREFIXED = true)(T val)
179         if (isArray!T)
180     {
181         static if (PREFIXED)
182             write7EncodedInt(cast(uint)val.length);
183 
184         file.rawWrite(val.serialize()[8..$]);
185     }
186 
187     void put(bool RAW = false, T)(T val)
188         if (!isArray!T)
189     {
190         size_t position = file.tell();
191         scope(exit) file.seek(position);
192         file.rawWrite(val.serialize!RAW);
193     }
194 
195     void put(T, bool PREFIXED = true)(T val)
196         if (isArray!T)
197     {
198         size_t position = file.tell();
199         scope(exit) file.seek(position);
200         static if (PREFIXED)
201             write7EncodedInt(cast(uint)val.length);
202 
203         file.rawWrite(val.serialize()[8..$]);
204     }
205 
206     immutable(CHAR)[] readString(CHAR, bool PREFIXED = false)()
207     {
208         static if (PREFIXED)
209             return cast(immutable(CHAR)[])read!(immutable(CHAR)[]);
210         else
211         {
212             immutable(CHAR)[] ret;
213             while (peek!CHAR != '\0' && position < size)
214                 ret ~= read!CHAR;
215             return ret;
216         }
217     }
218 
219     immutable(CHAR)[] peekString(CHAR, bool PREFIXED = false)()
220     {
221         size_t position = file.tell();
222         scope(exit) file.seek(position);
223         static if (PREFIXED)
224             return cast(immutable(CHAR)[])read!(immutable(CHAR)[]);
225         else
226         {
227             immutable(CHAR)[] ret;
228             while (peek!CHAR != '\0' && position < size)
229                 ret ~= read!CHAR;
230             return ret;
231         }
232     }
233 
234     void writeString(CHAR, bool PREFIXED = false)(immutable(CHAR)[] val)
235     {
236         write!(immutable(CHAR)[], PREFIXED)(val);
237     }
238 
239     void putString(CHAR, bool PREFIXED = false)(immutable(CHAR)[] val)
240     {
241         put!(immutable(CHAR)[], PREFIXED)(val);
242     }
243 
244     uint read7EncodedInt()
245     {
246         uint result = 0;
247         uint shift = 0;
248 
249         foreach (i; 0..5)
250         {
251             ubyte b = read!ubyte;
252             result |= cast(uint)(b & 0x7F) << shift;
253             if ((b & 0x80) == 0)
254                 return result;
255             shift += 7;
256         }
257         
258         return result;
259     }
260 
261     void write7EncodedInt(uint val)
262     {
263         foreach (i; 0..5)
264         {
265             byte b = cast(byte)(val & 0x7F);
266             val >>= 7;
267             if (val != 0)
268                 b |= 0x80;
269             write(b);
270             if (val == 0)
271                 return;
272         }
273     }
274 
275     void encrypt(size_t size, string key)
276     {
277         if (!mayRead(size))
278             encrypt(this.size, key);
279 
280         ubyte[] buff = peek!ubyte(size);
281         Mira256.encrypt(buff, key);
282         write!(ubyte[], false)(buff);
283     }
284 
285     void decrypt(size_t size, string key)
286     {
287         if (!mayRead(size))
288             decrypt(this.size, key);
289 
290         ubyte[] buff = peek!ubyte(size);
291         Mira256.decrypt(buff, key);
292         write!(ubyte[], false)(buff);
293     }
294 }