1 /*
2  * Copyright 2015 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Matthias Clasen <mclasen@redhat.com>
18  */
19 
20 #include "config.h"
21 
22 #include <gio/gio.h>
23 #include <gi18n.h>
24 
25 #include "gio-tool.h"
26 
27 
28 static gboolean force = FALSE;
29 static gboolean empty = FALSE;
30 static gboolean restore = FALSE;
31 static gboolean list = FALSE;
32 static const GOptionEntry entries[] = {
33   { "force", 'f', 0, G_OPTION_ARG_NONE, &force, N_("Ignore nonexistent files, never prompt"), NULL },
34   { "empty", 0, 0, G_OPTION_ARG_NONE, &empty, N_("Empty the trash"), NULL },
35   { "list", 0, 0, G_OPTION_ARG_NONE, &list, N_("List files in the trash with their original locations"), NULL },
36   { "restore", 0, 0, G_OPTION_ARG_NONE, &restore, N_("Restore a file from trash to its original location (possibly "
37                                                      "recreating the directory)"), NULL },
38   G_OPTION_ENTRY_NULL
39 };
40 
41 static void
delete_trash_file(GFile * file,gboolean del_file,gboolean del_children)42 delete_trash_file (GFile *file, gboolean del_file, gboolean del_children)
43 {
44   GFileInfo *info;
45   GFile *child;
46   GFileEnumerator *enumerator;
47 
48   g_return_if_fail (g_file_has_uri_scheme (file, "trash"));
49 
50   if (del_children)
51     {
52       enumerator = g_file_enumerate_children (file,
53                                               G_FILE_ATTRIBUTE_STANDARD_NAME ","
54                                               G_FILE_ATTRIBUTE_STANDARD_TYPE,
55                                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
56                                               NULL,
57                                               NULL);
58       if (enumerator)
59         {
60           while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL)
61             {
62               child = g_file_get_child (file, g_file_info_get_name (info));
63 
64               /* The g_file_delete operation works differently for locations
65                * provided by the trash backend as it prevents modifications of
66                * trashed items. For that reason, it is enough to call
67                * g_file_delete on top-level items only.
68                */
69               delete_trash_file (child, TRUE, FALSE);
70 
71               g_object_unref (child);
72               g_object_unref (info);
73             }
74           g_file_enumerator_close (enumerator, NULL, NULL);
75           g_object_unref (enumerator);
76         }
77     }
78 
79   if (del_file)
80     g_file_delete (file, NULL, NULL);
81 }
82 
83 static gboolean
restore_trash(GFile * file,gboolean force,GCancellable * cancellable,GError ** error)84 restore_trash (GFile         *file,
85                gboolean       force,
86                GCancellable  *cancellable,
87                GError       **error)
88 {
89   GFileInfo *info = NULL;
90   GFile *target = NULL;
91   GFile *dir_target = NULL;
92   gboolean ret = FALSE;
93   gchar *orig_path = NULL;
94   GError *local_error = NULL;
95 
96   info = g_file_query_info (file, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, G_FILE_QUERY_INFO_NONE, cancellable, &local_error);
97   if (local_error)
98     {
99       g_propagate_error (error, local_error);
100       goto exit_func;
101     }
102 
103   orig_path = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
104   if (!orig_path)
105     {
106       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Unable to find original path"));
107       goto exit_func;
108     }
109 
110   target = g_file_new_for_commandline_arg (orig_path);
111   g_free (orig_path);
112 
113   dir_target = g_file_get_parent (target);
114   if (dir_target)
115     {
116       g_file_make_directory_with_parents (dir_target, cancellable, &local_error);
117       if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
118         {
119           g_clear_error (&local_error);
120         }
121       else if (local_error != NULL)
122         {
123           g_propagate_prefixed_error (error, local_error, _("Unable to recreate original location: "));
124           goto exit_func;
125         }
126     }
127 
128   if (!g_file_move (file,
129                     target,
130                     force ? G_FILE_COPY_OVERWRITE : G_FILE_COPY_NONE,
131                     cancellable,
132                     NULL,
133                     NULL,
134                     &local_error))
135     {
136       g_propagate_prefixed_error (error, local_error, _("Unable to move file to its original location: "));
137       goto exit_func;
138     }
139   ret = TRUE;
140 
141 exit_func:
142   g_clear_object (&target);
143   g_clear_object (&dir_target);
144   g_clear_object (&info);
145   return ret;
146 }
147 
148 static gboolean
trash_list(GFile * file,GCancellable * cancellable,GError ** error)149 trash_list (GFile         *file,
150             GCancellable  *cancellable,
151             GError       **error)
152 {
153   GFileEnumerator *enumerator;
154   GFileInfo *info;
155   GError *local_error = NULL;
156   gboolean res;
157 
158   enumerator = g_file_enumerate_children (file,
159                                           G_FILE_ATTRIBUTE_STANDARD_NAME ","
160                                           G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
161                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
162                                           cancellable,
163                                           &local_error);
164   if (!enumerator)
165     {
166       g_propagate_error (error, local_error);
167       return FALSE;
168     }
169 
170   res = TRUE;
171   while ((info = g_file_enumerator_next_file (enumerator, cancellable, &local_error)) != NULL)
172     {
173       const char *name;
174       char *orig_path;
175       char *uri;
176       GFile* child;
177 
178       name = g_file_info_get_name (info);
179       child = g_file_get_child (file, name);
180       uri = g_file_get_uri (child);
181       g_object_unref (child);
182       orig_path = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
183 
184       g_print ("%s\t%s\n", uri, orig_path);
185 
186       g_object_unref (info);
187       g_free (orig_path);
188       g_free (uri);
189     }
190 
191   if (local_error)
192     {
193       g_propagate_error (error, local_error);
194       local_error = NULL;
195       res = FALSE;
196     }
197 
198   if (!g_file_enumerator_close (enumerator, cancellable, &local_error))
199     {
200       print_file_error (file, local_error->message);
201       g_clear_error (&local_error);
202       res = FALSE;
203     }
204 
205   return res;
206 }
207 
208 int
handle_trash(int argc,char * argv[],gboolean do_help)209 handle_trash (int argc, char *argv[], gboolean do_help)
210 {
211   GOptionContext *context;
212   gchar *param;
213   GError *error = NULL;
214   int retval = 0;
215   GFile *file;
216 
217   g_set_prgname ("gio trash");
218 
219   /* Translators: commandline placeholder */
220   param = g_strdup_printf ("[%s…]", _("LOCATION"));
221   context = g_option_context_new (param);
222   g_free (param);
223   g_option_context_set_help_enabled (context, FALSE);
224   g_option_context_set_summary (context,
225       _("Move/Restore files or directories to the trash."));
226   g_option_context_set_description (context,
227       _("Note: for --restore switch, if the original location of the trashed file \n"
228         "already exists, it will not be overwritten unless --force is set."));
229   g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
230 
231   if (do_help)
232     {
233       show_help (context, NULL);
234       g_option_context_free (context);
235       return 0;
236     }
237 
238   if (!g_option_context_parse (context, &argc, &argv, &error))
239     {
240       show_help (context, error->message);
241       g_error_free (error);
242       g_option_context_free (context);
243       return 1;
244     }
245 
246   if (argc > 1)
247     {
248       int i;
249 
250       for (i = 1; i < argc; i++)
251         {
252           file = g_file_new_for_commandline_arg (argv[i]);
253           error = NULL;
254           if (restore)
255             {
256               if (!g_file_has_uri_scheme (file, "trash"))
257                 {
258                   print_file_error (file, _("Location given doesn't start with trash:///"));
259                   retval = 1;
260                 }
261               else if (!restore_trash (file, force, NULL, &error))
262                 {
263                   print_file_error (file, error->message);
264                   retval = 1;
265                 }
266             }
267           else if (!g_file_trash (file, NULL, &error))
268             {
269               if (!force ||
270                   !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
271                 {
272                   print_file_error (file, error->message);
273                   retval = 1;
274                 }
275             }
276           g_clear_error (&error);
277           g_object_unref (file);
278         }
279     }
280   else if (list)
281     {
282       GFile *file;
283       file = g_file_new_for_uri ("trash:");
284       trash_list (file, NULL, &error);
285       if (error)
286         {
287           print_file_error (file, error->message);
288           g_clear_error (&error);
289           retval = 1;
290         }
291       g_object_unref (file);
292     }
293   else if (empty)
294     {
295       GFile *file;
296       file = g_file_new_for_uri ("trash:");
297       delete_trash_file (file, FALSE, TRUE);
298       g_object_unref (file);
299     }
300 
301   if (argc == 1 && !empty && !list)
302     {
303       show_help (context, _("No locations given"));
304       g_option_context_free (context);
305       return 1;
306     }
307 
308   g_option_context_free (context);
309 
310   return retval;
311 }
312