1 /*
2  *      fm-file-ops-xfer.c
3  *
4  *      Copyright 2009 PCMan <pcman.tw@gmail.com>
5  *      Copyright 2012 Vadim Ushakov <igeekless@gmail.com>
6  *      Copyright 2012-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
7  *      Copyright 2018 Nikita Sirgienko <warquark@gmail.com>
8  *
9  *      This file is a part of the Libfm library.
10  *
11  *      This library is free software; you can redistribute it and/or
12  *      modify it under the terms of the GNU Lesser General Public
13  *      License as published by the Free Software Foundation; either
14  *      version 2.1 of the License, or (at your option) any later version.
15  *
16  *      This library is distributed in the hope that it will be useful,
17  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  *      Lesser General Public License for more details.
20  *
21  *      You should have received a copy of the GNU Lesser General Public
22  *      License along with this library; if not, write to the Free Software
23  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29 
30 #include "fm-file-ops-job-xfer.h"
31 #include "fm-file-ops-job-delete.h"
32 #include <string.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include "fm-utils.h"
37 #include <glib/gi18n-lib.h>
38 
39 static const char query[]=
40     G_FILE_ATTRIBUTE_STANDARD_TYPE","
41     G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
42     G_FILE_ATTRIBUTE_STANDARD_NAME","
43     G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL","
44     G_FILE_ATTRIBUTE_STANDARD_SIZE","
45     G_FILE_ATTRIBUTE_UNIX_BLOCKS","
46     G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE","
47     G_FILE_ATTRIBUTE_ID_FILESYSTEM;
48 
49 static void progress_cb(goffset cur, goffset total, gpointer job);
50 
_fm_file_ops_job_check_paths(FmFileOpsJob * job,GFile * src,GFileInfo * src_inf,GFile * dest)51 static gboolean _fm_file_ops_job_check_paths(FmFileOpsJob* job, GFile* src, GFileInfo* src_inf, GFile* dest)
52 {
53     GError* err = NULL;
54     FmJob* fmjob = FM_JOB(job);
55     if(dest == NULL) /* as with search:/// */
56     {
57         err = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED,
58             _("Destination does not exist"));
59     }
60     else if(job->type == FM_FILE_OP_MOVE && g_file_equal(src, dest))
61     {
62         err = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED,
63             _("Source and destination are the same."));
64     }
65     else if(g_file_info_get_file_type(src_inf) == G_FILE_TYPE_DIRECTORY
66             && g_file_has_prefix(dest, src) )
67     {
68         const char* msg = NULL;
69         if(job->type == FM_FILE_OP_MOVE)
70             msg = _("Cannot move a folder into its sub folder");
71         else if(job->type == FM_FILE_OP_COPY)
72             msg = _("Cannot copy a folder into its sub folder");
73         else
74             msg = _("Destination is a sub folder of source");
75         err = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED, msg);
76     }
77     if(err)
78     {
79         if(!fm_job_is_cancelled(fmjob))
80         {
81             fm_file_ops_job_emit_cur_file(job, g_file_info_get_display_name(src_inf));
82             fm_job_emit_error(fmjob, err, FM_JOB_ERROR_CRITICAL);
83         }
84         g_error_free(err);
85     }
86     return (err == NULL);
87 }
88 
_fm_file_ops_job_copy_file(FmFileOpsJob * job,GFile * src,GFileInfo * inf,GFile * dest,FmFolder * src_folder,FmFolder * dest_folder)89 static gboolean _fm_file_ops_job_copy_file(FmFileOpsJob* job, GFile* src,
90                                            GFileInfo* inf, GFile* dest,
91                                            FmFolder *src_folder, /* if move */
92                                            FmFolder *dest_folder)
93 {
94     gboolean ret = FALSE;
95     gboolean delete_src = FALSE;
96     GError* err = NULL;
97     GFileType type;
98     guint64 size;
99     GFile* new_dest = NULL;
100     GFileCopyFlags flags;
101     FmJob* fmjob = FM_JOB(job);
102     FmPath *fm_dest;
103     guint32 mode;
104     gboolean skip_dir_content = FALSE;
105 
106     job->supported_options = FM_FILE_OP_RENAME | FM_FILE_OP_SKIP | FM_FILE_OP_OVERWRITE;
107     if( G_LIKELY(inf) )
108         g_object_ref(inf);
109     else
110     {
111 _retry_query_src_info:
112         inf = g_file_query_info(src, query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(fmjob), &err);
113         if( !inf )
114         {
115             FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
116             g_error_free(err);
117             err = NULL;
118             if(act == FM_JOB_RETRY)
119                 goto _retry_query_src_info;
120             return FALSE;
121         }
122     }
123 
124     if(!_fm_file_ops_job_check_paths(job, src, inf, dest)) /* also checks dest */
125     {
126         g_object_unref(inf);
127         return FALSE;
128     }
129 
130     /* if this is a cross-device move operation, delete source files. */
131     if( job->type == FM_FILE_OP_MOVE )
132         delete_src = TRUE;
133 
134     /* showing currently processed file. */
135     fm_file_ops_job_emit_cur_file(job, g_file_info_get_display_name(inf));
136 
137     type = g_file_info_get_file_type(inf);
138 
139     size = g_file_info_get_size(inf);
140     mode = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_UNIX_MODE);
141 
142     g_object_unref(inf);
143     inf = NULL;
144 
145     switch(type)
146     {
147     case G_FILE_TYPE_DIRECTORY:
148         {
149             GFileEnumerator* enu;
150             gboolean dir_created = FALSE;
151 _retry_mkdir:
152             if( !fm_job_is_cancelled(fmjob) && !job->skip_dir_content &&
153                 !g_file_make_directory(dest, fm_job_get_cancellable(fmjob), &err) )
154             {
155                 if(err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_EXISTS ||
156                                                  err->code == G_IO_ERROR_INVALID_FILENAME ||
157                                                  err->code == G_IO_ERROR_FILENAME_TOO_LONG))
158                 {
159                     GFile* dest_cp = new_dest;
160                     gboolean dest_exists = (err->code == G_IO_ERROR_EXISTS);
161                     FmFileOpOption opt = 0;
162                     g_error_free(err);
163                     err = NULL;
164 
165                     new_dest = NULL;
166                     opt = _fm_file_ops_job_ask_new_name(job, src, dest, &new_dest, dest_exists);
167                     if(!new_dest) /* restoring status quo */
168                         new_dest = dest_cp;
169                     else if(dest_cp) /* we got new new_dest, forget old one */
170                         g_object_unref(dest_cp);
171                     switch(opt)
172                     {
173                     case FM_FILE_OP_RENAME:
174                         dest = new_dest;
175                         goto _retry_mkdir;
176                         break;
177                     case FM_FILE_OP_SKIP:
178                         /* when a dir is skipped, we need to know its total size to calculate correct progress */
179                         job->finished += size;
180                         fm_file_ops_job_emit_percent(job);
181                         job->skip_dir_content = skip_dir_content = TRUE;
182                         dir_created = TRUE; /* pretend that dir creation succeeded */
183                         break;
184                     case FM_FILE_OP_OVERWRITE:
185                         dir_created = TRUE; /* pretend that dir creation succeeded */
186                         break;
187                     case FM_FILE_OP_CANCEL:
188                         fm_job_cancel(fmjob);
189                         break;
190                     case FM_FILE_OP_SKIP_ERROR: ; /* FIXME */
191                     }
192                 }
193                 else if(!fm_job_is_cancelled(fmjob))
194                 {
195                     FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
196                     g_error_free(err);
197                     err = NULL;
198                     if(act == FM_JOB_RETRY)
199                         goto _retry_mkdir;
200                 }
201                 job->finished += size;
202                 fm_file_ops_job_emit_percent(job);
203             }
204             else
205             {
206                 /* chmod the newly created dir properly */
207                 if(!fm_job_is_cancelled(fmjob) && !job->skip_dir_content)
208                 {
209                     if(mode)
210                     {
211 _retry_chmod_for_dir:
212                         mode |= (S_IRUSR|S_IWUSR); /* ensure we have rw permission to this file. */
213                         if( !g_file_set_attribute_uint32(dest, G_FILE_ATTRIBUTE_UNIX_MODE,
214                                                          mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
215                                                          fm_job_get_cancellable(fmjob), &err) )
216                         {
217                             FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
218                             g_error_free(err);
219                             err = NULL;
220                             if(act == FM_JOB_RETRY)
221                                 goto _retry_chmod_for_dir;
222                             /* FIXME: some filesystems may not support this. */
223                         }
224                     }
225                     dir_created = TRUE;
226                 }
227                 job->finished += size;
228                 fm_file_ops_job_emit_percent(job);
229             }
230 
231             if(!dir_created) /* if target dir is not created, don't copy dir content */
232             {
233                 if(!job->skip_dir_content)
234                     job->skip_dir_content = skip_dir_content = TRUE;
235             }
236 
237             /* the dest dir is created. let's copy its content. */
238             /* FIXME: handle the case when the dir cannot be created. */
239             else if(!fm_job_is_cancelled(fmjob))
240             {
241                 FmFolder *sub_folder;
242                 FmFolder *sub_src = NULL;
243 
244                 if (delete_src)
245                 {
246                     FmPath *src_path = fm_path_new_for_gfile(src);
247                     sub_src = fm_folder_find_by_path(src_path);
248                     fm_path_unref(src_path);
249                 }
250                 fm_dest = fm_path_new_for_gfile(dest);
251                 sub_folder = fm_folder_find_by_path(fm_dest);
252                 /* inform folder we created directory */
253                 if (!dest_folder || !_fm_folder_event_file_added(dest_folder, fm_dest))
254                     fm_path_unref(fm_dest);
255 _retry_enum_children:
256                 enu = g_file_enumerate_children(src, query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
257                                             fm_job_get_cancellable(fmjob), &err);
258                 if(enu)
259                 {
260                     int n_children = 0;
261                     int n_copied = 0;
262                     ret = TRUE;
263                     while( !fm_job_is_cancelled(fmjob) )
264                     {
265                         inf = g_file_enumerator_next_file(enu, fm_job_get_cancellable(fmjob), &err);
266                         if( inf )
267                         {
268                             ++n_children;
269                             /* don't overwrite dir content, only calculate progress. */
270                             if(G_UNLIKELY(job->skip_dir_content))
271                             {
272                                 /* FIXME: this is incorrect as we don't do the calculation recursively. */
273                                 job->finished += g_file_info_get_size(inf);
274                                 fm_file_ops_job_emit_percent(job);
275                             }
276                             else
277                             {
278                                 gboolean ret2;
279                                 GFile* sub = g_file_get_child(src, g_file_info_get_name(inf));
280                                 GFile* sub_dest;
281                                 char* tmp_basename;
282 
283                                 if(g_file_is_native(src) == g_file_is_native(dest))
284                                     /* both are native or both are virtual */
285                                     tmp_basename = NULL;
286                                 else if(g_file_is_native(src)) /* copy from native to virtual */
287                                     tmp_basename = g_filename_to_utf8(g_file_info_get_name(inf),
288                                                                       -1, NULL, NULL, NULL);
289                                     /* gvfs escapes it itself */
290                                 else /* copy from virtual to native */
291                                     /* display name as copying target more prefers, that basename (for example, for Google Drive)
292                                     See https://debarshiray.wordpress.com/2015/09/13/google-drive-and-gnome-what-is-a-volatile-path/ for some explanations*/
293                                     tmp_basename = fm_uri_subpath_to_native_subpath(g_file_info_get_display_name(inf), NULL);
294                                 sub_dest = g_file_get_child(dest,
295                                         tmp_basename ? tmp_basename : g_file_info_get_name(inf));
296                                 g_free(tmp_basename);
297 
298                                 ret2 = _fm_file_ops_job_copy_file(job, sub, inf, sub_dest, sub_src, sub_folder);
299                                 g_object_unref(sub);
300                                 g_object_unref(sub_dest);
301 
302                                 if(ret2)
303                                     ++n_copied;
304                                 else
305                                     ret = FALSE;
306                             }
307                             g_object_unref(inf);
308                         }
309                         else
310                         {
311                             if(err)
312                             {
313                                 fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
314                                 g_error_free(err);
315                                 err = NULL;
316                                 /* FM_JOB_RETRY is not supported here */
317                                 ret = FALSE;
318                             }
319                             else /* EOF is reached */
320                             {
321                                 /* all files are successfully copied. */
322                                 if(fm_job_is_cancelled(fmjob))
323                                     ret = FALSE;
324                                 else
325                                 {
326                                     if(dir_created) /* target dir is created */
327                                     {
328                                         /* some files are not copied */
329                                         if(n_children != n_copied)
330                                         {
331                                             /* if the copy actions are skipped deliberately, it's ok */
332                                             if(!job->skip_dir_content)
333                                                 ret = FALSE;
334                                         }
335                                     }
336                                     /* else job->skip_dir_content is TRUE */
337                                 }
338                                 break;
339                             }
340                         }
341                     }
342                     g_file_enumerator_close(enu, NULL, &err);
343                     g_object_unref(enu);
344                 }
345                 else
346                 {
347                     FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
348                     g_error_free(err);
349                     err = NULL;
350                     if(act == FM_JOB_RETRY)
351                         goto _retry_enum_children;
352                 }
353                 if (sub_src)
354                     g_object_unref(sub_src);
355                 if (sub_folder)
356                     g_object_unref(sub_folder);
357             }
358             if(job->skip_dir_content)
359                 delete_src = FALSE;
360             if(skip_dir_content)
361                 job->skip_dir_content = FALSE;
362         }
363         break;
364 
365     case G_FILE_TYPE_SPECIAL:
366         /* only handle FIFO for local files */
367         if(g_file_is_native(src) && g_file_is_native(dest))
368         {
369             char* src_path = g_file_get_path(src);
370             struct stat src_st;
371             int r;
372             r = lstat(src_path, &src_st);
373             g_free(src_path);
374             if(r == 0)
375             {
376                 /* Handle FIFO on native file systems. */
377                 if(S_ISFIFO(src_st.st_mode))
378                 {
379                     char* dest_path = g_file_get_path(dest);
380                     int r = mkfifo(dest_path, src_st.st_mode);
381                     g_free(dest_path);
382                     if( r == 0)
383                         ret = TRUE;
384                 }
385                 /* FIXME: how about block device, char device, and socket? */
386             }
387         }
388         if (!ret)
389         {
390             g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
391                         _("Cannot copy file '%s': not supported"),
392                         g_file_info_get_display_name(inf));
393             fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
394             g_clear_error(&err);
395         }
396         goto _file_copied;
397 
398     default:
399         flags = G_FILE_COPY_ALL_METADATA|G_FILE_COPY_NOFOLLOW_SYMLINKS;
400 _retry_copy:
401         if( !g_file_copy(src, dest, flags, fm_job_get_cancellable(fmjob),
402                          progress_cb, fmjob, &err) )
403         {
404             flags &= ~G_FILE_COPY_OVERWRITE;
405 
406             /* handle existing files or file name conflict */
407             if(err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_EXISTS ||
408                                              err->code == G_IO_ERROR_INVALID_FILENAME ||
409                                              err->code == G_IO_ERROR_FILENAME_TOO_LONG))
410             {
411                 GFile* dest_cp = new_dest;
412                 gboolean dest_exists = (err->code == G_IO_ERROR_EXISTS);
413                 FmFileOpOption opt = 0;
414                 g_error_free(err);
415                 err = NULL;
416 
417                 new_dest = NULL;
418                 opt = _fm_file_ops_job_ask_new_name(job, src, dest, &new_dest, dest_exists);
419                 if(!new_dest) /* restoring status quo */
420                     new_dest = dest_cp;
421                 else if(dest_cp) /* we got new new_dest, forget old one */
422                     g_object_unref(dest_cp);
423                 switch(opt)
424                 {
425                 case FM_FILE_OP_RENAME:
426                     dest = new_dest;
427                     goto _retry_copy;
428                     break;
429                 case FM_FILE_OP_OVERWRITE:
430                     flags |= G_FILE_COPY_OVERWRITE;
431                     goto _retry_copy;
432                     break;
433                 case FM_FILE_OP_CANCEL:
434                     fm_job_cancel(fmjob);
435                     break;
436                 case FM_FILE_OP_SKIP:
437                     ret = TRUE;
438                     delete_src = FALSE; /* don't delete source file. */
439                     break;
440                 case FM_FILE_OP_SKIP_ERROR: ; /* FIXME */
441                 }
442             }
443             else
444             {
445                 gboolean is_no_space = (err->domain == G_IO_ERROR &&
446                                         err->code == G_IO_ERROR_NO_SPACE);
447                 FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
448                 g_error_free(err);
449                 err = NULL;
450                 if(act == FM_JOB_RETRY)
451                 {
452                     job->current_file_finished = 0;
453                     goto _retry_copy;
454                 }
455                 /* FIXME: ask to leave partial content? */
456                 if(is_no_space)
457                     g_file_delete(dest, fm_job_get_cancellable(fmjob), NULL);
458                 ret = FALSE;
459                 delete_src = FALSE;
460             }
461         }
462         else
463             ret = TRUE;
464 
465 _file_copied:
466         job->finished += size;
467         job->current_file_finished = 0;
468 
469         if(ret && dest_folder)
470         {
471             fm_dest = fm_path_new_for_gfile(dest);
472             if(!_fm_folder_event_file_added(dest_folder, fm_dest))
473                 fm_path_unref(fm_dest);
474         }
475 
476         /* update progress */
477         fm_file_ops_job_emit_percent(job);
478         break;
479     }
480     /* if this is a cross-device move operation, delete source files. */
481     /* ret == TRUE means the copy is successful. */
482     if( !fm_job_is_cancelled(fmjob) && ret && delete_src )
483         ret = _fm_file_ops_job_delete_file(fmjob, src, inf, src_folder, TRUE); /* delete the source file. */
484 
485     if(new_dest)
486         g_object_unref(new_dest);
487 
488     return ret;
489 }
490 
_fm_file_ops_job_move_file(FmFileOpsJob * job,GFile * src,GFileInfo * inf,GFile * dest,FmPath * src_path,FmFolder * src_folder,FmFolder * dest_folder)491 gboolean _fm_file_ops_job_move_file(FmFileOpsJob* job, GFile* src,
492                                     GFileInfo* inf, GFile* dest, FmPath *src_path,
493                                     FmFolder *src_folder, FmFolder *dest_folder)
494 {
495     GError* err = NULL;
496     FmJob* fmjob = FM_JOB(job);
497     const char* src_fs_id;
498     gboolean ret = TRUE;
499     GFile* new_dest = NULL;
500 
501     job->supported_options = FM_FILE_OP_RENAME | FM_FILE_OP_SKIP | FM_FILE_OP_OVERWRITE;
502     if( G_LIKELY(inf) )
503         g_object_ref(inf);
504     else
505     {
506 _retry_query_src_info:
507         inf = g_file_query_info(src, query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(fmjob), &err);
508         if( !inf )
509         {
510             FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
511             g_error_free(err);
512             err = NULL;
513             if(act == FM_JOB_RETRY)
514                 goto _retry_query_src_info;
515             return FALSE;
516         }
517     }
518 
519     if(!_fm_file_ops_job_check_paths(job, src, inf, dest))
520     {
521         g_object_unref(inf);
522         return FALSE;
523     }
524 
525     src_fs_id = g_file_info_get_attribute_string(inf, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
526     /* Check if source and destination are on the same device */
527     if( job->type == FM_FILE_OP_UNTRASH || g_strcmp0(src_fs_id, job->dest_fs_id) == 0 ) /* same device */
528     {
529         guint64 size;
530         GFileCopyFlags flags = G_FILE_COPY_ALL_METADATA|G_FILE_COPY_NOFOLLOW_SYMLINKS;
531         FmPath *fm_dest;
532 
533         fm_dest = fm_path_new_for_gfile(dest);
534         /* showing currently processed file. */
535         fm_file_ops_job_emit_cur_file(job, g_file_info_get_display_name(inf));
536 _retry_move:
537         if( !g_file_move(src, dest, flags, fm_job_get_cancellable(fmjob), progress_cb, job, &err))
538         {
539             flags &= ~G_FILE_COPY_OVERWRITE;
540             if(err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
541             {
542                 GFile* dest_cp = new_dest;
543                 FmFileOpOption opt = 0;
544 
545                 new_dest = NULL;
546                 opt = _fm_file_ops_job_ask_new_name(job, src, dest, &new_dest, TRUE);
547                 if(!new_dest) /* restoring status quo */
548                     new_dest = dest_cp;
549                 else if(dest_cp) /* we got new new_dest, forget old one */
550                     g_object_unref(dest_cp);
551 
552                 g_error_free(err);
553                 err = NULL;
554 
555                 switch(opt)
556                 {
557                 case FM_FILE_OP_RENAME:
558                     dest = new_dest;
559                     goto _retry_move;
560                     break;
561                 case FM_FILE_OP_OVERWRITE:
562                     flags |= G_FILE_COPY_OVERWRITE;
563                     if(g_file_info_get_file_type(inf) == G_FILE_TYPE_DIRECTORY) /* merge dirs */
564                     {
565                         GFileEnumerator* enu = g_file_enumerate_children(src, query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
566                                                                         fm_job_get_cancellable(fmjob), &err);
567                         if(enu)
568                         {
569                             GFileInfo* child_inf;
570                             FmFolder *sub_src_folder = fm_folder_find_by_path(src_path);
571                             FmFolder *sub_dest_folder = fm_folder_find_by_path(fm_dest);
572                             while(!fm_job_is_cancelled(fmjob))
573                             {
574                                 child_inf = g_file_enumerator_next_file(enu, fm_job_get_cancellable(fmjob), &err);
575                                 if(child_inf)
576                                 {
577                                     GFile* child = g_file_get_child(src, g_file_info_get_name(child_inf));
578                                     GFile* child_dest = g_file_get_child(dest, g_file_info_get_name(child_inf));
579                                     FmPath *child_path = fm_path_new_for_gfile(child);
580                                     _fm_file_ops_job_move_file(job, child, child_inf, child_dest, child_path, sub_src_folder, sub_dest_folder);
581                                     g_object_unref(child);
582                                     g_object_unref(child_dest);
583                                     g_object_unref(child_inf);
584                                     fm_path_unref(child_path);
585                                 }
586                                 else
587                                 {
588                                     if(err) /* error */
589                                     {
590                                         fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
591                                         g_error_free(err);
592                                         err = NULL;
593                                     }
594                                     else /* EOF */
595                                     {
596                                         break;
597                                     }
598                                 }
599                             }
600                             if (sub_src_folder)
601                                 g_object_unref(sub_src_folder);
602                             if (sub_dest_folder)
603                                 g_object_unref(sub_dest_folder);
604                             g_object_unref(enu);
605                         }
606                         else
607                         {
608                             /*FmJobErrorAction act = */
609                             fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
610                             g_error_free(err);
611                             err = NULL;
612                             /* if(act == FM_JOB_RETRY)
613                                 goto _retry_move; */
614                         }
615 
616                         /* remove source dir after its content is merged with destination dir */
617                         if(!g_file_delete(src, fm_job_get_cancellable(fmjob), &err))
618                         {
619                             fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
620                             g_error_free(err);
621                             err = NULL;
622                             /* FIXME: should this be recoverable? */
623                         }
624                     }
625                     else /* the destination is a file, just overwrite it. */
626                         goto _retry_move;
627                     break;
628                 case FM_FILE_OP_CANCEL:
629                     fm_job_cancel(fmjob);
630                     ret = FALSE;
631                     break;
632                 case FM_FILE_OP_SKIP:
633                     ret = TRUE;
634                     break;
635                 case FM_FILE_OP_SKIP_ERROR: ; /* FIXME */
636                 }
637             }
638             if(err)
639             {
640                 FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
641                 g_error_free(err);
642                 err = NULL;
643                 if(act == FM_JOB_RETRY)
644                     goto _retry_move;
645             }
646             fm_path_unref(fm_dest);
647         }
648         else
649         {
650             if (src_folder)
651                 _fm_folder_event_file_deleted(src_folder, src_path);
652             if (!dest_folder || !_fm_folder_event_file_added(dest_folder, fm_dest))
653                 fm_path_unref(fm_dest);
654         }
655 /*
656         size = g_file_info_get_attribute_uint64(inf, G_FILE_ATTRIBUTE_UNIX_BLOCKS);
657         size *= g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE);
658 */
659         size = g_file_info_get_size(inf);
660 
661         job->finished += size;
662         fm_file_ops_job_emit_percent(job);
663     }
664     else /* use copy if they are on different devices */
665     {
666         /* use copy & delete */
667         /* source file will be deleted in _fm_file_ops_job_copy_file() */
668         ret = _fm_file_ops_job_copy_file(job, src, inf, dest, src_folder, dest_folder);
669     }
670 
671     if(new_dest)
672         g_object_unref(new_dest);
673 
674     g_object_unref(inf);
675     return ret;
676 }
677 
progress_cb(goffset cur,goffset total,gpointer data)678 static void progress_cb(goffset cur, goffset total, gpointer data)
679 {
680     FmFileOpsJob* job = FM_FILE_OPS_JOB(data);
681     job->current_file_finished = cur;
682     /* update progress */
683     fm_file_ops_job_emit_percent(job);
684 }
685 
_fm_file_ops_job_copy_run(FmFileOpsJob * job)686 gboolean _fm_file_ops_job_copy_run(FmFileOpsJob* job)
687 {
688     gboolean ret = TRUE;
689     GFile *dest_dir;
690     GList* l;
691     FmJob* fmjob = FM_JOB(job);
692     /* prepare the job, count total work needed with FmDeepCountJob */
693     FmDeepCountJob* dc = fm_deep_count_job_new(job->srcs, FM_DC_JOB_DEFAULT);
694     FmFolder *df;
695 
696     /* let the deep count job share the same cancellable object. */
697     fm_job_set_cancellable(FM_JOB(dc), fm_job_get_cancellable(fmjob));
698     fm_job_run_sync(FM_JOB(dc));
699     job->total = dc->total_size;
700     if(fm_job_is_cancelled(fmjob))
701     {
702         g_object_unref(dc);
703         return FALSE;
704     }
705     g_object_unref(dc);
706     g_debug("total size to copy: %llu", (long long unsigned int)job->total);
707 
708     dest_dir = fm_path_to_gfile(job->dest);
709     /* suspend updates for destination */
710     df = fm_folder_find_by_path(job->dest);
711     if (df)
712         fm_folder_block_updates(df);
713 
714     fm_file_ops_job_emit_prepared(job);
715 
716     for(l = fm_path_list_peek_head_link(job->srcs); !fm_job_is_cancelled(fmjob) && l; l=l->next)
717     {
718         FmPath* path = FM_PATH(l->data);
719         GFile* src = fm_path_to_gfile(path);
720         GFile* dest;
721         char* tmp_basename;
722 
723         if(g_file_is_native(src) && g_file_is_native(dest_dir))
724             /* both are native */
725             tmp_basename = NULL;
726         else if(g_file_is_native(src)) /* copy from native to virtual */
727             tmp_basename = g_filename_to_utf8(fm_path_get_basename(path),
728                                               -1, NULL, NULL, NULL);
729             /* gvfs escapes it itself */
730         else /* copy from virtual to native/virtual */
731         {
732             /* if we drop URI query onto native filesystem, omit query part */
733             const char *basename = fm_path_get_basename(path);
734             char *sub_name;
735 
736             sub_name = strchr(basename, '?');
737             if (sub_name)
738             {
739                 sub_name = g_strndup(basename, sub_name - basename);
740                 basename = strrchr(sub_name, G_DIR_SEPARATOR);
741                 if (basename)
742                     basename++;
743                 else
744                     basename = sub_name;
745                 tmp_basename = fm_uri_subpath_to_native_subpath(basename, NULL);
746                 g_free(sub_name);
747             }
748             else
749                 /* if not URI, better use display name */
750                 tmp_basename = fm_path_display_basename(path);
751         }
752         dest = g_file_get_child(dest_dir,
753                         tmp_basename ? tmp_basename : fm_path_get_basename(path));
754         g_free(tmp_basename);
755         if(!_fm_file_ops_job_copy_file(job, src, NULL, dest, NULL, df))
756             ret = FALSE;
757         g_object_unref(src);
758         if(dest != NULL)
759             g_object_unref(dest);
760     }
761 
762     /* g_debug("finished: %llu, total: %llu", job->finished, job->total); */
763     fm_file_ops_job_emit_percent(job);
764 
765     /* restore updates for destination */
766     if (df)
767     {
768         fm_folder_unblock_updates(df);
769         g_object_unref(df);
770     }
771     g_object_unref(dest_dir);
772     return ret;
773 }
774 
_fm_file_ops_job_move_run(FmFileOpsJob * job)775 gboolean _fm_file_ops_job_move_run(FmFileOpsJob* job)
776 {
777     GFile *dest_dir;
778     GFileInfo* inf;
779     GList* l;
780     GError* err = NULL;
781     FmJob* fmjob = FM_JOB(job);
782     dev_t dest_dev = 0;
783     gboolean ret = TRUE;
784     FmDeepCountJob* dc;
785     FmPath *parent = NULL;
786     FmFolder *df, *sf = NULL;
787 
788     /* get information of destination folder */
789     g_return_val_if_fail(job->dest, FALSE);
790     dest_dir = fm_path_to_gfile(job->dest);
791 _retry_query_dest_info:
792     inf = g_file_query_info(dest_dir, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL","
793                                   G_FILE_ATTRIBUTE_UNIX_DEVICE","
794                                   G_FILE_ATTRIBUTE_ID_FILESYSTEM","
795                                   G_FILE_ATTRIBUTE_UNIX_DEVICE, 0,
796                                   fm_job_get_cancellable(fmjob), &err);
797     if(inf)
798     {
799         job->dest_fs_id = g_intern_string(g_file_info_get_attribute_string(inf, G_FILE_ATTRIBUTE_ID_FILESYSTEM));
800         dest_dev = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_UNIX_DEVICE); /* needed by deep count */
801         g_object_unref(inf);
802     }
803     else
804     {
805         FmJobErrorAction act = fm_job_emit_error(fmjob, err, FM_JOB_ERROR_MODERATE);
806         g_error_free(err);
807         err = NULL;
808         if(act == FM_JOB_RETRY)
809             goto _retry_query_dest_info;
810         else
811         {
812             g_object_unref(dest_dir);
813             return FALSE;
814         }
815     }
816 
817     /* prepare the job, count total work needed with FmDeepCountJob */
818     dc = fm_deep_count_job_new(job->srcs, FM_DC_JOB_PREPARE_MOVE);
819     fm_deep_count_job_set_dest(dc, dest_dev, job->dest_fs_id);
820     fm_job_run_sync(FM_JOB(dc));
821     job->total = dc->total_size;
822 
823     if( fm_job_is_cancelled(FM_JOB(dc)) )
824     {
825         g_object_unref(dest_dir);
826         g_object_unref(dc);
827         return FALSE;
828     }
829     g_object_unref(dc);
830     g_debug("total size to move: %llu, dest_fs: %s",
831             (long long unsigned int)job->total, job->dest_fs_id);
832 
833     fm_file_ops_job_emit_prepared(job);
834     /* suspend updates for destination */
835     df = fm_folder_find_by_path(job->dest);
836     if (df)
837         fm_folder_block_updates(df);
838 
839     for(l = fm_path_list_peek_head_link(job->srcs); !fm_job_is_cancelled(fmjob) && l; l=l->next)
840     {
841         FmPath* path = FM_PATH(l->data);
842         GFile* src = fm_path_to_gfile(path);
843         GFile* dest;
844         char* tmp_basename;
845 
846         /* do with updates for source */
847         if (fm_path_get_parent(path) != parent && fm_path_get_parent(path) != NULL)
848         {
849             FmFolder *pf;
850 
851             pf = fm_folder_find_by_path(fm_path_get_parent(path));
852             if (pf != sf)
853             {
854                 if (sf)
855                 {
856                     fm_folder_unblock_updates(sf);
857                     g_object_unref(sf);
858                 }
859                 if (pf)
860                     fm_folder_block_updates(pf);
861                 sf = pf;
862             }
863             else if (pf)
864                 g_object_unref(pf);
865         }
866         parent = fm_path_get_parent(path);
867         if(g_file_is_native(src) && g_file_is_native(dest_dir))
868             /* both are native */
869             tmp_basename = NULL;
870         else if(g_file_is_native(src)) /* move from native to virtual */
871             tmp_basename = g_filename_to_utf8(fm_path_get_basename(path),
872                                               -1, NULL, NULL, NULL);
873             /* gvfs escapes it itself */
874         else /* move from virtual to native/virtual */
875         {
876             char *name = fm_path_display_basename(path);
877             tmp_basename = fm_uri_subpath_to_native_subpath(name, NULL);
878             g_free(name);
879         }
880         dest = g_file_get_child(dest_dir,
881                         tmp_basename ? tmp_basename : fm_path_get_basename(path));
882         g_free(tmp_basename);
883 
884         if(!_fm_file_ops_job_move_file(job, src, NULL, dest, path, sf, df))
885             ret = FALSE;
886         g_object_unref(src);
887         if(dest != NULL)
888             g_object_unref(dest);
889 
890         if(!ret)
891             break;
892     }
893     /* restore updates for destination and source */
894     if (df)
895     {
896         fm_folder_unblock_updates(df);
897         g_object_unref(df);
898     }
899     if (sf)
900     {
901         fm_folder_unblock_updates(sf);
902         g_object_unref(sf);
903     }
904 
905     g_object_unref(dest_dir);
906     return ret;
907 }
908