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