/* vi:set et ai sw=2 sts=2 ts=2: */ /*- * Copyright (c) 2005-2006 Benedikt Meurer * * 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, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "thunar/thunar-private.h" #include "thunar/thunar-renamer-progress.h" #include "thunar/thunar-util.h" /* Signal identifiers */ enum { FINISHED, LAST_SIGNAL, }; static void thunar_renamer_progress_finalize (GObject *object); static void thunar_renamer_progress_destroy (GtkWidget *object); static gboolean thunar_renamer_progress_next_idle (gpointer user_data); static void thunar_renamer_progress_next_idle_destroy (gpointer user_data); static void thunar_renamer_progress_run_helper (ThunarRenamerProgress *renamer_progress, GList *pairs); static void thunar_renamer_progress_run_error_dialog (ThunarRenamerProgress *renamer_progress, ThunarRenamerPair *pair, GError *error); struct _ThunarRenamerProgressClass { GtkAlignmentClass __parent__; }; struct _ThunarRenamerProgress { GtkAlignment __parent__; GtkWidget *bar; GList *pairs_renamed_current_run; /* list of pairs renamed in a given run */ GList *pairs_renamed_all_runs; /* list of pairs renamed across all runs */ GList *pairs_failed_current_run; GList *pairs_pending_current_run; gboolean show_dialog_on_error; gboolean pairs_undo; /* whether we're undoing previous changes */ /* the user may cancel the renaming operation while there are still failed pairs and runs remaining */ gboolean cancel_all_remaining_runs; /* internal main loop for the _rename() method */ guint next_idle_id; GMainLoop *next_idle_loop; }; G_DEFINE_TYPE (ThunarRenamerProgress, thunar_renamer_progress, GTK_TYPE_BOX) static void thunar_renamer_progress_class_init (ThunarRenamerProgressClass *klass) { GtkWidgetClass *gtkwidget_class; GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = thunar_renamer_progress_finalize; gtkwidget_class = GTK_WIDGET_CLASS (klass); gtkwidget_class->destroy = thunar_renamer_progress_destroy; } static void thunar_renamer_progress_init (ThunarRenamerProgress *renamer_progress) { gtk_orientable_set_orientation (GTK_ORIENTABLE (renamer_progress), GTK_ORIENTATION_HORIZONTAL); renamer_progress->bar = gtk_progress_bar_new (); gtk_widget_set_hexpand (renamer_progress->bar, TRUE); gtk_container_add (GTK_CONTAINER (renamer_progress), renamer_progress->bar); gtk_widget_show (renamer_progress->bar); } static void thunar_renamer_progress_finalize (GObject *object) { ThunarRenamerProgress *renamer_progress = THUNAR_RENAMER_PROGRESS (object); /* make sure we're not finalized while the main loop is active */ _thunar_assert (renamer_progress->next_idle_id == 0); _thunar_assert (renamer_progress->next_idle_loop == NULL); /* release the pairs */ thunar_renamer_pair_list_free (renamer_progress->pairs_renamed_current_run); thunar_renamer_pair_list_free (renamer_progress->pairs_renamed_all_runs); thunar_renamer_pair_list_free (renamer_progress->pairs_pending_current_run); (*G_OBJECT_CLASS (thunar_renamer_progress_parent_class)->finalize) (object); } static void thunar_renamer_progress_destroy (GtkWidget *object) { ThunarRenamerProgress *renamer_progress = THUNAR_RENAMER_PROGRESS (object); /* exit the internal main loop on destroy */ thunar_renamer_progress_cancel (renamer_progress); (*GTK_WIDGET_CLASS (thunar_renamer_progress_parent_class)->destroy) (object); } static void thunar_renamer_progress_run_error_dialog (ThunarRenamerProgress *renamer_progress, ThunarRenamerPair *pair, GError *error) { gchar *oldname; GtkWindow *toplevel; GtkWidget *message; gint response; if (g_strcmp0 (thunar_file_get_display_name (pair->file), thunar_file_get_basename (pair->file)) != 0) oldname = g_strconcat (thunar_file_get_display_name (pair->file), " (", thunar_file_get_basename (pair->file), ")", NULL); else oldname = g_strdup (thunar_file_get_display_name (pair->file)); /* determine the toplevel widget */ toplevel = (GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (renamer_progress)); /* tell the user that we failed */ message = gtk_message_dialog_new (toplevel, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE, _("Failed to rename \"%s\" to \"%s\"."), oldname, pair->name); gtk_window_set_title (GTK_WINDOW (message), _("Error")); /* release old name */ g_free (oldname); /* check if we should provide undo */ if (!renamer_progress->pairs_undo && renamer_progress->pairs_renamed_all_runs != NULL) { gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), _("You can either choose to skip this file and continue to rename the " "remaining files, or revert the previously renamed files to their " "previous names, or cancel the operation without reverting previous " "changes.")); gtk_dialog_add_button (GTK_DIALOG (message), _("_Cancel"), GTK_RESPONSE_CANCEL); gtk_dialog_add_button (GTK_DIALOG (message), _("_Revert Changes"), GTK_RESPONSE_REJECT); gtk_dialog_add_button (GTK_DIALOG (message), _("_Skip This File"), GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response (GTK_DIALOG (message), GTK_RESPONSE_ACCEPT); } else if (renamer_progress->pairs_pending_current_run != NULL) { gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), _("Do you want to skip this file and continue to rename the " "remaining files?")); gtk_dialog_add_button (GTK_DIALOG (message), _("_Cancel"), GTK_RESPONSE_CANCEL); gtk_dialog_add_button (GTK_DIALOG (message), _("_Skip This File"), GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response (GTK_DIALOG (message), GTK_RESPONSE_ACCEPT); } else { gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), "%s.", error->message); gtk_dialog_add_button (GTK_DIALOG (message), _("_Close"), GTK_RESPONSE_CANCEL); } /* run the dialog */ response = gtk_dialog_run (GTK_DIALOG (message)); if (response == GTK_RESPONSE_REJECT) { /* undo previous changes */ renamer_progress->pairs_undo = TRUE; /* release the todo pairs and use the done as todo */ thunar_renamer_pair_list_free (renamer_progress->pairs_pending_current_run); renamer_progress->pairs_pending_current_run = renamer_progress->pairs_renamed_all_runs; renamer_progress->pairs_renamed_all_runs = NULL; } else if (response != GTK_RESPONSE_ACCEPT) { /* canceled, just exit the main loop */ g_main_loop_quit (renamer_progress->next_idle_loop); } /* destroy the dialog */ gtk_widget_destroy (message); } static gboolean thunar_renamer_progress_next_idle (gpointer user_data) { ThunarRenamerProgress *renamer_progress = THUNAR_RENAMER_PROGRESS (user_data); ThunarRenamerPair *pair; GError *error = NULL; gchar *oldname; gchar text[128]; guint n_pairs_processed; guint n_total; GList *first; /* check if we have still pairs to go */ if (G_LIKELY (renamer_progress->pairs_pending_current_run != NULL)) { /* pop the first pair from the todo list */ first = g_list_first (renamer_progress->pairs_pending_current_run); pair = first->data; renamer_progress->pairs_pending_current_run = g_list_delete_link (renamer_progress->pairs_pending_current_run, first); /* determine the done/todo items */ n_pairs_processed = g_list_length (renamer_progress->pairs_renamed_current_run) + g_list_length (renamer_progress->pairs_failed_current_run) + 1; n_total = n_pairs_processed + g_list_length (renamer_progress->pairs_pending_current_run); /* update the progress bar text */ g_snprintf (text, sizeof (text), "%d/%d", n_pairs_processed, n_total); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (renamer_progress->bar), text); /* update the progress bar fraction */ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (renamer_progress->bar), CLAMP ((gdouble) n_pairs_processed / MAX (n_total, 1), 0.0, 1.0)); /* try to rename the file */ if (!thunar_file_rename (pair->file, pair->name, NULL, FALSE, &error)) { /* add pair to the list of failed pairs */ renamer_progress->pairs_failed_current_run = g_list_prepend (renamer_progress->pairs_failed_current_run, pair); /* check if error dialog box is to de displayed*/ if (renamer_progress->show_dialog_on_error) thunar_renamer_progress_run_error_dialog (renamer_progress, pair, error); /* clear the error */ g_clear_error (&error); } else { /* remember the old file name (for undo) */ oldname = g_strdup (thunar_file_get_basename (pair->file)); /* replace the newname with the oldname for the pair (-> undo) */ g_free (pair->name); pair->name = oldname; /* move the pair to the list of completed pairs */ renamer_progress->pairs_renamed_current_run = g_list_prepend (renamer_progress->pairs_renamed_current_run, pair); renamer_progress->pairs_renamed_all_runs = g_list_prepend (renamer_progress->pairs_renamed_all_runs, thunar_renamer_pair_copy (pair)); } } /* be sure to cancel the internal loop once we're done */ if (G_UNLIKELY (renamer_progress->pairs_pending_current_run == NULL)) g_main_loop_quit (renamer_progress->next_idle_loop); /* keep the idle source alive as long as we have anything to do */ return (renamer_progress->pairs_pending_current_run != NULL); } static void thunar_renamer_progress_next_idle_destroy (gpointer user_data) { THUNAR_RENAMER_PROGRESS (user_data)->next_idle_id = 0; } /** * thunar_renamer_progress_new: * * Allocates a new #ThunarRenamerProgress instance. * * Return value: the newly allocated #ThunarRenamerProgress. **/ GtkWidget * thunar_renamer_progress_new (void) { return g_object_new (THUNAR_TYPE_RENAMER_PROGRESS, NULL); } /** * thunar_renamer_progress_cancel: * @renamer_progress : a #ThunarRenamerProgress. * * Cancels any pending rename operation for @renamer_progress. **/ void thunar_renamer_progress_cancel (ThunarRenamerProgress *renamer_progress) { _thunar_return_if_fail (THUNAR_IS_RENAMER_PROGRESS (renamer_progress)); /* exit the internal main loop (if any) */ if (G_UNLIKELY (renamer_progress->next_idle_loop != NULL)) g_main_loop_quit (renamer_progress->next_idle_loop); renamer_progress->cancel_all_remaining_runs = TRUE; } /** * thunar_renamer_progress_running: * @renamer_progress : a #ThunarRenamerProgress. * * Returns %TRUE if @renamer_progress is running. * * Return value: %TRUE if @renamer_progress is running. **/ gboolean thunar_renamer_progress_running (ThunarRenamerProgress *renamer_progress) { _thunar_return_val_if_fail (THUNAR_IS_RENAMER_PROGRESS (renamer_progress), FALSE); return (renamer_progress->next_idle_loop != NULL); } /** * thunar_renamer_progress_run_helper: * @renamer_progress : a #ThunarRenamerProgress. * @pair_list : a #GList of #ThunarRenamePairs. * * Renames all #ThunarRenamePairs in the specified @pair_list * using the @renamer_progress. * * A helper function to be used by thunar_renamer_progress_run function. * This method starts a new main loop, and returns only after the * rename operation is done (or cancelled by a "destroy" signal). **/ static void thunar_renamer_progress_run_helper (ThunarRenamerProgress *renamer_progress, GList *pairs) { _thunar_return_if_fail (THUNAR_IS_RENAMER_PROGRESS (renamer_progress)); /* make sure we're not already renaming */ if (G_UNLIKELY (renamer_progress->next_idle_id != 0 || renamer_progress->next_idle_loop != NULL)) return; /* make sure to release the list of completed items */ thunar_renamer_pair_list_free (renamer_progress->pairs_renamed_current_run); renamer_progress->pairs_renamed_current_run = NULL; /* make sure to release the list of failed items */ thunar_renamer_pair_list_free (renamer_progress->pairs_failed_current_run); renamer_progress->pairs_failed_current_run = NULL; /* set the pairs on the todo list */ thunar_renamer_pair_list_free (renamer_progress->pairs_pending_current_run); renamer_progress->pairs_pending_current_run = thunar_renamer_pair_list_copy (pairs); /* schedule the idle source */ renamer_progress->next_idle_id = g_idle_add_full (G_PRIORITY_LOW, thunar_renamer_progress_next_idle, renamer_progress, thunar_renamer_progress_next_idle_destroy); /* run the inner main loop */ renamer_progress->next_idle_loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (renamer_progress->next_idle_loop); g_main_loop_unref (renamer_progress->next_idle_loop); renamer_progress->next_idle_loop = NULL; /* be sure to cancel any pending idle source */ if (G_UNLIKELY (renamer_progress->next_idle_id != 0)) g_source_remove (renamer_progress->next_idle_id); /* release the list of completed items */ thunar_renamer_pair_list_free (renamer_progress->pairs_renamed_current_run); renamer_progress->pairs_renamed_current_run = NULL; /* release the list of todo items */ thunar_renamer_pair_list_free (renamer_progress->pairs_pending_current_run); renamer_progress->pairs_pending_current_run = NULL; } /** * thunar_renamer_progress_run: * @renamer_progress : a #ThunarRenamerProgress. * @pair_list : a #GList of #ThunarRenamePairs. * * Renames all #ThunarRenamePairs in the specified @pair_list * using the @renamer_progress. * * This method uses thunar_renamer_progress_run_helper function to * rename all the given pairs. It first tries to rename all the pairs * and stores all the failed pairs. Then it sorts the failed pairs in * ascending order and again tries to rename them. If still some pairs * are left then it sorts them in descending order and then tries to * rename them. **/ void thunar_renamer_progress_run (ThunarRenamerProgress *renamer_progress, GList *pairs) { _thunar_return_if_fail (THUNAR_IS_RENAMER_PROGRESS (renamer_progress)); /* make sure we're not already renaming */ if (G_UNLIKELY (renamer_progress->next_idle_id != 0 || renamer_progress->next_idle_loop != NULL)) return; /* take an additional reference on the progress */ g_object_ref (G_OBJECT (renamer_progress)); /* initialize all the boolean variables to false */ renamer_progress->show_dialog_on_error = FALSE; renamer_progress->cancel_all_remaining_runs = FALSE; renamer_progress->pairs_undo = FALSE; /* make sure to release the list of total renamed items first */ thunar_renamer_pair_list_free (renamer_progress->pairs_renamed_all_runs); renamer_progress->pairs_renamed_all_runs = NULL; /* try to rename all the files for the first time */ thunar_renamer_progress_run_helper (renamer_progress, pairs); pairs = NULL; /** * while renaming a file, the new name can match with an existing file name, which is yet to be renamed. * by sorting them in ascending and descending sort, files causing such conflicts will be renamed first, and thus renamer can work. * try to rename all the failed files after sorting them in ascending order. **/ if (!renamer_progress->cancel_all_remaining_runs && renamer_progress->pairs_failed_current_run != NULL) { GList *temp_pairs; temp_pairs = thunar_renamer_pair_list_copy (renamer_progress->pairs_failed_current_run); temp_pairs = g_list_sort (temp_pairs, thunar_renamer_pair_compare_ascending); thunar_renamer_progress_run_helper (renamer_progress, temp_pairs); } /* try to rename all the failed files after sorting them in descending order */ if (!renamer_progress->cancel_all_remaining_runs && renamer_progress->pairs_failed_current_run != NULL) { GList *temp_pairs; /* make sure to show the error dialog box */ renamer_progress->show_dialog_on_error = TRUE; temp_pairs = thunar_renamer_pair_list_copy (renamer_progress->pairs_failed_current_run); temp_pairs = g_list_sort (temp_pairs, thunar_renamer_pair_compare_descending); thunar_renamer_progress_run_helper (renamer_progress, temp_pairs); } /* release the list of completed items */ thunar_renamer_pair_list_free (renamer_progress->pairs_failed_current_run); renamer_progress->pairs_failed_current_run = NULL; /* release the additional reference on the progress */ g_object_unref (G_OBJECT (renamer_progress)); }