• R/O
  • SSH

Commit

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Auxiliary tools for users of pixiv_down


Commit MetaInfo

Révision42f2bc4f33b51fe2b0d8903fe0b595c4f29fc1df (tree)
l'heure2023-01-29 12:27:49
Auteurnemophila <stigma@disr...>
Commiternemophila

Message de Log

Add mlib/trash

Currently only works on Posix systems, though this isn't too
different from mlib/directories. Support for other operating
systems (namely macOS and Windows) should happen soon.

Change Summary

Modification

diff -r a1e4a17f0e53 -r 42f2bc4f33b5 mlib/trash.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mlib/trash.d Sun Jan 29 13:27:49 2023 +1000
@@ -0,0 +1,312 @@
1+/*
2+ * This is free and unencumbered software released into the public domain.
3+ *
4+ * Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
5+ * software, either in source code form or as a compiled binary, for any purpose,
6+ * commercial or non-commercial, and by any means.
7+ *
8+ * In jurisdictions that recognize copyright laws, the author or authors of this
9+ * software dedicate any and all copyright interest in the software to the public
10+ * domain. We make this dedication for the benefit of the public at large and to
11+ * the detriment of our heirs and successors. We intend this dedication to be an
12+ * overt act of relinquishment in perpetuity of all present and future rights to
13+ * this software under copyright law.
14+ *
15+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+ */
22+
23+
24+/**
25+ * Common 'Trash' operations for the computers Recycle Bin.
26+ *
27+ * Only for POSIX systems (currently) and follows the XDG specification.
28+ *
29+ * Authors: mio <stigma@disroot.org>
30+ * Date: January 29, 2023
31+ * License: public domain
32+ * Standards: The FreeDesktop.org Trash Specification 1.0
33+ * Version: 0.1.0
34+ *
35+ * Macros:
36+ * DREF = <a href="https://dlang.org/phobos/$1.html#$2">$2</a>
37+ * LREF = <a href="#$1">$1</a>
38+ */
39+module mlib.trash;
40+
41+import core.stdc.errno;
42+
43+import std.file;
44+import std.path;
45+import std.process : environment;
46+import std.stdio;
47+
48+/*
49+ * Permanetely delete all trashed records.
50+ *
51+ * This currently throws an Exception as it's not yet implemented.
52+ */
53+// void emptyTrash()
54+// {
55+// throw new Exception(__PRETTY_FUNCTION__ ~ " not implemented");
56+// }
57+
58+/*
59+ * Restore one (or all: "") trashed records
60+ *
61+ * Params:
62+ * pathInTrash = The unique filename in the trash directory to
63+ * restore. By not providing an argument (or by
64+ * passing `""`) this will restore _all_ files.
65+ *
66+ * Note: This currently throws an Exception as it's not yet
67+ * implemented.
68+ */
69+// void restoreTrash(string pathInTrash = "")
70+// {
71+// throw new Exception(__PRETTY_FUNCTION__ ~ " not implemented");
72+// }
73+
74+/*
75+ * List all the files and directories currently inside the trash.
76+ *
77+ * Returns: A list of strings containing every filename in the trash.
78+ *
79+ * Note: This currently throws an Exception as it's not yet
80+ * implemented.
81+ */
82+// string[] listTrash()
83+// {
84+// throw new Exception(__PRETTY_FUNCTION__ ~ " not implemented");
85+// }
86+
87+
88+/**
89+ * Trash the file or directory at *path*.
90+ *
91+ * Params:
92+ * path = The path to move to the trash.
93+ *
94+ * Throws:
95+ * - $(DREF std_file, FileException) if the file cannot be trashed.
96+ */
97+void trash(string path)
98+{
99+ scope string pathInTrash;
100+ trash(path, pathInTrash);
101+}
102+
103+/**
104+ * Trash the file or directory at *path*, and sets *pathInTrash* to the
105+ * path at which the file can be found within the trash.
106+ *
107+ * Params:
108+ * path = The path to move to the trash.
109+ * pathInTrash = The path at which the newly trashed item can be found.
110+ *
111+ * Throws:
112+ * - $(DREF std_file, FileException) if the file cannot be trashed.
113+ */
114+void trash(string path, out string pathInTrash)
115+{
116+ version (Posix) {
117+ _posix_trash(path, pathInTrash);
118+ } else {
119+ throw new Exception(__PRETTY_FUNCTION__ ~ " is not supported on your OS");
120+ }
121+}
122+
123+/**
124+ * Erase the file from the operating system.
125+ *
126+ * This skips the "trashing" operation and unlinks the file from the
127+ * system and recovers the space. Files which have been erased are
128+ * not recoverable.
129+ *
130+ * Throws:
131+ * - $(DREF std_file, FileException) if the file cannot be removed.
132+ */
133+void erase(string path)
134+{
135+ // Really just a convenience function.
136+ remove(path);
137+}
138+
139+
140+private:
141+
142+/*
143+ * System independant functions.
144+ * These will call the system specific function.
145+ */
146+
147+ulong getDevice(string path) {
148+ version (Posix) {
149+ return _posix_getDevice(path);
150+ }
151+}
152+
153+string getHomeDirectory() {
154+ version (Posix) {
155+ return environment["HOME"];
156+ }
157+}
158+
159+bool isParent(string parent, string path) {
160+ import std.string : startsWith;
161+
162+ path = path.absolutePath;
163+ parent = parent.absolutePath;
164+
165+ return startsWith(path, parent);
166+}
167+
168+string getInfo(string src, string topdir) {
169+ import std.uri : encode;
170+ import std.datetime.systime : Clock;
171+
172+ if (false == isParent(topdir, src)) {
173+ src = src.absolutePath;
174+ } else {
175+ src = relativePath(src, topdir);
176+ }
177+
178+ string info = "[Trash Info]\n";
179+ info ~= "Path=" ~ encode(src) ~ "\n";
180+
181+
182+ /*
183+ * Prior to D 2.099.0, the toISOExtString method didn't
184+ * have a precision argument, which means it includes
185+ * fractional seconds by default. So to accommodate
186+ * for earlier versions, just trim it off.
187+ */
188+ static if (__VERSION__ < 2099L) {
189+ import std.string : split;
190+
191+ string dateTime = Clock.currTime.toISOExtString().split(".")[0];
192+
193+ info ~= "DeletionDate=" ~ dateTime ~ "\n";
194+ } else {
195+ info ~= "DeletionDate=" ~ Clock.currTime.toISOExtString(0) ~ "\n";
196+ }
197+
198+
199+ return info;
200+}
201+
202+/*
203+ * System specific implementation of the above functions.
204+ */
205+
206+version(Posix) {
207+
208+ import core.sys.posix.sys.stat;
209+ import std.conv : to;
210+ import std.string : toStringz;
211+
212+ void _posix_trash(string path, out string pathInTrash) {
213+ if (false == exists(path)) {
214+ throw new FileException(path, ENOENT);
215+ }
216+
217+ /* "When trashing a file or directory, the implementation SHOULD check
218+ * whether the user has the necessary permissions to delete it, before
219+ * starting the trashing operation itself". */
220+ uint attrs = getAttributes(path);
221+ if (false == ((S_IRUSR & attrs) && (S_IWUSR & attrs))) {
222+ throw new FileException(path, EACCES);
223+ }
224+
225+ ulong pathDev = getDevice(path);
226+ ulong trashDev = getDevice(getHomeDirectory());
227+
228+
229+ // $topdir
230+ string topdir;
231+ // $trash
232+ string trash;
233+
234+ /* w.r.t. homeTrash:
235+ * "Files that the user trashes from the same file system (device/partition) SHOULD
236+ * be stored here ... If this directory is needed for a trashing operation but does
237+ * not exist, the implementation SHOULD automatically create it, without warnings
238+ * or delays. */
239+ if (pathDev == trashDev) {
240+ topdir = _xdg_datahome();
241+ trash = buildPath(topdir, "Trash");
242+ } else {
243+ /* "The implementation MAY also support trashing files from the rest of the
244+ * system (including other partitions, shared network resources, and removable
245+ * devices) into the "home trash" directory."
246+ *
247+ * I can only really test the partitions and removable devices, but I don't
248+ * have my desktop setup with multiple partitions. Will check with removable
249+ * devices, but want to see same file system usage work first. */
250+ throw new Exception("The device for the Trash directory and the device for the path are different.");
251+ }
252+
253+ string basename = baseName(path);
254+ string filename = stripExtension(basename);
255+ string ext = extension(basename);
256+
257+ // $trash/files
258+ string filesDir = buildPath(trash, "files");
259+ if (false == exists(filesDir)) {
260+ mkdirRecurse(filesDir);
261+ }
262+
263+ // $trash/info
264+ string infoDir = buildPath(trash, "info");
265+ if (false == exists(infoDir)) {
266+ mkdirRecurse(infoDir);
267+ }
268+
269+ /* "The names in [$trash/files and $trash/info] are to be determined by the
270+ * implementation; the only limitation is that they must be unique within the
271+ * directory. Even if a file with the same name and location gets trashed many times,
272+ * each subsequent trashing must not overwrite a previous copy." */
273+ size_t counter = 0;
274+ string destname = basename;
275+ string infoFilename = destname.setExtension(".trashinfo");
276+ while (exists(buildPath(filesDir, destname)) || exists(buildPath(infoDir, infoFilename))) {
277+ counter += 1;
278+ destname = filename ~ "_" ~ to!string(counter) ~ ext;
279+ infoFilename = destname.setExtension(".trashinfo");
280+ }
281+
282+ {
283+ /* "When trashing a file or directory, the implementation MUST create the
284+ * corresponding file in $trash/info first." */
285+ auto infoFile = File(buildPath(infoDir, infoFilename), "w");
286+ infoFile.write(getInfo(path, topdir));
287+ }
288+ {
289+ string filesPath = buildPath(filesDir, destname);
290+ rename(path, filesPath);
291+ pathInTrash = filesPath;
292+ }
293+
294+ /* TODO: Directory size cache */
295+ }
296+
297+ ulong _posix_getDevice(string path) {
298+ stat_t statbuf;
299+ lstat(toStringz(path), &statbuf);
300+
301+ return statbuf.st_dev;
302+ }
303+
304+ string _xdg_datahome()
305+ {
306+ if ("XDG_DATA_HOME" in environment) {
307+ return environment["XDG_DATA_HOME"];
308+ } else {
309+ return buildPath(environment["HOME"], ".local", "share");
310+ }
311+ }
312+} // End of version(Posix)