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 static import std.stream; 21 import std..string; 22 import std.utf; 23 24 25 /* Add output range support to File 26 */ 27 struct RangeFile { 28 std.stream.File file; 29 30 void put(in ubyte[] bytes) { file.writeExact(bytes.ptr, bytes.length); } 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 file.seek(0, std.stream.SeekPos.End); 38 auto sz = file.position; 39 enforce(sz <= size_t.max, "File is too big to read to memory."); 40 file.seek(0, std.stream.SeekPos.Set); 41 auto ret = new ubyte[cast(size_t)sz]; 42 file.readExact(ret.ptr, ret.length); 43 return ret; 44 } 45 46 void rawRead(ubyte[] dst) { file.readExact(dst.ptr, dst.length); } 47 void write(string str) { put(str); } 48 void close() { file.close(); } 49 void flush() { file.flush(); } 50 @property ulong size() { return file.size; } 51 } 52 53 54 /** 55 Opens a file stream with the specified mode. 56 */ 57 RangeFile openFile(Path path, FileMode mode = FileMode.Read) 58 { 59 std.stream.FileMode fmode; 60 final switch(mode){ 61 case FileMode.Read: fmode = std.stream.FileMode.In; break; 62 case FileMode.ReadWrite: fmode = std.stream.FileMode.Out; break; 63 case FileMode.CreateTrunc: fmode = std.stream.FileMode.OutNew; break; 64 case FileMode.Append: fmode = std.stream.FileMode.Append; break; 65 } 66 auto ret = new std.stream.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(Path(path), mode); 74 } 75 76 77 /** 78 Moves or renames a file. 79 */ 80 void moveFile(Path from, Path 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 = Path of the source file 97 to = Path 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(Path from, Path 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 .copy(from.toNativeString(), to.toNativeString()); 117 118 // try to preserve ownership/permissions in Posix 119 version (Posix) { 120 import core.sys.posix.sys.stat; 121 import core.sys.posix.unistd; 122 import std.utf; 123 auto cspath = toUTFz!(const(char)*)(from.toNativeString()); 124 auto cdpath = toUTFz!(const(char)*)(to.toNativeString()); 125 stat_t st; 126 enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file."); 127 if (chown(cdpath, st.st_uid, st.st_gid) != 0) 128 st.st_mode &= ~(S_ISUID | S_ISGID); 129 chmod(cdpath, st.st_mode); 130 } 131 } 132 /// ditto 133 void copyFile(string from, string to) 134 { 135 copyFile(Path(from), Path(to)); 136 } 137 138 version (Windows) extern(Windows) int CreateHardLinkW(in wchar* to, in wchar* from, void* attr=null); 139 140 // guess whether 2 files are identical, ignores filename and content 141 private bool sameFile(Path a, Path b) 142 { 143 static assert(__traits(allMembers, FileInfo)[0] == "name"); 144 return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $]; 145 } 146 147 /** 148 Creates a hardlink. 149 */ 150 void hardLinkFile(Path from, Path to, bool overwrite = false) 151 { 152 if (existsFile(to)) { 153 enforce(overwrite, "Destination file already exists."); 154 if (auto fe = collectException!FileException(removeFile(to))) { 155 version (Windows) if (sameFile(from, to)) return; 156 throw fe; 157 } 158 } 159 160 version (Windows) 161 { 162 alias cstr = toUTFz!(const(wchar)*); 163 if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString))) 164 return; 165 } 166 else 167 { 168 import core.sys.posix.unistd : link; 169 alias cstr = toUTFz!(const(char)*); 170 if (!link(cstr(from.toNativeString), cstr(to.toNativeString))) 171 return; 172 } 173 // fallback to copy 174 copyFile(from, to, overwrite); 175 } 176 177 /** 178 Removes a file 179 */ 180 void removeFile(Path path) 181 { 182 removeFile(path.toNativeString()); 183 } 184 /// ditto 185 void removeFile(string path) { 186 std.file.remove(path); 187 } 188 189 /** 190 Checks if a file exists 191 */ 192 bool existsFile(Path path) { 193 return existsFile(path.toNativeString()); 194 } 195 /// ditto 196 bool existsFile(string path) 197 { 198 return std.file.exists(path); 199 } 200 201 /** Stores information about the specified file/directory into 'info' 202 203 Returns false if the file does not exist. 204 */ 205 FileInfo getFileInfo(Path path) 206 { 207 static if (__VERSION__ >= 2064) 208 auto ent = std.file.DirEntry(path.toNativeString()); 209 else auto ent = std.file.dirEntry(path.toNativeString()); 210 return makeFileInfo(ent); 211 } 212 /// ditto 213 FileInfo getFileInfo(string path) 214 { 215 return getFileInfo(Path(path)); 216 } 217 218 /** 219 Creates a new directory. 220 */ 221 void createDirectory(Path path) 222 { 223 mkdir(path.toNativeString()); 224 } 225 /// ditto 226 void createDirectory(string path) 227 { 228 createDirectory(Path(path)); 229 } 230 231 /** 232 Enumerates all files in the specified directory. 233 */ 234 void listDirectory(Path path, scope bool delegate(FileInfo info) del) 235 { 236 foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) ) 237 if( !del(makeFileInfo(ent)) ) 238 break; 239 } 240 /// ditto 241 void listDirectory(string path, scope bool delegate(FileInfo info) del) 242 { 243 listDirectory(Path(path), del); 244 } 245 /// ditto 246 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(Path path) 247 { 248 int iterator(scope int delegate(ref FileInfo) del){ 249 int ret = 0; 250 listDirectory(path, (fi){ 251 ret = del(fi); 252 return ret == 0; 253 }); 254 return ret; 255 } 256 return &iterator; 257 } 258 /// ditto 259 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path) 260 { 261 return iterateDirectory(Path(path)); 262 } 263 264 265 /** 266 Returns the current working directory. 267 */ 268 Path getWorkingDirectory() 269 { 270 return Path(std.file.getcwd()); 271 } 272 273 274 /** Contains general information about a file. 275 */ 276 struct FileInfo { 277 /// Name of the file (not including the path) 278 string name; 279 280 /// Size of the file (zero for directories) 281 ulong size; 282 283 /// Time of the last modification 284 SysTime timeModified; 285 286 /// Time of creation (not available on all operating systems/file systems) 287 SysTime timeCreated; 288 289 /// True if this is a symlink to an actual file 290 bool isSymlink; 291 292 /// True if this is a directory or a symlink pointing to a directory 293 bool isDirectory; 294 } 295 296 /** 297 Specifies how a file is manipulated on disk. 298 */ 299 enum FileMode { 300 /// The file is opened read-only. 301 Read, 302 /// The file is opened for read-write random access. 303 ReadWrite, 304 /// The file is truncated if it exists and created otherwise and the opened for read-write access. 305 CreateTrunc, 306 /// The file is opened for appending data to it and created if it does not exist. 307 Append 308 } 309 310 /** 311 Accesses the contents of a file as a stream. 312 */ 313 314 private FileInfo makeFileInfo(DirEntry ent) 315 { 316 FileInfo ret; 317 ret.name = baseName(ent.name); 318 if( ret.name.length == 0 ) ret.name = ent.name; 319 assert(ret.name.length > 0); 320 ret.isSymlink = ent.isSymlink; 321 try { 322 ret.isDirectory = ent.isDir; 323 ret.size = ent.size; 324 ret.timeModified = ent.timeLastModified; 325 version(Windows) ret.timeCreated = ent.timeCreated; 326 else ret.timeCreated = ent.timeLastModified; 327 } catch (Exception e) { 328 logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg); 329 } 330 return ret; 331 }