Tree of tag thunar-4.20.2 (commit 11a519f) from https://gitlab.xfce.org/xfce/thunar. Upstream history is not included here to keep the repository small.
790 lines
27 KiB
C
790 lines
27 KiB
C
/* vi:set et ai sw=2 sts=2 ts=2: */
|
|
/*-
|
|
* Copyright (c) 2022 Pratyaksh Gautam <pratyakshgautam11@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "thunar/thunar-job-operation.h"
|
|
|
|
#include "thunar/thunar-application.h"
|
|
#include "thunar/thunar-io-jobs.h"
|
|
#include "thunar/thunar-private.h"
|
|
|
|
/**
|
|
* SECTION:thunar-job-operation
|
|
* @Short_description: Stores a job operation (copy, move, trash, rename, etc.)
|
|
* @Title: ThunarJobOperation
|
|
*
|
|
* The #ThunarJobOperation class represents a single 'job operation', a file operation like copying, moving
|
|
* trashing, renaming etc. and its source/target locations.
|
|
*
|
|
*/
|
|
|
|
static void
|
|
thunar_job_operation_finalize (GObject *object);
|
|
static gint
|
|
thunar_job_operation_is_ancestor (gconstpointer descendant,
|
|
gconstpointer ancestor);
|
|
static void
|
|
thunar_job_operation_restore_from_trash (ThunarJobOperation *operation,
|
|
GError **error);
|
|
|
|
|
|
|
|
struct _ThunarJobOperation
|
|
{
|
|
GObject __parent__;
|
|
|
|
ThunarJobOperationKind operation_kind;
|
|
GList *source_file_list;
|
|
GList *target_file_list;
|
|
|
|
/* Files overwritten as a part of an operation */
|
|
GList *overwritten_files;
|
|
|
|
/**
|
|
* Optional timestamps (in seconds) which tell when the operation was started and ended.
|
|
* Only used for trash/restore operations.
|
|
**/
|
|
gint64 start_timestamp;
|
|
gint64 end_timestamp;
|
|
};
|
|
|
|
G_DEFINE_TYPE (ThunarJobOperation, thunar_job_operation, G_TYPE_OBJECT)
|
|
|
|
|
|
|
|
static void
|
|
thunar_job_operation_class_init (ThunarJobOperationClass *klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gobject_class->finalize = thunar_job_operation_finalize;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_job_operation_init (ThunarJobOperation *self)
|
|
{
|
|
self->operation_kind = THUNAR_JOB_OPERATION_KIND_COPY;
|
|
self->source_file_list = NULL;
|
|
self->target_file_list = NULL;
|
|
self->overwritten_files = NULL;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_job_operation_finalize (GObject *object)
|
|
{
|
|
ThunarJobOperation *op;
|
|
|
|
op = THUNAR_JOB_OPERATION (object);
|
|
|
|
g_list_free_full (op->source_file_list, g_object_unref);
|
|
g_list_free_full (op->target_file_list, g_object_unref);
|
|
g_list_free_full (op->overwritten_files, g_object_unref);
|
|
|
|
(*G_OBJECT_CLASS (thunar_job_operation_parent_class)->finalize) (object);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_new:
|
|
* @kind: The kind of operation being created.
|
|
*
|
|
* Creates a new #ThunarJobOperation of the given kind. Should be unref'd by the caller after use.
|
|
*
|
|
* Return value: (transfer full): the newly created #ThunarJobOperation
|
|
**/
|
|
ThunarJobOperation *
|
|
thunar_job_operation_new (ThunarJobOperationKind kind)
|
|
{
|
|
ThunarJobOperation *operation;
|
|
|
|
operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL);
|
|
operation->operation_kind = kind;
|
|
|
|
/* we store the start timestamp in seconds, so we need to divide by 1e6 */
|
|
operation->start_timestamp = g_get_real_time () / (gint64) 1e6;
|
|
|
|
return operation;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_add:
|
|
* @job_operation: a #ThunarJobOperation
|
|
* @source_file: a #GFile representing the source file
|
|
* @target_file: a #GFile representing the target file
|
|
*
|
|
* Adds the specified @source_file-@target_file pair to the given job operation.
|
|
**/
|
|
void
|
|
thunar_job_operation_add (ThunarJobOperation *job_operation,
|
|
GFile *source_file,
|
|
GFile *target_file)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
|
|
_thunar_return_if_fail (source_file == NULL || G_IS_FILE (source_file));
|
|
_thunar_return_if_fail (target_file == NULL || G_IS_FILE (target_file));
|
|
|
|
/* When a directory has a file operation applied to it (for e.g. deletion),
|
|
* the operation will also automatically get applied to its descendants.
|
|
* If the descendant of a that directory is then found, it will try to apply the operation
|
|
* to it again then, meaning the operation is attempted multiple times on the same file.
|
|
*
|
|
* So to avoid such issues on executing a job operation, if the source file is
|
|
* a descendant of an existing file, do not add it to the job operation. */
|
|
if (g_list_find_custom (job_operation->source_file_list, source_file, thunar_job_operation_is_ancestor) != NULL)
|
|
return;
|
|
|
|
/* Don't add NULL files to the list, since it causes memory management issues on object destruction */
|
|
if (source_file != NULL)
|
|
job_operation->source_file_list = g_list_append (job_operation->source_file_list, g_object_ref (source_file));
|
|
if (target_file != NULL)
|
|
job_operation->target_file_list = g_list_append (job_operation->target_file_list, g_object_ref (target_file));
|
|
}
|
|
|
|
|
|
|
|
/***
|
|
* thunar_job_operation_overwrite:
|
|
* @job_operation: a #ThunarJobOperation
|
|
* @overwritten_file: a #GFile representing the file that has been overwritten
|
|
*
|
|
* Logs a file overwritten as a part of an operation.
|
|
**/
|
|
void
|
|
thunar_job_operation_overwrite (ThunarJobOperation *job_operation,
|
|
GFile *overwritten_file)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
|
|
|
|
job_operation->overwritten_files = thunar_g_list_append_deep (job_operation->overwritten_files, overwritten_file);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_get_timestamps:
|
|
* @job_operation: a #ThunarJobOperation
|
|
* @start_timestamp: Will be set to the current start_timestamp of the #ThunarJobOperation
|
|
* @end_timestamp: Will be set to the current end_timestamp of the #ThunarJobOperation
|
|
*
|
|
* Getter for both timestamps
|
|
**/
|
|
void
|
|
thunar_job_operation_get_timestamps (ThunarJobOperation *job_operation,
|
|
gint64 *start_timestamp,
|
|
gint64 *end_timestamp)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
|
|
|
|
if (start_timestamp != NULL)
|
|
*start_timestamp = job_operation->start_timestamp;
|
|
if (end_timestamp != NULL)
|
|
*end_timestamp = job_operation->end_timestamp;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_set_start_timestamp:
|
|
* @job_operation: a #ThunarJobOperation
|
|
* @start_timestamp: the new start_timestamp of the #ThunarJobOperation
|
|
*
|
|
* Setter for start_timestamp
|
|
**/
|
|
void
|
|
thunar_job_operation_set_start_timestamp (ThunarJobOperation *job_operation,
|
|
gint64 start_timestamp)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
|
|
|
|
job_operation->start_timestamp = start_timestamp;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_set_end_timestamp:
|
|
* @job_operation: a #ThunarJobOperation
|
|
* @start_timestamp: the new end_timestamp of the #ThunarJobOperation
|
|
*
|
|
* Setter for end_timestamp
|
|
**/
|
|
void
|
|
thunar_job_operation_set_end_timestamp (ThunarJobOperation *job_operation,
|
|
gint64 end_timestamp)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
|
|
|
|
job_operation->end_timestamp = end_timestamp;
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_get_kind:
|
|
* @job_operation: A #ThunarJobOperation
|
|
*
|
|
* Get the kind of the operation
|
|
*
|
|
* Return value: The kind of the operation
|
|
**/
|
|
ThunarJobOperationKind
|
|
thunar_job_operation_get_kind (ThunarJobOperation *job_operation)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_JOB_OPERATION (job_operation), THUNAR_JOB_OPERATION_KIND_COPY);
|
|
return job_operation->operation_kind;
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_get_overwritten_files:
|
|
* @job_operation: A #ThunarJobOperation
|
|
*
|
|
* Get the overwritten_files of the operation
|
|
*
|
|
* Return value: The overwritten_files of the operation
|
|
**/
|
|
const GList *
|
|
thunar_job_operation_get_overwritten_files (ThunarJobOperation *job_operation)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_JOB_OPERATION (job_operation), NULL);
|
|
return job_operation->overwritten_files;
|
|
}
|
|
|
|
|
|
|
|
gboolean
|
|
thunar_job_operation_empty (ThunarJobOperation *job_operation)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_JOB_OPERATION (job_operation), TRUE);
|
|
|
|
if (job_operation->source_file_list == NULL && job_operation->target_file_list == NULL)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* thunar_job_operation_get_kind_nick:
|
|
* @job_operation: A #ThunarJobOperation
|
|
*
|
|
* Get the nick name of the operation's kind in string format.
|
|
* The string returned should NOT be freed.
|
|
*
|
|
* Return value: A string containing the nick name of the job operation's kind
|
|
**/
|
|
const gchar *
|
|
thunar_job_operation_get_kind_nick (ThunarJobOperation *job_operation)
|
|
{
|
|
GEnumClass *enum_class;
|
|
GEnumValue *enum_value;
|
|
|
|
/* the enum value of the operation kind, which will be used to get its nick name */
|
|
enum_class = g_type_class_ref (THUNAR_TYPE_JOB_OPERATION_KIND);
|
|
enum_value = g_enum_get_value (enum_class, job_operation->operation_kind);
|
|
|
|
return _(enum_value->value_nick);
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_new_invert:
|
|
* @job_operation: a #ThunarJobOperation
|
|
*
|
|
* Creates a new job operation which is the inverse of @job_operation.
|
|
* Should be unref'd by the caller after use.
|
|
*
|
|
* Return value: (transfer full): a newly created #ThunarJobOperation which is the inverse of @job_operation
|
|
**/
|
|
ThunarJobOperation *
|
|
thunar_job_operation_new_invert (ThunarJobOperation *job_operation)
|
|
{
|
|
ThunarJobOperation *inverted_operation;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_JOB_OPERATION (job_operation), NULL);
|
|
|
|
switch (job_operation->operation_kind)
|
|
{
|
|
case THUNAR_JOB_OPERATION_KIND_COPY:
|
|
inverted_operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL);
|
|
inverted_operation->operation_kind = THUNAR_JOB_OPERATION_KIND_DELETE;
|
|
inverted_operation->source_file_list = thunar_g_list_copy_deep (job_operation->target_file_list);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_MOVE:
|
|
inverted_operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL);
|
|
inverted_operation->operation_kind = THUNAR_JOB_OPERATION_KIND_MOVE;
|
|
inverted_operation->source_file_list = thunar_g_list_copy_deep (job_operation->target_file_list);
|
|
inverted_operation->target_file_list = thunar_g_list_copy_deep (job_operation->source_file_list);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_RENAME:
|
|
inverted_operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL);
|
|
inverted_operation->operation_kind = THUNAR_JOB_OPERATION_KIND_RENAME;
|
|
inverted_operation->source_file_list = thunar_g_list_copy_deep (job_operation->target_file_list);
|
|
inverted_operation->target_file_list = thunar_g_list_copy_deep (job_operation->source_file_list);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_TRASH:
|
|
inverted_operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL);
|
|
inverted_operation->operation_kind = THUNAR_JOB_OPERATION_KIND_RESTORE;
|
|
inverted_operation->target_file_list = thunar_g_list_copy_deep (job_operation->source_file_list);
|
|
inverted_operation->start_timestamp = job_operation->start_timestamp;
|
|
inverted_operation->end_timestamp = job_operation->end_timestamp;
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_CREATE_FILE:
|
|
case THUNAR_JOB_OPERATION_KIND_CREATE_FOLDER:
|
|
inverted_operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL);
|
|
inverted_operation->operation_kind = THUNAR_JOB_OPERATION_KIND_DELETE;
|
|
inverted_operation->source_file_list = thunar_g_list_copy_deep (job_operation->target_file_list);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_LINK:
|
|
inverted_operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL);
|
|
inverted_operation->operation_kind = THUNAR_JOB_OPERATION_KIND_UNLINK;
|
|
inverted_operation->source_file_list = thunar_g_list_copy_deep (job_operation->target_file_list);
|
|
inverted_operation->target_file_list = thunar_g_list_copy_deep (job_operation->source_file_list);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
return inverted_operation;
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_execute:
|
|
* @job_operation: a #ThunarJobOperation
|
|
* @error: A #GError to propagate any errors encountered.
|
|
*
|
|
* Executes the given @job_operation, depending on what kind of an operation it is.
|
|
*
|
|
* Return value: TRUE if the operation was canceled, FALSE otherwise
|
|
**/
|
|
gboolean
|
|
thunar_job_operation_execute (ThunarJobOperation *job_operation,
|
|
GError **error)
|
|
{
|
|
ThunarApplication *application;
|
|
GList *thunar_file_list = NULL;
|
|
GError *err = NULL;
|
|
ThunarJob *job = NULL;
|
|
ThunarFile *thunar_file;
|
|
GFile *parent_dir;
|
|
gchar *display_name;
|
|
GFile *template_file;
|
|
gboolean operation_canceled = FALSE;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_JOB_OPERATION (job_operation), FALSE);
|
|
|
|
application = thunar_application_get ();
|
|
|
|
switch (job_operation->operation_kind)
|
|
{
|
|
case THUNAR_JOB_OPERATION_KIND_DELETE:
|
|
case THUNAR_JOB_OPERATION_KIND_UNLINK:
|
|
for (GList *lp = job_operation->source_file_list; lp != NULL; lp = lp->next)
|
|
{
|
|
if (!G_IS_FILE (lp->data))
|
|
{
|
|
g_warning ("One of the files in the job operation list was not a valid GFile");
|
|
continue;
|
|
}
|
|
|
|
thunar_file = thunar_file_get (lp->data, &err);
|
|
|
|
if (err != NULL)
|
|
{
|
|
g_warning ("Failed to convert GFile to ThunarFile: %s", err->message);
|
|
g_clear_error (&err);
|
|
}
|
|
|
|
if (!THUNAR_IS_FILE (thunar_file))
|
|
{
|
|
g_warning ("One of the files in the job operation list did not convert to a valid ThunarFile");
|
|
continue;
|
|
}
|
|
|
|
thunar_file_list = g_list_append (thunar_file_list, thunar_file);
|
|
}
|
|
|
|
if (thunar_file_list == NULL)
|
|
{
|
|
*error = g_error_new (G_FILE_ERROR,
|
|
G_FILE_ERROR_NOENT,
|
|
"No files for deletion found.");
|
|
}
|
|
else
|
|
{
|
|
operation_canceled = thunar_application_unlink_files (application, NULL, thunar_file_list, TRUE, TRUE, THUNAR_OPERATION_LOG_OPERATIONS);
|
|
g_list_free_full (thunar_file_list, g_object_unref);
|
|
}
|
|
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_MOVE:
|
|
/* ensure that all the targets have parent directories which exist */
|
|
for (GList *lp = job_operation->target_file_list; lp != NULL; lp = lp->next)
|
|
{
|
|
parent_dir = g_file_get_parent (lp->data);
|
|
g_file_make_directory_with_parents (parent_dir, NULL, &err);
|
|
g_object_unref (parent_dir);
|
|
|
|
if (err != NULL)
|
|
{
|
|
/* there is no issue if the target directory already exists */
|
|
if (err->code == G_IO_ERROR_EXISTS)
|
|
{
|
|
g_clear_error (&err);
|
|
continue;
|
|
}
|
|
|
|
/* output the err message to console otherwise and abort */
|
|
g_warning ("Error while moving files: %s\n"
|
|
"Aborting operation\n",
|
|
err->message);
|
|
g_propagate_error (error, err);
|
|
g_object_unref (application);
|
|
return operation_canceled;
|
|
}
|
|
}
|
|
|
|
thunar_application_move_files (application, NULL,
|
|
job_operation->source_file_list, job_operation->target_file_list,
|
|
THUNAR_OPERATION_LOG_NO_OPERATIONS, NULL);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_RENAME:
|
|
for (GList *slp = job_operation->source_file_list, *tlp = job_operation->target_file_list;
|
|
slp != NULL && tlp != NULL;
|
|
slp = slp->next, tlp = tlp->next)
|
|
{
|
|
display_name = thunar_g_file_get_display_name (tlp->data);
|
|
thunar_file = thunar_file_get (slp->data, &err);
|
|
|
|
if (err != NULL)
|
|
{
|
|
g_warning ("Error while renaming files: %s\n", err->message);
|
|
g_propagate_error (error, err);
|
|
|
|
g_free (display_name);
|
|
if (thunar_file)
|
|
g_object_unref (thunar_file);
|
|
|
|
continue;
|
|
}
|
|
|
|
job = thunar_io_jobs_rename_file (thunar_file, display_name, THUNAR_OPERATION_LOG_NO_OPERATIONS);
|
|
exo_job_launch (EXO_JOB (job));
|
|
|
|
/* release our reference on the job (a ref is hold by itself, once it is launched) */
|
|
g_object_unref (job);
|
|
|
|
g_free (display_name);
|
|
g_object_unref (thunar_file);
|
|
}
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_RESTORE:
|
|
thunar_job_operation_restore_from_trash (job_operation, &err);
|
|
|
|
if (err != NULL)
|
|
{
|
|
g_warning ("Error while restoring files: %s\n", err->message);
|
|
g_propagate_error (error, err);
|
|
}
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_COPY:
|
|
thunar_application_copy_to (application, NULL,
|
|
job_operation->source_file_list, job_operation->target_file_list,
|
|
THUNAR_OPERATION_LOG_NO_OPERATIONS, NULL);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_CREATE_FILE:
|
|
template_file = NULL;
|
|
if (job_operation->source_file_list != NULL)
|
|
template_file = job_operation->source_file_list->data;
|
|
thunar_application_creat (application, NULL,
|
|
job_operation->target_file_list,
|
|
template_file,
|
|
NULL, THUNAR_OPERATION_LOG_NO_OPERATIONS);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_CREATE_FOLDER:
|
|
thunar_application_mkdir (application, NULL,
|
|
job_operation->target_file_list,
|
|
NULL, THUNAR_OPERATION_LOG_NO_OPERATIONS);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_TRASH:
|
|
/* Special case: 'THUNAR_JOB_OPERATION_KIND_TRASH' only can be triggered by redo */
|
|
/* Since we as well need to update the timestamps, we have to use THUNAR_OPERATION_LOG_ONLY_TIMESTAMPS */
|
|
/* 'thunar_job_operation_history_update_trash_timestamps' will then take care on update the existing job operation instead of adding a new one */
|
|
thunar_application_trash (application, NULL,
|
|
job_operation->source_file_list,
|
|
THUNAR_OPERATION_LOG_ONLY_TIMESTAMPS);
|
|
break;
|
|
|
|
case THUNAR_JOB_OPERATION_KIND_LINK:
|
|
for (GList *target_file = job_operation->target_file_list; target_file != NULL; target_file = target_file->next)
|
|
{
|
|
GFile *target_folder = g_file_get_parent (target_file->data);
|
|
thunar_application_link_into (application, NULL,
|
|
job_operation->source_file_list, target_folder,
|
|
THUNAR_OPERATION_LOG_NO_OPERATIONS, NULL);
|
|
g_object_unref (target_folder);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
_thunar_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
g_object_unref (application);
|
|
return operation_canceled;
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_is_ancestor:
|
|
* @ancestor: potential ancestor of @descendant. A #GFile
|
|
* @descendant: potential descendant of @ancestor. A #GFile
|
|
*
|
|
* Helper function for #g_list_find_custom.
|
|
*
|
|
* Return value: %0 if @ancestor is actually the ancestor of @descendant,
|
|
* %1 otherwise
|
|
**/
|
|
static gint
|
|
thunar_job_operation_is_ancestor (gconstpointer ancestor,
|
|
gconstpointer descendant)
|
|
{
|
|
if (thunar_g_file_is_descendant (G_FILE (descendant), G_FILE (ancestor)))
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_compare:
|
|
* @operation1: First operation for comparison
|
|
* @operation2: Second operation for comparison
|
|
*
|
|
* Helper function to compare operation kind, source and target list of two job operations.
|
|
* Note that timestamps are not compared on purpose.
|
|
*
|
|
* Return value: %0 if both operations match
|
|
* %1 otherwise
|
|
**/
|
|
gint
|
|
thunar_job_operation_compare (ThunarJobOperation *operation1,
|
|
ThunarJobOperation *operation2)
|
|
{
|
|
if (operation1->operation_kind != operation2->operation_kind)
|
|
return 1;
|
|
|
|
if (g_list_length (operation1->source_file_list) != g_list_length (operation2->source_file_list))
|
|
return 1;
|
|
|
|
if (g_list_length (operation1->target_file_list) != g_list_length (operation2->target_file_list))
|
|
return 1;
|
|
|
|
for (GList *lp1 = operation1->source_file_list, *lp2 = operation2->source_file_list;
|
|
lp1 != NULL && lp2 != NULL;
|
|
lp1 = lp1->next, lp2 = lp2->next)
|
|
{
|
|
if (!g_file_equal (lp1->data, lp2->data))
|
|
return 1;
|
|
}
|
|
|
|
for (GList *lp1 = operation1->target_file_list, *lp2 = operation2->target_file_list;
|
|
lp1 != NULL && lp2 != NULL;
|
|
lp1 = lp1->next, lp2 = lp2->next)
|
|
{
|
|
if (!g_file_equal (lp1->data, lp2->data))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_restore_from_trash::
|
|
* @operation: operation containing the information for the files which must be restored
|
|
* @error: a GError instance for error handling
|
|
*
|
|
* Helper function to restore files based on the given @operation
|
|
**/
|
|
static void
|
|
thunar_job_operation_restore_from_trash (ThunarJobOperation *operation,
|
|
GError **error)
|
|
{
|
|
GFileEnumerator *enumerator;
|
|
GFileInfo *info;
|
|
GFile *trash;
|
|
GFile *trashed_file;
|
|
GFile *original_file;
|
|
const char *original_path;
|
|
GDateTime *date;
|
|
GError *err = NULL;
|
|
gint64 deletion_time;
|
|
gpointer lookup;
|
|
GHashTable *files_trashed;
|
|
ThunarApplication *application;
|
|
GList *source_file_list = NULL;
|
|
GList *target_file_list = NULL;
|
|
|
|
|
|
/* enumerate over the files in the trash */
|
|
trash = g_file_new_for_uri ("trash:///");
|
|
enumerator = g_file_enumerate_children (trash,
|
|
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_TRASH_DELETION_DATE "," G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
NULL, &err);
|
|
|
|
if (err != NULL)
|
|
{
|
|
g_object_unref (trash);
|
|
g_object_unref (enumerator);
|
|
|
|
g_propagate_error (error, err);
|
|
return;
|
|
}
|
|
|
|
/* set up a hash table for the files we deleted */
|
|
files_trashed = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
|
|
|
|
/* add all the files that were deleted in the hash table so we can check if a file
|
|
* was deleted as a part of this operation or not in constant time. */
|
|
for (GList *lp = operation->target_file_list; lp != NULL; lp = lp->next)
|
|
{
|
|
GFile *parent = g_file_get_parent (lp->data);
|
|
gchar *real_path = NULL;
|
|
|
|
/* Try to resolve symlinks, otherwise Gfiles wont match */
|
|
/* (All files located in trash have symlinks resolved) */
|
|
if (parent != NULL)
|
|
{
|
|
GFile *parent_resolved = NULL;
|
|
gchar *basename = g_file_get_basename (lp->data);
|
|
parent_resolved = thunar_g_file_resolve_symlink (parent);
|
|
g_object_unref (parent);
|
|
if (parent_resolved != NULL && basename != NULL)
|
|
{
|
|
gchar *parent_path = g_file_get_path (parent_resolved);
|
|
real_path = g_build_filename (parent_path, basename, NULL);
|
|
g_free (parent_path);
|
|
}
|
|
g_free (basename);
|
|
g_object_unref (parent_resolved);
|
|
}
|
|
|
|
if (real_path != NULL)
|
|
g_hash_table_add (files_trashed, g_file_new_for_path (real_path));
|
|
else
|
|
g_hash_table_add (files_trashed, g_object_ref (lp->data));
|
|
g_free (real_path);
|
|
}
|
|
|
|
/* iterate over the files in the trash, adding them to source and target lists of
|
|
* the files which are to be restored and their original paths */
|
|
for (info = g_file_enumerator_next_file (enumerator, NULL, &err); info != NULL; info = g_file_enumerator_next_file (enumerator, NULL, &err))
|
|
{
|
|
if (err != NULL)
|
|
{
|
|
g_object_unref (trash);
|
|
g_object_unref (enumerator);
|
|
g_hash_table_unref (files_trashed);
|
|
g_object_unref (info);
|
|
|
|
g_propagate_error (error, err);
|
|
return;
|
|
}
|
|
|
|
/* get the original path of the file before deletion */
|
|
original_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
|
|
original_file = g_file_new_for_path (original_path);
|
|
|
|
/* get the deletion date reported by the file */
|
|
date = g_file_info_get_deletion_date (info);
|
|
deletion_time = g_date_time_to_unix (date);
|
|
g_date_time_unref (date);
|
|
|
|
/* see if we deleted the file in this session */
|
|
lookup = g_hash_table_lookup (files_trashed, original_file);
|
|
|
|
/* if we deleted the file in this session, and the current file we're looking at was deleted
|
|
* during the time the operation occurred, we conclude we found the right file */
|
|
if (lookup != NULL && operation->start_timestamp <= deletion_time && deletion_time <= operation->end_timestamp)
|
|
{
|
|
trashed_file = g_file_get_child (trash, g_file_info_get_name (info));
|
|
|
|
source_file_list = thunar_g_list_append_deep (source_file_list, trashed_file);
|
|
target_file_list = thunar_g_list_append_deep (target_file_list, original_file);
|
|
|
|
g_object_unref (trashed_file);
|
|
}
|
|
|
|
g_object_unref (info);
|
|
g_object_unref (original_file);
|
|
}
|
|
|
|
g_object_unref (trash);
|
|
g_object_unref (enumerator);
|
|
|
|
if (source_file_list != NULL && target_file_list != NULL)
|
|
{
|
|
/* restore the lists asynchronously using a move operation */
|
|
application = thunar_application_get ();
|
|
thunar_application_move_files (application, NULL, source_file_list, target_file_list, THUNAR_OPERATION_LOG_NO_OPERATIONS, NULL);
|
|
g_object_unref (application);
|
|
|
|
thunar_g_list_free_full (source_file_list);
|
|
thunar_g_list_free_full (target_file_list);
|
|
}
|
|
|
|
g_hash_table_unref (files_trashed);
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_get_action_text:
|
|
* @operation: Instance of #ThunarJobOperation
|
|
*
|
|
* Returns a text describing the current operation
|
|
* The caller is responsible to free the returned string using g_free() when no longer needed.
|
|
*
|
|
* Return value: pointer to the text string
|
|
**/
|
|
gchar *
|
|
thunar_job_operation_get_action_text (ThunarJobOperation *job_operation)
|
|
{
|
|
guint files_count = job_operation->source_file_list == NULL ? 0 : g_list_length (job_operation->source_file_list);
|
|
return g_strdup_printf (ngettext ("%d file", "%d files", files_count), files_count);
|
|
}
|