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.
596 lines
20 KiB
C
596 lines
20 KiB
C
/* vi:set et ai sw=2 sts=2 ts=2: */
|
|
/*-
|
|
* Copyright (c) 2022 Alexander Schwinn <alexxcons@xfce.org>
|
|
*
|
|
* 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-history.h"
|
|
|
|
#include "thunar/thunar-dialogs.h"
|
|
#include "thunar/thunar-enum-types.h"
|
|
#include "thunar/thunar-notify.h"
|
|
#include "thunar/thunar-preferences.h"
|
|
#include "thunar/thunar-private.h"
|
|
|
|
#include <libxfce4ui/libxfce4ui.h>
|
|
|
|
/**
|
|
* SECTION:thunar-job-operation-history
|
|
* @Short_description: Manages the logging of job operations (copy, move etc.) and undoing and redoing them
|
|
* @Title: ThunarJobOperationHistory
|
|
*
|
|
* The single #ThunarJobOperationHistory instance stores all job operations in a #GList
|
|
* and manages tools to manage the list and the next/previous operations which can be undone/redone */
|
|
|
|
/* property identifiers */
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CAN_UNDO,
|
|
PROP_CAN_REDO,
|
|
};
|
|
|
|
|
|
static void
|
|
thunar_job_operation_history_finalize (GObject *object);
|
|
static void
|
|
thunar_job_operation_history_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
|
|
|
|
struct _ThunarJobOperationHistory
|
|
{
|
|
GObject __parent__;
|
|
|
|
/* List of job operations which were logged */
|
|
GList *job_operation_list;
|
|
gint job_operation_list_max_size;
|
|
|
|
/* since the job operation list, lp_undo and lp_redo all refer to the same memory locations,
|
|
* which may be accessed by different threads, we need to protect this memory with a mutex */
|
|
GMutex job_operation_list_mutex;
|
|
|
|
/* List pointer to the operation which can be undone */
|
|
GList *lp_undo;
|
|
|
|
/* List pointer to the operation which can be redone */
|
|
GList *lp_redo;
|
|
};
|
|
|
|
static ThunarJobOperationHistory *job_operation_history;
|
|
|
|
G_DEFINE_TYPE (ThunarJobOperationHistory, thunar_job_operation_history, G_TYPE_OBJECT)
|
|
|
|
|
|
|
|
static void
|
|
thunar_job_operation_history_class_init (ThunarJobOperationHistoryClass *klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gobject_class->get_property = thunar_job_operation_history_get_property;
|
|
gobject_class->finalize = thunar_job_operation_history_finalize;
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CAN_UNDO,
|
|
g_param_spec_boolean ("can-undo",
|
|
"can-undo",
|
|
"can-undo",
|
|
FALSE,
|
|
EXO_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CAN_REDO,
|
|
g_param_spec_boolean ("can-redo",
|
|
"can-redo",
|
|
"can-redo",
|
|
FALSE,
|
|
EXO_PARAM_READABLE));
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_job_operation_history_init (ThunarJobOperationHistory *self)
|
|
{
|
|
ThunarPreferences *preferences;
|
|
|
|
self->job_operation_list = NULL;
|
|
self->lp_undo = NULL;
|
|
self->lp_redo = NULL;
|
|
|
|
preferences = thunar_preferences_get ();
|
|
g_object_get (G_OBJECT (preferences), "misc-undo-redo-history-size", &(self->job_operation_list_max_size), NULL);
|
|
g_object_unref (preferences);
|
|
|
|
g_mutex_init (&self->job_operation_list_mutex);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_job_operation_history_finalize (GObject *object)
|
|
{
|
|
ThunarJobOperationHistory *history = THUNAR_JOB_OPERATION_HISTORY (object);
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION_HISTORY (history));
|
|
|
|
g_list_free_full (history->job_operation_list, g_object_unref);
|
|
|
|
g_mutex_clear (&history->job_operation_list_mutex);
|
|
|
|
(*G_OBJECT_CLASS (thunar_job_operation_history_parent_class)->finalize) (object);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_job_operation_history_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CAN_UNDO:
|
|
g_value_set_boolean (value, thunar_job_operation_history_can_undo ());
|
|
break;
|
|
|
|
case PROP_CAN_REDO:
|
|
g_value_set_boolean (value, thunar_job_operation_history_can_redo ());
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_history_get_default:
|
|
*
|
|
* Returns a reference to the default #ThunarJobOperationHistory
|
|
* instance.
|
|
*
|
|
* The caller is responsible to free the returned instance
|
|
* using g_object_unref() when no longer needed.
|
|
*
|
|
* Return value: the default #ThunarJobOperationHistory instance.
|
|
**/
|
|
ThunarJobOperationHistory *
|
|
thunar_job_operation_history_get_default (void)
|
|
{
|
|
if (G_UNLIKELY (job_operation_history == NULL))
|
|
{
|
|
/* allocate the default monitor */
|
|
job_operation_history = g_object_new (THUNAR_TYPE_JOB_OPERATION_HISTORY, NULL);
|
|
g_object_add_weak_pointer (G_OBJECT (job_operation_history),
|
|
(gpointer) &job_operation_history);
|
|
}
|
|
else
|
|
{
|
|
/* take a reference for the caller */
|
|
g_object_ref (G_OBJECT (job_operation_history));
|
|
}
|
|
|
|
return job_operation_history;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_history_commit:
|
|
* @job_operation: a #ThunarJobOperation
|
|
*
|
|
* Commits, or registers, the given thunar_job_operation, adding the job operation
|
|
* to the job operation list.
|
|
**/
|
|
void
|
|
thunar_job_operation_history_commit (ThunarJobOperation *job_operation)
|
|
{
|
|
GList *new_list = NULL;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
|
|
|
|
if (thunar_job_operation_get_kind (job_operation) == THUNAR_JOB_OPERATION_KIND_TRASH)
|
|
{
|
|
/* set the timestamp for the operation, in seconds. g_get_real_time gives
|
|
* us the time in microseconds, so we need to divide by 1e6. */
|
|
thunar_job_operation_set_end_timestamp (job_operation, g_get_real_time () / (gint64) 1e6);
|
|
}
|
|
|
|
g_mutex_lock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
/* When a new operation is added, drop all previous operations which were undone from the list */
|
|
if (job_operation_history->lp_redo != NULL)
|
|
{
|
|
for (GList *lp = job_operation_history->job_operation_list;
|
|
lp != NULL && lp != job_operation_history->lp_redo;
|
|
lp = lp->next)
|
|
new_list = g_list_append (new_list, g_object_ref (lp->data));
|
|
g_list_free_full (job_operation_history->job_operation_list, g_object_unref);
|
|
job_operation_history->job_operation_list = new_list;
|
|
}
|
|
|
|
/* Add the new operation to our list */
|
|
job_operation_history->job_operation_list = g_list_append (job_operation_history->job_operation_list, g_object_ref (job_operation));
|
|
|
|
/* reset the undo pointer to latest operation and clear the redo pointer */
|
|
job_operation_history->lp_undo = g_list_last (job_operation_history->job_operation_list);
|
|
job_operation_history->lp_redo = NULL;
|
|
|
|
/* Limit the size of the list */
|
|
if (job_operation_history->job_operation_list_max_size != -1 && g_list_length (job_operation_history->job_operation_list) > (guint) (job_operation_history->job_operation_list_max_size))
|
|
{
|
|
GList *first = g_list_first (job_operation_history->job_operation_list);
|
|
job_operation_history->job_operation_list = g_list_remove_link (job_operation_history->job_operation_list, first);
|
|
g_list_free_full (first, g_object_unref);
|
|
}
|
|
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
/* Notify all subscribers of our properties */
|
|
g_object_notify (G_OBJECT (job_operation_history), "can-undo");
|
|
g_object_notify (G_OBJECT (job_operation_history), "can-redo");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_history_update_trash_timestamps:
|
|
* @job_operation: a #ThunarJobOperation
|
|
*
|
|
* Only updates the timestamps of the latest trash operation
|
|
* That is needed after 'redo' of a 'trash' operation,
|
|
* since it requires to set new timestamps (otherwise 'undo' of that operation wont work afterwards)
|
|
**/
|
|
void
|
|
thunar_job_operation_history_update_trash_timestamps (ThunarJobOperation *job_operation)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
|
|
|
|
if (thunar_job_operation_get_kind (job_operation) != THUNAR_JOB_OPERATION_KIND_TRASH)
|
|
return;
|
|
|
|
g_mutex_lock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
if (job_operation_history->lp_undo == NULL)
|
|
{
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return;
|
|
}
|
|
|
|
if (thunar_job_operation_compare (THUNAR_JOB_OPERATION (job_operation_history->lp_undo->data), job_operation) == 0)
|
|
{
|
|
gint64 start_timestamp, end_timestamp;
|
|
|
|
thunar_job_operation_get_timestamps (job_operation, &start_timestamp, &end_timestamp);
|
|
|
|
/* set the timestamp for the operation, in seconds. g_get_real_time gives
|
|
* us the time in microseconds, so we need to divide by 1e6. */
|
|
end_timestamp = g_get_real_time () / (gint64) 1e6;
|
|
|
|
thunar_job_operation_set_start_timestamp (THUNAR_JOB_OPERATION (job_operation_history->lp_undo->data), start_timestamp);
|
|
thunar_job_operation_set_end_timestamp (THUNAR_JOB_OPERATION (job_operation_history->lp_undo->data), end_timestamp);
|
|
}
|
|
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_history_undo:
|
|
* @parent: the parent #GtkWindow
|
|
*
|
|
* Undoes the latest job operation, by executing its inverse
|
|
**/
|
|
void
|
|
thunar_job_operation_history_undo (GtkWindow *parent)
|
|
{
|
|
ThunarJobOperation *operation_marker;
|
|
ThunarJobOperation *inverted_operation;
|
|
GString *warning_body;
|
|
gchar *file_uri;
|
|
GError *err = NULL;
|
|
const GList *overwritten_files;
|
|
gboolean operation_canceled;
|
|
|
|
g_mutex_lock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
/* Show a warning in case there is no operation to undo */
|
|
if (job_operation_history->lp_undo == NULL)
|
|
{
|
|
xfce_dialog_show_warning (parent,
|
|
_("No operation which can be undone has been performed yet.\n"
|
|
"(For some operations undo is not supported)"),
|
|
_("There is no operation to undo"));
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return;
|
|
}
|
|
|
|
/* the 'marked' operation */
|
|
operation_marker = job_operation_history->lp_undo->data;
|
|
|
|
/* fix position undo/redo pointers */
|
|
job_operation_history->lp_redo = job_operation_history->lp_undo;
|
|
job_operation_history->lp_undo = g_list_previous (job_operation_history->lp_undo);
|
|
|
|
/* warn the user if the previous operation is empty, since then there is nothing to undo */
|
|
if (thunar_job_operation_empty (operation_marker))
|
|
{
|
|
xfce_dialog_show_warning (parent,
|
|
_("The operation you are trying to undo does not have any files "
|
|
"associated with it, and thus cannot be undone."),
|
|
_("%s operation cannot be undone"), thunar_job_operation_get_kind_nick (operation_marker));
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return;
|
|
}
|
|
|
|
/* if there were files overwritten in the operation, warn about them */
|
|
overwritten_files = thunar_job_operation_get_overwritten_files (operation_marker);
|
|
if (overwritten_files != NULL)
|
|
{
|
|
gint index;
|
|
|
|
index = 1; /* one indexed for the dialog */
|
|
warning_body = g_string_new (_("The following files were overwritten in the operation "
|
|
"you are trying to undo and cannot be restored:\n"));
|
|
|
|
for (const GList *lp = overwritten_files; lp != NULL; lp = lp->next, index++)
|
|
{
|
|
file_uri = g_file_get_uri (lp->data);
|
|
g_string_append_printf (warning_body, "%d. %s\n", index, file_uri);
|
|
g_free (file_uri);
|
|
}
|
|
|
|
xfce_dialog_show_warning (parent,
|
|
warning_body->str,
|
|
_("%s operation can only be partially undone"),
|
|
thunar_job_operation_get_kind_nick (operation_marker));
|
|
|
|
g_string_free (warning_body, TRUE);
|
|
}
|
|
|
|
inverted_operation = thunar_job_operation_new_invert (operation_marker);
|
|
operation_canceled = thunar_job_operation_execute (inverted_operation, &err);
|
|
g_object_unref (inverted_operation);
|
|
|
|
if (err == NULL && !operation_canceled)
|
|
thunar_notify_undo (operation_marker);
|
|
|
|
if (err != NULL)
|
|
{
|
|
xfce_dialog_show_warning (parent,
|
|
err->message,
|
|
_("Failed to undo operation '%s'"),
|
|
thunar_job_operation_get_kind_nick (operation_marker));
|
|
g_clear_error (&err);
|
|
}
|
|
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
/* Notify all subscribers of our properties */
|
|
g_object_notify (G_OBJECT (job_operation_history), "can-undo");
|
|
g_object_notify (G_OBJECT (job_operation_history), "can-redo");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_history_redo:
|
|
* @parent: the parent #GtkWindow
|
|
*
|
|
* Redoes the last job operation which had been undone (if any)
|
|
**/
|
|
void
|
|
thunar_job_operation_history_redo (GtkWindow *parent)
|
|
{
|
|
ThunarJobOperation *operation_marker;
|
|
GString *warning_body;
|
|
gchar *file_uri;
|
|
GError *err = NULL;
|
|
const GList *overwritten_files;
|
|
gboolean operation_canceled;
|
|
|
|
g_mutex_lock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
/* Show a warning in case there is no operation to undo */
|
|
if (job_operation_history->lp_redo == NULL)
|
|
{
|
|
xfce_dialog_show_warning (parent,
|
|
_("No operation which can be redone available.\n"),
|
|
_("There is no operation to redo"));
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return;
|
|
}
|
|
|
|
/* the 'marked' operation */
|
|
operation_marker = job_operation_history->lp_redo->data;
|
|
|
|
/* fix position undo/redo pointers */
|
|
job_operation_history->lp_undo = job_operation_history->lp_redo;
|
|
job_operation_history->lp_redo = g_list_next (job_operation_history->lp_redo);
|
|
|
|
/* warn the user if the previous operation is empty, since then there is nothing to undo */
|
|
if (thunar_job_operation_empty (operation_marker))
|
|
{
|
|
xfce_dialog_show_warning (parent,
|
|
_("The operation you are trying to redo does not have any files "
|
|
"associated with it, and thus cannot be redone."),
|
|
_("%s operation cannot be redone"), thunar_job_operation_get_kind_nick (operation_marker));
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return;
|
|
}
|
|
|
|
/* if there were files overwritten in the operation, warn about them */
|
|
overwritten_files = thunar_job_operation_get_overwritten_files (operation_marker);
|
|
if (overwritten_files != NULL)
|
|
{
|
|
gint index;
|
|
|
|
index = 1; /* one indexed for the dialog */
|
|
warning_body = g_string_new (_("The following files were overwritten in the operation "
|
|
"you are trying to redo and cannot be restored:\n"));
|
|
|
|
for (const GList *lp = overwritten_files; lp != NULL; lp = lp->next, index++)
|
|
{
|
|
file_uri = g_file_get_uri (lp->data);
|
|
g_string_append_printf (warning_body, "%d. %s\n", index, file_uri);
|
|
g_free (file_uri);
|
|
}
|
|
|
|
xfce_dialog_show_warning (parent,
|
|
warning_body->str,
|
|
_("%s operation can only be partially redone"),
|
|
thunar_job_operation_get_kind_nick (operation_marker));
|
|
|
|
g_string_free (warning_body, TRUE);
|
|
}
|
|
|
|
operation_canceled = thunar_job_operation_execute (operation_marker, &err);
|
|
|
|
if (err == NULL && !operation_canceled)
|
|
thunar_notify_undo (operation_marker);
|
|
|
|
if (err != NULL)
|
|
{
|
|
xfce_dialog_show_warning (parent,
|
|
err->message,
|
|
_("Failed to redo operation '%s'"),
|
|
thunar_job_operation_get_kind_nick (operation_marker));
|
|
g_clear_error (&err);
|
|
}
|
|
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
/* Notify all subscribers of our properties */
|
|
g_object_notify (G_OBJECT (job_operation_history), "can-undo");
|
|
g_object_notify (G_OBJECT (job_operation_history), "can-redo");
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_history_can_undo:
|
|
*
|
|
* Returns whether or not there is an operation on the job operation list that can be undone.
|
|
*
|
|
* Return value: A gboolean representing whether or not there is an undoable operation
|
|
**/
|
|
gboolean
|
|
thunar_job_operation_history_can_undo (void)
|
|
{
|
|
g_mutex_lock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
if (job_operation_history->lp_undo == NULL)
|
|
{
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return FALSE;
|
|
}
|
|
|
|
if (thunar_job_operation_empty (job_operation_history->lp_undo->data))
|
|
{
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return FALSE;
|
|
}
|
|
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/* thunar_job_operation_history_can_redo:
|
|
*
|
|
* Returns whether or not there is an operation on the job operation list that can be redone.
|
|
*
|
|
* Return value: A gboolean representing whether or not there is an redoable operation
|
|
**/
|
|
gboolean
|
|
thunar_job_operation_history_can_redo (void)
|
|
{
|
|
g_mutex_lock (&job_operation_history->job_operation_list_mutex);
|
|
|
|
if (job_operation_history->lp_redo == NULL)
|
|
{
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return FALSE;
|
|
}
|
|
|
|
if (thunar_job_operation_empty (job_operation_history->lp_redo->data))
|
|
{
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return FALSE;
|
|
}
|
|
|
|
g_mutex_unlock (&job_operation_history->job_operation_list_mutex);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_history_get_undo_text:
|
|
*
|
|
* Returns the description of the undo action
|
|
* The returned string should be freed with g_free() when no longer needed.
|
|
*
|
|
* Return value: a newly-allocated string holding the text
|
|
**/
|
|
gchar *
|
|
thunar_job_operation_history_get_undo_text (void)
|
|
{
|
|
if (thunar_job_operation_history_can_undo ())
|
|
{
|
|
gchar *action_text = thunar_job_operation_get_action_text (job_operation_history->lp_undo->data);
|
|
/* TRANSLATORS: An example: 'Undo the latest 'copy' operation (2 files) */
|
|
gchar *final_text = g_strdup_printf (_("Undo the latest '%s' operation (%s)"), thunar_job_operation_get_kind_nick (job_operation_history->lp_undo->data), action_text);
|
|
g_free (action_text);
|
|
return final_text;
|
|
}
|
|
|
|
return g_strdup (gettext ("Undo the latest operation"));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_job_operation_history_get_redo_text
|
|
*
|
|
* Returns the description of the redo action.
|
|
* The returned string should be freed with g_free() when no longer needed.
|
|
*
|
|
* Return value: a newly-allocated string holding the text
|
|
**/
|
|
gchar *
|
|
thunar_job_operation_history_get_redo_text (void)
|
|
{
|
|
if (thunar_job_operation_history_can_redo ())
|
|
{
|
|
gchar *action_text = thunar_job_operation_get_action_text (job_operation_history->lp_redo->data);
|
|
/* TRANSLATORS: An example: 'Redo the latest 'copy' operation (2 files) */
|
|
gchar *final_text = g_strdup_printf (_("Redo the latest '%s' operation (%s)"), thunar_job_operation_get_kind_nick (job_operation_history->lp_redo->data), action_text);
|
|
g_free (action_text);
|
|
return final_text;
|
|
}
|
|
|
|
return g_strdup (gettext ("Redo the latest operation"));
|
|
}
|