1 /** 2 * A basic virtual filesystem API. 3 * Intended as a drop-in std.file replacement. 4 * VFS driver is indicated by "driver://" prefix 5 * ("//" cannot exist in a valid filesystem path). 6 * 7 * License: 8 * This Source Code Form is subject to the terms of 9 * the Mozilla Public License, v. 2.0. If a copy of 10 * the MPL was not distributed with this file, You 11 * can obtain one at http://mozilla.org/MPL/2.0/. 12 * 13 * Authors: 14 * Vladimir Panteleev <vladimir@thecybershadow.net> 15 */ 16 17 module ae.sys.vfs; 18 19 // User interface: 20 21 /// Read entire file at given location. 22 void[] read(string path) { return getVFS(path).read(path); } 23 24 /// Write entire file at given location (overwrite if exists). 25 void write(string path, const(void)[] data) { return getVFS(path).write(path, data); } 26 27 /// Check if file/directory exists at location. 28 @property bool exists(string path) { return getVFS(path).exists(path); } 29 30 /// Delete file at location. 31 void remove(string path) { return getVFS(path).remove(path); } 32 33 /// Create directory ( and parents as necessary) at location, if it does not exist. 34 void mkdirRecurse(string path) { return getVFS(path).mkdirRecurse(path); } 35 36 /// Rename file at location. Clobber destination, if it exists. 37 void rename(string from, string to) 38 { 39 if (getVFSName(from) == getVFSName(to)) 40 return getVFS(from).rename(from, to); 41 else 42 throw new Exception("Cannot rename across VFS"); 43 } 44 45 /// Enumerate directory entries. 46 /// Returns an array of file/directory names only. 47 string[] listDir(string path) { return getVFS(path).listDir(path); } 48 49 /// Get MD5 digest of file at location. 50 ubyte[16] mdFile(string path) { return getVFS(path).mdFile(path); } 51 52 /// std.file shims 53 S readText(S = string)(string name) 54 { 55 auto result = cast(S) read(name); 56 import std.utf; 57 validate(result); 58 return result; 59 } 60 61 /// ditto 62 void copy(string from, string to) 63 { 64 if (getVFSName(from) == getVFSName(to)) 65 getVFS(from).copy(from, to); 66 else 67 write(to, read(from)); 68 } 69 70 /// ae.sys.file shims 71 void move(string src, string dst) 72 { 73 try 74 src.rename(dst); 75 catch (Exception e) 76 { 77 auto tmp = dst ~ ".ae-tmp"; 78 if (tmp.exists) tmp.remove(); 79 scope(exit) if (tmp.exists) tmp.remove(); 80 src.copy(tmp); 81 tmp.rename(dst); 82 src.remove(); 83 } 84 } 85 86 /// ditto 87 void ensurePathExists(string fn) 88 { 89 import std.path; 90 auto path = dirName(fn); 91 if (!exists(path)) 92 mkdirRecurse(path); 93 } 94 95 /// ditto 96 void safeWrite(string fn, in void[] data) 97 { 98 auto tmp = fn ~ ".ae-tmp"; 99 write(tmp, data); 100 if (fn.exists) fn.remove(); 101 tmp.rename(fn); 102 } 103 104 /// ditto 105 void touch(string path) 106 { 107 if (!getVFSName(path)) 108 return ae.sys.file.touch(path); 109 else 110 safeWrite(path, read(path)); 111 } 112 113 114 // Implementer interface: 115 116 /// Abstract VFS driver base class. 117 class VFS 118 { 119 /// Read entire file at given location. 120 abstract void[] read(string path); 121 122 /// Write entire file at given location (overwrite if exists). 123 abstract void write(string path, const(void)[] data); 124 125 /// Check if file/directory exists at location. 126 abstract bool exists(string path); 127 128 /// Delete file at location. 129 abstract void remove(string path); 130 131 /// Copy file from one location to another (same VFS driver). 132 void copy(string from, string to) { write(to, read(from)); } 133 134 /// Rename file at location. Clobber destination, if it exists. 135 void rename(string from, string to) { copy(from, to); remove(from); } 136 137 /// Create directory (and parents as necessary) at location, if it does not exist. 138 abstract void mkdirRecurse(string path); 139 140 /// Get MD5 digest of file at location. 141 ubyte[16] mdFile(string path) { import std.digest.md; return md5Of(read(path)); } 142 143 /// Enumerate directory entries. 144 string[] listDir(string path) { assert(false, "Not implemented"); } 145 } 146 147 VFS[string] registry; 148 149 /// Test a VFS at a certain path. Must end with directory separator. 150 void testVFS(string base) 151 { 152 import std.exception; 153 154 auto testPath0 = base ~ "ae-test0.txt"; 155 auto testPath1 = base ~ "ae-test1.txt"; 156 157 scope(exit) if (testPath0.exists) testPath0.remove(); 158 scope(exit) if (testPath1.exists) testPath1.remove(); 159 160 write(testPath0, "Hello"); 161 assert(testPath0.exists); 162 assert(readText(testPath0) == "Hello"); 163 164 copy(testPath0, testPath1); 165 assert(testPath1.exists); 166 assert(readText(testPath1) == "Hello"); 167 168 remove(testPath0); 169 assert(!testPath0.exists); 170 assertThrown(testPath0.readText()); 171 172 rename(testPath1, testPath0); 173 assert(testPath0.exists); 174 assert(readText(testPath0) == "Hello"); 175 assert(!testPath1.exists); 176 assertThrown(testPath1.readText()); 177 } 178 179 // Other: 180 181 bool isVFSPath(string path) 182 { 183 import ae.utils.text; 184 return path.contains("://"); 185 } 186 187 string getVFSName(string path) 188 { 189 import std.string; 190 auto index = indexOf(path, "://"); 191 return index > 0 ? path[0..index] : null; 192 } 193 194 VFS getVFS(string path) 195 { 196 auto vfsName = getVFSName(path); 197 auto pvfs = vfsName in registry; 198 assert(pvfs, "Unknown VFS: " ~ vfsName); 199 return *pvfs; 200 } 201 202 private: 203 204 static import std.file, ae.sys.file; 205 206 ///////////////////////////////////////////////////////////////////////////// 207 208 /// Pass-thru native filesystem driver. 209 class FS : VFS 210 { 211 override void[] read(string path) { return std.file.read(path); } 212 override void write(string path, const(void)[] data) { return std.file.write(path, data); } 213 override bool exists(string path) { return std.file.exists(path); } 214 override void remove(string path) { return std.file.remove(path); } 215 override void copy(string from, string to) { std.file.copy(from, to); } 216 override void rename(string from, string to) { std.file.rename(from, to); } 217 override void mkdirRecurse(string path) { std.file.mkdirRecurse(path); } 218 override ubyte[16] mdFile(string path) { return ae.sys.file.mdFile(path); } 219 220 override string[] listDir(string path) 221 { 222 import std.algorithm, std.path, std.array; 223 return std.file.dirEntries(path, std.file.SpanMode.shallow).map!(de => de.baseName).array; 224 } 225 226 static this() 227 { 228 registry[null] = new FS(); 229 } 230 } 231 232 unittest 233 { 234 testVFS(""); 235 }