Skip to content

Instantly share code, notes, and snippets.

@FreeSlave
Last active June 21, 2016 15:58
Show Gist options
  • Save FreeSlave/d7239c41816329e5fd58e20c2c78b74d to your computer and use it in GitHub Desktop.
Save FreeSlave/d7239c41816329e5fd58e20c2c78b74d to your computer and use it in GitHub Desktop.
Moving files to trashcan on Windows and Freedesktop in D programming language
/**
* 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