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 | { NULL } |
39 | }; |
40 | |
41 | static void |
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 | flags: 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, name: 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 (file: child, TRUE, FALSE); |
70 | |
71 | g_object_unref (object: child); |
72 | g_object_unref (object: info); |
73 | } |
74 | g_file_enumerator_close (enumerator, NULL, NULL); |
75 | g_object_unref (object: enumerator); |
76 | } |
77 | } |
78 | |
79 | if (del_file) |
80 | g_file_delete (file, NULL, NULL); |
81 | } |
82 | |
83 | static gboolean |
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, flags: G_FILE_QUERY_INFO_NONE, cancellable, error: &local_error); |
97 | if (local_error) |
98 | { |
99 | g_propagate_error (dest: error, src: 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 (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_FOUND, _("Unable to find original path" )); |
107 | goto exit_func; |
108 | } |
109 | |
110 | target = g_file_new_for_commandline_arg (arg: orig_path); |
111 | g_free (mem: orig_path); |
112 | |
113 | dir_target = g_file_get_parent (file: target); |
114 | if (dir_target) |
115 | { |
116 | g_file_make_directory_with_parents (file: dir_target, cancellable, error: &local_error); |
117 | if (g_error_matches (error: local_error, G_IO_ERROR, code: G_IO_ERROR_EXISTS)) |
118 | { |
119 | g_clear_error (err: &local_error); |
120 | } |
121 | else if (local_error != NULL) |
122 | { |
123 | g_propagate_prefixed_error (dest: error, src: local_error, _("Unable to recreate original location: " )); |
124 | goto exit_func; |
125 | } |
126 | } |
127 | |
128 | if (!g_file_move (source: file, |
129 | destination: target, |
130 | flags: force ? G_FILE_COPY_OVERWRITE : G_FILE_COPY_NONE, |
131 | cancellable, |
132 | NULL, |
133 | NULL, |
134 | error: &local_error)) |
135 | { |
136 | g_propagate_prefixed_error (dest: error, src: 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 |
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 | flags: G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, |
162 | cancellable, |
163 | error: &local_error); |
164 | if (!enumerator) |
165 | { |
166 | g_propagate_error (dest: error, src: local_error); |
167 | return FALSE; |
168 | } |
169 | |
170 | res = TRUE; |
171 | while ((info = g_file_enumerator_next_file (enumerator, cancellable, error: &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 (file: child); |
181 | g_object_unref (object: child); |
182 | orig_path = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH); |
183 | |
184 | g_print (format: "%s\t%s\n" , uri, orig_path); |
185 | |
186 | g_object_unref (object: info); |
187 | g_free (mem: orig_path); |
188 | g_free (mem: uri); |
189 | } |
190 | |
191 | if (local_error) |
192 | { |
193 | g_propagate_error (dest: error, src: local_error); |
194 | local_error = NULL; |
195 | res = FALSE; |
196 | } |
197 | |
198 | if (!g_file_enumerator_close (enumerator, cancellable, error: &local_error)) |
199 | { |
200 | print_file_error (file, message: local_error->message); |
201 | g_clear_error (err: &local_error); |
202 | res = FALSE; |
203 | } |
204 | |
205 | return res; |
206 | } |
207 | |
208 | int |
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 (prgname: "gio trash" ); |
218 | |
219 | /* Translators: commandline placeholder */ |
220 | param = g_strdup_printf (format: "[%s…]" , _("LOCATION" )); |
221 | context = g_option_context_new (parameter_string: param); |
222 | g_free (mem: 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: &argc, argv: &argv, error: &error)) |
239 | { |
240 | show_help (context, message: 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 (arg: argv[i]); |
253 | error = NULL; |
254 | if (restore) |
255 | { |
256 | if (!g_file_has_uri_scheme (file, uri_scheme: "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: &error)) |
262 | { |
263 | print_file_error (file, message: error->message); |
264 | retval = 1; |
265 | } |
266 | } |
267 | else if (!g_file_trash (file, NULL, error: &error)) |
268 | { |
269 | if (!force || |
270 | !g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_NOT_FOUND)) |
271 | { |
272 | print_file_error (file, message: error->message); |
273 | retval = 1; |
274 | } |
275 | } |
276 | g_clear_error (err: &error); |
277 | g_object_unref (object: file); |
278 | } |
279 | } |
280 | else if (list) |
281 | { |
282 | GFile *file; |
283 | file = g_file_new_for_uri (uri: "trash:" ); |
284 | trash_list (file, NULL, error: &error); |
285 | if (error) |
286 | { |
287 | print_file_error (file, message: error->message); |
288 | g_clear_error (err: &error); |
289 | retval = 1; |
290 | } |
291 | g_object_unref (object: file); |
292 | } |
293 | else if (empty) |
294 | { |
295 | GFile *file; |
296 | file = g_file_new_for_uri (uri: "trash:" ); |
297 | delete_trash_file (file, FALSE, TRUE); |
298 | g_object_unref (object: 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 | |