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