Last active
June 21, 2016 15:58
-
-
Save FreeSlave/d7239c41816329e5fd58e20c2c78b74d to your computer and use it in GitHub Desktop.
Moving files to trashcan on Windows and Freedesktop in D programming language
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Moving files to trashcan on Windows and Freedesktop. | |
* Copyright: | |
* Roman Chistokhodov, 2016 | |
* License: | |
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
*/ | |
import std.path; | |
import std.string; | |
version(Posix) | |
{ | |
private @trusted string numberedBaseName(string path, uint number) { | |
import std.format; | |
return format("%s %s%s", path.baseName.stripExtension, number, path.extension); | |
} | |
unittest | |
{ | |
assert(numberedBaseName("/root/file.ext", 1) == "file 1.ext"); | |
assert(numberedBaseName("/root/file", 2) == "file 2"); | |
} | |
import std.conv : octal, to; | |
import std.file; | |
import std.exception : collectException; | |
import std.process : environment; | |
import std.traits : isSomeString; | |
import std.range : ElementEncodingType; | |
import core.sys.posix.unistd; | |
import core.sys.posix.sys.stat; | |
import core.sys.posix.sys.types; | |
enum mode_t privateMode = octal!700; | |
private bool ensureExists(string dir) nothrow | |
{ | |
bool ok; | |
try { | |
ok = dir.exists; | |
if (!ok) { | |
mkdirRecurse(dir.dirName); | |
ok = mkdir(dir.toStringz, privateMode) == 0; | |
} else { | |
ok = dir.isDir; | |
} | |
} catch(Exception e) { | |
ok = false; | |
} | |
return ok; | |
} | |
private string xdgBaseDir(string envvar, string fallback, string subfolder = null, bool shouldCreate = false) nothrow { | |
string dir; | |
collectException(environment.get(envvar), dir); | |
if (dir.length == 0) { | |
string home; | |
collectException(environment.get("HOME"), home); | |
dir = home.length ? buildPath(home, fallback) : null; | |
} | |
if (dir.length) { | |
if (shouldCreate) { | |
if (ensureExists(dir)) { | |
if (subfolder.length) { | |
string path = buildPath(dir, subfolder); | |
try { | |
if (!path.exists) { | |
mkdirRecurse(path); | |
} | |
return path; | |
} catch(Exception e) { | |
} | |
} else { | |
return dir; | |
} | |
} | |
} else { | |
return buildPath(dir, subfolder); | |
} | |
} | |
return null; | |
} | |
@trusted string xdgDataHome(string subfolder = null, bool shouldCreate = false) nothrow { | |
return xdgBaseDir("XDG_DATA_HOME", ".local/share", subfolder, shouldCreate); | |
} | |
@trusted String escapeValue(String)(String value) pure if (isSomeString!String && is(ElementEncodingType!String : char)) { | |
return value.replace("\\", `\\`.to!String).replace("\n", `\n`.to!String).replace("\r", `\r`.to!String).replace("\t", `\t`.to!String); | |
} | |
} | |
@trusted void moveToTrash(string path) | |
{ | |
if (!path.isAbsolute) { | |
throw new Exception("Path must be absolute"); | |
} | |
if (!path.exists) { | |
throw new Exception("Path does not exist"); | |
} | |
version(Windows) { | |
import std.windows.syserror; | |
import core.sys.windows.shellapi; | |
import core.sys.windows.winbase; | |
import core.stdc.wchar_; | |
import std.utf; | |
SHFILEOPSTRUCTW fileOp; | |
fileOp.wFunc = FO_DELETE; | |
fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR | FOF_ALLOWUNDO; | |
auto wFileName = (path ~ "\0\0").toUTF16(); | |
fileOp.pFrom = wFileName.ptr; | |
int r = SHFileOperation(&fileOp); | |
if (r != 0) { | |
wchar[1024] msg; | |
auto len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, null, r, 0, msg.ptr, msg.length - 1, null); | |
if (len) { | |
throw new Exception(msg[0..len].toUTF8().stripRight); | |
} else { | |
throw new Exception("File deletion error"); | |
} | |
} | |
} else version(Posix) { //actually should check for freedesktop, thus skipping Android and OSX. | |
string trashInfoDir = xdgDataHome("Trash/info", true); | |
if (!trashInfoDir.length) { | |
throw new Exception("Could not access trash info folder"); | |
} | |
string trashFilePathsDir = xdgDataHome("Trash/files", true); | |
if (!trashFilePathsDir.length) { | |
throw new Exception("Could not access trash files folder"); | |
} | |
string trashInfoPath = buildPath(trashInfoDir, path.baseName ~ ".trashinfo"); | |
string trashFilePath = buildPath(trashFilePathsDir, path.baseName); | |
uint number = 1; | |
while(trashInfoPath.exists || trashFilePath.exists) { | |
string baseName = numberedBaseName(path, number); | |
trashInfoPath = buildPath(trashInfoDir, baseName ~ ".trashinfo"); | |
trashFilePath = buildPath(trashFilePathsDir, baseName); | |
number++; | |
} | |
import std.datetime; | |
auto currentTime = Clock.currTime; | |
currentTime.fracSecs = Duration.zero; | |
string contents = "[Trash Info]\nPath=" ~ path.escapeValue() ~ "\nDeletionDate=" ~ currentTime.toISOExtString() ~ "\n"; | |
write(trashInfoPath, contents); | |
path.rename(trashFilePath); | |
} | |
} | |
void main(string[] args) | |
{ | |
import std.stdio; | |
foreach(arg; args[1..$]) { | |
try { | |
moveToTrash(arg.absolutePath); | |
} catch(Exception e) { | |
stderr.writefln("Error while moving '%s' to trash: %s", arg, e.msg); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment