1 module libd.io.filesystem;
2 
3 import libd.io.stream, libd.util.errorhandling, libd.datastructures.smartptr, libd.data.conv, libd.data.format;
4 
5 @nogc nothrow:
6 
7 enum FileOpenMode
8 {
9     FAILSAFE,
10     createIfNotExists,
11     createAlways,
12     openExisting,
13     openAlways,
14     truncateExisting
15 }
16 
17 enum FileUsage
18 {
19     FAILSAFE,
20     read = 1 << 2,
21     write = 1 << 1,
22     readWrite = read | write
23 }
24 
25 struct FileStream
26 {
27     static assert(isStream!FileStream);
28 
29     @disable this(this){}
30 
31     private
32     {
33         FileT _file;
34         FileUsage _usage;
35     }
36 
37     @nogc nothrow:
38 
39     @("not for public use")
40     this(FileT file, FileUsage usage)
41     {
42         this._file = file;
43         this._usage = usage;
44     }
45 
46     ~this()
47     {
48         if(this._file)
49         {
50             version(Windows)
51             {
52                 CloseHandle(this._file);
53                 this._file = null;
54             }
55             else version(Posix)
56             {
57                 import runtime.system.posix;
58                 close(this._file);
59                 this._file = FileT.min;
60             }
61         }
62     }
63 
64     SimpleResult!size_t write(const void[] data)
65     {
66         if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open."));
67         return fileWriteImpl(this._file, data);
68     }
69 
70     SimpleResult!size_t read(scope void[] data)
71     {
72         if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open."));
73         return fileReadImpl(this._file, data);
74     }
75     
76     bool hasData()
77     {
78         return false; // TODO
79     }
80     
81     bool isOpen()
82     {
83         version(Windows) return this._file !is null;
84         else return this._file > -1;
85     }
86 
87     SimpleResult!size_t getPosition()
88     {
89         if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open."));
90         return fileGetPositionImpl(this._file);
91     }
92 
93     SimpleResult!void setPosition(size_t position)
94     {
95         if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open."));
96         return fileSetPositionImpl(this._file, position);
97     }
98 
99     SimpleResult!size_t getSize()
100     {
101         if(!this.isOpen) return typeof(return)(raise("This FileStream isn't open."));
102         return fileGetSizeImpl(this._file);
103     }
104     
105     bool canPosition()
106     {
107         return true;
108     }
109 
110     bool canWrite()
111     {
112         return (this._usage & FileUsage.write) > 0;
113     }
114 
115     bool canRead()
116     {
117         return (this._usage & FileUsage.read) > 0;
118     }
119 }
120 
121 SimpleResult!(Shared!FileStream) fsOpen(const char[] file, FileOpenMode mode, FileUsage usage)
122 {
123     auto result = fileOpenImpl(file, mode, usage);
124     if(!result.isValid)
125         return typeof(return)(result.error());
126     return typeof(return)(makeShared(FileStream(result.value, usage)));
127 }
128 
129 bool fsExists(const char[] path)
130 {
131     return fsExistsImpl(path);
132 }
133 
134 SimpleResult!void fsDelete(const char[] path)
135 {
136     return fsDeleteImpl(path);
137 }
138 
139 SimpleResult!(Shared!(Array!ubyte)) fsRead(const char[] file)
140 {
141     auto result = fsOpen(file, FileOpenMode.openExisting, FileUsage.read);
142     auto array  = Array!ubyte.init;
143 
144     if(!result.isValid)
145         return typeof(return)(result.error);
146 
147     auto ptr = result.value;
148     auto stream = ptr.ptrUnsafe;
149 
150     array.length = stream.getSize().value;
151     stream.read(array[0..$]);
152 
153     return typeof(return)(makeShared(array));
154 }
155 
156 version(Windows)
157 {
158     import runtime.system.windows;
159     alias FileT = HANDLE;
160 
161     SimpleResult!FileT fileOpenImpl(const char[] file, FileOpenMode mode, FileUsage usage)
162     {
163         DWORD accessRights;
164         if(usage & FileUsage.read)
165             accessRights |= GENERIC_READ;
166         if(usage & FileUsage.write)
167             accessRights |= GENERIC_WRITE;
168 
169         String zeroTerm = file;
170         auto handle = CreateFileA(
171             zeroTerm[0..$].ptr,
172             accessRights,
173             0,
174             null,
175             cast(DWORD)mode, // values match up with win api
176             FILE_ATTRIBUTE_NORMAL,
177             null
178         );
179 
180         if(handle == INVALID_HANDLE_VALUE)
181         {
182             auto error = GetLastError();
183             bcstring message;
184 
185             switch(error)
186             {
187                 case ERROR_FILE_EXISTS: message = "Could not open file: file already exists"; break;
188                 case ERROR_FILE_NOT_FOUND: message = "Could not open file: file does not exist"; break;
189                 default: message = "Could not open file: unknown"; break;
190             }
191 
192             return typeof(return)(raise(
193                 message,
194                 error
195             ));
196         }
197 
198         return typeof(return)(handle);
199     }
200 
201     SimpleResult!size_t fileWriteImpl(FileT file, const scope void[] data)
202     {
203         uint amountRead;
204         const result = WriteFile(
205             file,
206             data.ptr,
207             cast(uint)data.length,
208             &amountRead,
209             null
210         );
211 
212         if(!result)
213             return typeof(return)(raise("Error? TODO", GetLastError()));
214 
215         return typeof(return)(cast(size_t)amountRead);
216     }
217 
218     SimpleResult!size_t fileGetSizeImpl(FileT file)
219     {
220         long size;
221         const result = GetFileSizeEx(file, &size);
222 
223         if(!result || size < 0)
224             return typeof(return)(raise("TODO", GetLastError()));
225 
226         return typeof(return)(cast(size_t)size);
227     }
228 
229     SimpleResult!void fileSetPositionImpl(FileT file, size_t position)
230     {
231         assert(position <= long.max);
232         const result = SetFilePointerEx(
233             file,
234             cast(long)position,
235             null,
236             0 // FILE_BEGIN
237         );
238 
239         if(!result)
240             return typeof(return)(raise("TODO", GetLastError()));
241 
242         return typeof(return)();
243     }
244 
245     SimpleResult!size_t fileGetPositionImpl(FileT file)
246     {
247         long position;
248         const result = SetFilePointerEx(
249             file,
250             0,
251             &position,
252             1 // FILE_CURRENT
253         );
254 
255         if(!result || position < 0)
256             return typeof(return)(raise("TODO", GetLastError()));
257 
258         return typeof(return)(cast(size_t)position);
259     }
260 
261     SimpleResult!size_t fileReadImpl(FileT file, scope void[] data)
262     {
263         assert(data.length <= uint.max);
264 
265         uint read;
266         const result = ReadFile(
267             file,
268             data.ptr,
269             cast(uint)data.length,
270             &read,
271             null
272         );
273 
274         if(!result)
275             return typeof(return)(raise("TODO", GetLastError()));
276 
277         return typeof(return)(cast(size_t)read);
278     }
279 
280     bool fsExistsImpl(const char[] path)
281     {
282         String zeroTerm = path;
283         return cast(bool)PathFileExistsA(zeroTerm[0..$].ptr);
284     }
285 
286     SimpleResult!void fsDeleteImpl(const char[] path)
287     {
288         String zeroTerm = path;
289         const result = DeleteFileA(zeroTerm[0..$].ptr);
290         
291         if(!result)
292             return typeof(return)(raise(GetLastErrorAsString()));
293 
294         return typeof(return)();
295     }
296 }
297 else version(Posix)
298 {
299     import runtime.system.posix;
300     alias FileT = int;
301 
302     SimpleResult!FileT fileOpenImpl(const char[] file, FileOpenMode mode, FileUsage usage)
303     {
304         int flags;
305         if(usage == FileUsage.readWrite)
306             flags |= O_RDWR;
307         else if (usage == FileUsage.read)
308             flags |= O_RDONLY;
309         else if(usage == FileUsage.write)
310             flags |= O_WRONLY;
311         else
312             assert(false, "No file usage was specified");
313 
314         final switch(mode) with(FileOpenMode)
315         {
316             case createIfNotExists: flags |= O_CREAT; break;
317             case createAlways: flags |= O_CREAT | O_TRUNC; break;
318             case openExisting: break;
319             case openAlways: flags |= O_CREAT; break;
320             case truncateExisting: flags |= O_TRUNC; break;
321             case FAILSAFE: assert(false, "FAILSAFE was used.");
322         }
323 
324         int fd = open(String(file).ptrUnsafe, flags, 0x1B6);
325         if(fd < 0)
326             return typeof(return)(raise("Could not open file at {0} (errno {1})".format(file, g_errno).value, g_errno));
327 
328         return typeof(return)(fd);
329     }
330 
331     SimpleResult!size_t fileWriteImpl(FileT file, const scope void[] data)
332     {
333         const wrote = write(file, data.ptr, data.length);
334         if(wrote < 0)
335             return typeof(return)(raise("Could not write to file (errno {0})".format(g_errno).value, g_errno));
336 
337         return typeof(return)(cast(size_t)wrote);
338     }
339 
340     SimpleResult!size_t fileGetSizeImpl(FileT file)
341     {
342         stat stats;
343         const result = fstat(file, &stats);
344         if(result < 0)
345             return typeof(return)(raise("fstat failed (errno {0})".format(g_errno).value, g_errno));
346 
347         return typeof(return)(cast(size_t)stats.st_size);
348     }
349 
350     SimpleResult!void fileSetPositionImpl(FileT file, size_t position)
351     {
352         assert(position <= long.max);
353         const result = lseek(file, position, SEEK_SET);
354         if(result < 0)
355             return typeof(return)(raise("lseek failed (errno {0})".format(g_errno).value, g_errno));
356 
357         return typeof(return)();
358     }
359 
360     SimpleResult!size_t fileGetPositionImpl(FileT file)
361     {
362         const result = lseek(file, 0, SEEK_CUR);
363         if(result < 0)
364             return typeof(return)(raise("lseek failed (errno {0})".format(g_errno).value, g_errno));
365 
366         return typeof(return)(cast(size_t)result);
367     }
368 
369     SimpleResult!size_t fileReadImpl(FileT file, scope void[] data)
370     {
371         const result = read(file, data.ptr, data.length);
372         if(!result)
373             return typeof(return)(raise("read failed (errno {0})".format(g_errno).value, g_errno));
374 
375         return typeof(return)(cast(size_t)result);
376     }
377 
378     bool fsExistsImpl(const char[] path)
379     {
380         String zeroTerm = path;
381         return cast(bool)access(zeroTerm[0..$].ptr, F_OK);
382     }
383 
384     SimpleResult!void fsDeleteImpl(const char[] path)
385     {
386         String zeroTerm = path;
387         auto result = unlink(zeroTerm.ptrUnsafe);
388         if(result == EISDIR)
389             result = rmdir(zeroTerm.ptrUnsafe);
390         if(result < 0)
391             return typeof(return)(raise("unlink/rmdir failed (errno {0})".format(g_errno).value, g_errno));
392         return typeof(return)();
393     }
394 }