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 }