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.path; 11 12 import dub.internal.logging; 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 /// Writes `buffer` to a file 26 public void writeFile(NativePath path, const void[] buffer) 27 { 28 std.file.write(path.toNativeString(), buffer); 29 } 30 31 /// Returns the content of a file 32 public ubyte[] readFile(NativePath path) 33 { 34 return cast(ubyte[]) std.file.read(path.toNativeString()); 35 } 36 37 /// Returns the content of a file as text 38 public string readText(NativePath path) 39 { 40 return std.file.readText(path.toNativeString()); 41 } 42 43 /** 44 Moves or renames a file. 45 */ 46 void moveFile(NativePath from, NativePath to) 47 { 48 moveFile(from.toNativeString(), to.toNativeString()); 49 } 50 /// ditto 51 void moveFile(string from, string to) 52 { 53 std.file.rename(from, to); 54 } 55 56 /** 57 Copies a file. 58 59 Note that attributes and time stamps are currently not retained. 60 61 Params: 62 from = NativePath of the source file 63 to = NativePath for the destination file 64 overwrite = If true, any file existing at the destination path will be 65 overwritten. If this is false, an exception will be thrown should 66 a file already exist at the destination path. 67 This also sets the target as writable to ensure future invocations 68 will not fail. 69 70 Throws: 71 An Exception if the copy operation fails for some reason. 72 */ 73 void copyFile(NativePath from, NativePath to, bool overwrite = false) 74 { 75 enforce(existsFile(from), "Source file does not exist."); 76 77 if (existsFile(to)) { 78 enforce(overwrite, "Destination file already exists."); 79 // remove file before copy to allow "overwriting" files that are in 80 // use on Linux 81 removeFile(to); 82 } 83 84 static if (is(PreserveAttributes)) 85 { 86 .copy(from.toNativeString(), to.toNativeString(), PreserveAttributes.yes); 87 } 88 else 89 { 90 .copy(from.toNativeString(), to.toNativeString()); 91 // try to preserve ownership/permissions in Posix 92 version (Posix) { 93 import core.sys.posix.sys.stat; 94 import core.sys.posix.unistd; 95 import std.utf; 96 auto cspath = toUTFz!(const(char)*)(from.toNativeString()); 97 auto cdpath = toUTFz!(const(char)*)(to.toNativeString()); 98 stat_t st; 99 enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file."); 100 if (chown(cdpath, st.st_uid, st.st_gid) != 0) 101 st.st_mode &= ~(S_ISUID | S_ISGID); 102 chmod(cdpath, st.st_mode); 103 } 104 } 105 if (overwrite) makeWritable(to); 106 } 107 /// ditto 108 void copyFile(string from, string to) 109 { 110 copyFile(NativePath(from), NativePath(to)); 111 } 112 113 private bool isWritable(NativePath name) 114 { 115 version (Windows) 116 { 117 import core.sys.windows.windows; 118 119 return (name.toNativeString.getAttributes & FILE_ATTRIBUTE_READONLY) == 0; 120 } 121 else version (Posix) 122 { 123 import core.sys.posix.sys.stat; 124 125 return (name.toNativeString.getAttributes & S_IWUSR) != 0; 126 } 127 else 128 static assert(false, "Needs implementation."); 129 } 130 131 private void makeWritable(NativePath name) 132 { 133 makeWritable(name.toNativeString); 134 } 135 136 private void makeWritable(string name) 137 { 138 version (Windows) 139 { 140 import core.sys.windows.windows; 141 142 name.setAttributes(name.getAttributes & ~FILE_ATTRIBUTE_READONLY); 143 } 144 else version (Posix) 145 { 146 import core.sys.posix.sys.stat; 147 148 name.setAttributes(name.getAttributes | S_IWUSR); 149 } 150 else 151 static assert(false, "Needs implementation."); 152 } 153 154 /** 155 Removes a file 156 */ 157 void removeFile(NativePath path) 158 { 159 removeFile(path.toNativeString()); 160 } 161 /// ditto 162 void removeFile(string path) { 163 std.file.remove(path); 164 } 165 166 /** 167 Checks if a file exists 168 */ 169 bool existsFile(NativePath path) { 170 return existsFile(path.toNativeString()); 171 } 172 /// ditto 173 bool existsFile(string path) 174 { 175 return std.file.exists(path); 176 } 177 178 /// Checks if a directory exists 179 bool existsDirectory(NativePath path) { 180 if( !existsFile(path) ) return false; 181 auto fi = getFileInfo(path); 182 return fi.isDirectory; 183 } 184 185 /** Stores information about the specified file/directory into 'info' 186 187 Returns false if the file does not exist. 188 */ 189 FileInfo getFileInfo(NativePath path) 190 { 191 auto ent = std.file.DirEntry(path.toNativeString()); 192 return makeFileInfo(ent); 193 } 194 /// ditto 195 FileInfo getFileInfo(string path) 196 { 197 return getFileInfo(NativePath(path)); 198 } 199 200 /** 201 Creates a new directory. 202 */ 203 void ensureDirectory(NativePath path) 204 { 205 mkdirRecurse(path.toNativeString()); 206 } 207 208 /** 209 Enumerates all files in the specified directory. 210 */ 211 int delegate(scope int delegate(ref FileInfo)) iterateDirectory(NativePath path) 212 { 213 int iterator(scope int delegate(ref FileInfo) del){ 214 foreach (DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow)) { 215 auto fi = makeFileInfo(ent); 216 if (auto res = del(fi)) 217 return res; 218 } 219 return 0; 220 } 221 return &iterator; 222 } 223 224 /** 225 Returns the current working directory. 226 */ 227 NativePath getWorkingDirectory() 228 { 229 return NativePath(std.file.getcwd()); 230 } 231 232 233 /** Contains general information about a file. 234 */ 235 struct FileInfo { 236 /// Name of the file (not including the path) 237 string name; 238 239 /// Size of the file (zero for directories) 240 ulong size; 241 242 /// Time of the last modification 243 SysTime timeModified; 244 245 /// True if this is a symlink to an actual file 246 bool isSymlink; 247 248 /// True if this is a directory or a symlink pointing to a directory 249 bool isDirectory; 250 } 251 252 /** 253 Specifies how a file is manipulated on disk. 254 */ 255 enum FileMode { 256 /// The file is opened read-only. 257 read, 258 /// The file is opened for read-write random access. 259 readWrite, 260 /// The file is truncated if it exists and created otherwise and the opened for read-write access. 261 createTrunc, 262 /// The file is opened for appending data to it and created if it does not exist. 263 append 264 } 265 266 /** 267 Accesses the contents of a file as a stream. 268 */ 269 270 private FileInfo makeFileInfo(DirEntry ent) 271 { 272 FileInfo ret; 273 ret.name = baseName(ent.name); 274 if( ret.name.length == 0 ) ret.name = ent.name; 275 assert(ret.name.length > 0); 276 ret.isSymlink = ent.isSymlink; 277 try { 278 ret.isDirectory = ent.isDir; 279 ret.size = ent.size; 280 ret.timeModified = ent.timeLastModified; 281 } catch (Exception e) { 282 logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg); 283 } 284 return ret; 285 }