thunar-usagebars/thunar/thunar-job-operation-history.c
4aiman e969f94310 thunar 4.20.2 upstream
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.
2026-11-03 22:42:48 +03:00

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"));
}