1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* CajaUndoStackManager - Manages undo of file operations (implementation)
4  *
5  * Copyright (C) 2007-2010 Amos Brocco
6  * Copyright (C) 2011 Stefano Karapetsas
7  * Copyright (C) 2012-2021 The MATE developers
8  *
9  * Authors: Amos Brocco <amos.brocco@unifr.ch>,
10  *          Stefano Karapetsas <stefano@karapetsas.com>
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25  * Boston, MA 02110-1301, USA.
26  */
27 
28 #include "caja-undostack-manager.h"
29 #include "caja-file-operations.h"
30 #include "caja-file.h"
31 #include <gio/gio.h>
32 #include <glib/gprintf.h>
33 #include <glib-object.h>
34 #include <glib/gi18n.h>
35 #include <locale.h>
36 #include <gdk/gdk.h>
37 
38 /* *****************************************************************
39  Private fields
40  ***************************************************************** */
41 
42 struct _CajaUndoStackActionData
43 {
44   /* Common stuff */
45   CajaUndoStackActionType type;
46   gboolean isValid;
47   gboolean locked;              /* True if the action is being undone/redone */
48   gboolean freed;               /* True if the action must be freed after undo/redo */
49   guint count;                  /* Size of affected uris (count of items) */
50   CajaUndoStackManager *manager;    /* Pointer to the manager */
51 
52   /* Copy / Move stuff */
53   GFile *src_dir;
54   GFile *dest_dir;
55   GList *sources;               /* Relative to src_dir */
56   GList *destinations;          /* Relative to dest_dir */
57 
58   /* Cached labels/descriptions */
59   char *undo_label;
60   char *undo_description;
61   char *redo_label;
62   char *redo_description;
63 
64   /* Create new file/folder stuff/set permissions */
65   char *template;
66   char *target_uri;
67 
68   /* Rename stuff */
69   char *old_uri;
70   char *new_uri;
71 
72   /* Trash stuff */
73   GHashTable *trashed;
74 
75   /* Recursive change permissions stuff */
76   GHashTable *original_permissions;
77   guint32 dir_mask;
78   guint32 dir_permissions;
79   guint32 file_mask;
80   guint32 file_permissions;
81 
82   /* Single file change permissions stuff */
83   guint32 current_permissions;
84   guint32 new_permissions;
85 
86   /* Group */
87   char *original_group_name_or_id;
88   char *new_group_name_or_id;
89 
90   /* Owner */
91   char *original_user_name_or_id;
92   char *new_user_name_or_id;
93 
94 };
95 
96 struct _CajaUndoStackManagerPrivate
97 {
98   /* Private fields */
99   GQueue *stack;
100   guint undo_levels;
101   guint index;
102   GMutex mutex;                /* Used to protect access to stack (because of async file ops) */
103   gboolean dispose_has_run;
104   gboolean undo_redo_flag;
105   gboolean confirm_delete;
106 };
107 
108 /* *****************************************************************
109  Properties management prototypes
110  ***************************************************************** */
111 enum
112 {
113   PROP_UNDOSTACK_MANAGER_0, PROP_UNDO_LEVELS
114 };
115 
116 static void caja_undostack_manager_set_property (GObject * object,
117     guint prop_id, const GValue * value, GParamSpec * pspec);
118 
119 static void caja_undostack_manager_get_property (GObject * object,
120     guint prop_id, GValue * value, GParamSpec * pspec);
121 
122 /* *****************************************************************
123  Destructors prototypes
124  ***************************************************************** */
125 static void caja_undostack_manager_finalize (GObject * object);
126 
127 static void caja_undostack_manager_dispose (GObject * object);
128 
129 /* *****************************************************************
130  Type definition
131  ***************************************************************** */
132 G_DEFINE_TYPE_WITH_PRIVATE (CajaUndoStackManager, caja_undostack_manager,
133     G_TYPE_OBJECT);
134 
135 /* *****************************************************************
136  Private methods prototypes
137  ***************************************************************** */
138 
139 static void stack_clear_n_oldest (GQueue * stack, guint n);
140 
141 static void stack_fix_size (CajaUndoStackManagerPrivate * priv);
142 
143 static gboolean can_undo (CajaUndoStackManagerPrivate * priv);
144 
145 static gboolean can_redo (CajaUndoStackManagerPrivate * priv);
146 
147 static void stack_push_action (CajaUndoStackManagerPrivate * priv,
148     CajaUndoStackActionData * action);
149 
150 static CajaUndoStackActionData
151     * stack_scroll_left (CajaUndoStackManagerPrivate * priv);
152 
153 static CajaUndoStackActionData
154     * stack_scroll_right (CajaUndoStackManagerPrivate * priv);
155 
156 static CajaUndoStackActionData
157     * get_next_redo_action (CajaUndoStackManagerPrivate * priv);
158 
159 static CajaUndoStackActionData
160     * get_next_undo_action (CajaUndoStackManagerPrivate * priv);
161 
162 static gchar *get_undo_label (CajaUndoStackActionData * action);
163 
164 static gchar *get_undo_description (CajaUndoStackActionData * action);
165 
166 static gchar *get_redo_label (CajaUndoStackActionData * action);
167 
168 static gchar *get_redo_description (CajaUndoStackActionData * action);
169 
170 static void do_menu_update (CajaUndoStackManager * manager);
171 
172 static void free_undostack_action (gpointer data, gpointer user_data);
173 
174 static void undostack_dispose_all (GQueue * queue);
175 
176 static void undo_redo_done_transfer_callback (GHashTable * debuting_uris,
177     gpointer data);
178 
179 static void undo_redo_op_callback (gpointer callback_data);
180 
181 static void undo_redo_done_rename_callback (CajaFile * file,
182     GFile * result_location, GError * error, gpointer callback_data);
183 
184 static void undo_redo_done_delete_callback (GHashTable * debuting_uris,
185     gboolean user_cancel, gpointer callback_data);
186 
187 static void undo_redo_done_create_callback (GFile * new_file,
188     gpointer callback_data);
189 
190 static void clear_redo_actions (CajaUndoStackManagerPrivate * priv);
191 
192 static gchar *get_first_target_short_name (CajaUndoStackActionData *
193     action);
194 
195 static GList *construct_gfile_list (const GList * urilist, GFile * parent);
196 
197 static GList *construct_gfile_list_from_uri (char *uri);
198 
199 static GList *uri_list_to_gfile_list (GList * urilist);
200 
201 static char *get_uri_basename (char *uri);
202 
203 static char *get_uri_parent (char *uri);
204 
205 static char *get_uri_parent_path (char *uri);
206 
207 static GHashTable *retrieve_files_to_restore (GHashTable * trashed);
208 
209 /* *****************************************************************
210  Base functions
211  ***************************************************************** */
212 static void
caja_undostack_manager_class_init(CajaUndoStackManagerClass * klass)213 caja_undostack_manager_class_init (CajaUndoStackManagerClass * klass)
214 {
215   GParamSpec *undo_levels;
216   GObjectClass *g_object_class;
217 
218   /* Create properties */
219   undo_levels = g_param_spec_uint ("undo-levels", "undo levels",
220       "Number of undo levels to be stored",
221       1, UINT_MAX, 30, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
222 
223   /* Set properties get/set methods */
224   g_object_class = G_OBJECT_CLASS (klass);
225 
226   g_object_class->set_property = caja_undostack_manager_set_property;
227   g_object_class->get_property = caja_undostack_manager_get_property;
228 
229   /* Install properties */
230   g_object_class_install_property (g_object_class, PROP_UNDO_LEVELS,
231       undo_levels);
232 
233   /* The UI menu needs to update its status */
234   g_signal_new ("request-menu-update",
235       G_TYPE_FROM_CLASS (klass),
236       G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE |
237       G_SIGNAL_NO_HOOKS, 0, NULL, NULL,
238       g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
239 
240   /* Hook deconstructors */
241   g_object_class->dispose = caja_undostack_manager_dispose;
242   g_object_class->finalize = caja_undostack_manager_finalize;
243 }
244 
245 static void
caja_undostack_manager_init(CajaUndoStackManager * self)246 caja_undostack_manager_init (CajaUndoStackManager * self)
247 {
248   CajaUndoStackManagerPrivate *priv;
249 
250   priv = caja_undostack_manager_get_instance_private (self);
251 
252   self->priv = priv;
253 
254   /* Initialize private fields */
255   priv->stack = g_queue_new ();
256   g_mutex_init (&priv->mutex);
257   priv->index = 0;
258   priv->dispose_has_run = FALSE;
259   priv->undo_redo_flag = FALSE;
260 }
261 
262 static void
caja_undostack_manager_dispose(GObject * object)263 caja_undostack_manager_dispose (GObject * object)
264 {
265   CajaUndoStackManager *self = CAJA_UNDOSTACK_MANAGER (object);
266   CajaUndoStackManagerPrivate *priv = self->priv;
267 
268   if (priv->dispose_has_run)
269     return;
270 
271   g_mutex_lock (&priv->mutex);
272 
273   /* Free each undoable action in the stack and the stack itself */
274   undostack_dispose_all (priv->stack);
275   g_queue_free (priv->stack);
276   g_mutex_unlock (&priv->mutex);
277   g_mutex_clear (&priv->mutex);
278 
279   priv->dispose_has_run = TRUE;
280 
281   G_OBJECT_CLASS (caja_undostack_manager_parent_class)->dispose (object);
282 }
283 
284 static void
caja_undostack_manager_finalize(GObject * object)285 caja_undostack_manager_finalize (GObject * object)
286 {
287   G_OBJECT_CLASS (caja_undostack_manager_parent_class)->finalize (object);
288 }
289 
290 /* *****************************************************************
291  Property management
292  ***************************************************************** */
293 static void
caja_undostack_manager_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)294 caja_undostack_manager_set_property (GObject * object, guint prop_id,
295     const GValue * value, GParamSpec * pspec)
296 {
297   g_return_if_fail (IS_CAJA_UNDOSTACK_MANAGER (object));
298 
299   CajaUndoStackManager *manager = CAJA_UNDOSTACK_MANAGER (object);
300   CajaUndoStackManagerPrivate *priv = manager->priv;
301   guint new_undo_levels;
302 
303   switch (prop_id) {
304     case PROP_UNDO_LEVELS:
305       new_undo_levels = g_value_get_uint (value);
306       if (new_undo_levels > 0 && (priv->undo_levels != new_undo_levels)) {
307         priv->undo_levels = new_undo_levels;
308         g_mutex_lock (&priv->mutex);
309         stack_fix_size (priv);
310         g_mutex_unlock (&priv->mutex);
311         do_menu_update (manager);
312       }
313       break;
314     default:
315       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
316       break;
317   }
318 }
319 
320 static void
caja_undostack_manager_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)321 caja_undostack_manager_get_property (GObject * object, guint prop_id,
322     GValue * value, GParamSpec * pspec)
323 {
324   g_return_if_fail (IS_CAJA_UNDOSTACK_MANAGER (object));
325 
326   CajaUndoStackManager *manager = CAJA_UNDOSTACK_MANAGER (object);
327   CajaUndoStackManagerPrivate *priv = manager->priv;
328 
329   switch (prop_id) {
330     case PROP_UNDO_LEVELS:
331       g_value_set_uint (value, priv->undo_levels);
332       break;
333 
334     default:
335       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
336       break;
337   }
338 }
339 
340 /* *****************************************************************
341  Public methods
342  ***************************************************************** */
343 
344 /** ****************************************************************
345  * Returns the undo stack manager instance (singleton pattern)
346  ** ****************************************************************/
347 CajaUndoStackManager *
caja_undostack_manager_instance(void)348 caja_undostack_manager_instance (void)
349 {
350   static CajaUndoStackManager *manager = NULL;
351 
352   if (manager == NULL) {
353     manager =
354         g_object_new (TYPE_CAJA_UNDOSTACK_MANAGER, "undo-levels", 32, NULL);
355   }
356 
357   return manager;
358 }
359 
360 /** ****************************************************************
361  * True if undoing / redoing
362  ** ****************************************************************/
363 gboolean
caja_undostack_manager_is_undo_redo(CajaUndoStackManager * manager)364 caja_undostack_manager_is_undo_redo (CajaUndoStackManager * manager)
365 {
366   CajaUndoStackManagerPrivate *priv = manager->priv;
367   if (priv->undo_redo_flag) {
368     return TRUE;
369   }
370 
371   return FALSE;
372 }
373 
374 void
caja_undostack_manager_request_menu_update(CajaUndoStackManager * manager)375 caja_undostack_manager_request_menu_update (CajaUndoStackManager *
376     manager)
377 {
378   do_menu_update (manager);
379 }
380 
381 /** ****************************************************************
382  * Redoes the last file operation
383  ** ****************************************************************/
384 void
caja_undostack_manager_redo(CajaUndoStackManager * manager,GtkWidget * parent_view,CajaUndostackFinishCallback cb)385 caja_undostack_manager_redo (CajaUndoStackManager * manager,
386     GtkWidget * parent_view, CajaUndostackFinishCallback cb)
387 {
388   CajaUndoStackManagerPrivate *priv = manager->priv;
389 
390   g_mutex_lock (&priv->mutex);
391 
392   CajaUndoStackActionData *action = stack_scroll_left (priv);
393 
394   /* Action will be NULL if redo is not possible */
395   if (action != NULL) {
396     action->locked = TRUE;
397   }
398 
399   g_mutex_unlock (&priv->mutex);
400 
401   do_menu_update (manager);
402 
403   if (action != NULL) {
404     action->locked = TRUE;      /* Remember to unlock when redo is finished */
405     priv->undo_redo_flag = TRUE;
406     switch (action->type) {
407       case CAJA_UNDOSTACK_COPY:
408       {
409         GList *uris;
410 
411         uris = construct_gfile_list (action->sources, action->src_dir);
412         caja_file_operations_copy (uris, NULL,
413             action->dest_dir, NULL, undo_redo_done_transfer_callback, action);
414         g_list_free_full (uris, g_object_unref);
415         break;
416       }
417       case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE:
418       {
419         char *new_name;
420         char *puri;
421 
422         puri = get_uri_parent (action->target_uri);
423         new_name = get_uri_basename (action->target_uri);
424         caja_file_operations_new_file_from_template (NULL,
425             NULL,
426             puri,
427             new_name, action->template, undo_redo_done_create_callback, action);
428         g_free (puri);
429         g_free (new_name);
430         break;
431       }
432       case CAJA_UNDOSTACK_DUPLICATE:
433       {
434         GList *uris;
435 
436         uris = construct_gfile_list (action->sources, action->src_dir);
437         caja_file_operations_duplicate (uris, NULL, NULL,
438             undo_redo_done_transfer_callback, action);
439     	g_list_free_full (uris, g_object_unref);
440         break;
441       }
442       case CAJA_UNDOSTACK_RESTOREFROMTRASH:
443       case CAJA_UNDOSTACK_MOVE:
444       {
445         GList *uris;
446 
447         uris = construct_gfile_list (action->sources, action->src_dir);
448         caja_file_operations_move (uris, NULL,
449             action->dest_dir, NULL, undo_redo_done_transfer_callback, action);
450     	g_list_free_full (uris, g_object_unref);
451         break;
452       }
453       case CAJA_UNDOSTACK_RENAME:
454       {
455         CajaFile *file;
456         char *new_name;
457 
458         new_name = get_uri_basename (action->new_uri);
459         file = caja_file_get_by_uri (action->old_uri);
460         caja_file_rename (file, new_name,
461             undo_redo_done_rename_callback, action);
462         g_object_unref (file);
463         g_free (new_name);
464         break;
465       }
466       case CAJA_UNDOSTACK_CREATEEMPTYFILE:
467       {
468         char *new_name;
469         char *puri;
470 
471         puri = get_uri_parent (action->target_uri);
472         new_name = get_uri_basename (action->target_uri);
473         caja_file_operations_new_file (NULL, NULL, puri,
474             new_name,
475             action->template,
476             0, undo_redo_done_create_callback, action);
477         g_free (puri);
478         g_free (new_name);
479         break;
480       }
481       case CAJA_UNDOSTACK_CREATEFOLDER:
482       {
483         char *puri;
484 
485         puri = get_uri_parent (action->target_uri);
486         caja_file_operations_new_folder (NULL, NULL, puri,
487             undo_redo_done_create_callback, action);
488         g_free (puri);
489         break;
490       }
491       case CAJA_UNDOSTACK_MOVETOTRASH:
492         if (g_hash_table_size (action->trashed) > 0) {
493           GList *uris;
494 
495           GList *uri_to_trash = g_hash_table_get_keys (action->trashed);
496           uris = uri_list_to_gfile_list (uri_to_trash);
497           priv->undo_redo_flag = TRUE;
498           caja_file_operations_trash_or_delete
499               (uris, NULL, undo_redo_done_delete_callback, action);
500           g_list_free (uri_to_trash);
501     	  g_list_free_full (uris, g_object_unref);
502         }
503         break;
504       case CAJA_UNDOSTACK_CREATELINK:
505       {
506         GList *uris;
507 
508         uris = construct_gfile_list (action->sources, action->src_dir);
509         caja_file_operations_link (uris, NULL,
510             action->dest_dir, NULL, undo_redo_done_transfer_callback, action);
511     	g_list_free_full (uris, g_object_unref);
512         break;
513       }
514       case CAJA_UNDOSTACK_SETPERMISSIONS:
515       {
516         CajaFile *file;
517 
518         file = caja_file_get_by_uri (action->target_uri);
519         caja_file_set_permissions (file,
520             action->new_permissions, undo_redo_done_rename_callback, action);
521         g_object_unref (file);
522         break;
523       }
524       case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS:
525       {
526         char *puri;
527 
528         puri = g_file_get_uri (action->dest_dir);
529         caja_file_set_permissions_recursive (puri,
530             action->file_permissions,
531             action->file_mask,
532             action->dir_permissions,
533             action->dir_mask, undo_redo_op_callback, action);
534         g_free (puri);
535         break;
536       }
537       case CAJA_UNDOSTACK_CHANGEGROUP:
538       {
539         CajaFile *file;
540 
541         file = caja_file_get_by_uri (action->target_uri);
542         caja_file_set_group (file,
543             action->new_group_name_or_id,
544             undo_redo_done_rename_callback, action);
545         g_object_unref (file);
546         break;
547       }
548       case CAJA_UNDOSTACK_CHANGEOWNER:
549       {
550         CajaFile *file;
551 
552         file = caja_file_get_by_uri (action->target_uri);
553         caja_file_set_owner (file,
554             action->new_user_name_or_id,
555             undo_redo_done_rename_callback, action);
556         g_object_unref (file);
557         break;
558       }
559       case CAJA_UNDOSTACK_DELETE:
560       default:
561         priv->undo_redo_flag = FALSE;
562         break;                  /* We shouldn't be here */
563     }
564   }
565 
566   (*cb) ((gpointer) parent_view);
567 }
568 
569 /** ****************************************************************
570  * Undoes the last file operation
571  ** ****************************************************************/
572 void
caja_undostack_manager_undo(CajaUndoStackManager * manager,GtkWidget * parent_view,CajaUndostackFinishCallback cb)573 caja_undostack_manager_undo (CajaUndoStackManager * manager,
574     GtkWidget * parent_view, CajaUndostackFinishCallback cb)
575 {
576   CajaUndoStackManagerPrivate *priv = manager->priv;
577   GList *uris = NULL;
578 
579   g_mutex_lock (&priv->mutex);
580 
581   CajaUndoStackActionData *action = stack_scroll_right (priv);
582 
583   if (action != NULL) {
584     action->locked = TRUE;
585   }
586 
587   g_mutex_unlock (&priv->mutex);
588 
589   do_menu_update (manager);
590 
591   if (action != NULL) {
592     priv->undo_redo_flag = TRUE;
593     switch (action->type) {
594       case CAJA_UNDOSTACK_CREATEEMPTYFILE:
595       case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE:
596       case CAJA_UNDOSTACK_CREATEFOLDER:
597         uris = construct_gfile_list_from_uri (action->target_uri);
598       case CAJA_UNDOSTACK_COPY:
599       case CAJA_UNDOSTACK_DUPLICATE:
600       case CAJA_UNDOSTACK_CREATELINK:
601         if (!uris) {
602           uris = construct_gfile_list (action->destinations, action->dest_dir);
603           uris = g_list_reverse (uris); // Deleting must be done in reverse
604         }
605 
606         caja_file_operations_delete (uris, NULL,
607             undo_redo_done_delete_callback, action);
608     	g_list_free_full (uris, g_object_unref);
609 
610         break;
611       case CAJA_UNDOSTACK_RESTOREFROMTRASH:
612         uris = construct_gfile_list (action->destinations, action->dest_dir);
613         caja_file_operations_trash_or_delete (uris, NULL,
614             undo_redo_done_delete_callback, action);
615     	g_list_free_full (uris, g_object_unref);
616         break;
617       case CAJA_UNDOSTACK_MOVETOTRASH:
618       {
619         GHashTable *files_to_restore;
620 
621         files_to_restore = retrieve_files_to_restore (action->trashed);
622         if (g_hash_table_size (files_to_restore) > 0) {
623           GList *l;
624           GList *gfiles_in_trash = g_hash_table_get_keys (files_to_restore);
625           GFile *item = NULL;
626           GFile *dest = NULL;
627 
628           for (l = gfiles_in_trash; l != NULL; l = l->next) {
629             char *value;
630 
631             item = l->data;
632             value = g_hash_table_lookup (files_to_restore, item);
633             dest = g_file_new_for_uri (value);
634             g_file_move (item, dest,
635                 G_FILE_COPY_NOFOLLOW_SYMLINKS, NULL, NULL, NULL, NULL);
636             g_object_unref (dest);
637           }
638 
639           g_list_free (gfiles_in_trash);
640         }
641         g_hash_table_destroy (files_to_restore);
642         /* Here we must do what's necessary for the callback */
643         undo_redo_done_transfer_callback (NULL, action);
644         break;
645       }
646       case CAJA_UNDOSTACK_MOVE:
647         uris = construct_gfile_list (action->destinations, action->dest_dir);
648         caja_file_operations_move (uris, NULL,
649             action->src_dir, NULL, undo_redo_done_transfer_callback, action);
650     	g_list_free_full (uris, g_object_unref);
651         break;
652       case CAJA_UNDOSTACK_RENAME:
653       {
654         CajaFile *file;
655         char *new_name;
656 
657         new_name = get_uri_basename (action->old_uri);
658         file = caja_file_get_by_uri (action->new_uri);
659         caja_file_rename (file, new_name,
660             undo_redo_done_rename_callback, action);
661         g_object_unref (file);
662         g_free (new_name);
663         break;
664       }
665       case CAJA_UNDOSTACK_SETPERMISSIONS:
666       {
667         CajaFile *file;
668 
669         file = caja_file_get_by_uri (action->target_uri);
670         caja_file_set_permissions (file,
671             action->current_permissions,
672             undo_redo_done_rename_callback, action);
673         g_object_unref (file);
674         break;
675       }
676       case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS:
677         if (g_hash_table_size (action->original_permissions) > 0) {
678           GList *gfiles_list =
679               g_hash_table_get_keys (action->original_permissions);
680 
681           GList *l;
682           GFile *dest = NULL;
683 
684           for (l = gfiles_list; l != NULL; l = l->next) {
685             guint32 *perm;
686             char *item;
687 
688             item = l->data;
689             perm = g_hash_table_lookup (action->original_permissions, item);
690             dest = g_file_new_for_uri (item);
691             g_file_set_attribute_uint32 (dest,
692                 G_FILE_ATTRIBUTE_UNIX_MODE,
693                 *perm, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
694             g_object_unref (dest);
695           }
696 
697           g_list_free (gfiles_list);
698           /* Here we must do what's necessary for the callback */
699           undo_redo_done_transfer_callback (NULL, action);
700         }
701         break;
702       case CAJA_UNDOSTACK_CHANGEGROUP:
703       {
704         CajaFile *file;
705 
706         file = caja_file_get_by_uri (action->target_uri);
707         caja_file_set_group (file,
708             action->original_group_name_or_id,
709             undo_redo_done_rename_callback, action);
710         g_object_unref (file);
711         break;
712       }
713       case CAJA_UNDOSTACK_CHANGEOWNER:
714       {
715         CajaFile *file;
716 
717         file = caja_file_get_by_uri (action->target_uri);
718         caja_file_set_owner (file,
719             action->original_user_name_or_id,
720             undo_redo_done_rename_callback, action);
721         g_object_unref (file);
722         break;
723       }
724       case CAJA_UNDOSTACK_DELETE:
725       default:
726         priv->undo_redo_flag = FALSE;
727         break;                  /* We shouldn't be here */
728     }
729   }
730 
731   (*cb) ((gpointer) parent_view);
732 }
733 
734 /** ****************************************************************
735  * Adds an operation to the stack
736  ** ****************************************************************/
737 void
caja_undostack_manager_add_action(CajaUndoStackManager * manager,CajaUndoStackActionData * action)738 caja_undostack_manager_add_action (CajaUndoStackManager * manager,
739     CajaUndoStackActionData * action)
740 {
741   CajaUndoStackManagerPrivate *priv = manager->priv;
742 
743   if (!action)
744     return;
745 
746   if (!(action && action->isValid)) {
747     free_undostack_action ((gpointer) action, NULL);
748     return;
749   }
750 
751   action->manager = manager;
752 
753   g_mutex_lock (&priv->mutex);
754 
755   stack_push_action (priv, action);
756 
757   g_mutex_unlock (&priv->mutex);
758 
759   do_menu_update (manager);
760 }
761 
762 static GList *
get_all_trashed_items(GQueue * stack)763 get_all_trashed_items (GQueue *stack)
764 {
765   CajaUndoStackActionData *action = NULL;
766   GList *trash = NULL;
767   GList *l;
768   GQueue *tmp_stack = g_queue_copy(stack);
769 
770   while ((action = (CajaUndoStackActionData *) g_queue_pop_tail (tmp_stack)) != NULL)
771     if (action->trashed)
772         for (l = g_hash_table_get_keys (action->trashed); l != NULL; l=l->next) {
773                 trash = g_list_append(trash, l->data);
774         }
775 
776   g_queue_free (tmp_stack);
777   return (trash);
778 }
779 
780 static gboolean
is_destination_uri_action_partof_trashed(GList * trash,GList * g)781 is_destination_uri_action_partof_trashed(GList *trash, GList *g)
782 {
783     GList *l;
784     char *uri;
785 
786     for (l = trash; l != NULL; l=l->next) {
787         for (; g != NULL; g=g->next) {
788             //printf ("destinations: %s\n", g_file_get_uri(l->data));
789             uri = g_file_get_uri(g->data);
790             if (!strcmp (uri, l->data)) {
791                //printf ("GG %s\nZZ %s\n", uri, l->data);
792                g_free (uri);
793                return TRUE;
794             }
795             g_free (uri);
796         }
797     }
798 
799     return FALSE;
800 }
801 /** ****************************************************************
802  * Callback after emptying the trash
803  ** ****************************************************************/
804 void
caja_undostack_manager_trash_has_emptied(CajaUndoStackManager * manager)805 caja_undostack_manager_trash_has_emptied (CajaUndoStackManager *
806     manager)
807 {
808   CajaUndoStackManagerPrivate *priv = manager->priv;
809 
810   /* Clear actions from the oldest to the newest move to trash */
811 
812   g_mutex_lock (&priv->mutex);
813 
814   clear_redo_actions (priv);
815   CajaUndoStackActionData *action = NULL;
816 
817   GList *g;
818   GQueue *tmp_stack = g_queue_copy(priv->stack);
819   GList *trash = get_all_trashed_items (tmp_stack);
820   while ((action = (CajaUndoStackActionData *) g_queue_pop_tail (tmp_stack)) != NULL)
821   {
822     if (action->destinations && action->dest_dir) {
823         /* what a pain rebuild again and again an uri
824         ** TODO change the struct add uri elements */
825         g = construct_gfile_list (action->destinations, action->dest_dir);
826         /* remove action for trashed item uris == destination action */
827         if (is_destination_uri_action_partof_trashed(trash, g)) {
828                 g_queue_remove (priv->stack, action);
829                 continue;
830         }
831     }
832     if (action->type == CAJA_UNDOSTACK_MOVETOTRASH) {
833         g_queue_remove (priv->stack, action);
834     }
835   }
836 
837   g_queue_free (tmp_stack);
838   g_mutex_unlock (&priv->mutex);
839   do_menu_update (manager);
840 }
841 
842 /** ****************************************************************
843  * Returns the modification time for the given file (used for undo trash)
844  ** ****************************************************************/
845 guint64
caja_undostack_manager_get_file_modification_time(GFile * file)846 caja_undostack_manager_get_file_modification_time (GFile * file)
847 {
848   GFileInfo *info;
849   guint64 mtime;
850 
851   info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
852       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, FALSE, NULL);
853   if (info == NULL) {
854     return -1;
855   }
856 
857   mtime = g_file_info_get_attribute_uint64 (info,
858       G_FILE_ATTRIBUTE_TIME_MODIFIED);
859 
860   g_object_unref (info);
861 
862   return mtime;
863 }
864 
865 /** ****************************************************************
866  * Returns a new undo data container
867  ** ****************************************************************/
868 CajaUndoStackActionData *
caja_undostack_manager_data_new(CajaUndoStackActionType type,gint items_count)869 caja_undostack_manager_data_new (CajaUndoStackActionType type,
870     gint items_count)
871 {
872   CajaUndoStackActionData *data =
873       g_slice_new0 (CajaUndoStackActionData);
874   data->type = type;
875   data->count = items_count;
876 
877   if (type == CAJA_UNDOSTACK_MOVETOTRASH) {
878     data->trashed =
879         g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
880   } else if (type == CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS) {
881     data->original_permissions =
882         g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
883   }
884 
885   return data;
886 }
887 
888 /** ****************************************************************
889  * Sets the source directory
890  ** ****************************************************************/
891 void
caja_undostack_manager_data_set_src_dir(CajaUndoStackActionData * data,GFile * src)892 caja_undostack_manager_data_set_src_dir (CajaUndoStackActionData *
893     data, GFile * src)
894 {
895   if (!data)
896     return;
897 
898   data->src_dir = src;
899 }
900 
901 /** ****************************************************************
902  * Sets the destination directory
903  ** ****************************************************************/
904 void
caja_undostack_manager_data_set_dest_dir(CajaUndoStackActionData * data,GFile * dest)905 caja_undostack_manager_data_set_dest_dir (CajaUndoStackActionData *
906     data, GFile * dest)
907 {
908   if (!data)
909     return;
910 
911   data->dest_dir = dest;
912 }
913 
914 /** ****************************************************************
915  * Pushes an origin, target pair in an existing undo data container
916  ** ****************************************************************/
caja_undostack_manager_data_add_origin_target_pair(CajaUndoStackActionData * data,GFile * origin,GFile * target)917 void caja_undostack_manager_data_add_origin_target_pair
918     (CajaUndoStackActionData * data, GFile * origin, GFile * target)
919 {
920 
921   if (!data)
922     return;
923 
924   char *src_relative = g_file_get_relative_path (data->src_dir, origin);
925   data->sources = g_list_append (data->sources, src_relative);
926   char *dest_relative = g_file_get_relative_path (data->dest_dir, target);
927   data->destinations = g_list_append (data->destinations, dest_relative);
928 
929   data->isValid = TRUE;
930 }
931 
932 /** ****************************************************************
933  * Pushes an trashed file with modification time in an existing undo data container
934  ** ****************************************************************/
935 void
caja_undostack_manager_data_add_trashed_file(CajaUndoStackActionData * data,GFile * file,guint64 mtime)936 caja_undostack_manager_data_add_trashed_file (CajaUndoStackActionData
937     * data, GFile * file, guint64 mtime)
938 {
939 
940   if (!data)
941     return;
942 
943   guint64 *modificationTime;
944   modificationTime = (guint64 *) g_malloc (sizeof (guint64));
945   *modificationTime = mtime;
946 
947   char *originalURI = g_file_get_uri (file);
948 
949   g_hash_table_insert (data->trashed, originalURI, modificationTime);
950 
951   data->isValid = TRUE;
952 }
953 
954 /** ****************************************************************
955  * Pushes a recursive permission change data in an existing undo data container
956  ** ****************************************************************/
caja_undostack_manager_data_add_file_permissions(CajaUndoStackActionData * data,GFile * file,guint32 permission)957 void caja_undostack_manager_data_add_file_permissions
958     (CajaUndoStackActionData * data, GFile * file, guint32 permission)
959 {
960 
961   if (!data)
962     return;
963 
964   guint32 *currentPermission;
965   currentPermission = (guint32 *) g_malloc (sizeof (guint32));
966   *currentPermission = permission;
967 
968   char *originalURI = g_file_get_uri (file);
969 
970   g_hash_table_insert (data->original_permissions, originalURI,
971       currentPermission);
972 
973   data->isValid = TRUE;
974 }
975 
976 /** ****************************************************************
977  * Sets the original file permission in an existing undo data container
978  ** ****************************************************************/
caja_undostack_manager_data_set_file_permissions(CajaUndoStackActionData * data,char * uri,guint32 current_permissions,guint32 new_permissions)979 void caja_undostack_manager_data_set_file_permissions
980     (CajaUndoStackActionData * data, char *uri,
981     guint32 current_permissions, guint32 new_permissions)
982 {
983 
984   if (!data)
985     return;
986 
987   data->target_uri = uri;
988 
989   data->current_permissions = current_permissions;
990   data->new_permissions = new_permissions;
991 
992   data->isValid = TRUE;
993 }
994 
995 /** ****************************************************************
996  * Sets the change owner information in an existing undo data container
997  ** ****************************************************************/
caja_undostack_manager_data_set_owner_change_information(CajaUndoStackActionData * data,char * uri,const char * current_user,const char * new_user)998 void caja_undostack_manager_data_set_owner_change_information
999     (CajaUndoStackActionData * data, char *uri,
1000     const char *current_user, const char *new_user)
1001 {
1002 
1003   if (!data)
1004     return;
1005 
1006   data->target_uri = uri;
1007 
1008   data->original_user_name_or_id = g_strdup (current_user);
1009   data->new_user_name_or_id = g_strdup (new_user);
1010 
1011   data->isValid = TRUE;
1012 }
1013 
1014 /** ****************************************************************
1015  * Sets the change group information in an existing undo data container
1016  ** ****************************************************************/
caja_undostack_manager_data_set_group_change_information(CajaUndoStackActionData * data,char * uri,const char * current_group,const char * new_group)1017 void caja_undostack_manager_data_set_group_change_information
1018     (CajaUndoStackActionData * data, char *uri,
1019     const char *current_group, const char *new_group)
1020 {
1021 
1022   if (!data)
1023     return;
1024 
1025   data->target_uri = uri;
1026 
1027   data->original_group_name_or_id = g_strdup (current_group);
1028   data->new_group_name_or_id = g_strdup (new_group);
1029 
1030   data->isValid = TRUE;
1031 }
1032 
1033 /** ****************************************************************
1034  * Sets the permission change mask
1035  ** ****************************************************************/
caja_undostack_manager_data_set_recursive_permissions(CajaUndoStackActionData * data,guint32 file_permissions,guint32 file_mask,guint32 dir_permissions,guint32 dir_mask)1036 void caja_undostack_manager_data_set_recursive_permissions
1037     (CajaUndoStackActionData * data, guint32 file_permissions,
1038     guint32 file_mask, guint32 dir_permissions, guint32 dir_mask)
1039 {
1040 
1041   if (!data)
1042     return;
1043 
1044   data->file_permissions = file_permissions;
1045   data->file_mask = file_mask;
1046   data->dir_permissions = dir_permissions;
1047   data->dir_mask = dir_mask;
1048 
1049   data->isValid = TRUE;
1050 }
1051 
1052 /** ****************************************************************
1053  * Sets create file information
1054  ** ****************************************************************/
1055 void
caja_undostack_manager_data_set_create_data(CajaUndoStackActionData * data,char * target_uri,char * template)1056 caja_undostack_manager_data_set_create_data (CajaUndoStackActionData *
1057     data, char *target_uri, char *template)
1058 {
1059 
1060   if (!data)
1061     return;
1062 
1063   data->template = g_strdup (template);
1064   data->target_uri = g_strdup (target_uri);
1065 
1066   data->isValid = TRUE;
1067 }
1068 
1069 /** ****************************************************************
1070  * Sets rename information
1071  ** ****************************************************************/
caja_undostack_manager_data_set_rename_information(CajaUndoStackActionData * data,GFile * old_file,GFile * new_file)1072 void caja_undostack_manager_data_set_rename_information
1073     (CajaUndoStackActionData * data, GFile * old_file, GFile * new_file)
1074 {
1075 
1076   if (!data)
1077     return;
1078 
1079   data->old_uri = g_file_get_uri (old_file);
1080   data->new_uri = g_file_get_uri (new_file);
1081 
1082   data->isValid = TRUE;
1083 }
1084 
1085 /* *****************************************************************
1086  Private methods (nothing to see here, move along)
1087  ***************************************************************** */
1088 
1089 static CajaUndoStackActionData *
stack_scroll_right(CajaUndoStackManagerPrivate * priv)1090 stack_scroll_right (CajaUndoStackManagerPrivate * priv)
1091 {
1092   gpointer data = NULL;
1093 
1094   if (!can_undo (priv))
1095     return NULL;
1096 
1097   data = g_queue_peek_nth (priv->stack, priv->index);
1098   if (priv->index < g_queue_get_length (priv->stack)) {
1099     priv->index++;
1100   }
1101 
1102   return data;
1103 }
1104 
1105 /** ---------------------------------------------------------------- */
1106 static CajaUndoStackActionData *
stack_scroll_left(CajaUndoStackManagerPrivate * priv)1107 stack_scroll_left (CajaUndoStackManagerPrivate * priv)
1108 {
1109   gpointer data = NULL;
1110 
1111   if (!can_redo (priv))
1112     return NULL;
1113 
1114   priv->index--;
1115   data = g_queue_peek_nth (priv->stack, priv->index);
1116 
1117   return data;
1118 }
1119 
1120 /** ---------------------------------------------------------------- */
1121 static void
stack_clear_n_oldest(GQueue * stack,guint n)1122 stack_clear_n_oldest (GQueue * stack, guint n)
1123 {
1124   guint i;
1125   CajaUndoStackActionData *action = NULL;
1126 
1127   for (i = 0; i < n; i++) {
1128     if ((action = (CajaUndoStackActionData *) g_queue_pop_tail (stack)) == NULL)
1129         break;
1130     if (action->locked) {
1131       action->freed = TRUE;
1132     } else {
1133       free_undostack_action (action, NULL);
1134     }
1135   }
1136 }
1137 
1138 /** ---------------------------------------------------------------- */
1139 static void
stack_fix_size(CajaUndoStackManagerPrivate * priv)1140 stack_fix_size (CajaUndoStackManagerPrivate * priv)
1141 {
1142   guint length = g_queue_get_length (priv->stack);
1143 
1144   if (length > priv->undo_levels) {
1145     if (priv->index > (priv->undo_levels + 1)) {
1146       /* If the index will fall off the stack
1147        * move it back to the maximum position */
1148       priv->index = priv->undo_levels + 1;
1149     }
1150     stack_clear_n_oldest (priv->stack, length - (priv->undo_levels));
1151   }
1152 }
1153 
1154 /** ---------------------------------------------------------------- */
1155 static void
clear_redo_actions(CajaUndoStackManagerPrivate * priv)1156 clear_redo_actions (CajaUndoStackManagerPrivate * priv)
1157 {
1158   while (priv->index > 0) {
1159     CajaUndoStackActionData *head = (CajaUndoStackActionData *)
1160         g_queue_pop_head (priv->stack);
1161     free_undostack_action (head, NULL);
1162     priv->index--;
1163   }
1164 }
1165 
1166 /** ---------------------------------------------------------------- */
1167 static void
stack_push_action(CajaUndoStackManagerPrivate * priv,CajaUndoStackActionData * action)1168 stack_push_action (CajaUndoStackManagerPrivate * priv,
1169     CajaUndoStackActionData * action)
1170 {
1171   guint length;
1172 
1173   clear_redo_actions (priv);
1174 
1175   g_queue_push_head (priv->stack, (gpointer) action);
1176   length = g_queue_get_length (priv->stack);
1177 
1178   if (length > priv->undo_levels) {
1179     stack_fix_size (priv);
1180   }
1181 }
1182 
1183 /** ---------------------------------------------------------------- */
1184 static gchar *
get_first_target_short_name(CajaUndoStackActionData * action)1185 get_first_target_short_name (CajaUndoStackActionData * action)
1186 {
1187   GList *targets_first;
1188   gchar *file_name;
1189 
1190   targets_first = g_list_first (action->destinations);
1191   file_name = (gchar *) g_strdup (targets_first->data);
1192 
1193   return file_name;
1194 }
1195 
1196 /** ---------------------------------------------------------------- */
1197 static gchar *
get_undo_description(CajaUndoStackActionData * action)1198 get_undo_description (CajaUndoStackActionData * action)
1199 {
1200   gchar *description = NULL;
1201   gchar *source = NULL;
1202   guint count;
1203 
1204   if (action != NULL) {
1205     if (action->undo_description == NULL) {
1206       if (action->src_dir) {
1207         source = g_file_get_path (action->src_dir);
1208       }
1209       count = action->count;
1210       switch (action->type) {
1211         case CAJA_UNDOSTACK_COPY:
1212           if (count != 1) {
1213             description = g_strdup_printf (_("Delete %d copied items"), count);
1214           } else {
1215             gchar *name = get_first_target_short_name (action);
1216             description = g_strdup_printf (_("Delete '%s'"), name);
1217             g_free (name);
1218           }
1219           break;
1220         case CAJA_UNDOSTACK_DUPLICATE:
1221           if (count != 1) {
1222             description =
1223                 g_strdup_printf (_("Delete %d duplicated items"), count);
1224           } else {
1225             gchar *name = get_first_target_short_name (action);
1226             description = g_strdup_printf (_("Delete '%s'"), name);
1227             g_free (name);
1228           }
1229           break;
1230         case CAJA_UNDOSTACK_MOVE:
1231           if (count != 1) {
1232             description =
1233                 g_strdup_printf (_
1234                 ("Move %d items back to '%s'"), count, source);
1235           } else {
1236             gchar *name = get_first_target_short_name (action);
1237             description =
1238                 g_strdup_printf (_("Move '%s' back to '%s'"), name, source);
1239             g_free (name);
1240           }
1241           break;
1242         case CAJA_UNDOSTACK_RENAME:
1243         {
1244           char *from_name = get_uri_basename (action->new_uri);
1245           char *to_name = get_uri_basename (action->old_uri);
1246           description =
1247               g_strdup_printf (_("Rename '%s' as '%s'"), from_name, to_name);
1248           g_free (from_name);
1249           g_free (to_name);
1250         }
1251           break;
1252         case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE:
1253         case CAJA_UNDOSTACK_CREATEEMPTYFILE:
1254         case CAJA_UNDOSTACK_CREATEFOLDER:
1255         {
1256           char *name = get_uri_basename (action->target_uri);
1257           description = g_strdup_printf (_("Delete '%s'"), name);
1258           g_free (name);
1259         }
1260           break;
1261         case CAJA_UNDOSTACK_MOVETOTRASH:
1262         {
1263           count = g_hash_table_size (action->trashed);
1264           if (count != 1) {
1265             description =
1266                 g_strdup_printf (_("Restore %d items from trash"), count);
1267           } else {
1268             GList *keys = g_hash_table_get_keys (action->trashed);
1269             GList *first = g_list_first (keys);
1270             char *item = (char *) first->data;
1271             char *name = get_uri_basename (item);
1272             char *orig_path = get_uri_parent_path (item);
1273             description =
1274                 g_strdup_printf (_("Restore '%s' to '%s'"), name, orig_path);
1275             g_free (name);
1276             g_free (orig_path);
1277             g_list_free (keys);
1278           }
1279         }
1280           break;
1281         case CAJA_UNDOSTACK_RESTOREFROMTRASH:
1282         {
1283           if (count != 1) {
1284             description =
1285                 g_strdup_printf (_("Move %d items back to trash"), count);
1286           } else {
1287             gchar *name = get_first_target_short_name (action);
1288             description = g_strdup_printf (_("Move '%s' back to trash"), name);
1289             g_free (name);
1290           }
1291         }
1292           break;
1293         case CAJA_UNDOSTACK_CREATELINK:
1294         {
1295           if (count != 1) {
1296             description =
1297                 g_strdup_printf (_("Delete links to %d items"), count);
1298           } else {
1299             gchar *name = get_first_target_short_name (action);
1300             description = g_strdup_printf (_("Delete link to '%s'"), name);
1301             g_free (name);
1302           }
1303         }
1304           break;
1305         case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS:
1306         {
1307           char *name = g_file_get_path (action->dest_dir);
1308           description =
1309               g_strdup_printf (_
1310               ("Restore original permissions of items enclosed in '%s'"), name);
1311           g_free (name);
1312         }
1313           break;
1314         case CAJA_UNDOSTACK_SETPERMISSIONS:
1315         {
1316           char *name = get_uri_basename (action->target_uri);
1317           description =
1318               g_strdup_printf (_("Restore original permissions of '%s'"), name);
1319           g_free (name);
1320         }
1321           break;
1322         case CAJA_UNDOSTACK_CHANGEGROUP:
1323         {
1324           char *name = get_uri_basename (action->target_uri);
1325           description =
1326               g_strdup_printf (_
1327               ("Restore group of '%s' to '%s'"),
1328               name, action->original_group_name_or_id);
1329           g_free (name);
1330         }
1331           break;
1332         case CAJA_UNDOSTACK_CHANGEOWNER:
1333         {
1334           char *name = get_uri_basename (action->target_uri);
1335           description =
1336               g_strdup_printf (_
1337               ("Restore owner of '%s' to '%s'"),
1338               name, action->original_user_name_or_id);
1339           g_free (name);
1340         }
1341           break;
1342         default:
1343           break;
1344       }
1345       if (source) {
1346         g_free (source);
1347       }
1348       action->undo_description = description;
1349     } else {
1350       return action->undo_description;
1351     }
1352   }
1353 
1354   return description;
1355 }
1356 
1357 /** ---------------------------------------------------------------- */
1358 static gchar *
get_redo_description(CajaUndoStackActionData * action)1359 get_redo_description (CajaUndoStackActionData * action)
1360 {
1361   gchar *description = NULL;
1362   gchar *destination = NULL;
1363   guint count;
1364 
1365   if (action != NULL) {
1366     if (action->redo_description == NULL) {
1367       if (action->dest_dir) {
1368         destination = g_file_get_path (action->dest_dir);
1369       }
1370       count = action->count;
1371       switch (action->type) {
1372         case CAJA_UNDOSTACK_COPY:
1373           if (count != 1) {
1374             description =
1375                 g_strdup_printf (_
1376                 ("Copy %d items to '%s'"), count, destination);
1377           } else {
1378             gchar *name = get_first_target_short_name (action);
1379             description =
1380                 g_strdup_printf (_("Copy '%s' to '%s'"), name, destination);
1381             g_free (name);
1382           }
1383           break;
1384         case CAJA_UNDOSTACK_DUPLICATE:
1385           if (count != 1) {
1386             description =
1387                 g_strdup_printf (_
1388                 ("Duplicate of %d items in '%s'"), count, destination);
1389           } else {
1390             gchar *name = get_first_target_short_name (action);
1391             description =
1392                 g_strdup_printf (_
1393                 ("Duplicate '%s' in '%s'"), name, destination);
1394             g_free (name);
1395           }
1396           break;
1397         case CAJA_UNDOSTACK_MOVE:
1398           if (count != 1) {
1399             description =
1400                 g_strdup_printf (_
1401                 ("Move %d items to '%s'"), count, destination);
1402           } else {
1403             gchar *name = get_first_target_short_name (action);
1404             description =
1405                 g_strdup_printf (_("Move '%s' to '%s'"), name, destination);
1406             g_free (name);
1407           }
1408           break;
1409         case CAJA_UNDOSTACK_RENAME:
1410         {
1411           char *from_name = get_uri_basename (action->old_uri);
1412           char *to_name = get_uri_basename (action->new_uri);
1413           description =
1414               g_strdup_printf (_("Rename '%s' as '%s'"), from_name, to_name);
1415           g_free (from_name);
1416           g_free (to_name);
1417         }
1418           break;
1419         case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE:
1420         {
1421           char *name = get_uri_basename (action->target_uri);
1422           description =
1423               g_strdup_printf (_("Create new file '%s' from template "), name);
1424           g_free (name);
1425         }
1426           break;
1427         case CAJA_UNDOSTACK_CREATEEMPTYFILE:
1428         {
1429           char *name = get_uri_basename (action->target_uri);
1430           description = g_strdup_printf (_("Create an empty file '%s'"), name);
1431           g_free (name);
1432         }
1433           break;
1434         case CAJA_UNDOSTACK_CREATEFOLDER:
1435         {
1436           char *name = get_uri_basename (action->target_uri);
1437           description = g_strdup_printf (_("Create a new folder '%s'"), name);
1438           g_free (name);
1439         }
1440           break;
1441         case CAJA_UNDOSTACK_MOVETOTRASH:
1442         {
1443           count = g_hash_table_size (action->trashed);
1444           if (count != 1) {
1445             description = g_strdup_printf (_("Move %d items to trash"), count);
1446           } else {
1447             GList *keys = g_hash_table_get_keys (action->trashed);
1448             GList *first = g_list_first (keys);
1449             char *item = (char *) first->data;
1450             char *name = get_uri_basename (item);
1451             description = g_strdup_printf (_("Move '%s' to trash"), name);
1452             g_free (name);
1453             g_list_free (keys);
1454           }
1455         }
1456           break;
1457         case CAJA_UNDOSTACK_RESTOREFROMTRASH:
1458         {
1459           if (count != 1) {
1460             description =
1461                 g_strdup_printf (_("Restore %d items from trash"), count);
1462           } else {
1463             gchar *name = get_first_target_short_name (action);
1464             description = g_strdup_printf (_("Restore '%s' from trash"), name);
1465             g_free (name);
1466           }
1467         }
1468           break;
1469         case CAJA_UNDOSTACK_CREATELINK:
1470         {
1471           if (count != 1) {
1472             description =
1473                 g_strdup_printf (_("Create links to %d items"), count);
1474           } else {
1475             gchar *name = get_first_target_short_name (action);
1476             description = g_strdup_printf (_("Create link to '%s'"), name);
1477             g_free (name);
1478           }
1479         }
1480           break;
1481         case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS:
1482         {
1483           char *name = g_file_get_path (action->dest_dir);
1484           description =
1485               g_strdup_printf (_("Set permissions of items enclosed in '%s'"),
1486               name);
1487           g_free (name);
1488         }
1489           break;
1490         case CAJA_UNDOSTACK_SETPERMISSIONS:
1491         {
1492           char *name = get_uri_basename (action->target_uri);
1493           description = g_strdup_printf (_("Set permissions of '%s'"), name);
1494           g_free (name);
1495         }
1496           break;
1497         case CAJA_UNDOSTACK_CHANGEGROUP:
1498         {
1499           char *name = get_uri_basename (action->target_uri);
1500           description =
1501               g_strdup_printf (_
1502               ("Set group of '%s' to '%s'"),
1503               name, action->new_group_name_or_id);
1504           g_free (name);
1505         }
1506           break;
1507         case CAJA_UNDOSTACK_CHANGEOWNER:
1508         {
1509           char *name = get_uri_basename (action->target_uri);
1510           description =
1511               g_strdup_printf (_
1512               ("Set owner of '%s' to '%s'"), name, action->new_user_name_or_id);
1513           g_free (name);
1514         }
1515           break;
1516         default:
1517           break;
1518       }
1519       if (destination) {
1520         g_free (destination);
1521       }
1522       action->redo_description = description;
1523     } else {
1524       return action->redo_description;
1525     }
1526   }
1527 
1528   return description;
1529 }
1530 
1531 /** ---------------------------------------------------------------- */
1532 static gchar *
get_undo_label(CajaUndoStackActionData * action)1533 get_undo_label (CajaUndoStackActionData * action)
1534 {
1535   gchar *label = NULL;
1536   guint count;
1537 
1538   if (action != NULL) {
1539     if (action->undo_label == NULL) {
1540       count = action->count;
1541       switch (action->type) {
1542         case CAJA_UNDOSTACK_COPY:
1543           label = g_strdup_printf (ngettext
1544               ("_Undo copy of %d item",
1545                   "_Undo copy of %d items", count), count);
1546           break;
1547         case CAJA_UNDOSTACK_DUPLICATE:
1548           label = g_strdup_printf (ngettext
1549               ("_Undo duplicate of %d item",
1550                   "_Undo duplicate of %d items", count), count);
1551           break;
1552         case CAJA_UNDOSTACK_MOVE:
1553           label = g_strdup_printf (ngettext
1554               ("_Undo move of %d item",
1555                   "_Undo move of %d items", count), count);
1556           break;
1557         case CAJA_UNDOSTACK_RENAME:
1558           label = g_strdup_printf (ngettext
1559               ("_Undo rename of %d item",
1560                   "_Undo rename of %d items", count), count);
1561           break;
1562         case CAJA_UNDOSTACK_CREATEEMPTYFILE:
1563           label = g_strdup_printf (_("_Undo creation of an empty file"));
1564           break;
1565         case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE:
1566           label = g_strdup_printf (_("_Undo creation of a file from template"));
1567           break;
1568         case CAJA_UNDOSTACK_CREATEFOLDER:
1569           label = g_strdup_printf (ngettext
1570               ("_Undo creation of %d folder",
1571                   "_Undo creation of %d folders", count), count);
1572           break;
1573         case CAJA_UNDOSTACK_MOVETOTRASH:
1574           label = g_strdup_printf (ngettext
1575               ("_Undo move to trash of %d item",
1576                   "_Undo move to trash of %d items", count), count);
1577           break;
1578         case CAJA_UNDOSTACK_RESTOREFROMTRASH:
1579           label = g_strdup_printf (ngettext
1580               ("_Undo restore from trash of %d item",
1581                   "_Undo restore from trash of %d items", count), count);
1582           break;
1583         case CAJA_UNDOSTACK_CREATELINK:
1584           label = g_strdup_printf (ngettext
1585               ("_Undo create link to %d item",
1586                   "_Undo create link to %d items", count), count);
1587           break;
1588         case CAJA_UNDOSTACK_DELETE:
1589           label = g_strdup_printf (ngettext
1590               ("_Undo delete of %d item",
1591                   "_Undo delete of %d items", count), count);
1592           break;
1593         case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS:
1594           label = g_strdup_printf (ngettext
1595               ("Undo recursive change permissions of %d item",
1596                   "Undo recursive change permissions of %d items",
1597                   count), count);
1598           break;
1599         case CAJA_UNDOSTACK_SETPERMISSIONS:
1600           label = g_strdup_printf (ngettext
1601               ("Undo change permissions of %d item",
1602                   "Undo change permissions of %d items", count), count);
1603           break;
1604         case CAJA_UNDOSTACK_CHANGEGROUP:
1605           label = g_strdup_printf (ngettext
1606               ("Undo change group of %d item",
1607                   "Undo change group of %d items", count), count);
1608           break;
1609         case CAJA_UNDOSTACK_CHANGEOWNER:
1610           label = g_strdup_printf (ngettext
1611               ("Undo change owner of %d item",
1612                   "Undo change owner of %d items", count), count);
1613           break;
1614         default:
1615           break;
1616       }
1617       action->undo_label = label;
1618     } else {
1619       return action->undo_label;
1620     }
1621   }
1622 
1623   return label;
1624 }
1625 
1626 /** ---------------------------------------------------------------- */
1627 static gchar *
get_redo_label(CajaUndoStackActionData * action)1628 get_redo_label (CajaUndoStackActionData * action)
1629 {
1630   gchar *label = NULL;
1631   guint count;
1632 
1633   if (action != NULL) {
1634     if (action->redo_label == NULL) {
1635       count = action->count;
1636       switch (action->type) {
1637         case CAJA_UNDOSTACK_COPY:
1638           label = g_strdup_printf (ngettext
1639               ("_Redo copy of %d item",
1640                   "_Redo copy of %d items", count), count);
1641           break;
1642         case CAJA_UNDOSTACK_DUPLICATE:
1643           label = g_strdup_printf (ngettext
1644               ("_Redo duplicate of %d item",
1645                   "_Redo duplicate of %d items", count), count);
1646           break;
1647         case CAJA_UNDOSTACK_MOVE:
1648           label = g_strdup_printf (ngettext
1649               ("_Redo move of %d item",
1650                   "_Redo move of %d items", count), count);
1651           break;
1652         case CAJA_UNDOSTACK_RENAME:
1653           label = g_strdup_printf (ngettext
1654               ("_Redo rename of %d item",
1655                   "_Redo rename of %d items", count), count);
1656           break;
1657         case CAJA_UNDOSTACK_CREATEEMPTYFILE:
1658           label = g_strdup_printf (_("_Redo creation of an empty file"));
1659           break;
1660         case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE:
1661           label = g_strdup_printf (_("_Redo creation of a file from template"));
1662           break;
1663         case CAJA_UNDOSTACK_CREATEFOLDER:
1664           label = g_strdup_printf (ngettext
1665               ("_Redo creation of %d folder",
1666                   "_Redo creation of %d folders", count), count);
1667           break;
1668         case CAJA_UNDOSTACK_MOVETOTRASH:
1669           label = g_strdup_printf (ngettext
1670               ("_Redo move to trash of %d item",
1671                   "_Redo move to trash of %d items", count), count);
1672           break;
1673         case CAJA_UNDOSTACK_RESTOREFROMTRASH:
1674           label = g_strdup_printf (ngettext
1675               ("_Redo restore from trash of %d item",
1676                   "_Redo restore from trash of %d items", count), count);
1677           break;
1678         case CAJA_UNDOSTACK_CREATELINK:
1679           label = g_strdup_printf (ngettext
1680               ("_Redo create link to %d item",
1681                   "_Redo create link to %d items", count), count);
1682           break;
1683         case CAJA_UNDOSTACK_DELETE:
1684           label = g_strdup_printf (ngettext
1685               ("_Redo delete of %d item",
1686                   "_Redo delete of %d items", count), count);
1687           break;
1688         case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS:
1689           label = g_strdup_printf (ngettext
1690               ("Redo recursive change permissions of %d item",
1691                   "Redo recursive change permissions of %d items",
1692                   count), count);
1693           break;
1694         case CAJA_UNDOSTACK_SETPERMISSIONS:
1695           label = g_strdup_printf (ngettext
1696               ("Redo change permissions of %d item",
1697                   "Redo change permissions of %d items", count), count);
1698           break;
1699         case CAJA_UNDOSTACK_CHANGEGROUP:
1700           label = g_strdup_printf (ngettext
1701               ("Redo change group of %d item",
1702                   "Redo change group of %d items", count), count);
1703           break;
1704         case CAJA_UNDOSTACK_CHANGEOWNER:
1705           label = g_strdup_printf (ngettext
1706               ("Redo change owner of %d item",
1707                   "Redo change owner of %d items", count), count);
1708           break;
1709         default:
1710           break;
1711       }
1712       action->redo_label = label;
1713     } else {
1714       return action->redo_label;
1715     }
1716   }
1717 
1718   return label;
1719 }
1720 
1721 /** ---------------------------------------------------------------- */
1722 static void
undo_redo_done_transfer_callback(GHashTable * debuting_uris,gpointer data)1723 undo_redo_done_transfer_callback (GHashTable * debuting_uris, gpointer data)
1724 {
1725   CajaUndoStackActionData *action;
1726 
1727   action = (CajaUndoStackActionData *) data;
1728 
1729   /* If the action needed to be freed but was locked, free now */
1730   if (action->freed) {
1731     free_undostack_action (action, NULL);
1732   } else {
1733     action->locked = FALSE;
1734   }
1735 
1736   CajaUndoStackManager *manager = action->manager;
1737   manager->priv->undo_redo_flag = FALSE;
1738 
1739   /* Update menus */
1740   do_menu_update (action->manager);
1741 }
1742 
1743 /** ---------------------------------------------------------------- */
1744 static void
undo_redo_done_delete_callback(GHashTable * debuting_uris,gboolean user_cancel,gpointer callback_data)1745 undo_redo_done_delete_callback (GHashTable *
1746     debuting_uris, gboolean user_cancel, gpointer callback_data)
1747 {
1748   undo_redo_done_transfer_callback (debuting_uris, callback_data);
1749 }
1750 
1751 /** ---------------------------------------------------------------- */
1752 static void
undo_redo_done_create_callback(GFile * new_file,gpointer callback_data)1753 undo_redo_done_create_callback (GFile * new_file, gpointer callback_data)
1754 {
1755   undo_redo_done_transfer_callback (NULL, callback_data);
1756 }
1757 
1758 /** ---------------------------------------------------------------- */
1759 static void
undo_redo_op_callback(gpointer callback_data)1760 undo_redo_op_callback (gpointer callback_data)
1761 {
1762   undo_redo_done_transfer_callback (NULL, callback_data);
1763 }
1764 
1765 /** ---------------------------------------------------------------- */
1766 static void
undo_redo_done_rename_callback(CajaFile * file,GFile * result_location,GError * error,gpointer callback_data)1767 undo_redo_done_rename_callback (CajaFile * file,
1768     GFile * result_location, GError * error, gpointer callback_data)
1769 {
1770   undo_redo_done_transfer_callback (NULL, callback_data);
1771 }
1772 
1773 /** ---------------------------------------------------------------- */
1774 static void
free_undostack_action(gpointer data,gpointer user_data)1775 free_undostack_action (gpointer data, gpointer user_data)
1776 {
1777   CajaUndoStackActionData *action = (CajaUndoStackActionData *) data;
1778 
1779   if (!action)
1780     return;
1781 
1782   g_free (action->template);
1783   g_free (action->target_uri);
1784   g_free (action->old_uri);
1785   g_free (action->new_uri);
1786 
1787   g_free (action->undo_label);
1788   g_free (action->undo_description);
1789   g_free (action->redo_label);
1790   g_free (action->redo_description);
1791 
1792   g_free (action->original_group_name_or_id);
1793   g_free (action->original_user_name_or_id);
1794   g_free (action->new_group_name_or_id);
1795   g_free (action->new_user_name_or_id);
1796 
1797   if (action->sources) {
1798     g_list_free_full (action->sources, g_free);
1799   }
1800   if (action->destinations) {
1801     g_list_free_full (action->destinations, g_free);
1802   }
1803 
1804   if (action->trashed) {
1805     g_hash_table_destroy (action->trashed);
1806   }
1807 
1808   if (action->original_permissions) {
1809     g_hash_table_destroy (action->original_permissions);
1810   }
1811 
1812   if (action->src_dir)
1813     g_object_unref (action->src_dir);
1814   if (action->dest_dir)
1815     g_object_unref (action->dest_dir);
1816 
1817   if (action)
1818     g_slice_free (CajaUndoStackActionData, action);
1819 }
1820 
1821 /** ---------------------------------------------------------------- */
1822 static void
undostack_dispose_all(GQueue * queue)1823 undostack_dispose_all (GQueue * queue)
1824 {
1825   g_queue_foreach (queue, free_undostack_action, NULL);
1826 }
1827 
1828 /** ---------------------------------------------------------------- */
1829 static gboolean
can_undo(CajaUndoStackManagerPrivate * priv)1830 can_undo (CajaUndoStackManagerPrivate * priv)
1831 {
1832   return (get_next_undo_action (priv) != NULL);
1833 }
1834 
1835 /** ---------------------------------------------------------------- */
1836 static gboolean
can_redo(CajaUndoStackManagerPrivate * priv)1837 can_redo (CajaUndoStackManagerPrivate * priv)
1838 {
1839   return (get_next_redo_action (priv) != NULL);
1840 }
1841 
1842 /** ---------------------------------------------------------------- */
1843 static CajaUndoStackActionData *
get_next_redo_action(CajaUndoStackManagerPrivate * priv)1844 get_next_redo_action (CajaUndoStackManagerPrivate * priv)
1845 {
1846   if (g_queue_is_empty (priv->stack)) {
1847     return NULL;
1848   }
1849 
1850   if (priv->index == 0) {
1851     /* ... no redo actions */
1852     return NULL;
1853   }
1854 
1855   CajaUndoStackActionData *action = g_queue_peek_nth (priv->stack,
1856       priv->index - 1);
1857 
1858   if (action->locked) {
1859     return NULL;
1860   } else {
1861     return action;
1862   }
1863 }
1864 
1865 /** ---------------------------------------------------------------- */
1866 static CajaUndoStackActionData *
get_next_undo_action(CajaUndoStackManagerPrivate * priv)1867 get_next_undo_action (CajaUndoStackManagerPrivate * priv)
1868 {
1869   if (g_queue_is_empty (priv->stack)) {
1870     return NULL;
1871   }
1872 
1873   guint stack_size = g_queue_get_length (priv->stack);
1874 
1875   if (priv->index == stack_size) {
1876     return NULL;
1877   }
1878 
1879   CajaUndoStackActionData *action = g_queue_peek_nth (priv->stack,
1880       priv->index);
1881 
1882   if (action->locked) {
1883     return NULL;
1884   } else {
1885     return action;
1886   }
1887 }
1888 
1889 /** ---------------------------------------------------------------- */
1890 static void
do_menu_update(CajaUndoStackManager * manager)1891 do_menu_update (CajaUndoStackManager * manager)
1892 {
1893 
1894   if (!manager)
1895     return;
1896 
1897   CajaUndoStackActionData *action;
1898   CajaUndoStackManagerPrivate *priv = manager->priv;
1899   CajaUndoStackMenuData *data = g_slice_new0 (CajaUndoStackMenuData);
1900 
1901   g_mutex_lock (&priv->mutex);
1902 
1903   action = get_next_undo_action (priv);
1904   data->undo_label = get_undo_label (action);
1905   data->undo_description = get_undo_description (action);
1906 
1907   action = get_next_redo_action (priv);
1908 
1909   data->redo_label = get_redo_label (action);
1910   data->redo_description = get_redo_description (action);
1911 
1912   g_mutex_unlock (&priv->mutex);
1913 
1914   /* Update menus */
1915   g_signal_emit_by_name (manager, "request-menu-update", data);
1916 
1917   /* Free the signal data */
1918   // Note: we do not own labels and descriptions, they are part of the action.
1919   g_slice_free (CajaUndoStackMenuData, data);
1920 }
1921 
1922 /** ---------------------------------------------------------------- */
1923 static GList *
construct_gfile_list(const GList * urilist,GFile * parent)1924 construct_gfile_list (const GList * urilist, GFile * parent)
1925 {
1926   const GList *l;
1927   GList *file_list = NULL;
1928   GFile *file = NULL;
1929 
1930   for (l = urilist; l != NULL; l = l->next) {
1931     file = g_file_get_child (parent, l->data);
1932     file_list = g_list_append (file_list, file);
1933   }
1934 
1935   return file_list;
1936 }
1937 
1938 /** ---------------------------------------------------------------- */
1939 static GList *
construct_gfile_list_from_uri(char * uri)1940 construct_gfile_list_from_uri (char *uri)
1941 {
1942   GList *file_list = NULL;
1943   GFile *file;
1944 
1945   file = g_file_new_for_uri (uri);
1946   file_list = g_list_append (file_list, file);
1947 
1948   return file_list;
1949 }
1950 
1951 /** ---------------------------------------------------------------- */
1952 static GList *
uri_list_to_gfile_list(GList * urilist)1953 uri_list_to_gfile_list (GList * urilist)
1954 {
1955   const GList *l;
1956   GList *file_list = NULL;
1957   GFile *file = NULL;
1958 
1959   for (l = urilist; l != NULL; l = l->next) {
1960     file = g_file_new_for_uri (l->data);
1961     file_list = g_list_append (file_list, file);
1962   }
1963 
1964   return file_list;
1965 }
1966 
1967 /** ---------------------------------------------------------------- */
1968 static char *
get_uri_basename(char * uri)1969 get_uri_basename (char *uri)
1970 {
1971   GFile *f = g_file_new_for_uri (uri);
1972   char *basename = g_file_get_basename (f);
1973   g_object_unref (f);
1974   return basename;
1975 }
1976 
1977 /** ---------------------------------------------------------------- */
1978 static char *
get_uri_parent(char * uri)1979 get_uri_parent (char *uri)
1980 {
1981   GFile *f = g_file_new_for_uri (uri);
1982   GFile *p = g_file_get_parent (f);
1983   char *parent = g_file_get_uri (p);
1984   g_object_unref (f);
1985   g_object_unref (p);
1986   return parent;
1987 }
1988 
1989 /** ---------------------------------------------------------------- */
1990 static char *
get_uri_parent_path(char * uri)1991 get_uri_parent_path (char *uri)
1992 {
1993   GFile *f = g_file_new_for_uri (uri);
1994   GFile *p = g_file_get_parent (f);
1995   char *parent = g_file_get_path (p);
1996   g_object_unref (f);
1997   g_object_unref (p);
1998   return parent;
1999 }
2000 
2001 /** ---------------------------------------------------------------- */
2002 static GHashTable *
retrieve_files_to_restore(GHashTable * trashed)2003 retrieve_files_to_restore (GHashTable * trashed)
2004 {
2005   if ((!(g_hash_table_size (trashed))) > 0) {
2006     return NULL;
2007   }
2008 
2009   GFile *trash = g_file_new_for_uri ("trash:");
2010 
2011   GFileEnumerator *enumerator = g_file_enumerate_children (trash,
2012       G_FILE_ATTRIBUTE_STANDARD_NAME
2013       ","
2014       G_FILE_ATTRIBUTE_TIME_MODIFIED
2015       ","
2016       G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
2017       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, FALSE, NULL);
2018 
2019   GHashTable *to_restore = g_hash_table_new_full (g_direct_hash,
2020       g_direct_equal, g_object_unref, g_free);
2021 
2022   if (enumerator) {
2023     GFileInfo *info;
2024     while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
2025       /* Retrieve the original file uri */
2026       const char *origpath = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
2027       if (origpath == NULL) {
2028         g_warning ("The item cannot be restored from trash: could not determine original location");
2029         continue;
2030       }
2031 
2032       GFile *origfile = g_file_new_for_path (origpath);
2033       char *origuri = g_file_get_uri (origfile);
2034       g_object_unref (origfile);
2035 
2036       gboolean origuri_inserted = FALSE;
2037       gpointer lookupvalue = g_hash_table_lookup (trashed, origuri);
2038 
2039       if (lookupvalue) {
2040         guint64 *mtime = (guint64 *) lookupvalue;
2041         guint64 mtime_item = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
2042         if (*mtime == mtime_item) {
2043           GFile *item = g_file_get_child (trash, g_file_info_get_name (info)); /* File in the trash */
2044           g_hash_table_insert (to_restore, item, origuri);
2045           origuri_inserted = TRUE;
2046         }
2047       }
2048 
2049       if (!origuri_inserted) {
2050         g_free (origuri);
2051       }
2052     }
2053 
2054     g_file_enumerator_close (enumerator, FALSE, NULL);
2055     g_object_unref (enumerator);
2056   }
2057 
2058   g_object_unref (trash);
2059 
2060   return to_restore;
2061 }
2062 
2063 /** ---------------------------------------------------------------- */
2064