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.path;
11 
12 import dub.internal.logging;
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 /// Writes `buffer` to a file
26 public void writeFile(NativePath path, const void[] buffer)
27 {
28 	std.file.write(path.toNativeString(), buffer);
29 }
30 
31 /// Returns the content of a file
32 public ubyte[] readFile(NativePath path)
33 {
34 	return cast(ubyte[]) std.file.read(path.toNativeString());
35 }
36 
37 /// Returns the content of a file as text
38 public string readText(NativePath path)
39 {
40     return std.file.readText(path.toNativeString());
41 }
42 
43 /**
44 	Moves or renames a file.
45 */
46 void moveFile(NativePath from, NativePath to)
47 {
48 	moveFile(from.toNativeString(), to.toNativeString());
49 }
50 /// ditto
51 void moveFile(string from, string to)
52 {
53 	std.file.rename(from, to);
54 }
55 
56 /**
57 	Copies a file.
58 
59 	Note that attributes and time stamps are currently not retained.
60 
61 	Params:
62 		from = NativePath of the source file
63 		to = NativePath for the destination file
64 		overwrite = If true, any file existing at the destination path will be
65 			overwritten. If this is false, an exception will be thrown should
66 			a file already exist at the destination path.
67 			This also sets the target as writable to ensure future invocations
68 			will not fail.
69 
70 	Throws:
71 		An Exception if the copy operation fails for some reason.
72 */
73 void copyFile(NativePath from, NativePath to, bool overwrite = false)
74 {
75 	enforce(existsFile(from), "Source file does not exist.");
76 
77 	if (existsFile(to)) {
78 		enforce(overwrite, "Destination file already exists.");
79 		// remove file before copy to allow "overwriting" files that are in
80 		// use on Linux
81 		removeFile(to);
82 	}
83 
84 	static if (is(PreserveAttributes))
85 	{
86 		.copy(from.toNativeString(), to.toNativeString(), PreserveAttributes.yes);
87 	}
88 	else
89 	{
90 		.copy(from.toNativeString(), to.toNativeString());
91 		// try to preserve ownership/permissions in Posix
92 		version (Posix) {
93 			import core.sys.posix.sys.stat;
94 			import core.sys.posix.unistd;
95 			import std.utf;
96 			auto cspath = toUTFz!(const(char)*)(from.toNativeString());
97 			auto cdpath = toUTFz!(const(char)*)(to.toNativeString());
98 			stat_t st;
99 			enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file.");
100 			if (chown(cdpath, st.st_uid, st.st_gid) != 0)
101 				st.st_mode &= ~(S_ISUID | S_ISGID);
102 			chmod(cdpath, st.st_mode);
103 		}
104 	}
105 	if (overwrite) makeWritable(to);
106 }
107 /// ditto
108 void copyFile(string from, string to)
109 {
110 	copyFile(NativePath(from), NativePath(to));
111 }
112 
113 private bool isWritable(NativePath name)
114 {
115 	version (Windows)
116 	{
117 		import core.sys.windows.windows;
118 
119 		return (name.toNativeString.getAttributes & FILE_ATTRIBUTE_READONLY) == 0;
120 	}
121 	else version (Posix)
122 	{
123 		import core.sys.posix.sys.stat;
124 
125 		return (name.toNativeString.getAttributes & S_IWUSR) != 0;
126 	}
127 	else
128 		static assert(false, "Needs implementation.");
129 }
130 
131 private void makeWritable(NativePath name)
132 {
133 	makeWritable(name.toNativeString);
134 }
135 
136 private void makeWritable(string name)
137 {
138 	version (Windows)
139 	{
140 		import core.sys.windows.windows;
141 
142 		name.setAttributes(name.getAttributes & ~FILE_ATTRIBUTE_READONLY);
143 	}
144 	else version (Posix)
145 	{
146 		import core.sys.posix.sys.stat;
147 
148 		name.setAttributes(name.getAttributes | S_IWUSR);
149 	}
150 	else
151 		static assert(false, "Needs implementation.");
152 }
153 
154 /**
155 	Removes a file
156 */
157 void removeFile(NativePath path)
158 {
159 	removeFile(path.toNativeString());
160 }
161 /// ditto
162 void removeFile(string path) {
163 	std.file.remove(path);
164 }
165 
166 /**
167 	Checks if a file exists
168 */
169 bool existsFile(NativePath path) {
170 	return existsFile(path.toNativeString());
171 }
172 /// ditto
173 bool existsFile(string path)
174 {
175 	return std.file.exists(path);
176 }
177 
178 /// Checks if a directory exists
179 bool existsDirectory(NativePath path) {
180 	if( !existsFile(path) ) return false;
181 	auto fi = getFileInfo(path);
182 	return fi.isDirectory;
183 }
184 
185 /** Stores information about the specified file/directory into 'info'
186 
187 	Returns false if the file does not exist.
188 */
189 FileInfo getFileInfo(NativePath path)
190 {
191 	auto ent = std.file.DirEntry(path.toNativeString());
192 	return makeFileInfo(ent);
193 }
194 /// ditto
195 FileInfo getFileInfo(string path)
196 {
197 	return getFileInfo(NativePath(path));
198 }
199 
200 /**
201 	Creates a new directory.
202 */
203 void ensureDirectory(NativePath path)
204 {
205 	mkdirRecurse(path.toNativeString());
206 }
207 
208 /**
209 	Enumerates all files in the specified directory.
210 */
211 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(NativePath path)
212 {
213 	int iterator(scope int delegate(ref FileInfo) del){
214 		foreach (DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow)) {
215 			auto fi = makeFileInfo(ent);
216 			if (auto res = del(fi))
217 				return res;
218 		}
219 		return 0;
220 	}
221 	return &iterator;
222 }
223 
224 /**
225 	Returns the current working directory.
226 */
227 NativePath getWorkingDirectory()
228 {
229 	return NativePath(std.file.getcwd());
230 }
231 
232 
233 /** Contains general information about a file.
234 */
235 struct FileInfo {
236 	/// Name of the file (not including the path)
237 	string name;
238 
239 	/// Size of the file (zero for directories)
240 	ulong size;
241 
242 	/// Time of the last modification
243 	SysTime timeModified;
244 
245 	/// True if this is a symlink to an actual file
246 	bool isSymlink;
247 
248 	/// True if this is a directory or a symlink pointing to a directory
249 	bool isDirectory;
250 }
251 
252 /**
253 	Specifies how a file is manipulated on disk.
254 */
255 enum FileMode {
256 	/// The file is opened read-only.
257 	read,
258 	/// The file is opened for read-write random access.
259 	readWrite,
260 	/// The file is truncated if it exists and created otherwise and the opened for read-write access.
261 	createTrunc,
262 	/// The file is opened for appending data to it and created if it does not exist.
263 	append
264 }
265 
266 /**
267 	Accesses the contents of a file as a stream.
268 */
269 
270 private FileInfo makeFileInfo(DirEntry ent)
271 {
272 	FileInfo ret;
273 	ret.name = baseName(ent.name);
274 	if( ret.name.length == 0 ) ret.name = ent.name;
275 	assert(ret.name.length > 0);
276 	ret.isSymlink = ent.isSymlink;
277 	try {
278 		ret.isDirectory = ent.isDir;
279 		ret.size = ent.size;
280 		ret.timeModified = ent.timeLastModified;
281 	} catch (Exception e) {
282 		logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg);
283 	}
284 	return ret;
285 }