1 /** 2 * Moving files and directories to trash can. 3 * Copyright: 4 * Roman Chistokhodov, 2016 5 * License: 6 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 7 */ 8 9 module trashcan; 10 11 import std.path; 12 import std.string; 13 import std.file; 14 15 import isfreedesktop; 16 17 static if (isFreedesktop) 18 { 19 private: 20 import std.format : format; 21 @trusted string numberedBaseName(string path, uint number) { 22 return format("%s %s%s", path.baseName.stripExtension, number, path.extension); 23 } 24 25 unittest 26 { 27 assert(numberedBaseName("/root/file.ext", 1) == "file 1.ext"); 28 assert(numberedBaseName("/root/file", 2) == "file 2"); 29 } 30 31 @trusted string escapeValue(string value) pure { 32 return value.replace("\\", `\\`).replace("\n", `\n`).replace("\r", `\r`).replace("\t", `\t`); 33 } 34 } 35 36 version(OSX) 37 { 38 private: 39 import core.sys.posix.dlfcn; 40 41 struct FSRef { 42 char[80] hidden; 43 }; 44 45 alias ubyte Boolean; 46 alias int OSStatus; 47 alias uint OptionBits; 48 49 extern(C) @nogc @system OSStatus _dummy_FSPathMakeRefWithOptions(const(char)* path, OptionBits, FSRef*, Boolean*) nothrow {return 0;} 50 extern(C) @nogc @system OSStatus _dummy_FSMoveObjectToTrashSync(const(FSRef)*, FSRef*, OptionBits) nothrow {return 0;} 51 } 52 53 /** 54 * Move file or directory to trash can. 55 * Params: 56 * path = Path of item to remove. Must be absolute. 57 * Throws: 58 * Exception when given path is not absolute or does not exist or some error occured during operation. 59 */ 60 @trusted void moveToTrash(string path) 61 { 62 if (!path.isAbsolute) { 63 throw new Exception("Path must be absolute"); 64 } 65 if (!path.exists) { 66 throw new Exception("Path does not exist"); 67 } 68 69 version(Windows) { 70 import core.sys.windows.shellapi; 71 import core.sys.windows.winbase; 72 import core.stdc.wchar_; 73 import std.windows.syserror; 74 import std.utf; 75 76 SHFILEOPSTRUCTW fileOp; 77 fileOp.wFunc = FO_DELETE; 78 fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR | FOF_ALLOWUNDO; 79 auto wFileName = (path ~ "\0\0").toUTF16(); 80 fileOp.pFrom = wFileName.ptr; 81 int r = SHFileOperation(&fileOp); 82 if (r != 0) { 83 wchar[1024] msg; 84 auto len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, null, r, 0, msg.ptr, msg.length - 1, null); 85 86 if (len) { 87 throw new Exception(msg[0..len].toUTF8().stripRight); 88 } else { 89 throw new Exception("File deletion error"); 90 } 91 } 92 } else version(OSX) { 93 import std.exception; 94 95 void* handle = dlopen("CoreServices.framework/Versions/A/CoreServices", RTLD_NOW | RTLD_LOCAL); 96 if (handle !is null) { 97 scope(exit) dlclose(handle); 98 99 auto ptrFSPathMakeRefWithOptions = cast(typeof(&_dummy_FSPathMakeRefWithOptions))dlsym(handle, "FSPathMakeRefWithOptions"); 100 if (ptrFSPathMakeRefWithOptions is null) { 101 throw new Exception(fromStringz(dlerror()).idup); 102 } 103 104 auto ptrFSMoveObjectToTrashSync = cast(typeof(&_dummy_FSMoveObjectToTrashSync))dlsym(handle, "FSMoveObjectToTrashSync"); 105 if (ptrFSMoveObjectToTrashSync is null) { 106 throw new Exception(fromStringz(dlerror()).idup); 107 } 108 109 FSRef source; 110 enforce(ptrFSPathMakeRefWithOptions(toStringz(path), 1, &source, null) == 0, "Could not make FSRef from path"); 111 FSRef target; 112 enforce(ptrFSMoveObjectToTrashSync(&source, &target, 0) == 0, "Could not move path to trash"); 113 } else { 114 throw new Exception(fromStringz(dlerror()).idup); 115 } 116 } else { 117 static if (isFreedesktop) { 118 import xdgpaths; 119 120 string trashInfoDir = xdgDataHome("Trash/info", true); 121 if (!trashInfoDir.length) { 122 throw new Exception("Could not access trash info folder"); 123 } 124 string trashFilePathsDir = xdgDataHome("Trash/files", true); 125 if (!trashFilePathsDir.length) { 126 throw new Exception("Could not access trash files folder"); 127 } 128 129 string trashInfoPath = buildPath(trashInfoDir, path.baseName ~ ".trashinfo"); 130 string trashFilePath = buildPath(trashFilePathsDir, path.baseName); 131 uint number = 1; 132 133 while(trashInfoPath.exists || trashFilePath.exists) { 134 string baseName = numberedBaseName(path, number); 135 trashInfoPath = buildPath(trashInfoDir, baseName ~ ".trashinfo"); 136 trashFilePath = buildPath(trashFilePathsDir, baseName); 137 number++; 138 } 139 140 import std.datetime; 141 auto currentTime = Clock.currTime; 142 currentTime.fracSecs = Duration.zero; 143 string contents = format("[Trash Info]\nPath=%s\nDeletionDate=%s\n", path.escapeValue(), currentTime.toISOExtString()); 144 write(trashInfoPath, contents); 145 path.rename(trashFilePath); 146 } else { 147 static assert("Unsupported platform"); 148 } 149 } 150 }