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 <ae@cy.md> 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 an empty directory. 34 void mkdir(string path) { return getVFS(path).mkdir(path); } 35 36 /// Create directory (and parents as necessary) at location, if it does not exist. 37 void mkdirRecurse(string path) { return getVFS(path).mkdirRecurse(path); } 38 39 /// Rename file at location. Clobber destination, if it exists. 40 void rename(string from, string to) 41 { 42 if (getVFSName(from) == getVFSName(to)) 43 return getVFS(from).rename(from, to); 44 else 45 throw new Exception("Cannot rename across VFS"); 46 } 47 48 /// Enumerate directory entries. 49 /// Returns an array of file/directory names only. 50 string[] listDir(string path) { return getVFS(path).listDir(path); } 51 52 /// Remove a directory and all its contents recursively. 53 void rmdirRecurse(string path) { return getVFS(path).rmdirRecurse(path); } 54 55 /// Get MD5 digest of file at location. 56 ubyte[16] mdFile(string path) { return getVFS(path).mdFile(path); } 57 58 /// std.file shims 59 S readText(S = string)(string name) 60 { 61 auto result = cast(S) read(name); 62 import std.utf : validate; 63 validate(result); 64 return result; 65 } 66 67 /// ditto 68 void copy(string from, string to) 69 { 70 if (getVFSName(from) == getVFSName(to)) 71 getVFS(from).copy(from, to); 72 else 73 write(to, read(from)); 74 } 75 76 /// ae.sys.file shims 77 void move(string src, string dst) 78 { 79 try 80 src.rename(dst); 81 catch (Exception e) 82 { 83 auto tmp = dst ~ ".ae-tmp"; 84 if (tmp.exists) tmp.remove(); 85 scope(exit) if (tmp.exists) tmp.remove(); 86 src.copy(tmp); 87 tmp.rename(dst); 88 src.remove(); 89 } 90 } 91 92 /// ditto 93 void ensurePathExists(string fn) 94 { 95 import std.path; 96 auto path = dirName(fn); 97 if (!exists(path)) 98 mkdirRecurse(path); 99 } 100 101 /// ditto 102 void safeWrite(string fn, in void[] data) 103 { 104 auto tmp = fn ~ ".ae-tmp"; 105 write(tmp, data); 106 if (fn.exists) fn.remove(); 107 tmp.rename(fn); 108 } 109 110 /// ditto 111 void touch(string path) 112 { 113 if (!getVFSName(path)) 114 return ae.sys.file.touch(path); 115 else 116 safeWrite(path, read(path)); 117 } 118 119 120 // Implementer interface: 121 122 /// Abstract VFS driver base class. 123 class VFS 124 { 125 /// Read entire file at given location. 126 abstract void[] read(string path); 127 128 /// Write entire file at given location (overwrite if exists). 129 abstract void write(string path, const(void)[] data); 130 131 /// Check if file/directory exists at location. 132 abstract bool exists(string path); 133 134 /// Delete file at location. 135 abstract void remove(string path); 136 137 /// Copy file from one location to another (same VFS driver). 138 void copy(string from, string to) { write(to, read(from)); } 139 140 /// Rename file at location. Clobber destination, if it exists. 141 void rename(string from, string to) { copy(from, to); remove(from); } 142 143 /// Create an empty directory. 144 void mkdir(string path) { assert(false, "Not implemented"); } 145 146 /// Create directory (and parents as necessary) at location, if it does not exist. 147 abstract void mkdirRecurse(string path); 148 149 /// Get MD5 digest of file at location. 150 ubyte[16] mdFile(string path) { import std.digest.md; return md5Of(read(path)); } 151 152 /// Enumerate directory entries. 153 string[] listDir(string path) { assert(false, "Not implemented"); } 154 155 /// Remove a directory and all its contents recursively. 156 void rmdirRecurse(string path) { assert(false, "Not implemented"); } 157 } 158 159 /// The VFS registry, a mapping from "protocol" (part before "://") to VFS implementation. 160 VFS[string] registry; 161 162 /// Test a VFS at a certain path. Must end with directory separator. 163 void testVFS(string base) 164 { 165 import std.exception; 166 167 auto testPath0 = base ~ "ae-test0.txt"; 168 auto testPath1 = base ~ "ae-test1.txt"; 169 170 scope(exit) if (testPath0.exists) testPath0.remove(); 171 scope(exit) if (testPath1.exists) testPath1.remove(); 172 173 write(testPath0, "Hello"); 174 assert(testPath0.exists); 175 assert(readText(testPath0) == "Hello"); 176 177 copy(testPath0, testPath1); 178 assert(testPath1.exists); 179 assert(readText(testPath1) == "Hello"); 180 181 remove(testPath0); 182 assert(!testPath0.exists); 183 assertThrown(testPath0.readText()); 184 185 rename(testPath1, testPath0); 186 assert(testPath0.exists); 187 assert(readText(testPath0) == "Hello"); 188 assert(!testPath1.exists); 189 assertThrown(testPath1.readText()); 190 } 191 192 // Other: 193 194 bool isVFSPath(string path) 195 { 196 import ae.utils.text; 197 return path.contains("://"); 198 } /// 199 200 string getVFSName(string path) 201 { 202 import std.string; 203 auto index = indexOf(path, "://"); 204 return index > 0 ? path[0..index] : null; 205 } /// 206 207 VFS getVFS(string path) 208 { 209 auto vfsName = getVFSName(path); 210 auto pvfs = vfsName in registry; 211 assert(pvfs, "Unknown VFS: " ~ vfsName); 212 return *pvfs; 213 } /// 214 215 private: 216 217 static import std.file, ae.sys.file; 218 219 ///////////////////////////////////////////////////////////////////////////// 220 221 /// Pass-thru native filesystem driver. 222 class FS : VFS 223 { 224 override void[] read(string path) { return std.file.read(path); } 225 override void write(string path, const(void)[] data) { return std.file.write(path, data); } 226 override bool exists(string path) { return std.file.exists(path); } 227 override void remove(string path) { return std.file.remove(path); } 228 override void copy(string from, string to) { std.file.copy(from, to); } 229 override void rename(string from, string to) { std.file.rename(from, to); } 230 override void mkdirRecurse(string path) { std.file.mkdirRecurse(path); } 231 override ubyte[16] mdFile(string path) { return ae.sys.file.mdFile(path); } 232 233 override string[] listDir(string path) 234 { 235 import std.algorithm, std.path, std.array; 236 return std.file.dirEntries(path, std.file.SpanMode.shallow).map!(de => de.baseName).array; 237 } 238 239 override void mkdir(string path) { std.file.mkdir(path); } 240 override void rmdirRecurse(string path) { std.file.rmdirRecurse(path); } 241 242 static this() 243 { 244 registry[null] = new FS(); 245 } 246 } 247 248 unittest 249 { 250 testVFS(""); 251 }