Newer
Older
dub_jkp / source / dub / internal / vibecompat / core / file.d
@Mathias Lang Mathias Lang on 10 Mar 2024 7 KB Trivial: Simplify ensureDirectory
  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.  
  68. Throws:
  69. An Exception if the copy operation fails for some reason.
  70. */
  71. void copyFile(NativePath from, NativePath to, bool overwrite = false)
  72. {
  73. enforce(existsFile(from), "Source file does not exist.");
  74.  
  75. if (existsFile(to)) {
  76. enforce(overwrite, "Destination file already exists.");
  77. // remove file before copy to allow "overwriting" files that are in
  78. // use on Linux
  79. removeFile(to);
  80. }
  81.  
  82. static if (is(PreserveAttributes))
  83. {
  84. .copy(from.toNativeString(), to.toNativeString(), PreserveAttributes.yes);
  85. }
  86. else
  87. {
  88. .copy(from.toNativeString(), to.toNativeString());
  89. // try to preserve ownership/permissions in Posix
  90. version (Posix) {
  91. import core.sys.posix.sys.stat;
  92. import core.sys.posix.unistd;
  93. import std.utf;
  94. auto cspath = toUTFz!(const(char)*)(from.toNativeString());
  95. auto cdpath = toUTFz!(const(char)*)(to.toNativeString());
  96. stat_t st;
  97. enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file.");
  98. if (chown(cdpath, st.st_uid, st.st_gid) != 0)
  99. st.st_mode &= ~(S_ISUID | S_ISGID);
  100. chmod(cdpath, st.st_mode);
  101. }
  102. }
  103. }
  104. /// ditto
  105. void copyFile(string from, string to)
  106. {
  107. copyFile(NativePath(from), NativePath(to));
  108. }
  109.  
  110. version (Windows) extern(Windows) int CreateHardLinkW(const(wchar)* to, const(wchar)* from, void* attr=null);
  111.  
  112. // guess whether 2 files are identical, ignores filename and content
  113. private bool sameFile(NativePath a, NativePath b)
  114. {
  115. version (Posix) {
  116. auto st_a = std.file.DirEntry(a.toNativeString).statBuf;
  117. auto st_b = std.file.DirEntry(b.toNativeString).statBuf;
  118. return st_a == st_b;
  119. } else {
  120. static assert(__traits(allMembers, FileInfo)[0] == "name");
  121. return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $];
  122. }
  123. }
  124.  
  125. private bool isWritable(NativePath name)
  126. {
  127. version (Windows)
  128. {
  129. import core.sys.windows.windows;
  130.  
  131. return (name.toNativeString.getAttributes & FILE_ATTRIBUTE_READONLY) == 0;
  132. }
  133. else version (Posix)
  134. {
  135. import core.sys.posix.sys.stat;
  136.  
  137. return (name.toNativeString.getAttributes & S_IWUSR) != 0;
  138. }
  139. else
  140. static assert(false, "Needs implementation.");
  141. }
  142.  
  143. private void makeWritable(NativePath name)
  144. {
  145. makeWritable(name.toNativeString);
  146. }
  147.  
  148. private void makeWritable(string name)
  149. {
  150. version (Windows)
  151. {
  152. import core.sys.windows.windows;
  153.  
  154. name.setAttributes(name.getAttributes & ~FILE_ATTRIBUTE_READONLY);
  155. }
  156. else version (Posix)
  157. {
  158. import core.sys.posix.sys.stat;
  159.  
  160. name.setAttributes(name.getAttributes | S_IWUSR);
  161. }
  162. else
  163. static assert(false, "Needs implementation.");
  164. }
  165.  
  166. /**
  167. Creates a hardlink if possible, a copy otherwise.
  168.  
  169. If `from` is read-only and `overwrite` is true, then a copy is made instead
  170. and `to` is made writable; so that repeating the command will not fail.
  171. */
  172. void hardLinkFile(NativePath from, NativePath to, bool overwrite = false)
  173. {
  174. if (existsFile(to)) {
  175. enforce(overwrite, "Destination file already exists.");
  176. if (auto fe = collectException!FileException(removeFile(to))) {
  177. if (sameFile(from, to)) return;
  178. throw fe;
  179. }
  180. }
  181. const writeAccessChangeRequired = overwrite && !isWritable(from);
  182. if (!writeAccessChangeRequired)
  183. {
  184. version (Windows)
  185. {
  186. alias cstr = toUTFz!(const(wchar)*);
  187. if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString)))
  188. return;
  189. }
  190. else
  191. {
  192. import core.sys.posix.unistd : link;
  193. alias cstr = toUTFz!(const(char)*);
  194. if (!link(cstr(from.toNativeString), cstr(to.toNativeString)))
  195. return;
  196. }
  197. }
  198. // fallback to copy
  199. copyFile(from, to, overwrite);
  200. if (writeAccessChangeRequired)
  201. to.makeWritable;
  202. }
  203.  
  204. /**
  205. Removes a file
  206. */
  207. void removeFile(NativePath path)
  208. {
  209. removeFile(path.toNativeString());
  210. }
  211. /// ditto
  212. void removeFile(string path) {
  213. std.file.remove(path);
  214. }
  215.  
  216. /**
  217. Checks if a file exists
  218. */
  219. bool existsFile(NativePath path) {
  220. return existsFile(path.toNativeString());
  221. }
  222. /// ditto
  223. bool existsFile(string path)
  224. {
  225. return std.file.exists(path);
  226. }
  227.  
  228. /// Checks if a directory exists
  229. bool existsDirectory(NativePath path) {
  230. if( !existsFile(path) ) return false;
  231. auto fi = getFileInfo(path);
  232. return fi.isDirectory;
  233. }
  234.  
  235. /** Stores information about the specified file/directory into 'info'
  236.  
  237. Returns false if the file does not exist.
  238. */
  239. FileInfo getFileInfo(NativePath path)
  240. {
  241. auto ent = std.file.DirEntry(path.toNativeString());
  242. return makeFileInfo(ent);
  243. }
  244. /// ditto
  245. FileInfo getFileInfo(string path)
  246. {
  247. return getFileInfo(NativePath(path));
  248. }
  249.  
  250. /**
  251. Creates a new directory.
  252. */
  253. void ensureDirectory(NativePath path)
  254. {
  255. mkdirRecurse(path.toNativeString());
  256. }
  257.  
  258. /**
  259. Enumerates all files in the specified directory.
  260. */
  261. int delegate(scope int delegate(ref FileInfo)) iterateDirectory(NativePath path)
  262. {
  263. int iterator(scope int delegate(ref FileInfo) del){
  264. foreach (DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow)) {
  265. auto fi = makeFileInfo(ent);
  266. if (auto res = del(fi))
  267. return res;
  268. }
  269. return 0;
  270. }
  271. return &iterator;
  272. }
  273.  
  274. /**
  275. Returns the current working directory.
  276. */
  277. NativePath getWorkingDirectory()
  278. {
  279. return NativePath(std.file.getcwd());
  280. }
  281.  
  282.  
  283. /** Contains general information about a file.
  284. */
  285. struct FileInfo {
  286. /// Name of the file (not including the path)
  287. string name;
  288.  
  289. /// Size of the file (zero for directories)
  290. ulong size;
  291.  
  292. /// Time of the last modification
  293. SysTime timeModified;
  294.  
  295. /// True if this is a symlink to an actual file
  296. bool isSymlink;
  297.  
  298. /// True if this is a directory or a symlink pointing to a directory
  299. bool isDirectory;
  300. }
  301.  
  302. /**
  303. Specifies how a file is manipulated on disk.
  304. */
  305. enum FileMode {
  306. /// The file is opened read-only.
  307. read,
  308. /// The file is opened for read-write random access.
  309. readWrite,
  310. /// The file is truncated if it exists and created otherwise and the opened for read-write access.
  311. createTrunc,
  312. /// The file is opened for appending data to it and created if it does not exist.
  313. append
  314. }
  315.  
  316. /**
  317. Accesses the contents of a file as a stream.
  318. */
  319.  
  320. private FileInfo makeFileInfo(DirEntry ent)
  321. {
  322. FileInfo ret;
  323. ret.name = baseName(ent.name);
  324. if( ret.name.length == 0 ) ret.name = ent.name;
  325. assert(ret.name.length > 0);
  326. ret.isSymlink = ent.isSymlink;
  327. try {
  328. ret.isDirectory = ent.isDir;
  329. ret.size = ent.size;
  330. ret.timeModified = ent.timeLastModified;
  331. } catch (Exception e) {
  332. logDiagnostic("Failed to get extended file information for %s: %s", ret.name, e.msg);
  333. }
  334. return ret;
  335. }