1 /**
2 	File handling functions and types.
3 
4 	Copyright: © 2012-2014 RejectedSoftware e.K.
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.core.file;
9 
10 public import vibe.core.stream;
11 
12 import vibe.core.drivers.threadedfile; // temporarily needed tp get mkstemps to work
13 import vibe.core.driver;
14 
15 import core.stdc.stdio;
16 import std.datetime;
17 import std.exception;
18 import std.file;
19 import std.path;
20 import std.string;
21 
22 version(Posix){
23 	private extern(C) int mkstemps(char* templ, int suffixlen);
24 }
25 
26 @safe:
27 
28 
29 /**
30 	Opens a file stream with the specified mode.
31 */
32 FileStream openFile(Path path, FileMode mode = FileMode.read)
33 {
34 	return getEventDriver().openFile(path, mode);
35 }
36 /// ditto
37 FileStream openFile(string path, FileMode mode = FileMode.read)
38 {
39 	return openFile(Path(path), mode);
40 }
41 
42 
43 /**
44 	Read a whole file into a buffer.
45 
46 	If the supplied buffer is large enough, it will be used to store the
47 	contents of the file. Otherwise, a new buffer will be allocated.
48 
49 	Params:
50 		path = The path of the file to read
51 		buffer = An optional buffer to use for storing the file contents
52 */
53 ubyte[] readFile(Path path, ubyte[] buffer = null, size_t max_size = size_t.max)
54 {
55 	auto fil = openFile(path);
56 	scope (exit) fil.close();
57 	enforce(fil.size <= max_size, "File is too big.");
58 	auto sz = cast(size_t)fil.size;
59 	auto ret = sz <= buffer.length ? buffer[0 .. sz] : new ubyte[sz];
60 	fil.read(ret);
61 	return ret;
62 }
63 /// ditto
64 ubyte[] readFile(string path, ubyte[] buffer = null, size_t max_size = size_t.max)
65 {
66 	return readFile(Path(path), buffer, max_size);
67 }
68 
69 
70 /**
71 	Write a whole file at once.
72 */
73 void writeFile(Path path, in ubyte[] contents)
74 {
75 	auto fil = openFile(path, FileMode.createTrunc);
76 	scope (exit) fil.close();
77 	fil.write(contents);
78 }
79 /// ditto
80 void writeFile(string path, in ubyte[] contents)
81 {
82 	writeFile(Path(path), contents);
83 }
84 
85 /**
86 	Convenience function to append to a file.
87 */
88 void appendToFile(Path path, string data) {
89 	auto fil = openFile(path, FileMode.append);
90 	scope(exit) fil.close();
91 	fil.write(data);
92 }
93 /// ditto
94 void appendToFile(string path, string data)
95 {
96 	appendToFile(Path(path), data);
97 }
98 
99 /**
100 	Read a whole UTF-8 encoded file into a string.
101 
102 	The resulting string will be sanitized and will have the
103 	optional byte order mark (BOM) removed.
104 */
105 string readFileUTF8(Path path)
106 {
107 	import vibe.utils.string;
108 
109 	return stripUTF8Bom(sanitizeUTF8(readFile(path)));
110 }
111 /// ditto
112 string readFileUTF8(string path)
113 {
114 	return readFileUTF8(Path(path));
115 }
116 
117 
118 /**
119 	Write a string into a UTF-8 encoded file.
120 
121 	The file will have a byte order mark (BOM) prepended.
122 */
123 void writeFileUTF8(Path path, string contents)
124 {
125 	static immutable ubyte[] bom = [0xEF, 0xBB, 0xBF];
126 	auto fil = openFile(path, FileMode.createTrunc);
127 	scope (exit) fil.close();
128 	fil.write(bom);
129 	fil.write(contents);
130 }
131 
132 /**
133 	Creates and opens a temporary file for writing.
134 */
135 FileStream createTempFile(string suffix = null)
136 {
137 	version(Windows){
138 		import std.conv : to;
139 		string tmpname;
140 		() @trusted {
141 			auto fn = tmpnam(null);
142 			enforce(fn !is null, "Failed to generate temporary name.");
143 			tmpname = to!string(fn);
144 		} ();
145 		if( tmpname.startsWith("\\") ) tmpname = tmpname[1 .. $];
146 		tmpname ~= suffix;
147 		return openFile(tmpname, FileMode.createTrunc);
148 	} else {
149 		enum pattern ="/tmp/vtmp.XXXXXX";
150 		scope templ = new char[pattern.length+suffix.length+1];
151 		templ[0 .. pattern.length] = pattern;
152 		templ[pattern.length .. $-1] = (suffix)[];
153 		templ[$-1] = '\0';
154 		assert(suffix.length <= int.max);
155 		auto fd = () @trusted { return mkstemps(templ.ptr, cast(int)suffix.length); } ();
156 		enforce(fd >= 0, "Failed to create temporary file.");
157 		return new ThreadedFileStream(fd, Path(templ[0 .. $-1].idup), FileMode.createTrunc);
158 	}
159 }
160 
161 /**
162 	Moves or renames a file.
163 
164 	Params:
165 		from = Path to the file/directory to move/rename.
166 		to = The target path
167 		copy_fallback = Determines if copy/remove should be used in case of the
168 			source and destination path pointing to different devices.
169 */
170 void moveFile(Path from, Path to, bool copy_fallback = false)
171 {
172 	moveFile(from.toNativeString(), to.toNativeString(), copy_fallback);
173 }
174 /// ditto
175 void moveFile(string from, string to, bool copy_fallback = false)
176 {
177 	if (!copy_fallback) {
178 		std.file.rename(from, to);
179 	} else {
180 		try {
181 			std.file.rename(from, to);
182 		} catch (FileException e) {
183 			std.file.copy(from, to);
184 			std.file.remove(from);
185 		}
186 	}
187 }
188 
189 /**
190 	Copies a file.
191 
192 	Note that attributes and time stamps are currently not retained.
193 
194 	Params:
195 		from = Path of the source file
196 		to = Path for the destination file
197 		overwrite = If true, any file existing at the destination path will be
198 			overwritten. If this is false, an exception will be thrown should
199 			a file already exist at the destination path.
200 
201 	Throws:
202 		An Exception if the copy operation fails for some reason.
203 */
204 void copyFile(Path from, Path to, bool overwrite = false)
205 {
206 	{
207 		auto src = openFile(from, FileMode.read);
208 		scope(exit) src.close();
209 		enforce(overwrite || !existsFile(to), "Destination file already exists.");
210 		auto dst = openFile(to, FileMode.createTrunc);
211 		scope(exit) dst.close();
212 		src.pipe(dst);
213 	}
214 
215 	// TODO: retain attributes and time stamps
216 }
217 /// ditto
218 void copyFile(string from, string to)
219 {
220 	copyFile(Path(from), Path(to));
221 }
222 
223 /**
224 	Removes a file
225 */
226 void removeFile(Path path)
227 {
228 	removeFile(path.toNativeString());
229 }
230 /// ditto
231 void removeFile(string path)
232 {
233 	std.file.remove(path);
234 }
235 
236 /**
237 	Checks if a file exists
238 */
239 bool existsFile(Path path) nothrow
240 {
241 	return existsFile(path.toNativeString());
242 }
243 /// ditto
244 bool existsFile(string path) nothrow
245 {
246 	return std.file.exists(path);
247 }
248 
249 /** Stores information about the specified file/directory into 'info'
250 
251 	Throws: A `FileException` is thrown if the file does not exist.
252 */
253 FileInfo getFileInfo(Path path)
254 @trusted {
255 	auto ent = DirEntry(path.toNativeString());
256 	return makeFileInfo(ent);
257 }
258 /// ditto
259 FileInfo getFileInfo(string path)
260 {
261 	return getFileInfo(Path(path));
262 }
263 
264 /**
265 	Creates a new directory.
266 */
267 void createDirectory(Path path)
268 {
269 	mkdir(path.toNativeString());
270 }
271 /// ditto
272 void createDirectory(string path)
273 {
274 	createDirectory(Path(path));
275 }
276 
277 /**
278 	Enumerates all files in the specified directory.
279 */
280 void listDirectory(Path path, scope bool delegate(FileInfo info) del)
281 @trusted {
282 	foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) )
283 		if( !del(makeFileInfo(ent)) )
284 			break;
285 }
286 /// ditto
287 void listDirectory(string path, scope bool delegate(FileInfo info) del)
288 {
289 	listDirectory(Path(path), del);
290 }
291 /// ditto
292 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(Path path)
293 {
294 	int iterator(scope int delegate(ref FileInfo) del){
295 		int ret = 0;
296 		listDirectory(path, (fi){
297 			ret = del(fi);
298 			return ret == 0;
299 		});
300 		return ret;
301 	}
302 	return &iterator;
303 }
304 /// ditto
305 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path)
306 {
307 	return iterateDirectory(Path(path));
308 }
309 
310 /**
311 	Starts watching a directory for changes.
312 */
313 DirectoryWatcher watchDirectory(Path path, bool recursive = true)
314 {
315 	return getEventDriver().watchDirectory(path, recursive);
316 }
317 // ditto
318 DirectoryWatcher watchDirectory(string path, bool recursive = true)
319 {
320 	return watchDirectory(Path(path), recursive);
321 }
322 
323 /**
324 	Returns the current working directory.
325 */
326 Path getWorkingDirectory()
327 {
328 	return Path(() @trusted { return std.file.getcwd(); } ());
329 }
330 
331 
332 /** Contains general information about a file.
333 */
334 struct FileInfo {
335 	/// Name of the file (not including the path)
336 	string name;
337 
338 	/// Size of the file (zero for directories)
339 	ulong size;
340 
341 	/// Time of the last modification
342 	SysTime timeModified;
343 
344 	/// Time of creation (not available on all operating systems/file systems)
345 	SysTime timeCreated;
346 
347 	/// True if this is a symlink to an actual file
348 	bool isSymlink;
349 
350 	/// True if this is a directory or a symlink pointing to a directory
351 	bool isDirectory;
352 }
353 
354 /**
355 	Specifies how a file is manipulated on disk.
356 */
357 enum FileMode {
358 	/// The file is opened read-only.
359 	read,
360 	/// The file is opened for read-write random access.
361 	readWrite,
362 	/// The file is truncated if it exists or created otherwise and then opened for read-write access.
363 	createTrunc,
364 	/// The file is opened for appending data to it and created if it does not exist.
365 	append
366 }
367 
368 /**
369 	Accesses the contents of a file as a stream.
370 */
371 interface FileStream : RandomAccessStream {
372 @safe:
373 
374 	/// The path of the file.
375 	@property Path path() const nothrow;
376 
377 	/// Determines if the file stream is still open
378 	@property bool isOpen() const;
379 
380 	/// Closes the file handle.
381 	void close();
382 }
383 
384 
385 /**
386 	Interface for directory watcher implementations.
387 
388 	Directory watchers monitor the contents of a directory (wither recursively or non-recursively)
389 	for changes, such as file additions, deletions or modifications.
390 */
391 interface DirectoryWatcher {
392 @safe:
393 
394 	/// The path of the watched directory
395 	@property Path path() const;
396 
397 	/// Indicates if the directory is watched recursively
398 	@property bool recursive() const;
399 
400 	/** Fills the destination array with all changes that occurred since the last call.
401 
402 		The function will block until either directory changes have occurred or until the
403 		timeout has elapsed. Specifying a negative duration will cause the function to
404 		wait without a timeout.
405 
406 		Params:
407 			dst = The destination array to which the changes will be appended
408 			timeout = Optional timeout for the read operation
409 
410 		Returns:
411 			If the call completed successfully, true is returned.
412 	*/
413 	bool readChanges(ref DirectoryChange[] dst, Duration timeout = dur!"seconds"(-1));
414 }
415 
416 
417 /** Specifies the kind of change in a watched directory.
418 */
419 enum DirectoryChangeType {
420 	/// A file or directory was added
421 	added,
422 	/// A file or directory was deleted
423 	removed,
424 	/// A file or directory was modified
425 	modified
426 }
427 
428 
429 /** Describes a single change in a watched directory.
430 */
431 struct DirectoryChange {
432 	/// The type of change
433 	DirectoryChangeType type;
434 
435 	/// Path of the file/directory that was changed
436 	Path path;
437 }
438 
439 
440 private FileInfo makeFileInfo(DirEntry ent)
441 @trusted {
442 	FileInfo ret;
443 	ret.name = baseName(ent.name);
444 	if( ret.name.length == 0 ) ret.name = ent.name;
445 	assert(ret.name.length > 0);
446 	ret.size = ent.size;
447 	ret.timeModified = ent.timeLastModified;
448 	version(Windows) ret.timeCreated = ent.timeCreated;
449 	else ret.timeCreated = ent.timeLastModified;
450 	ret.isSymlink = ent.isSymlink;
451 	ret.isDirectory = ent.isDir;
452 	return ret;
453 }