1 /** 2 File handling functions and types. 3 4 Copyright: © 2012-2014 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 vibe.core.file; 9 10 public import vibe.core.stream; 11 12 import vibe.core.drivers.threadedfile; // temporarily needed tp get mkstemps to work 13 import vibe.core.driver; 14 15 import core.stdc.stdio; 16 import std.datetime; 17 import std.exception; 18 import std.file; 19 import std.path; 20 import std.string; 21 22 version(Posix){ 23 private extern(C) int mkstemps(char* templ, int suffixlen); 24 } 25 26 @safe: 27 28 29 /** 30 Opens a file stream with the specified mode. 31 */ 32 FileStream openFile(Path path, FileMode mode = FileMode.read) 33 { 34 return getEventDriver().openFile(path, mode); 35 } 36 /// ditto 37 FileStream openFile(string path, FileMode mode = FileMode.read) 38 { 39 return openFile(Path(path), mode); 40 } 41 42 43 /** 44 Read a whole file into a buffer. 45 46 If the supplied buffer is large enough, it will be used to store the 47 contents of the file. Otherwise, a new buffer will be allocated. 48 49 Params: 50 path = The path of the file to read 51 buffer = An optional buffer to use for storing the file contents 52 */ 53 ubyte[] readFile(Path path, ubyte[] buffer = null, size_t max_size = size_t.max) 54 { 55 auto fil = openFile(path); 56 scope (exit) fil.close(); 57 enforce(fil.size <= max_size, "File is too big."); 58 auto sz = cast(size_t)fil.size; 59 auto ret = sz <= buffer.length ? buffer[0 .. sz] : new ubyte[sz]; 60 fil.read(ret); 61 return ret; 62 } 63 /// ditto 64 ubyte[] readFile(string path, ubyte[] buffer = null, size_t max_size = size_t.max) 65 { 66 return readFile(Path(path), buffer, max_size); 67 } 68 69 70 /** 71 Write a whole file at once. 72 */ 73 void writeFile(Path path, in ubyte[] contents) 74 { 75 auto fil = openFile(path, FileMode.createTrunc); 76 scope (exit) fil.close(); 77 fil.write(contents); 78 } 79 /// ditto 80 void writeFile(string path, in ubyte[] contents) 81 { 82 writeFile(Path(path), contents); 83 } 84 85 /** 86 Convenience function to append to a file. 87 */ 88 void appendToFile(Path path, string data) { 89 auto fil = openFile(path, FileMode.append); 90 scope(exit) fil.close(); 91 fil.write(data); 92 } 93 /// ditto 94 void appendToFile(string path, string data) 95 { 96 appendToFile(Path(path), data); 97 } 98 99 /** 100 Read a whole UTF-8 encoded file into a string. 101 102 The resulting string will be sanitized and will have the 103 optional byte order mark (BOM) removed. 104 */ 105 string readFileUTF8(Path path) 106 { 107 import vibe.utils.string; 108 109 return stripUTF8Bom(sanitizeUTF8(readFile(path))); 110 } 111 /// ditto 112 string readFileUTF8(string path) 113 { 114 return readFileUTF8(Path(path)); 115 } 116 117 118 /** 119 Write a string into a UTF-8 encoded file. 120 121 The file will have a byte order mark (BOM) prepended. 122 */ 123 void writeFileUTF8(Path path, string contents) 124 { 125 static immutable ubyte[] bom = [0xEF, 0xBB, 0xBF]; 126 auto fil = openFile(path, FileMode.createTrunc); 127 scope (exit) fil.close(); 128 fil.write(bom); 129 fil.write(contents); 130 } 131 132 /** 133 Creates and opens a temporary file for writing. 134 */ 135 FileStream createTempFile(string suffix = null) 136 { 137 version(Windows){ 138 import std.conv : to; 139 string tmpname; 140 () @trusted { 141 auto fn = tmpnam(null); 142 enforce(fn !is null, "Failed to generate temporary name."); 143 tmpname = to!string(fn); 144 } (); 145 if( tmpname.startsWith("\\") ) tmpname = tmpname[1 .. $]; 146 tmpname ~= suffix; 147 return openFile(tmpname, FileMode.createTrunc); 148 } else { 149 enum pattern ="/tmp/vtmp.XXXXXX"; 150 scope templ = new char[pattern.length+suffix.length+1]; 151 templ[0 .. pattern.length] = pattern; 152 templ[pattern.length .. $-1] = (suffix)[]; 153 templ[$-1] = '\0'; 154 assert(suffix.length <= int.max); 155 auto fd = () @trusted { return mkstemps(templ.ptr, cast(int)suffix.length); } (); 156 enforce(fd >= 0, "Failed to create temporary file."); 157 return new ThreadedFileStream(fd, Path(templ[0 .. $-1].idup), FileMode.createTrunc); 158 } 159 } 160 161 /** 162 Moves or renames a file. 163 164 Params: 165 from = Path to the file/directory to move/rename. 166 to = The target path 167 copy_fallback = Determines if copy/remove should be used in case of the 168 source and destination path pointing to different devices. 169 */ 170 void moveFile(Path from, Path to, bool copy_fallback = false) 171 { 172 moveFile(from.toNativeString(), to.toNativeString(), copy_fallback); 173 } 174 /// ditto 175 void moveFile(string from, string to, bool copy_fallback = false) 176 { 177 if (!copy_fallback) { 178 std.file.rename(from, to); 179 } else { 180 try { 181 std.file.rename(from, to); 182 } catch (FileException e) { 183 std.file.copy(from, to); 184 std.file.remove(from); 185 } 186 } 187 } 188 189 /** 190 Copies a file. 191 192 Note that attributes and time stamps are currently not retained. 193 194 Params: 195 from = Path of the source file 196 to = Path for the destination file 197 overwrite = If true, any file existing at the destination path will be 198 overwritten. If this is false, an exception will be thrown should 199 a file already exist at the destination path. 200 201 Throws: 202 An Exception if the copy operation fails for some reason. 203 */ 204 void copyFile(Path from, Path to, bool overwrite = false) 205 { 206 { 207 auto src = openFile(from, FileMode.read); 208 scope(exit) src.close(); 209 enforce(overwrite || !existsFile(to), "Destination file already exists."); 210 auto dst = openFile(to, FileMode.createTrunc); 211 scope(exit) dst.close(); 212 src.pipe(dst); 213 } 214 215 // TODO: retain attributes and time stamps 216 } 217 /// ditto 218 void copyFile(string from, string to) 219 { 220 copyFile(Path(from), Path(to)); 221 } 222 223 /** 224 Removes a file 225 */ 226 void removeFile(Path path) 227 { 228 removeFile(path.toNativeString()); 229 } 230 /// ditto 231 void removeFile(string path) 232 { 233 std.file.remove(path); 234 } 235 236 /** 237 Checks if a file exists 238 */ 239 bool existsFile(Path path) nothrow 240 { 241 return existsFile(path.toNativeString()); 242 } 243 /// ditto 244 bool existsFile(string path) nothrow 245 { 246 return std.file.exists(path); 247 } 248 249 /** Stores information about the specified file/directory into 'info' 250 251 Throws: A `FileException` is thrown if the file does not exist. 252 */ 253 FileInfo getFileInfo(Path path) 254 @trusted { 255 auto ent = DirEntry(path.toNativeString()); 256 return makeFileInfo(ent); 257 } 258 /// ditto 259 FileInfo getFileInfo(string path) 260 { 261 return getFileInfo(Path(path)); 262 } 263 264 /** 265 Creates a new directory. 266 */ 267 void createDirectory(Path path) 268 { 269 mkdir(path.toNativeString()); 270 } 271 /// ditto 272 void createDirectory(string path) 273 { 274 createDirectory(Path(path)); 275 } 276 277 /** 278 Enumerates all files in the specified directory. 279 */ 280 void listDirectory(Path path, scope bool delegate(FileInfo info) del) 281 @trusted { 282 foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) ) 283 if( !del(makeFileInfo(ent)) ) 284 break; 285 } 286 /// ditto 287 void listDirectory(string path, scope bool delegate(FileInfo info) del) 288 { 289 listDirectory(Path(path), del); 290 } 291 /// ditto 292 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(Path path) 293 { 294 int iterator(scope int delegate(ref FileInfo) del){ 295 int ret = 0; 296 listDirectory(path, (fi){ 297 ret = del(fi); 298 return ret == 0; 299 }); 300 return ret; 301 } 302 return &iterator; 303 } 304 /// ditto 305 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path) 306 { 307 return iterateDirectory(Path(path)); 308 } 309 310 /** 311 Starts watching a directory for changes. 312 */ 313 DirectoryWatcher watchDirectory(Path path, bool recursive = true) 314 { 315 return getEventDriver().watchDirectory(path, recursive); 316 } 317 // ditto 318 DirectoryWatcher watchDirectory(string path, bool recursive = true) 319 { 320 return watchDirectory(Path(path), recursive); 321 } 322 323 /** 324 Returns the current working directory. 325 */ 326 Path getWorkingDirectory() 327 { 328 return Path(() @trusted { return std.file.getcwd(); } ()); 329 } 330 331 332 /** Contains general information about a file. 333 */ 334 struct FileInfo { 335 /// Name of the file (not including the path) 336 string name; 337 338 /// Size of the file (zero for directories) 339 ulong size; 340 341 /// Time of the last modification 342 SysTime timeModified; 343 344 /// Time of creation (not available on all operating systems/file systems) 345 SysTime timeCreated; 346 347 /// True if this is a symlink to an actual file 348 bool isSymlink; 349 350 /// True if this is a directory or a symlink pointing to a directory 351 bool isDirectory; 352 } 353 354 /** 355 Specifies how a file is manipulated on disk. 356 */ 357 enum FileMode { 358 /// The file is opened read-only. 359 read, 360 /// The file is opened for read-write random access. 361 readWrite, 362 /// The file is truncated if it exists or created otherwise and then opened for read-write access. 363 createTrunc, 364 /// The file is opened for appending data to it and created if it does not exist. 365 append 366 } 367 368 /** 369 Accesses the contents of a file as a stream. 370 */ 371 interface FileStream : RandomAccessStream { 372 @safe: 373 374 /// The path of the file. 375 @property Path path() const nothrow; 376 377 /// Determines if the file stream is still open 378 @property bool isOpen() const; 379 380 /// Closes the file handle. 381 void close(); 382 } 383 384 385 /** 386 Interface for directory watcher implementations. 387 388 Directory watchers monitor the contents of a directory (wither recursively or non-recursively) 389 for changes, such as file additions, deletions or modifications. 390 */ 391 interface DirectoryWatcher { 392 @safe: 393 394 /// The path of the watched directory 395 @property Path path() const; 396 397 /// Indicates if the directory is watched recursively 398 @property bool recursive() const; 399 400 /** Fills the destination array with all changes that occurred since the last call. 401 402 The function will block until either directory changes have occurred or until the 403 timeout has elapsed. Specifying a negative duration will cause the function to 404 wait without a timeout. 405 406 Params: 407 dst = The destination array to which the changes will be appended 408 timeout = Optional timeout for the read operation 409 410 Returns: 411 If the call completed successfully, true is returned. 412 */ 413 bool readChanges(ref DirectoryChange[] dst, Duration timeout = dur!"seconds"(-1)); 414 } 415 416 417 /** Specifies the kind of change in a watched directory. 418 */ 419 enum DirectoryChangeType { 420 /// A file or directory was added 421 added, 422 /// A file or directory was deleted 423 removed, 424 /// A file or directory was modified 425 modified 426 } 427 428 429 /** Describes a single change in a watched directory. 430 */ 431 struct DirectoryChange { 432 /// The type of change 433 DirectoryChangeType type; 434 435 /// Path of the file/directory that was changed 436 Path path; 437 } 438 439 440 private FileInfo makeFileInfo(DirEntry ent) 441 @trusted { 442 FileInfo ret; 443 ret.name = baseName(ent.name); 444 if( ret.name.length == 0 ) ret.name = ent.name; 445 assert(ret.name.length > 0); 446 ret.size = ent.size; 447 ret.timeModified = ent.timeLastModified; 448 version(Windows) ret.timeCreated = ent.timeCreated; 449 else ret.timeCreated = ent.timeLastModified; 450 ret.isSymlink = ent.isSymlink; 451 ret.isDirectory = ent.isDir; 452 return ret; 453 }