1 /**
2 	File handling.
3 
4 	Copyright: © 2012 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 dub.internal.vibecompat.core.file;
9 
10 public import dub.internal.vibecompat.inet.url;
11 
12 import dub.internal.vibecompat.core.log;
13 
14 import std.conv;
15 import core.stdc.stdio;
16 import std.datetime;
17 import std.exception;
18 import std.file;
19 import std.path;
20 import std.stdio;
21 import std.string;
22 import std.utf;
23 
24 
25 /* Add output range support to File
26 */
27 struct RangeFile {
28 @safe:
29 	std.stdio.File file;
30 
31 	void put(scope const ubyte[] bytes) @trusted { file.rawWrite(bytes); }
32 	void put(scope const char[] str) { put(cast(const(ubyte)[])str); }
33 	void put(char ch) @trusted { put((&ch)[0 .. 1]); }
34 	void put(dchar ch) { char[4] chars; put(chars[0 .. encode(chars, ch)]); }
35 
36 	ubyte[] readAll()
37 	{
38 		auto sz = this.size;
39 		enforce(sz <= size_t.max, "File is too big to read to memory.");
40 		() @trusted { file.seek(0, SEEK_SET); } ();
41 		auto ret = new ubyte[cast(size_t)sz];
42 		rawRead(ret);
43 		return ret;
44 	}
45 
46 	void rawRead(ubyte[] dst) @trusted { enforce(file.rawRead(dst).length == dst.length, "Failed to readall bytes from file."); }
47 	void write(string str) { put(str); }
48 	void close() @trusted { file.close(); }
49 	void flush() @trusted { file.flush(); }
50 	@property ulong size() @trusted { return file.size; }
51 }
52 
53 
54 /**
55 	Opens a file stream with the specified mode.
56 */
57 RangeFile openFile(NativePath path, FileMode mode = FileMode.read)
58 {
59 	string fmode;
60 	final switch(mode){
61 		case FileMode.read: fmode = "rb"; break;
62 		case FileMode.readWrite: fmode = "r+b"; break;
63 		case FileMode.createTrunc: fmode = "wb"; break;
64 		case FileMode.append: fmode = "ab"; break;
65 	}
66 	auto ret = std.stdio.File(path.toNativeString(), fmode);
67 	assert(ret.isOpen);
68 	return RangeFile(ret);
69 }
70 /// ditto
71 RangeFile openFile(string path, FileMode mode = FileMode.read)
72 {
73 	return openFile(NativePath(path), mode);
74 }
75 
76 
77 /**
78 	Moves or renames a file.
79 */
80 void moveFile(NativePath from, NativePath to)
81 {
82 	moveFile(from.toNativeString(), to.toNativeString());
83 }
84 /// ditto
85 void moveFile(string from, string to)
86 {
87 	std.file.rename(from, to);
88 }
89 
90 /**
91 	Copies a file.
92 
93 	Note that attributes and time stamps are currently not retained.
94 
95 	Params:
96 		from = NativePath of the source file
97 		to = NativePath for the destination file
98 		overwrite = If true, any file existing at the destination path will be
99 			overwritten. If this is false, an excpetion will be thrown should
100 			a file already exist at the destination path.
101 
102 	Throws:
103 		An Exception if the copy operation fails for some reason.
104 */
105 void copyFile(NativePath from, NativePath to, bool overwrite = false)
106 {
107 	enforce(existsFile(from), "Source file does not exist.");
108 
109 	if (existsFile(to)) {
110 		enforce(overwrite, "Destination file already exists.");
111 		// remove file before copy to allow "overwriting" files that are in
112 		// use on Linux
113 		removeFile(to);
114 	}
115 
116 	static if (is(PreserveAttributes))
117 	{
118 		.copy(from.toNativeString(), to.toNativeString(), PreserveAttributes.yes);
119 	}
120 	else
121 	{
122 		.copy(from.toNativeString(), to.toNativeString());
123 		// try to preserve ownership/permissions in Posix
124 		version (Posix) {
125 			import core.sys.posix.sys.stat;
126 			import core.sys.posix.unistd;
127 			import std.utf;
128 			auto cspath = toUTFz!(const(char)*)(from.toNativeString());
129 			auto cdpath = toUTFz!(const(char)*)(to.toNativeString());
130 			stat_t st;
131 			enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file.");
132 			if (chown(cdpath, st.st_uid, st.st_gid) != 0)
133 				st.st_mode &= ~(S_ISUID | S_ISGID);
134 			chmod(cdpath, st.st_mode);
135 		}
136 	}
137 }
138 /// ditto
139 void copyFile(string from, string to)
140 {
141 	copyFile(NativePath(from), NativePath(to));
142 }
143 
144 version (Windows) extern(Windows) int CreateHardLinkW(in wchar* to, in wchar* from, void* attr=null);
145 
146 // guess whether 2 files are identical, ignores filename and content
147 private bool sameFile(NativePath a, NativePath b)
148 {
149 	version (Posix) {
150 		auto st_a = std.file.DirEntry(a.toNativeString).statBuf;
151 		auto st_b = std.file.DirEntry(b.toNativeString).statBuf;
152 		return st_a == st_b;
153 	} else {
154 		static assert(__traits(allMembers, FileInfo)[0] == "name");
155 		return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $];
156 	}
157 }
158 
159 /**
160 	Creates a hardlink.
161 */
162 void hardLinkFile(NativePath from, NativePath to, bool overwrite = false)
163 {
164 	if (existsFile(to)) {
165 		enforce(overwrite, "Destination file already exists.");
166 		if (auto fe = collectException!FileException(removeFile(to))) {
167 			if (sameFile(from, to)) return;
168 			throw fe;
169 		}
170 	}
171 
172 	version (Windows)
173 	{
174 		alias cstr = toUTFz!(const(wchar)*);
175 		if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString)))
176 			return;
177 	}
178 	else
179 	{
180 		import core.sys.posix.unistd : link;
181 		alias cstr = toUTFz!(const(char)*);
182 		if (!link(cstr(from.toNativeString), cstr(to.toNativeString)))
183 			return;
184 	}
185 	// fallback to copy
186 	copyFile(from, to, overwrite);
187 }
188 
189 /**
190 	Removes a file
191 */
192 void removeFile(NativePath path)
193 {
194 	removeFile(path.toNativeString());
195 }
196 /// ditto
197 void removeFile(string path) {
198 	std.file.remove(path);
199 }
200 
201 /**
202 	Checks if a file exists
203 */
204 bool existsFile(NativePath path) {
205 	return existsFile(path.toNativeString());
206 }
207 /// ditto
208 bool existsFile(string path)
209 {
210 	return std.file.exists(path);
211 }
212 
213 /** Stores information about the specified file/directory into 'info'
214 
215 	Returns false if the file does not exist.
216 */
217 FileInfo getFileInfo(NativePath path)
218 {
219 	auto ent = std.file.DirEntry(path.toNativeString());
220 	return makeFileInfo(ent);
221 }
222 /// ditto
223 FileInfo getFileInfo(string path)
224 {
225 	return getFileInfo(NativePath(path));
226 }
227 
228 /**
229 	Creates a new directory.
230 */
231 void createDirectory(NativePath path)
232 {
233 	mkdir(path.toNativeString());
234 }
235 /// ditto
236 void createDirectory(string path)
237 {
238 	createDirectory(NativePath(path));
239 }
240 
241 /**
242 	Enumerates all files in the specified directory.
243 */
244 void listDirectory(NativePath path, scope bool delegate(FileInfo info) del)
245 {
246 	foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) )
247 		if( !del(makeFileInfo(ent)) )
248 			break;
249 }
250 /// ditto
251 void listDirectory(string path, scope bool delegate(FileInfo info) del)
252 {
253 	listDirectory(NativePath(path), del);
254 }
255 /// ditto
256 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(NativePath path)
257 {
258 	int iterator(scope int delegate(ref FileInfo) del){
259 		int ret = 0;
260 		listDirectory(path, (fi){
261 			ret = del(fi);
262 			return ret == 0;
263 		});
264 		return ret;
265 	}
266 	return &iterator;
267 }
268 /// ditto
269 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path)
270 {
271 	return iterateDirectory(NativePath(path));
272 }
273 
274 
275 /**
276 	Returns the current working directory.
277 */
278 NativePath getWorkingDirectory()
279 {
280 	return NativePath(std.file.getcwd());
281 }
282 
283 
284 /** Contains general information about a file.
285 */
286 struct FileInfo {
287 	/// Name of the file (not including the path)
288 	string name;
289 
290 	/// Size of the file (zero for directories)
291 	ulong size;
292 
293 	/// Time of the last modification
294 	SysTime timeModified;
295 
296 	/// Time of creation (not available on all operating systems/file systems)
297 	SysTime timeCreated;
298 
299 	/// True if this is a symlink to an actual file
300 	bool isSymlink;
301 
302 	/// True if this is a directory or a symlink pointing to a directory
303 	bool isDirectory;
304 }
305 
306 /**
307 	Specifies how a file is manipulated on disk.
308 */
309 enum FileMode {
310 	/// The file is opened read-only.
311 	read,
312 	/// The file is opened for read-write random access.
313 	readWrite,
314 	/// The file is truncated if it exists and created otherwise and the opened for read-write access.
315 	createTrunc,
316 	/// The file is opened for appending data to it and created if it does not exist.
317 	append
318 }
319 
320 /**
321 	Accesses the contents of a file as a stream.
322 */
323 
324 private FileInfo makeFileInfo(DirEntry ent)
325 {
326 	FileInfo ret;
327 	ret.name = baseName(ent.name);
328 	if( ret.name.length == 0 ) ret.name = ent.name;
329 	assert(ret.name.length > 0);
330 	ret.isSymlink = ent.isSymlink;
331 	try {
332 		ret.isDirectory = ent.isDir;
333 		ret.size = ent.size;
334 		ret.timeModified = ent.timeLastModified;
335 		version(Windows) ret.timeCreated = ent.timeCreated;
336 		else ret.timeCreated = ent.timeLastModified;
337 	} catch (Exception e) {
338 		logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg);
339 	}
340 	return ret;
341 }