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