1 // Written in the D programming language.
2
3 /**
4
5 This module contains cross-platform file access utilities
6
7
8
9 Synopsis:
10
11 ----
12 import exlib.files;
13 ----
14
15 Copyright: Vadim Lopatin, 2014
16 License: Boost License 1.0
17 Authors: Vadim Lopatin, coolreader.org@gmail.com
18 */
19 module dlangui.core.files;
20
21 import std.algorithm;
22
23 private import dlangui.core.logger;
24 private import std.process;
25 private import std.path;
26 private import std.file;
27 private import std.utf;
28
29 version (Windows) {
30 /// path delimiter (\ for windows, / for others)
31 immutable char PATH_DELIMITER = '\\';
32 } else {
33 /// path delimiter (\ for windows, / for others)
34 immutable char PATH_DELIMITER = '/';
35 }
36
37 /// Filesystem root entry / bookmark types
38 enum RootEntryType : uint {
39 /// filesystem root
40 ROOT,
41 /// current user home
42 HOME,
43 /// removable drive
44 REMOVABLE,
45 /// fixed drive
46 FIXED,
47 /// network
48 NETWORK,
49 /// cd rom
50 CDROM,
51 /// sd card
52 SDCARD,
53 /// custom bookmark
54 BOOKMARK,
55 }
56
57 /// Filesystem root entry item
58 struct RootEntry {
59 private RootEntryType _type;
60 private string _path;
61 private dstring _display;
62 this(RootEntryType type, string path, dstring display = null) {
63 _type = type;
64 _path = path;
65 _display = display;
66 if (display is null) {
67 _display = toUTF32(baseName(path));
68 }
69 }
70 /// Returns type
71 @property RootEntryType type() { return _type; }
72 /// Returns path
73 @property string path() { return _path; }
74 /// Returns display label
75 @property dstring label() { return _display; }
76 /// Returns icon resource id
77 @property string icon() {
78 switch (type) {
79 case RootEntryType.NETWORK:
80 return "folder-network";
81 case RootEntryType.BOOKMARK:
82 return "folder-bookmark";
83 case RootEntryType.CDROM:
84 return "drive-optical";
85 case RootEntryType.FIXED:
86 return "drive-harddisk";
87 case RootEntryType.HOME:
88 return "user-home";
89 case RootEntryType.ROOT:
90 return "computer";
91 case RootEntryType.SDCARD:
92 return "media-flash-sd-mmc";
93 case RootEntryType.REMOVABLE:
94 return "device-removable-media";
95 default:
96 return "folder-blue";
97 }
98 }
99 }
100
101 /// Returns
102 @property RootEntry homeEntry() {
103 return RootEntry(RootEntryType.HOME, homePath);
104 }
105
106 /// returns array of system root entries
107 @property RootEntry[] getRootPaths() {
108 RootEntry[] res;
109 res ~= RootEntry(RootEntryType.HOME, homePath);
110 version (posix) {
111 res ~= RootEntry(RootEntryType.ROOT, "/", "File System"d);
112 }
113 version (Windows) {
114 import win32.windows;
115 uint mask = GetLogicalDrives();
116 for (int i = 0; i < 26; i++) {
117 if (mask & (1 << i)) {
118 char letter = cast(char)('A' + i);
119 string path = "" ~ letter ~ ":\\";
120 dstring display = ""d ~ letter ~ ":"d;
121 // detect drive type
122 RootEntryType type;
123 uint wtype = GetDriveTypeA(("" ~ path).ptr);
124 //Log.d("Drive ", path, " type ", wtype);
125 switch (wtype) {
126 case DRIVE_REMOVABLE:
127 type = RootEntryType.REMOVABLE;
128 break;
129 case DRIVE_REMOTE:
130 type = RootEntryType.NETWORK;
131 break;
132 case DRIVE_CDROM:
133 type = RootEntryType.CDROM;
134 break;
135 default:
136 type = RootEntryType.FIXED;
137 break;
138 }
139 res ~= RootEntry(type, path, display);
140 }
141 }
142 }
143 return res;
144 }
145
146 /// returns true if directory is root directory (e.g. / or C:\)
147 bool isRoot(string path) {
148 string root = rootName(path);
149 if (path.equal(root))
150 return true;
151 return false;
152 }
153
154 /// returns parent directory for specified path
155 string parentDir(string path) {
156 return buildNormalizedPath(path, "..");
157 }
158
159 /// check filename with pattern (currently only *.ext pattern is supported)
160 bool filterFilename(string filename, string pattern) {
161 if (pattern.equal("*.*"))
162 return true; // matches any
163 if (pattern.length < 3)
164 return false;
165 if (pattern[0] != '*' || pattern[1] != '.')
166 return false;
167 return filename.endsWith(pattern[1..$]);
168 }
169
170 /// Filters file name by pattern list
171 bool filterFilename(string filename, string[] filters) {
172 if (filters.length == 0)
173 return true; // no filters - show all
174 foreach(pattern; filters) {
175 if (filterFilename(filename, pattern))
176 return true;
177 }
178 return false;
179 }
180
181 /** List directory content
182
183 Optionally filters file names by filter.
184
185 Result will be placed into entries array.
186
187 Returns true if directory exists and listed successfully, false otherwise.
188 */
189 bool listDirectory(string dir, bool includeDirs, bool includeFiles, bool showHiddenFiles, string[] filters, ref DirEntry[] entries) {
190
191 entries.length = 0;
192
193 if (!isDir(dir)) {
194 return false;
195 }
196
197 if (!isRoot(dir) && includeDirs) {
198 entries ~= DirEntry(appendPath(dir, ".."));
199 }
200
201 try {
202 DirEntry[] dirs;
203 DirEntry[] files;
204 foreach (DirEntry e; dirEntries(dir, SpanMode.shallow)) {
205 string fn = baseName(e.name);
206 if (!showHiddenFiles && fn.startsWith("."))
207 continue;
208 if (e.isDir) {
209 dirs ~= e;
210 } else if (e.isFile) {
211 files ~= e;
212 }
213 }
214 if (includeDirs)
215 foreach(DirEntry e; dirs)
216 entries ~= e;
217 if (includeFiles)
218 foreach(DirEntry e; files)
219 if (filterFilename(e.name, filters))
220 entries ~= e;
221 return true;
222 } catch (FileException e) {
223 return false;
224 }
225
226 }
227
228 /** Returns true if char ch is / or \ slash */
229 bool isPathDelimiter(char ch) {
230 return ch == '/' || ch == '\\';
231 }
232
233 /// Returns current directory
234 @property string currentDir() {
235 return getcwd();
236 }
237
238 /** Returns current executable path only, including last path delimiter - removes executable name from result of std.file.thisExePath() */
239 @property string exePath() {
240 string path = thisExePath();
241 int lastSlash = 0;
242 for (int i = 0; i < path.length; i++)
243 if (path[i] == PATH_DELIMITER)
244 lastSlash = i;
245 return path[0 .. lastSlash + 1];
246 }
247
248 /// Returns user's home directory
249 @property string homePath() {
250 string path;
251 version (Windows) {
252 path = environment.get("USERPROFILE");
253 if (path is null)
254 path = environment.get("HOME");
255 } else {
256 path = environment.get("HOME");
257 }
258 if (path is null)
259 path = "."; // fallback to current directory
260 return path;
261 }
262
263 /**
264
265 Returns application data directory
266
267 On unix, it will return path to subdirectory in home directory - e.g. /home/user/.subdir if ".subdir" is passed as a paramter.
268
269 On windows, it will return path to subdir in APPDATA directory - e.g. C:\Users\User\AppData\Roaming\.subdir.
270
271 */
272 string appDataPath(string subdir = null) {
273 string path;
274 version (Windows) {
275 path = environment.get("APPDATA");
276 }
277 if (path is null)
278 path = homePath;
279 if (subdir !is null) {
280 path ~= PATH_DELIMITER;
281 path ~= subdir;
282 }
283 return path;
284 }
285
286 /// Converts path delimiters to standard for platform inplace in buffer(e.g. / to \ on windows, \ to / on posix), returns buf
287 char[] convertPathDelimiters(char[] buf) {
288 foreach(ref ch; buf) {
289 version (Windows) {
290 if (ch == '/')
291 ch = '\\';
292 } else {
293 if (ch == '\\')
294 ch = '/';
295 }
296 }
297 return buf;
298 }
299
300 /** Converts path delimiters to standard for platform (e.g. / to \ on windows, \ to / on posix) */
301 string convertPathDelimiters(string src) {
302 char[] buf = src.dup;
303 return cast(string)convertPathDelimiters(buf);
304 }
305
306 /** Appends file path parts with proper delimiters e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
307 string appendPath(string[] pathItems ...) {
308 char[] buf;
309 foreach (s; pathItems) {
310 if (buf.length && !isPathDelimiter(buf[$-1]))
311 buf ~= PATH_DELIMITER;
312 buf ~= s;
313 }
314 return convertPathDelimiters(buf).dup;
315 }
316
317 /** Appends file path parts with proper delimiters (as well converts delimiters inside path to system) to buffer e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
318 char[] appendPath(char[] buf, string[] pathItems ...) {
319 foreach (s; pathItems) {
320 if (buf.length && !isPathDelimiter(buf[$-1]))
321 buf ~= PATH_DELIMITER;
322 buf ~= s;
323 }
324 return convertPathDelimiters(buf);
325 }
326
327 /** Split path into elements, e.g. /home/user/dir1 -> ["home", "user", "dir1"], "c:\dir1\dir2" -> ["c:", "dir1", "dir2"] */
328 string[] splitPath(string path) {
329 string[] res;
330 int start = 0;
331 for (int i = 0; i <= path.length; i++) {
332 char ch = i < path.length ? path[i] : 0;
333 if (ch == '\\' || ch == '/' || ch == 0) {
334 if (start < i)
335 res ~= path[start .. i].dup;
336 start = i + 1;
337 }
338 }
339 return res;
340 }