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