1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*-
3 * Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #ifdef HAVE_ERRNO_H
26 #include <errno.h>
27 #endif
28
29 #include <gio/gio.h>
30 #include <glib/gstdio.h>
31
32 #include <thunar/thunar-application.h>
33 #include <thunar/thunar-enum-types.h>
34 #include <thunar/thunar-gio-extensions.h>
35 #include <thunar/thunar-io-scan-directory.h>
36 #include <thunar/thunar-io-jobs.h>
37 #include <thunar/thunar-io-jobs-util.h>
38 #include <thunar/thunar-job.h>
39 #include <thunar/thunar-private.h>
40 #include <thunar/thunar-simple-job.h>
41 #include <thunar/thunar-thumbnail-cache.h>
42 #include <thunar/thunar-transfer-job.h>
43
44
45
46 static GList *
_tij_collect_nofollow(ThunarJob * job,GList * base_file_list,gboolean unlinking,GError ** error)47 _tij_collect_nofollow (ThunarJob *job,
48 GList *base_file_list,
49 gboolean unlinking,
50 GError **error)
51 {
52 GError *err = NULL;
53 GList *child_file_list = NULL;
54 GList *file_list = NULL;
55 GList *lp;
56
57 /* recursively collect the files */
58 for (lp = base_file_list;
59 err == NULL && lp != NULL && !exo_job_is_cancelled (EXO_JOB (job));
60 lp = lp->next)
61 {
62 /* try to scan the directory */
63 child_file_list = thunar_io_scan_directory (job, lp->data,
64 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
65 TRUE, unlinking, FALSE, &err);
66
67 /* prepend the new files to the existing list */
68 file_list = thunar_g_file_list_prepend (file_list, lp->data);
69 file_list = g_list_concat (child_file_list, file_list);
70 }
71
72 /* check if we failed */
73 if (err != NULL || exo_job_is_cancelled (EXO_JOB (job)))
74 {
75 if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
76 g_error_free (err);
77 else
78 g_propagate_error (error, err);
79
80 /* release the collected files */
81 thunar_g_file_list_free (file_list);
82
83 return NULL;
84 }
85
86 return file_list;
87 }
88
89
90
91 static gboolean
_tij_delete_file(GFile * file,GCancellable * cancellable,GError ** error)92 _tij_delete_file (GFile *file,
93 GCancellable *cancellable,
94 GError **error)
95 {
96 gchar *path;
97
98 if (!g_file_is_native (file))
99 return g_file_delete (file, cancellable, error);
100
101 /* adapted from g_local_file_delete of gio/glocalfile.c */
102 path = g_file_get_path (file);
103
104 if (g_remove (path) == 0)
105 {
106 g_free (path);
107 return TRUE;
108 }
109
110 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
111 _("Error removing file: %s"), g_strerror (errno));
112
113 g_free (path);
114 return FALSE;
115 }
116
117
118
119 static gboolean
_thunar_io_jobs_create(ThunarJob * job,GArray * param_values,GError ** error)120 _thunar_io_jobs_create (ThunarJob *job,
121 GArray *param_values,
122 GError **error)
123 {
124 GFileOutputStream *stream;
125 ThunarJobResponse response = THUNAR_JOB_RESPONSE_CANCEL;
126 GFileInfo *info;
127 GError *err = NULL;
128 GList *file_list;
129 GList *lp;
130 gchar *base_name;
131 gchar *display_name;
132 guint n_processed = 0;
133 GFile *template_file;
134 GFileInputStream *template_stream = NULL;
135
136 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
137 _thunar_return_val_if_fail (param_values != NULL, FALSE);
138 _thunar_return_val_if_fail (param_values->len == 2, FALSE);
139 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
140
141 /* get the file list */
142 file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 0));
143 template_file = g_value_get_object (&g_array_index (param_values, GValue, 1));
144
145 /* we know the total amount of files to be processed */
146 thunar_job_set_total_files (THUNAR_JOB (job), file_list);
147
148 /* check if we need to open the template */
149 if (template_file != NULL)
150 {
151 /* open read stream to feed in the new files */
152 template_stream = g_file_read (template_file, exo_job_get_cancellable (EXO_JOB (job)), &err);
153 if (G_UNLIKELY (template_stream == NULL))
154 {
155 g_propagate_error (error, err);
156 return FALSE;
157 }
158 }
159
160 /* iterate over all files in the list */
161 for (lp = file_list;
162 err == NULL && lp != NULL && !exo_job_is_cancelled (EXO_JOB (job));
163 lp = lp->next, n_processed++)
164 {
165 g_assert (G_IS_FILE (lp->data));
166
167 /* update progress information */
168 thunar_job_processing_file (THUNAR_JOB (job), lp, n_processed);
169
170 again:
171 /* try to create the file */
172 stream = g_file_create (lp->data,
173 G_FILE_CREATE_NONE,
174 exo_job_get_cancellable (EXO_JOB (job)),
175 &err);
176
177 /* abort if the job was cancelled */
178 if (exo_job_is_cancelled (EXO_JOB (job)))
179 break;
180
181 /* check if creating failed */
182 if (stream == NULL)
183 {
184 if (err->code == G_IO_ERROR_EXISTS)
185 {
186 g_clear_error (&err);
187
188 /* the file already exists, query its display name */
189 info = g_file_query_info (lp->data,
190 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
191 G_FILE_QUERY_INFO_NONE,
192 exo_job_get_cancellable (EXO_JOB (job)),
193 NULL);
194
195 /* abort if the job was cancelled */
196 if (exo_job_is_cancelled (EXO_JOB (job)))
197 break;
198
199 /* determine the display name, using the basename as a fallback */
200 if (info != NULL)
201 {
202 display_name = g_strdup (g_file_info_get_display_name (info));
203 g_object_unref (info);
204 }
205 else
206 {
207 base_name = g_file_get_basename (lp->data);
208 display_name = g_filename_display_name (base_name);
209 g_free (base_name);
210 }
211
212 /* ask the user whether he wants to overwrite the existing file */
213 response = thunar_job_ask_overwrite (THUNAR_JOB (job),
214 _("The file \"%s\" already exists"),
215 display_name);
216
217 /* check if we should overwrite */
218 if (response == THUNAR_JOB_RESPONSE_REPLACE)
219 {
220 /* try to remove the file. fail if not possible */
221 if (_tij_delete_file (lp->data, exo_job_get_cancellable (EXO_JOB (job)), &err))
222 goto again;
223 }
224
225 /* clean up */
226 g_free (display_name);
227 }
228 else
229 {
230 /* determine display name of the file */
231 base_name = g_file_get_basename (lp->data);
232 display_name = g_filename_display_basename (base_name);
233 g_free (base_name);
234
235 /* ask the user whether to skip/retry this path (cancels the job if not) */
236 response = thunar_job_ask_skip (THUNAR_JOB (job),
237 _("Failed to create empty file \"%s\": %s"),
238 display_name, err->message);
239 g_free (display_name);
240
241 g_clear_error (&err);
242
243 /* go back to the beginning if the user wants to retry */
244 if (response == THUNAR_JOB_RESPONSE_RETRY)
245 goto again;
246 }
247 }
248 else
249 {
250 if (template_stream != NULL)
251 {
252 /* write the template into the new file */
253 g_output_stream_splice (G_OUTPUT_STREAM (stream),
254 G_INPUT_STREAM (template_stream),
255 G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
256 exo_job_get_cancellable (EXO_JOB (job)),
257 NULL);
258 }
259
260 g_object_unref (stream);
261 }
262 }
263
264 if (template_stream != NULL)
265 g_object_unref (template_stream);
266
267 /* check if we have failed */
268 if (err != NULL)
269 {
270 g_propagate_error (error, err);
271 return FALSE;
272 }
273
274 /* check if the job was cancelled */
275 if (exo_job_is_cancelled (EXO_JOB (job)))
276 return FALSE;
277
278 /* emit the "new-files" signal with the given file list */
279 thunar_job_new_files (THUNAR_JOB (job), file_list);
280
281 return TRUE;
282 }
283
284
285
286 ThunarJob *
thunar_io_jobs_create_files(GList * file_list,GFile * template_file)287 thunar_io_jobs_create_files (GList *file_list,
288 GFile *template_file)
289 {
290 return thunar_simple_job_launch (_thunar_io_jobs_create, 2,
291 THUNAR_TYPE_G_FILE_LIST, file_list,
292 G_TYPE_FILE, template_file);
293 }
294
295
296
297 static gboolean
_thunar_io_jobs_mkdir(ThunarJob * job,GArray * param_values,GError ** error)298 _thunar_io_jobs_mkdir (ThunarJob *job,
299 GArray *param_values,
300 GError **error)
301 {
302 ThunarJobResponse response;
303 GFileInfo *info;
304 GError *err = NULL;
305 GList *file_list;
306 GList *lp;
307 gchar *base_name;
308 gchar *display_name;
309 guint n_processed = 0;
310
311 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
312 _thunar_return_val_if_fail (param_values != NULL, FALSE);
313 _thunar_return_val_if_fail (param_values->len == 1, FALSE);
314 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
315
316 file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 0));
317
318 /* we know the total list of files to process */
319 thunar_job_set_total_files (THUNAR_JOB (job), file_list);
320
321 for (lp = file_list;
322 err == NULL && lp != NULL && !exo_job_is_cancelled (EXO_JOB (job));
323 lp = lp->next, n_processed++)
324 {
325 g_assert (G_IS_FILE (lp->data));
326
327 /* update progress information */
328 thunar_job_processing_file (THUNAR_JOB (job), lp, n_processed);
329
330 again:
331 /* try to create the directory */
332 if (!g_file_make_directory (lp->data, exo_job_get_cancellable (EXO_JOB (job)), &err))
333 {
334 if (err->code == G_IO_ERROR_EXISTS)
335 {
336 g_error_free (err);
337 err = NULL;
338
339 /* abort if the job was cancelled */
340 if (exo_job_is_cancelled (EXO_JOB (job)))
341 break;
342
343 /* the file already exists, query its display name */
344 info = g_file_query_info (lp->data,
345 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
346 G_FILE_QUERY_INFO_NONE,
347 exo_job_get_cancellable (EXO_JOB (job)),
348 NULL);
349
350 /* abort if the job was cancelled */
351 if (exo_job_is_cancelled (EXO_JOB (job)))
352 break;
353
354 /* determine the display name, using the basename as a fallback */
355 if (info != NULL)
356 {
357 display_name = g_strdup (g_file_info_get_display_name (info));
358 g_object_unref (info);
359 }
360 else
361 {
362 base_name = g_file_get_basename (lp->data);
363 display_name = g_filename_display_name (base_name);
364 g_free (base_name);
365 }
366
367 /* ask the user whether he wants to overwrite the existing file */
368 response = thunar_job_ask_overwrite (THUNAR_JOB (job),
369 _("The file \"%s\" already exists"),
370 display_name);
371
372 /* check if we should overwrite it */
373 if (response == THUNAR_JOB_RESPONSE_REPLACE)
374 {
375 /* try to remove the file, fail if not possible */
376 if (_tij_delete_file (lp->data, exo_job_get_cancellable (EXO_JOB (job)), &err))
377 goto again;
378 }
379
380 /* clean up */
381 g_free (display_name);
382 }
383 else
384 {
385 /* determine the display name of the file */
386 base_name = g_file_get_basename (lp->data);
387 display_name = g_filename_display_basename (base_name);
388 g_free (base_name);
389
390 /* ask the user whether to skip/retry this path (cancels the job if not) */
391 response = thunar_job_ask_skip (THUNAR_JOB (job),
392 _("Failed to create directory \"%s\": %s"),
393 display_name, err->message);
394 g_free (display_name);
395
396 g_error_free (err);
397 err = NULL;
398
399 /* go back to the beginning if the user wants to retry */
400 if (response == THUNAR_JOB_RESPONSE_RETRY)
401 goto again;
402 }
403 }
404 }
405
406 /* check if we have failed */
407 if (err != NULL)
408 {
409 g_propagate_error (error, err);
410 return FALSE;
411 }
412
413 /* check if the job was cancelled */
414 if (exo_job_is_cancelled (EXO_JOB (job)))
415 return FALSE;
416
417 /* emit the "new-files" signal with the given file list */
418 thunar_job_new_files (THUNAR_JOB (job), file_list);
419
420 return TRUE;
421 }
422
423
424
425 ThunarJob *
thunar_io_jobs_make_directories(GList * file_list)426 thunar_io_jobs_make_directories (GList *file_list)
427 {
428 return thunar_simple_job_launch (_thunar_io_jobs_mkdir, 1,
429 THUNAR_TYPE_G_FILE_LIST, file_list);
430 }
431
432
433
434 static gboolean
_thunar_io_jobs_unlink(ThunarJob * job,GArray * param_values,GError ** error)435 _thunar_io_jobs_unlink (ThunarJob *job,
436 GArray *param_values,
437 GError **error)
438 {
439 ThunarThumbnailCache *thumbnail_cache;
440 ThunarApplication *application;
441 ThunarJobResponse response;
442 GFileInfo *info;
443 GError *err = NULL;
444 GList *file_list;
445 GList *lp;
446 gchar *base_name;
447 gchar *display_name;
448 guint n_processed = 0;
449
450 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
451 _thunar_return_val_if_fail (param_values != NULL, FALSE);
452 _thunar_return_val_if_fail (param_values->len == 1, FALSE);
453 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
454
455 /* get the file list */
456 file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 0));
457
458 /* tell the user that we're preparing to unlink the files */
459 exo_job_info_message (EXO_JOB (job), _("Preparing..."));
460
461 /* recursively collect files for removal, not following any symlinks */
462 file_list = _tij_collect_nofollow (job, file_list, TRUE, &err);
463
464 /* free the file list and fail if there was an error or the job was cancelled */
465 if (err != NULL || exo_job_is_cancelled (EXO_JOB (job)))
466 {
467 if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
468 g_error_free (err);
469 else
470 g_propagate_error (error, err);
471
472 thunar_g_file_list_free (file_list);
473 return FALSE;
474 }
475
476 /* we know the total list of files to process */
477 thunar_job_set_total_files (THUNAR_JOB (job), file_list);
478
479 /* take a reference on the thumbnail cache */
480 application = thunar_application_get ();
481 thumbnail_cache = thunar_application_get_thumbnail_cache (application);
482 g_object_unref (application);
483
484 /* remove all the files */
485 for (lp = file_list;
486 lp != NULL && !exo_job_is_cancelled (EXO_JOB (job));
487 lp = lp->next, n_processed++)
488 {
489 g_assert (G_IS_FILE (lp->data));
490
491 /* skip root folders which cannot be deleted anyway */
492 if (thunar_g_file_is_root (lp->data))
493 continue;
494
495 /* update progress information */
496 thunar_job_processing_file (THUNAR_JOB (job), lp, n_processed);
497
498 again:
499 /* try to delete the file */
500 if (_tij_delete_file (lp->data, exo_job_get_cancellable (EXO_JOB (job)), &err))
501 {
502 /* notify the thumbnail cache that the corresponding thumbnail can also
503 * be deleted now */
504 thunar_thumbnail_cache_delete_file (thumbnail_cache, lp->data);
505 }
506 else
507 {
508 /* query the file info for the display name */
509 info = g_file_query_info (lp->data,
510 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
511 G_FILE_QUERY_INFO_NONE,
512 exo_job_get_cancellable (EXO_JOB (job)),
513 NULL);
514
515 /* abort if the job was cancelled */
516 if (exo_job_is_cancelled (EXO_JOB (job)))
517 {
518 g_clear_error (&err);
519 break;
520 }
521
522 /* determine the display name, using the basename as a fallback */
523 if (info != NULL)
524 {
525 display_name = g_strdup (g_file_info_get_display_name (info));
526 g_object_unref (info);
527 }
528 else
529 {
530 base_name = g_file_get_basename (lp->data);
531 display_name = g_filename_display_name (base_name);
532 g_free (base_name);
533 }
534
535 /* ask the user whether he wants to skip this file */
536 response = thunar_job_ask_skip (THUNAR_JOB (job),
537 _("Could not delete file \"%s\": %s"),
538 display_name, err->message);
539 g_free (display_name);
540
541 /* clear the error */
542 g_clear_error (&err);
543
544 /* check whether to retry */
545 if (response == THUNAR_JOB_RESPONSE_RETRY)
546 goto again;
547 }
548 }
549
550 /* release the thumbnail cache */
551 g_object_unref (thumbnail_cache);
552
553 /* release the file list */
554 thunar_g_file_list_free (file_list);
555
556 if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
557 return FALSE;
558 else
559 return TRUE;
560 }
561
562
563
564 ThunarJob *
thunar_io_jobs_unlink_files(GList * file_list)565 thunar_io_jobs_unlink_files (GList *file_list)
566 {
567 return thunar_simple_job_launch (_thunar_io_jobs_unlink, 1,
568 THUNAR_TYPE_G_FILE_LIST, file_list);
569 }
570
571
572
573 ThunarJob *
thunar_io_jobs_move_files(GList * source_file_list,GList * target_file_list)574 thunar_io_jobs_move_files (GList *source_file_list,
575 GList *target_file_list)
576 {
577 ThunarJob *job;
578
579 _thunar_return_val_if_fail (source_file_list != NULL, NULL);
580 _thunar_return_val_if_fail (target_file_list != NULL, NULL);
581 _thunar_return_val_if_fail (g_list_length (source_file_list) == g_list_length (target_file_list), NULL);
582
583 job = thunar_transfer_job_new (source_file_list, target_file_list,
584 THUNAR_TRANSFER_JOB_MOVE);
585 thunar_job_set_pausable (job, TRUE);
586
587 return THUNAR_JOB (exo_job_launch (EXO_JOB (job)));
588 }
589
590
591
592 ThunarJob *
thunar_io_jobs_copy_files(GList * source_file_list,GList * target_file_list)593 thunar_io_jobs_copy_files (GList *source_file_list,
594 GList *target_file_list)
595 {
596 ThunarJob *job;
597
598 _thunar_return_val_if_fail (source_file_list != NULL, NULL);
599 _thunar_return_val_if_fail (target_file_list != NULL, NULL);
600 _thunar_return_val_if_fail (g_list_length (source_file_list) == g_list_length (target_file_list), NULL);
601
602 job = thunar_transfer_job_new (source_file_list, target_file_list,
603 THUNAR_TRANSFER_JOB_COPY);
604 thunar_job_set_pausable (job, TRUE);
605
606 return THUNAR_JOB (exo_job_launch (EXO_JOB (job)));
607 }
608
609
610
611 static GFile *
_thunar_io_jobs_link_file(ThunarJob * job,GFile * source_file,GFile * target_file,GError ** error)612 _thunar_io_jobs_link_file (ThunarJob *job,
613 GFile *source_file,
614 GFile *target_file,
615 GError **error)
616 {
617 ThunarJobResponse response;
618 GError *err = NULL;
619 gchar *base_name;
620 gchar *display_name;
621 gchar *source_path;
622 gint n;
623
624 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), NULL);
625 _thunar_return_val_if_fail (G_IS_FILE (source_file), NULL);
626 _thunar_return_val_if_fail (G_IS_FILE (target_file), NULL);
627 _thunar_return_val_if_fail (error == NULL || *error == NULL, NULL);
628
629 /* abort on cancellation */
630 if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
631 return NULL;
632
633 /* try to determine the source path */
634 source_path = g_file_get_path (source_file);
635 if (source_path == NULL)
636 {
637 base_name = g_file_get_basename (source_file);
638 display_name = g_filename_display_name (base_name);
639 g_set_error (&err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
640 _("Could not create symbolic link to \"%s\" "
641 "because it is not a local file"), display_name);
642 g_free (display_name);
643 g_free (base_name);
644 }
645
646 /* various attempts to create the symbolic link */
647 while (err == NULL)
648 {
649 if (!g_file_equal (source_file, target_file))
650 {
651 /* try to create the symlink */
652 if (g_file_make_symbolic_link (target_file, source_path,
653 exo_job_get_cancellable (EXO_JOB (job)),
654 &err))
655 {
656 /* release the source path */
657 g_free (source_path);
658
659 /* return the real target file */
660 return g_object_ref (target_file);
661 }
662 }
663 else
664 {
665 for (n = 1; err == NULL; ++n)
666 {
667 GFile *duplicate_file = thunar_io_jobs_util_next_duplicate_file (job,
668 source_file,
669 FALSE, n,
670 &err);
671
672 if (err == NULL)
673 {
674 /* try to create the symlink */
675 if (g_file_make_symbolic_link (duplicate_file, source_path,
676 exo_job_get_cancellable (EXO_JOB (job)),
677 &err))
678 {
679 /* release the source path */
680 g_free (source_path);
681
682 /* return the real target file */
683 return duplicate_file;
684 }
685
686 /* release the duplicate file, we no longer need it */
687 g_object_unref (duplicate_file);
688 }
689
690 if (err != NULL && err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
691 {
692 /* this duplicate already exists => clear the error and try the next alternative */
693 g_clear_error (&err);
694 }
695 }
696 }
697
698 /* check if we can recover from this error */
699 if (err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
700 {
701 /* ask the user whether to replace the target file */
702 response = thunar_job_ask_overwrite (job, "%s", err->message);
703
704 /* reset the error */
705 g_clear_error (&err);
706
707 /* propagate the cancelled error if the job was aborted */
708 if (exo_job_set_error_if_cancelled (EXO_JOB (job), &err))
709 break;
710
711 /* try to delete the file */
712 if (response == THUNAR_JOB_RESPONSE_REPLACE)
713 {
714 /* try to remove the target file. if not possible, err will be set and
715 * the while loop will be aborted */
716 _tij_delete_file (target_file, exo_job_get_cancellable (EXO_JOB (job)), &err);
717 }
718
719 /* tell the caller that we skipped this file if the user doesn't want to
720 * overwrite it */
721 if (response == THUNAR_JOB_RESPONSE_SKIP)
722 return g_object_ref (source_file);
723 }
724 }
725
726 _thunar_assert (err != NULL);
727
728 /* free the source path */
729 g_free (source_path);
730
731 g_propagate_error (error, err);
732 return NULL;
733 }
734
735
736
737 static gboolean
_thunar_io_jobs_link(ThunarJob * job,GArray * param_values,GError ** error)738 _thunar_io_jobs_link (ThunarJob *job,
739 GArray *param_values,
740 GError **error)
741 {
742 ThunarThumbnailCache *thumbnail_cache;
743 ThunarApplication *application;
744 GError *err = NULL;
745 GFile *real_target_file;
746 GList *new_files_list = NULL;
747 GList *source_file_list;
748 GList *sp;
749 GList *target_file_list;
750 GList *tp;
751 guint n_processed = 0;
752
753 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
754 _thunar_return_val_if_fail (param_values != NULL, FALSE);
755 _thunar_return_val_if_fail (param_values->len == 2, FALSE);
756 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
757
758 source_file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 0));
759 target_file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 1));
760
761 /* we know the total list of paths to process */
762 thunar_job_set_total_files (THUNAR_JOB (job), source_file_list);
763
764 /* take a reference on the thumbnail cache */
765 application = thunar_application_get ();
766 thumbnail_cache = thunar_application_get_thumbnail_cache (application);
767 g_object_unref (application);
768
769 /* process all files */
770 for (sp = source_file_list, tp = target_file_list;
771 err == NULL && sp != NULL && tp != NULL;
772 sp = sp->next, tp = tp->next, n_processed++)
773 {
774 _thunar_assert (G_IS_FILE (sp->data));
775 _thunar_assert (G_IS_FILE (tp->data));
776
777 /* update progress information */
778 thunar_job_processing_file (THUNAR_JOB (job), sp, n_processed);
779
780 /* try to create the symbolic link */
781 real_target_file = _thunar_io_jobs_link_file (job, sp->data, tp->data, &err);
782 if (real_target_file != NULL)
783 {
784 /* queue the file for the folder update unless it was skipped */
785 if (sp->data != real_target_file)
786 {
787 new_files_list = thunar_g_file_list_prepend (new_files_list,
788 real_target_file);
789
790 /* notify the thumbnail cache that we need to copy the original
791 * thumbnail for the symlink to have one too */
792 thunar_thumbnail_cache_copy_file (thumbnail_cache, sp->data,
793 real_target_file);
794
795 }
796
797 /* release the real target file */
798 g_object_unref (real_target_file);
799 }
800 }
801
802 /* release the thumbnail cache */
803 g_object_unref (thumbnail_cache);
804
805 if (err != NULL)
806 {
807 thunar_g_file_list_free (new_files_list);
808 g_propagate_error (error, err);
809 return FALSE;
810 }
811 else
812 {
813 thunar_job_new_files (THUNAR_JOB (job), new_files_list);
814 thunar_g_file_list_free (new_files_list);
815 return TRUE;
816 }
817 }
818
819
820
821 ThunarJob *
thunar_io_jobs_link_files(GList * source_file_list,GList * target_file_list)822 thunar_io_jobs_link_files (GList *source_file_list,
823 GList *target_file_list)
824 {
825 _thunar_return_val_if_fail (source_file_list != NULL, NULL);
826 _thunar_return_val_if_fail (target_file_list != NULL, NULL);
827 _thunar_return_val_if_fail (g_list_length (source_file_list) == g_list_length (target_file_list), NULL);
828
829 return thunar_simple_job_launch (_thunar_io_jobs_link, 2,
830 THUNAR_TYPE_G_FILE_LIST, source_file_list,
831 THUNAR_TYPE_G_FILE_LIST, target_file_list);
832 }
833
834
835
836 static gboolean
_thunar_io_jobs_trash(ThunarJob * job,GArray * param_values,GError ** error)837 _thunar_io_jobs_trash (ThunarJob *job,
838 GArray *param_values,
839 GError **error)
840 {
841 ThunarThumbnailCache *thumbnail_cache;
842 ThunarApplication *application;
843 ThunarJobResponse response;
844 GError *err = NULL;
845 GList *file_list;
846 GList *lp;
847
848 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
849 _thunar_return_val_if_fail (param_values != NULL, FALSE);
850 _thunar_return_val_if_fail (param_values->len == 1, FALSE);
851 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
852
853 file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 0));
854
855 if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
856 return FALSE;
857
858 /* take a reference on the thumbnail cache */
859 application = thunar_application_get ();
860 thumbnail_cache = thunar_application_get_thumbnail_cache (application);
861 g_object_unref (application);
862
863 for (lp = file_list; err == NULL && lp != NULL; lp = lp->next)
864 {
865 _thunar_assert (G_IS_FILE (lp->data));
866
867 /* trash the file or folder */
868 g_file_trash (lp->data, exo_job_get_cancellable (EXO_JOB (job)), &err);
869
870 if (err != NULL)
871 {
872 response = thunar_job_ask_delete (job, "%s", err->message);
873
874 g_clear_error (&err);
875
876 if (response == THUNAR_JOB_RESPONSE_CANCEL)
877 break;
878
879 if (response == THUNAR_JOB_RESPONSE_YES)
880 _tij_delete_file (lp->data, exo_job_get_cancellable (EXO_JOB (job)), &err);
881 }
882
883 /* update the thumbnail cache */
884 thunar_thumbnail_cache_cleanup_file (thumbnail_cache, lp->data);
885 }
886
887 /* release the thumbnail cache */
888 g_object_unref (thumbnail_cache);
889
890 if (err != NULL)
891 {
892 g_propagate_error (error, err);
893 return FALSE;
894 }
895 else
896 {
897 return TRUE;
898 }
899 }
900
901
902
903 ThunarJob *
thunar_io_jobs_trash_files(GList * file_list)904 thunar_io_jobs_trash_files (GList *file_list)
905 {
906 _thunar_return_val_if_fail (file_list != NULL, NULL);
907
908 return thunar_simple_job_launch (_thunar_io_jobs_trash, 1,
909 THUNAR_TYPE_G_FILE_LIST, file_list);
910 }
911
912
913
914 ThunarJob *
thunar_io_jobs_restore_files(GList * source_file_list,GList * target_file_list)915 thunar_io_jobs_restore_files (GList *source_file_list,
916 GList *target_file_list)
917 {
918 ThunarJob *job;
919
920 _thunar_return_val_if_fail (source_file_list != NULL, NULL);
921 _thunar_return_val_if_fail (target_file_list != NULL, NULL);
922 _thunar_return_val_if_fail (g_list_length (source_file_list) == g_list_length (target_file_list), NULL);
923
924 job = thunar_transfer_job_new (source_file_list, target_file_list,
925 THUNAR_TRANSFER_JOB_MOVE);
926
927 return THUNAR_JOB (exo_job_launch (EXO_JOB (job)));
928 }
929
930
931
932 static gboolean
_thunar_io_jobs_chown(ThunarJob * job,GArray * param_values,GError ** error)933 _thunar_io_jobs_chown (ThunarJob *job,
934 GArray *param_values,
935 GError **error)
936 {
937 ThunarJobResponse response;
938 const gchar *message;
939 GFileInfo *info;
940 gboolean recursive;
941 GError *err = NULL;
942 GList *file_list;
943 GList *lp;
944 gint uid;
945 gint gid;
946 guint n_processed = 0;
947
948 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
949 _thunar_return_val_if_fail (param_values != NULL, FALSE);
950 _thunar_return_val_if_fail (param_values->len == 4, FALSE);
951 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
952
953 file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 0));
954 uid = g_value_get_int (&g_array_index (param_values, GValue, 1));
955 gid = g_value_get_int (&g_array_index (param_values, GValue, 2));
956 recursive = g_value_get_boolean (&g_array_index (param_values, GValue, 3));
957
958 _thunar_assert ((uid >= 0 || gid >= 0) && !(uid >= 0 && gid >= 0));
959
960 /* collect the files for the chown operation */
961 if (recursive)
962 file_list = _tij_collect_nofollow (job, file_list, FALSE, &err);
963 else
964 file_list = thunar_g_file_list_copy (file_list);
965
966 if (err != NULL)
967 {
968 g_propagate_error (error, err);
969 return FALSE;
970 }
971
972 /* we know the total list of files to process */
973 thunar_job_set_total_files (THUNAR_JOB (job), file_list);
974
975 /* change the ownership of all files */
976 for (lp = file_list; lp != NULL && err == NULL; lp = lp->next, n_processed++)
977 {
978 /* update progress information */
979 thunar_job_processing_file (THUNAR_JOB (job), lp, n_processed);
980
981 /* try to query information about the file */
982 info = g_file_query_info (lp->data,
983 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
984 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
985 exo_job_get_cancellable (EXO_JOB (job)),
986 &err);
987
988 if (err != NULL)
989 break;
990
991 retry_chown:
992 if (uid >= 0)
993 {
994 /* try to change the owner UID */
995 g_file_set_attribute_uint32 (lp->data,
996 G_FILE_ATTRIBUTE_UNIX_UID, uid,
997 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
998 exo_job_get_cancellable (EXO_JOB (job)),
999 &err);
1000 }
1001 else if (gid >= 0)
1002 {
1003 /* try to change the owner GID */
1004 g_file_set_attribute_uint32 (lp->data,
1005 G_FILE_ATTRIBUTE_UNIX_GID, gid,
1006 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1007 exo_job_get_cancellable (EXO_JOB (job)),
1008 &err);
1009 }
1010
1011 /* check if there was a recoverable error */
1012 if (err != NULL && !exo_job_is_cancelled (EXO_JOB (job)))
1013 {
1014 /* generate a useful error message */
1015 message = G_LIKELY (uid >= 0) ? _("Failed to change the owner of \"%s\": %s")
1016 : _("Failed to change the group of \"%s\": %s");
1017
1018 /* ask the user whether to skip/retry this file */
1019 response = thunar_job_ask_skip (THUNAR_JOB (job), message,
1020 g_file_info_get_display_name (info),
1021 err->message);
1022
1023 /* clear the error */
1024 g_clear_error (&err);
1025
1026 /* check whether to retry */
1027 if (response == THUNAR_JOB_RESPONSE_RETRY)
1028 goto retry_chown;
1029 }
1030
1031 /* release file information */
1032 g_object_unref (info);
1033 }
1034
1035 /* release the file list */
1036 thunar_g_file_list_free (file_list);
1037
1038 if (err != NULL)
1039 {
1040 g_propagate_error (error, err);
1041 return FALSE;
1042 }
1043 else
1044 {
1045 return TRUE;
1046 }
1047 }
1048
1049
1050
1051 ThunarJob *
thunar_io_jobs_change_group(GList * files,guint32 gid,gboolean recursive)1052 thunar_io_jobs_change_group (GList *files,
1053 guint32 gid,
1054 gboolean recursive)
1055 {
1056 _thunar_return_val_if_fail (files != NULL, NULL);
1057
1058 /* files are released when the list if destroyed */
1059 g_list_foreach (files, (GFunc) (void (*)(void)) g_object_ref, NULL);
1060
1061 return thunar_simple_job_launch (_thunar_io_jobs_chown, 4,
1062 THUNAR_TYPE_G_FILE_LIST, files,
1063 G_TYPE_INT, -1,
1064 G_TYPE_INT, (gint) gid,
1065 G_TYPE_BOOLEAN, recursive);
1066 }
1067
1068
1069
1070 static gboolean
_thunar_io_jobs_chmod(ThunarJob * job,GArray * param_values,GError ** error)1071 _thunar_io_jobs_chmod (ThunarJob *job,
1072 GArray *param_values,
1073 GError **error)
1074 {
1075 ThunarJobResponse response;
1076 GFileInfo *info;
1077 gboolean recursive;
1078 GError *err = NULL;
1079 GList *file_list;
1080 GList *lp;
1081 guint n_processed = 0;
1082 ThunarFileMode dir_mask;
1083 ThunarFileMode dir_mode;
1084 ThunarFileMode file_mask;
1085 ThunarFileMode file_mode;
1086 ThunarFileMode mask;
1087 ThunarFileMode mode;
1088 ThunarFileMode old_mode;
1089 ThunarFileMode new_mode;
1090
1091 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
1092 _thunar_return_val_if_fail (param_values != NULL, FALSE);
1093 _thunar_return_val_if_fail (param_values->len == 6, FALSE);
1094 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1095
1096 file_list = g_value_get_boxed (&g_array_index (param_values, GValue, 0));
1097 dir_mask = g_value_get_flags (&g_array_index (param_values, GValue, 1));
1098 dir_mode = g_value_get_flags (&g_array_index (param_values, GValue, 2));
1099 file_mask = g_value_get_flags (&g_array_index (param_values, GValue, 3));
1100 file_mode = g_value_get_flags (&g_array_index (param_values, GValue, 4));
1101 recursive = g_value_get_boolean (&g_array_index (param_values, GValue, 5));
1102
1103 /* collect the files for the chown operation */
1104 if (recursive)
1105 file_list = _tij_collect_nofollow (job, file_list, FALSE, &err);
1106 else
1107 file_list = thunar_g_file_list_copy (file_list);
1108
1109 if (err != NULL)
1110 {
1111 g_propagate_error (error, err);
1112 return FALSE;
1113 }
1114
1115 /* we know the total list of files to process */
1116 thunar_job_set_total_files (THUNAR_JOB (job), file_list);
1117
1118 /* change the ownership of all files */
1119 for (lp = file_list; lp != NULL && err == NULL; lp = lp->next, n_processed++)
1120 {
1121 /* update progress information */
1122 thunar_job_processing_file (THUNAR_JOB (job), lp, n_processed);
1123
1124 /* try to query information about the file */
1125 info = g_file_query_info (lp->data,
1126 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
1127 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1128 G_FILE_ATTRIBUTE_UNIX_MODE,
1129 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1130 exo_job_get_cancellable (EXO_JOB (job)),
1131 &err);
1132
1133 if (err != NULL)
1134 break;
1135
1136 retry_chown:
1137 /* different actions depending on the type of the file */
1138 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
1139 {
1140 mask = dir_mask;
1141 mode = dir_mode;
1142 }
1143 else
1144 {
1145 mask = file_mask;
1146 mode = file_mode;
1147 }
1148
1149 /* determine the current mode */
1150 old_mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
1151
1152 /* generate the new mode, taking the old mode (which contains file type
1153 * information) into account */
1154 new_mode = ((old_mode & ~mask) | mode) & 07777;
1155
1156 if (old_mode != new_mode)
1157 {
1158 /* try to change the file mode */
1159 g_file_set_attribute_uint32 (lp->data,
1160 G_FILE_ATTRIBUTE_UNIX_MODE, new_mode,
1161 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1162 exo_job_get_cancellable (EXO_JOB (job)),
1163 &err);
1164 }
1165
1166 /* check if there was a recoverable error */
1167 if (err != NULL && !exo_job_is_cancelled (EXO_JOB (job)))
1168 {
1169 /* ask the user whether to skip/retry this file */
1170 response = thunar_job_ask_skip (job,
1171 _("Failed to change the permissions of \"%s\": %s"),
1172 g_file_info_get_display_name (info),
1173 err->message);
1174
1175 /* clear the error */
1176 g_clear_error (&err);
1177
1178 /* check whether to retry */
1179 if (response == THUNAR_JOB_RESPONSE_RETRY)
1180 goto retry_chown;
1181 }
1182
1183 /* release file information */
1184 g_object_unref (info);
1185 }
1186
1187 /* release the file list */
1188 thunar_g_file_list_free (file_list);
1189
1190 if (err != NULL)
1191 {
1192 g_propagate_error (error, err);
1193 return FALSE;
1194 }
1195 else
1196 {
1197 return TRUE;
1198 }
1199 return TRUE;
1200 }
1201
1202
1203
1204 ThunarJob *
thunar_io_jobs_change_mode(GList * files,ThunarFileMode dir_mask,ThunarFileMode dir_mode,ThunarFileMode file_mask,ThunarFileMode file_mode,gboolean recursive)1205 thunar_io_jobs_change_mode (GList *files,
1206 ThunarFileMode dir_mask,
1207 ThunarFileMode dir_mode,
1208 ThunarFileMode file_mask,
1209 ThunarFileMode file_mode,
1210 gboolean recursive)
1211 {
1212 _thunar_return_val_if_fail (files != NULL, NULL);
1213
1214 /* files are released when the list if destroyed */
1215 g_list_foreach (files, (GFunc) (void (*)(void)) g_object_ref, NULL);
1216
1217 return thunar_simple_job_launch (_thunar_io_jobs_chmod, 6,
1218 THUNAR_TYPE_G_FILE_LIST, files,
1219 THUNAR_TYPE_FILE_MODE, dir_mask,
1220 THUNAR_TYPE_FILE_MODE, dir_mode,
1221 THUNAR_TYPE_FILE_MODE, file_mask,
1222 THUNAR_TYPE_FILE_MODE, file_mode,
1223 G_TYPE_BOOLEAN, recursive);
1224 }
1225
1226
1227
1228 static gboolean
_thunar_io_jobs_ls(ThunarJob * job,GArray * param_values,GError ** error)1229 _thunar_io_jobs_ls (ThunarJob *job,
1230 GArray *param_values,
1231 GError **error)
1232 {
1233 GError *err = NULL;
1234 GFile *directory;
1235 GList *file_list = NULL;
1236
1237 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
1238 _thunar_return_val_if_fail (param_values != NULL, FALSE);
1239 _thunar_return_val_if_fail (param_values->len == 1, FALSE);
1240 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1241
1242 if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
1243 return FALSE;
1244
1245 /* determine the directory to list */
1246 directory = g_value_get_object (&g_array_index (param_values, GValue, 0));
1247
1248 /* make sure the object is valid */
1249 _thunar_assert (G_IS_FILE (directory));
1250
1251 /* collect directory contents (non-recursively) */
1252 file_list = thunar_io_scan_directory (job, directory,
1253 G_FILE_QUERY_INFO_NONE,
1254 FALSE, FALSE, TRUE, &err);
1255
1256 /* abort on errors or cancellation */
1257 if (err != NULL)
1258 {
1259 g_propagate_error (error, err);
1260 return FALSE;
1261 }
1262 else if (exo_job_set_error_if_cancelled (EXO_JOB (job), &err))
1263 {
1264 g_propagate_error (error, err);
1265 return FALSE;
1266 }
1267
1268 /* check if we have any files to report */
1269 if (G_LIKELY (file_list != NULL))
1270 {
1271 /* emit the "files-ready" signal */
1272 if (!thunar_job_files_ready (THUNAR_JOB (job), file_list))
1273 {
1274 /* none of the handlers took over the file list, so it's up to us
1275 * to destroy it */
1276 thunar_g_file_list_free (file_list);
1277 }
1278 }
1279
1280 /* there should be no errors here */
1281 _thunar_assert (err == NULL);
1282
1283 /* propagate cancellation error */
1284 if (exo_job_set_error_if_cancelled (EXO_JOB (job), &err))
1285 {
1286 g_propagate_error (error, err);
1287 return FALSE;
1288 }
1289
1290 return TRUE;
1291 }
1292
1293
1294
1295 ThunarJob *
thunar_io_jobs_list_directory(GFile * directory)1296 thunar_io_jobs_list_directory (GFile *directory)
1297 {
1298 _thunar_return_val_if_fail (G_IS_FILE (directory), NULL);
1299
1300 return thunar_simple_job_launch (_thunar_io_jobs_ls, 1, G_TYPE_FILE, directory);
1301 }
1302
1303
1304
1305 static gboolean
_thunar_io_jobs_rename_notify(ThunarFile * file)1306 _thunar_io_jobs_rename_notify (ThunarFile *file)
1307 {
1308 _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
1309
1310 /* tell the associated folder that the file was renamed */
1311 thunarx_file_info_renamed (THUNARX_FILE_INFO (file));
1312
1313 /* emit the file changed signal */
1314 thunar_file_changed (file);
1315
1316 return FALSE;
1317 }
1318
1319
1320
1321 static gboolean
_thunar_io_jobs_rename(ThunarJob * job,GArray * param_values,GError ** error)1322 _thunar_io_jobs_rename (ThunarJob *job,
1323 GArray *param_values,
1324 GError **error)
1325 {
1326 const gchar *display_name;
1327 ThunarFile *file;
1328 GError *err = NULL;
1329
1330 _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
1331 _thunar_return_val_if_fail (param_values != NULL, FALSE);
1332 _thunar_return_val_if_fail (param_values->len == 2, FALSE);
1333 _thunar_return_val_if_fail (G_VALUE_HOLDS (&g_array_index (param_values, GValue, 0), THUNAR_TYPE_FILE), FALSE);
1334 _thunar_return_val_if_fail (G_VALUE_HOLDS_STRING (&g_array_index (param_values, GValue, 1)), FALSE);
1335 _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1336
1337 if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
1338 return FALSE;
1339
1340 /* determine the file and display name */
1341 file = g_value_get_object (&g_array_index (param_values, GValue, 0));
1342 display_name = g_value_get_string (&g_array_index (param_values, GValue, 1));
1343
1344 /* try to rename the file */
1345 if (thunar_file_rename (file, display_name, exo_job_get_cancellable (EXO_JOB (job)), TRUE, &err))
1346 {
1347 exo_job_send_to_mainloop (EXO_JOB (job),
1348 (GSourceFunc) _thunar_io_jobs_rename_notify,
1349 g_object_ref (file), g_object_unref);
1350 }
1351
1352 /* abort on errors or cancellation */
1353 if (err != NULL)
1354 {
1355 g_propagate_error (error, err);
1356 return FALSE;
1357 }
1358
1359 return TRUE;
1360 }
1361
1362
1363
1364 ThunarJob *
thunar_io_jobs_rename_file(ThunarFile * file,const gchar * display_name)1365 thunar_io_jobs_rename_file (ThunarFile *file,
1366 const gchar *display_name)
1367 {
1368 _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
1369 _thunar_return_val_if_fail (g_utf8_validate (display_name, -1, NULL), NULL);
1370
1371 return thunar_simple_job_launch (_thunar_io_jobs_rename, 2,
1372 THUNAR_TYPE_FILE, file,
1373 G_TYPE_STRING, display_name);
1374 }
1375