1 /** 2 File handling. 3 4 Copyright: © 2012 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 dub.internal.vibecompat.core.file; 9 10 public import dub.internal.vibecompat.inet.url; 11 12 import dub.internal.vibecompat.core.log; 13 14 import std.conv; 15 import core.stdc.stdio; 16 import std.datetime; 17 import std.exception; 18 import std.file; 19 import std.path; 20 import std.stdio; 21 import std.string; 22 import std.utf; 23 24 25 /* Add output range support to File 26 */ 27 struct RangeFile { 28 std.stdio.File file; 29 30 void put(in ubyte[] bytes) { file.rawWrite(bytes); } 31 void put(in char[] str) { put(cast(ubyte[])str); } 32 void put(char ch) { put((&ch)[0 .. 1]); } 33 void put(dchar ch) { char[4] chars; put(chars[0 .. encode(chars, ch)]); } 34 35 ubyte[] readAll() 36 { 37 auto sz = file.size; 38 enforce(sz <= size_t.max, "File is too big to read to memory."); 39 file.seek(0, SEEK_SET); 40 auto ret = new ubyte[cast(size_t)sz]; 41 rawRead(ret); 42 return ret; 43 } 44 45 void rawRead(ubyte[] dst) { enforce(file.rawRead(dst).length == dst.length, "Failed to readall bytes from file."); } 46 void write(string str) { put(str); } 47 void close() { file.close(); } 48 void flush() { file.flush(); } 49 @property ulong size() { return file.size; } 50 } 51 52 53 /** 54 Opens a file stream with the specified mode. 55 */ 56 RangeFile openFile(Path path, FileMode mode = FileMode.read) 57 { 58 string fmode; 59 final switch(mode){ 60 case FileMode.read: fmode = "rb"; break; 61 case FileMode.readWrite: fmode = "r+b"; break; 62 case FileMode.createTrunc: fmode = "wb"; break; 63 case FileMode.append: fmode = "ab"; break; 64 } 65 auto ret = std.stdio.File(path.toNativeString(), fmode); 66 assert(ret.isOpen); 67 return RangeFile(ret); 68 } 69 /// ditto 70 RangeFile openFile(string path, FileMode mode = FileMode.read) 71 { 72 return openFile(Path(path), mode); 73 } 74 75 76 /** 77 Moves or renames a file. 78 */ 79 void moveFile(Path from, Path to) 80 { 81 moveFile(from.toNativeString(), to.toNativeString()); 82 } 83 /// ditto 84 void moveFile(string from, string to) 85 { 86 std.file.rename(from, to); 87 } 88 89 /** 90 Copies a file. 91 92 Note that attributes and time stamps are currently not retained. 93 94 Params: 95 from = Path of the source file 96 to = Path for the destination file 97 overwrite = If true, any file existing at the destination path will be 98 overwritten. If this is false, an excpetion will be thrown should 99 a file already exist at the destination path. 100 101 Throws: 102 An Exception if the copy operation fails for some reason. 103 */ 104 void copyFile(Path from, Path to, bool overwrite = false) 105 { 106 enforce(existsFile(from), "Source file does not exist."); 107 108 if (existsFile(to)) { 109 enforce(overwrite, "Destination file already exists."); 110 // remove file before copy to allow "overwriting" files that are in 111 // use on Linux 112 removeFile(to); 113 } 114 115 .copy(from.toNativeString(), to.toNativeString()); 116 117 // try to preserve ownership/permissions in Posix 118 version (Posix) { 119 import core.sys.posix.sys.stat; 120 import core.sys.posix.unistd; 121 import std.utf; 122 auto cspath = toUTFz!(const(char)*)(from.toNativeString()); 123 auto cdpath = toUTFz!(const(char)*)(to.toNativeString()); 124 stat_t st; 125 enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file."); 126 if (chown(cdpath, st.st_uid, st.st_gid) != 0) 127 st.st_mode &= ~(S_ISUID | S_ISGID); 128 chmod(cdpath, st.st_mode); 129 } 130 } 131 /// ditto 132 void copyFile(string from, string to) 133 { 134 copyFile(Path(from), Path(to)); 135 } 136 137 version (Windows) extern(Windows) int CreateHardLinkW(in wchar* to, in wchar* from, void* attr=null); 138 139 // guess whether 2 files are identical, ignores filename and content 140 private bool sameFile(Path a, Path b) 141 { 142 static assert(__traits(allMembers, FileInfo)[0] == "name"); 143 return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $]; 144 } 145 146 /** 147 Creates a hardlink. 148 */ 149 void hardLinkFile(Path from, Path to, bool overwrite = false) 150 { 151 if (existsFile(to)) { 152 enforce(overwrite, "Destination file already exists."); 153 if (auto fe = collectException!FileException(removeFile(to))) { 154 version (Windows) if (sameFile(from, to)) return; 155 throw fe; 156 } 157 } 158 159 version (Windows) 160 { 161 alias cstr = toUTFz!(const(wchar)*); 162 if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString))) 163 return; 164 } 165 else 166 { 167 import core.sys.posix.unistd : link; 168 alias cstr = toUTFz!(const(char)*); 169 if (!link(cstr(from.toNativeString), cstr(to.toNativeString))) 170 return; 171 } 172 // fallback to copy 173 copyFile(from, to, overwrite); 174 } 175 176 /** 177 Removes a file 178 */ 179 void removeFile(Path path) 180 { 181 removeFile(path.toNativeString()); 182 } 183 /// ditto 184 void removeFile(string path) { 185 std.file.remove(path); 186 } 187 188 /** 189 Checks if a file exists 190 */ 191 bool existsFile(Path path) { 192 return existsFile(path.toNativeString()); 193 } 194 /// ditto 195 bool existsFile(string path) 196 { 197 return std.file.exists(path); 198 } 199 200 /** Stores information about the specified file/directory into 'info' 201 202 Returns false if the file does not exist. 203 */ 204 FileInfo getFileInfo(Path path) 205 { 206 static if (__VERSION__ >= 2064) 207 auto ent = std.file.DirEntry(path.toNativeString()); 208 else auto ent = std.file.dirEntry(path.toNativeString()); 209 return makeFileInfo(ent); 210 } 211 /// ditto 212 FileInfo getFileInfo(string path) 213 { 214 return getFileInfo(Path(path)); 215 } 216 217 /** 218 Creates a new directory. 219 */ 220 void createDirectory(Path path) 221 { 222 mkdir(path.toNativeString()); 223 } 224 /// ditto 225 void createDirectory(string path) 226 { 227 createDirectory(Path(path)); 228 } 229 230 /** 231 Enumerates all files in the specified directory. 232 */ 233 void listDirectory(Path path, scope bool delegate(FileInfo info) del) 234 { 235 foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) ) 236 if( !del(makeFileInfo(ent)) ) 237 break; 238 } 239 /// ditto 240 void listDirectory(string path, scope bool delegate(FileInfo info) del) 241 { 242 listDirectory(Path(path), del); 243 } 244 /// ditto 245 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(Path path) 246 { 247 int iterator(scope int delegate(ref FileInfo) del){ 248 int ret = 0; 249 listDirectory(path, (fi){ 250 ret = del(fi); 251 return ret == 0; 252 }); 253 return ret; 254 } 255 return &iterator; 256 } 257 /// ditto 258 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path) 259 { 260 return iterateDirectory(Path(path)); 261 } 262 263 264 /** 265 Returns the current working directory. 266 */ 267 Path getWorkingDirectory() 268 { 269 return Path(std.file.getcwd()); 270 } 271 272 273 /** Contains general information about a file. 274 */ 275 struct FileInfo { 276 /// Name of the file (not including the path) 277 string name; 278 279 /// Size of the file (zero for directories) 280 ulong size; 281 282 /// Time of the last modification 283 SysTime timeModified; 284 285 /// Time of creation (not available on all operating systems/file systems) 286 SysTime timeCreated; 287 288 /// True if this is a symlink to an actual file 289 bool isSymlink; 290 291 /// True if this is a directory or a symlink pointing to a directory 292 bool isDirectory; 293 } 294 295 /** 296 Specifies how a file is manipulated on disk. 297 */ 298 enum FileMode { 299 /// The file is opened read-only. 300 read, 301 /// The file is opened for read-write random access. 302 readWrite, 303 /// The file is truncated if it exists and created otherwise and the opened for read-write access. 304 createTrunc, 305 /// The file is opened for appending data to it and created if it does not exist. 306 append 307 } 308 309 /** 310 Accesses the contents of a file as a stream. 311 */ 312 313 private FileInfo makeFileInfo(DirEntry ent) 314 { 315 FileInfo ret; 316 ret.name = baseName(ent.name); 317 if( ret.name.length == 0 ) ret.name = ent.name; 318 assert(ret.name.length > 0); 319 ret.isSymlink = ent.isSymlink; 320 try { 321 ret.isDirectory = ent.isDir; 322 ret.size = ent.size; 323 ret.timeModified = ent.timeLastModified; 324 version(Windows) ret.timeCreated = ent.timeCreated; 325 else ret.timeCreated = ent.timeLastModified; 326 } catch (Exception e) { 327 logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg); 328 } 329 return ret; 330 }