Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix implementation of move_to_trash() on Linux #44021

Merged
merged 1 commit into from
Dec 7, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 114 additions & 32 deletions platform/linuxbsd/os_linuxbsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,65 +303,147 @@ static String get_mountpoint(const String &p_path) {
}

Error OS_LinuxBSD::move_to_trash(const String &p_path) {
String trash_can = "";
int err_code;
List<String> args;
args.push_back(p_path);
args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args.
Error result = execute("gio", args, true, nullptr, nullptr, &err_code); // For GNOME based machines.
if (result == OK && !err_code) {
return OK;
} else if (err_code == 2) {
return ERR_FILE_NOT_FOUND;
}

args.pop_front();
args.push_front("move");
args.push_back("trash:/"); // The command is `kioclient5 move <file_name> trash:/`.
result = execute("kioclient5", args, true, nullptr, nullptr, &err_code); // For KDE based machines.
if (result == OK && !err_code) {
return OK;
} else if (err_code == 2) {
return ERR_FILE_NOT_FOUND;
}

args.pop_front();
args.pop_back();
result = execute("gvfs-trash", args, true, nullptr, nullptr, &err_code); // For older Linux machines.
if (result == OK && !err_code) {
return OK;
} else if (err_code == 2) {
return ERR_FILE_NOT_FOUND;
}

// If the commands `kioclient5`, `gio` or `gvfs-trash` don't exist on the system we do it manually.
String trash_path = "";
String mnt = get_mountpoint(p_path);

// If there is a directory "[Mountpoint]/.Trash-[UID]/files", use it as the trash can.
// If there is a directory "[Mountpoint]/.Trash-[UID], use it as the trash can.
if (mnt != "") {
String path(mnt + "/.Trash-" + itos(getuid()) + "/files");
String path(mnt + "/.Trash-" + itos(getuid()));
struct stat s;
if (!stat(path.utf8().get_data(), &s)) {
trash_can = path;
trash_path = path;
}
}

// Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash/files" as the trash can.
if (trash_can == "") {
// Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash" as the trash can.
if (trash_path == "") {
char *dhome = getenv("XDG_DATA_HOME");
if (dhome) {
trash_can = String(dhome) + "/Trash/files";
trash_path = String(dhome) + "/Trash";
}
}

// Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash/files" as the trash can.
if (trash_can == "") {
// Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash" as the trash can.
if (trash_path == "") {
char *home = getenv("HOME");
if (home) {
trash_can = String(home) + "/.local/share/Trash/files";
trash_path = String(home) + "/.local/share/Trash";
}
}

// Issue an error if none of the previous locations is appropriate for the trash can.
if (trash_can == "") {
ERR_PRINT("move_to_trash: Could not determine the trash can location");
return FAILED;
}
ERR_FAIL_COND_V_MSG(trash_path == "", FAILED, "Could not determine the trash can location");

// Create needed directories for decided trash can location.
DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error err = dir_access->make_dir_recursive(trash_can);
memdelete(dir_access);
{
DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error err = dir_access->make_dir_recursive(trash_path);
dakennedyd marked this conversation as resolved.
Show resolved Hide resolved

// Issue an error if trash can is not created proprely.
ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"");
err = dir_access->make_dir_recursive(trash_path + "/files");
ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/files");
err = dir_access->make_dir_recursive(trash_path + "/info");
ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/info");
memdelete(dir_access);
}

// Issue an error if trash can is not created proprely.
if (err != OK) {
ERR_PRINT("move_to_trash: Could not create the trash can \"" + trash_can + "\"");
return err;
// The trash can is successfully created, now we check that we don't exceed our file name length limit.
// If the file name is too long trim it so we can add the identifying number and ".trashinfo".
// Assumes that the file name length limit is 255 characters.
String file_name = basename(p_path.utf8().get_data());
if (file_name.length() > 240) {
file_name = file_name.substr(0, file_name.length() - 15);
}

String dest_path = trash_path + "/files/" + file_name;
struct stat buff;
int id_number = 0;
String fn = file_name;

// Checks if a resource with the same name already exist in the trash can,
// if there is, add an identifying number to our resource's name.
while (stat(dest_path.utf8().get_data(), &buff) == 0) {
id_number++;

// Added a limit to check for identically named files already on the trash can
// if there are too many it could make the editor unresponsive.
ERR_FAIL_COND_V_MSG(id_number > 99, FAILED, "Too many identically named resources already in the trash can.");
fn = file_name + "." + itos(id_number);
dest_path = trash_path + "/files/" + fn;
}
file_name = fn;

// Generates the .trashinfo file
OS::Date date = OS::get_singleton()->get_date(false);
OS::Time time = OS::get_singleton()->get_time(false);
String timestamp = vformat("%04d-%02d-%02dT%02d:%02d:", date.year, date.month, date.day, time.hour, time.min);
timestamp = vformat("%s%02d", timestamp, time.sec); // vformat only supports up to 6 arguments.
String trash_info = "[Trash Info]\nPath=" + p_path.http_escape() + "\nDeletionDate=" + timestamp + "\n";
pouleyKetchoupp marked this conversation as resolved.
Show resolved Hide resolved
{
Error err;
FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file:" + trash_path + "/info/" + file_name + ".trashinfo");
file->store_string(trash_info);
file->close();

// Rename our resource before moving it to the trash can.
DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
err = dir_access->rename(p_path, p_path.get_base_dir() + "/" + file_name);
ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + p_path + "\"");
memdelete(dir_access);
}

// The trash can is successfully created, now move the given resource to it.
// Move the given resource to the trash can.
// Do not use DirAccess:rename() because it can't move files across multiple mountpoints.
List<String> mv_args;
mv_args.push_back(p_path);
mv_args.push_back(trash_can);
int retval;
err = execute("mv", mv_args, true, nullptr, nullptr, &retval);

// Issue an error if "mv" failed to move the given resource to the trash can.
if (err != OK || retval != 0) {
ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_can + "\"");
return FAILED;
mv_args.push_back(p_path.get_base_dir() + "/" + file_name);
mv_args.push_back(trash_path + "/files");
{
int retval;
Error err = execute("mv", mv_args, true, nullptr, nullptr, &retval);

// Issue an error if "mv" failed to move the given resource to the trash can.
if (err != OK || retval != 0) {
ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_path + "/files\"");
DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
err = dir_access->rename(p_path.get_base_dir() + "/" + file_name, p_path);
memdelete(dir_access);
ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename " + p_path.get_base_dir() + "/" + file_name + " back to its original name:" + p_path);
return FAILED;
}
}

return OK;
}

Expand Down