1 module nulib.io.stream.file;
2 import nulib.io.stream;
3 import nulib.c.stdio;
4 import nulib.c.string;
5 import nulib.c.errno;
6 import nulib.math : min;
7 import nulib.string;
8 import numem;
9 
10 /**
11     Flags
12 */
13 enum FileModeFlags {
14     read    = 1,
15     write   = 2,
16     append  = 3,
17 
18     update  = 0x10,
19     binary  = 0x20,
20 }
21 
22 /**
23     A file stream.
24 */
25 class FileStream : Stream {
26 private:
27 @nogc @safe:
28     FILE* handle;
29     FileModeFlags flags;
30     size_t fLength;
31 
32     void parseFileFlags(immutable(fchar_t)[] flags) nothrow {
33         this.flags = cast(FileModeFlags)0;
34         loop: foreach(i; 0..flags.length) {
35             switch(flags[i]) {
36                 case 'r':
37                     this.flags |= FileModeFlags.read;
38                     break;
39                 case 'w':
40                     this.flags |= FileModeFlags.write;
41                     break;
42                 case 'a':
43                     this.flags |= FileModeFlags.append;
44                     break;
45                 case 'b':
46                     this.flags |= FileModeFlags.binary;
47                     break;
48                 case '+':
49                     this.flags |= FileModeFlags.update;
50                     break;
51                 default: break loop;
52             }
53         }
54     }
55 
56     bool canUpdate() nothrow {
57         return (flags & FileModeFlags.update) == FileModeFlags.update;
58     }
59 
60     void updateLength() nothrow @trusted {
61         if (!handle)
62             return;
63         
64         if (canSeek) {
65             this.seek(0, SeekOrigin.end);
66             this.fLength = this.tell();
67             this.seek(0, SeekOrigin.start);
68         }
69     }
70 
71 public:
72 
73     /**
74         Whether the stream can be read from.
75 
76         Returns:
77             $(D true) if you can read data from the stream,
78             $(D false) otherwise.
79     */
80     override @property bool canRead() nothrow {
81         if (!handle)
82             return false;
83         
84         if (this.canUpdate)
85             return true;
86         
87         return (flags & FileModeFlags.read) != 0;
88     }
89 
90     /**
91         Whether the stream can be written to.
92 
93         Returns:
94             $(D true) if you can write data to the stream,
95             $(D false) otherwise.
96     */
97     override @property bool canWrite() nothrow {
98         if (!handle)
99             return false;
100         
101         if (this.canUpdate)
102             return true;
103         
104         return (flags & FileModeFlags.write) != 0;
105     }
106     
107     /**
108         Whether the stream can be seeked.
109 
110         Returns:
111             $(D true) if you can seek through the stream,
112             $(D false) otherwise.
113     */
114     override @property bool canSeek() nothrow {
115         if (!handle)
116             return false;
117         
118         if (this.canUpdate)
119             return true;
120         
121         return (flags & 3) > 0;
122     }
123 
124     /**
125         Whether the stream can timeout during operations.
126 
127         Returns:
128             $(D true) if the stream may time out during operations,
129             $(D false) otherwise.
130     */
131     override @property bool canTimeout() nothrow { return false; }
132 
133     /**
134         Whether the stream can be flushed to disk.
135 
136         Returns:
137             $(D true) if the stream may be flushed,
138             $(D false) otherwise.
139     */
140     override @property bool canFlush() nothrow {
141         if (!handle)
142             return false;
143         
144         return (flags & 3) > 0;
145     }
146 
147     /**
148         Length of the stream.
149 
150         Returns:
151             Length of the stream, or $(D -1) if the length is unknown.
152     */
153     override @property ptrdiff_t length() nothrow { 
154         if (!handle)
155             return -1;
156         
157         return fLength;
158     }
159 
160     /**
161         Position in stream
162     
163         Returns
164             Position in the stream, or $(D -1) if the position is unknown.
165     */
166     override @property ptrdiff_t tell() nothrow @trusted {
167         if (!handle)
168             return -1;
169         
170         return ftell(handle);
171     }
172 
173     /**
174         Timeout in milliseconds before a read operation will fail.
175 
176         Returns
177             A timeout in milliseconds, or $(D 0) if there's no timeout.
178     */
179     override @property int readTimeout() nothrow { return 0; }
180 
181     /**
182         Timeout in milliseconds before a write operation will fail.
183 
184         Returns
185             A timeout in milliseconds, or $(D 0) if there's no timeout.
186     */
187     override @property int writeTimeout() nothrow { return 0; }
188 
189     // Destructor
190     ~this() nothrow @trusted {
191         this.close();
192     }
193 
194     /**
195         Constructs a file stream.
196 
197         Params:
198             path =  Path to the file to open
199             mode =  Mode flags to open the file with.
200     */
201     this(string path, string mode) @trusted {
202         auto rpath = path.toSupportedEncoding();
203         auto rmode = mode.toSupportedEncoding();
204 
205         this.handle = _fopen(rpath.ptr, rmode.ptr);
206         if(!handle) {
207             this.flags = cast(FileModeFlags)0;
208 
209             nstring errMsg = strerror(errno);
210             throw nogc_new!NuException(errMsg);
211         }
212 
213         this.parseFileFlags(rmode);
214         this.updateLength();
215     }
216 
217     /**
218         Clears all buffers of the stream and causes data to be written to the underlying device.
219     
220         Returns:
221             $(D true) if the flush operation succeeded,
222             $(D false) otherwise.
223     */
224     override bool flush() nothrow @trusted {
225         if (!handle)
226             return false;
227         
228         if (!canFlush)
229             return false;
230         
231         return fflush(handle) == 0; 
232     }
233 
234     /**
235         Sets the reading position within the stream
236 
237         Returns
238             The new position in the stream, or a $(D StreamError) if the 
239             seek operation failed.
240         
241         See_Also:
242             $(D StreamError)
243     */
244     override
245     long seek(ptrdiff_t offset, SeekOrigin origin = SeekOrigin.start) nothrow @trusted {
246         if (!handle)
247             return StreamError.invalidState;
248         
249         if (!canSeek())
250             return StreamError.notSupported;
251 
252         int status = fseek(handle, offset, origin);
253         return status == 0 ? tell() : StreamError.outOfRange;
254     }
255 
256     /**
257         Closes the stream.
258     */
259     override
260     void close() nothrow @trusted {
261         if (handle) {
262             cast(void)fclose(handle);
263             this.handle = null;
264         }
265     }
266 
267     /**
268         Reads bytes from the specified stream in to the specified buffer
269         
270         Notes:
271             The position and length to read is specified by the slice of `buffer`.  
272             Use slicing operation to specify a range to read to.
273 
274         Returns:
275             The amount of bytes read from the stream, 
276             or a $(D StreamError).
277         
278         See_Also:
279             $(D StreamError)
280     */
281     override
282     ptrdiff_t read(ubyte[] buffer) nothrow @trusted {
283         if (!handle)
284             return StreamError.invalidState;
285         
286         if (!this.canRead)
287             return StreamError.notSupported;
288 
289         size_t rc = fread(cast(void*)buffer.ptr, 1, buffer.length, handle);
290         return rc;
291     }
292 
293     /**
294         Writes bytes from the specified buffer in to the stream
295 
296         Notes
297             The position and length to write is specified by the slice of `buffer`.  
298             Use slicing operation to specify a range to write from.
299 
300         Returns:
301             The amount of bytes read from the stream, 
302             or a $(D StreamError).
303         
304         See_Also:
305             $(D StreamError)
306     */
307     override
308     ptrdiff_t write(ubyte[] buffer) nothrow @trusted {
309         if (!handle)
310             return StreamError.invalidState;
311         
312         if (!this.canWrite)
313             return StreamError.notSupported;
314         
315         size_t rc = fwrite(cast(void*)buffer.ptr, 1, buffer.length, handle);
316         return rc;
317     }
318 }
319 
320 
321 
322 //
323 //          IMPLEMENTATION DETAILS.
324 //
325 
326 private:
327 
328 StringImpl!(fchar_t) toSupportedEncoding(string str) @nogc {
329     static if (is (fchar_t == char)) {
330         return StringImpl!(fchar_t)(str);
331     } else {
332         import nulib.text.unicode : decode, encode;
333         import numem.core.memory : nu_dup, nu_resize;
334 
335         auto tmp = str.nu_dup();
336         auto rstr = encode!(StringImpl!(fchar_t))(decode(tmp), false);
337         tmp = tmp.nu_resize(0);
338         return rstr;
339     }
340 }
341 
342 
343 version (CRuntime_Microsoft) 
344     alias _fopen = _wfopen;
345 else
346     alias _fopen = fopen;
347 
348 version (CRuntime_Microsoft)
349     alias fchar_t = wchar;
350 else
351     alias fchar_t = char;