Newer
Older
dub_jkp / source / dub / internal / vibecompat / core / file.d
  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. }