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