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