source: trunk/third/gnome-vfs/libgnomevfs/gnome-vfs-xfer.c @ 18126

Revision 18126, 67.1 KB checked in by ghudson, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18125, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3/* gnome-vfs-xfer.c - File transfers in the GNOME Virtual File System.
4
5   Copyright (C) 1999 Free Software Foundation
6   Copyright (C) 2000, 2001 Eazel, Inc.
7
8   The Gnome Library is free software; you can redistribute it and/or
9   modify it under the terms of the GNU Library General Public License as
10   published by the Free Software Foundation; either version 2 of the
11   License, or (at your option) any later version.
12
13   The Gnome Library is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16   Library General Public License for more details.
17
18   You should have received a copy of the GNU Library General Public
19   License along with the Gnome Library; see the file COPYING.LIB.  If not,
20   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21   Boston, MA 02111-1307, USA.
22
23   Authors:
24   Ettore Perazzoli <ettore@comm2000.it>
25   Pavel Cisler <pavel@eazel.com>
26*/
27
28/* FIXME bugzilla.eazel.com 1197:
29   Check that all the flags passed by address are set at least once by
30   functions that are expected to set them.  */
31/* FIXME bugzilla.eazel.com 1198:
32   There should be a concept of a `context' in the file methods that would
33   allow us to set a prefix URI for all the operations.  This way we can
34   greatly optimize access to "strange" file systems.  */
35
36#include <config.h>
37#include "gnome-vfs-xfer.h"
38
39#include "gnome-vfs-private.h"
40#include "gnome-vfs.h"
41#include <string.h>
42#include <sys/time.h>
43
44/* Implementation of file transfers (`gnome_vfs_xfer*()' functions).  */
45
46static GnomeVFSResult   remove_directory        (GnomeVFSURI *uri,
47                                                 gboolean recursive,
48                                                 GnomeVFSProgressCallbackState *progress,
49                                                 GnomeVFSXferOptions xfer_options,
50                                                 GnomeVFSXferErrorMode *error_mode,
51                                                 gboolean *skip);
52
53
54enum {
55        /* size used for accounting for the expense of copying directories,
56         * doing a move operation, etc. in a progress callback
57         * (during a move the file size is used for a regular file).
58         */
59        DEFAULT_SIZE_OVERHEAD = 1024
60};
61
62/* in asynch mode the progress callback does a context switch every time
63 * it gets called. We'll only call it every now and then to not loose a
64 * lot of performance
65 */
66#define UPDATE_PERIOD ((gint64) (100 * 1000))
67
68static gint64
69system_time (void)
70{
71        struct timeval time_of_day;
72
73        gettimeofday (&time_of_day, NULL);
74        return (gint64) time_of_day.tv_usec + ((gint64) time_of_day.tv_sec) * 1000000;
75}
76
77static void
78init_progress (GnomeVFSProgressCallbackState *progress_state,
79               GnomeVFSXferProgressInfo *progress_info)
80{
81        progress_info->source_name = NULL;
82        progress_info->target_name = NULL;
83        progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
84        progress_info->vfs_status = GNOME_VFS_OK;
85        progress_info->phase = GNOME_VFS_XFER_PHASE_INITIAL;
86        progress_info->file_index = 0;
87        progress_info->files_total = 0;
88        progress_info->bytes_total = 0;
89        progress_info->file_size = 0;
90        progress_info->bytes_copied = 0;
91        progress_info->total_bytes_copied = 0;
92        progress_info->duplicate_name = NULL;
93        progress_info->top_level_item = FALSE;
94
95        progress_state->progress_info = progress_info;
96        progress_state->sync_callback = NULL;
97        progress_state->update_callback = NULL;
98        progress_state->async_job_data = NULL;
99        progress_state->next_update_callback_time = 0;
100        progress_state->next_text_update_callback_time = 0;
101        progress_state->update_callback_period = UPDATE_PERIOD;
102}
103
104static void
105free_progress (GnomeVFSXferProgressInfo *progress_info)
106{
107        g_free (progress_info->source_name);
108        progress_info->source_name = NULL;
109        g_free (progress_info->target_name);
110        progress_info->target_name = NULL;
111}
112
113static void
114progress_set_source_target_uris (GnomeVFSProgressCallbackState *progress,
115              const GnomeVFSURI *source_uri, const GnomeVFSURI *dest_uri)
116{
117        g_free (progress->progress_info->source_name);
118        progress->progress_info->source_name = source_uri ? gnome_vfs_uri_to_string (source_uri,
119                                                       GNOME_VFS_URI_HIDE_NONE) : NULL;
120        g_free (progress->progress_info->target_name);
121        progress->progress_info->target_name = dest_uri ? gnome_vfs_uri_to_string (dest_uri,
122                                                       GNOME_VFS_URI_HIDE_NONE) : NULL;
123
124}
125
126static void
127progress_set_source_target (GnomeVFSProgressCallbackState *progress,
128              const char *source, const char *dest)
129{
130        g_free (progress->progress_info->source_name);
131        progress->progress_info->source_name = source ? g_strdup (source) : NULL;
132        g_free (progress->progress_info->target_name);
133        progress->progress_info->target_name = dest ? g_strdup (dest) : NULL;
134
135}
136
137static int
138call_progress (GnomeVFSProgressCallbackState *progress, GnomeVFSXferPhase phase)
139{
140        int result;
141
142        /* FIXME bugzilla.eazel.com 1199: should use proper progress result values from an enum here */
143
144        result = 0;
145        progress_set_source_target_uris (progress, NULL, NULL);
146
147        progress->next_update_callback_time = system_time () + progress->update_callback_period;
148       
149        progress->progress_info->phase = phase;
150
151        if (progress->sync_callback != NULL)
152                result = (* progress->sync_callback) (progress->progress_info, progress->user_data);
153
154        if (progress->update_callback != NULL) {
155                result = (* progress->update_callback) (progress->progress_info,
156                        progress->async_job_data);
157        }
158
159        return result; 
160}
161
162static GnomeVFSXferErrorAction
163call_progress_with_current_names (GnomeVFSProgressCallbackState *progress, GnomeVFSXferPhase phase)
164{
165        int result;
166
167        result = GNOME_VFS_XFER_ERROR_ACTION_ABORT;
168
169        progress->next_update_callback_time = system_time () + progress->update_callback_period;
170        progress->next_update_callback_time = progress->next_text_update_callback_time;
171       
172        progress->progress_info->phase = phase;
173
174        if (progress->sync_callback != NULL) {
175                result = (* progress->sync_callback) (progress->progress_info, progress->user_data);
176        }
177       
178        if (progress->update_callback != NULL) {
179                result = (* progress->update_callback) (progress->progress_info,
180                        progress->async_job_data);
181        }
182
183        return result; 
184}
185
186static int
187call_progress_uri (GnomeVFSProgressCallbackState *progress,
188                   const GnomeVFSURI *source_uri, const GnomeVFSURI *dest_uri,
189                   GnomeVFSXferPhase phase)
190{
191        int result;
192
193        result = 0;
194        progress_set_source_target_uris (progress, source_uri, dest_uri);
195
196        progress->next_text_update_callback_time = system_time () + progress->update_callback_period;
197        progress->next_update_callback_time = progress->next_text_update_callback_time;
198       
199        progress->progress_info->phase = phase;
200
201        if (progress->sync_callback != NULL) {
202                result = (* progress->sync_callback) (progress->progress_info, progress->user_data);
203        }
204       
205        if (progress->update_callback != NULL) {
206                result = (* progress->update_callback) (progress->progress_info,
207                        progress->async_job_data);
208        }
209       
210        return result; 
211}
212
213static gboolean
214at_end (GnomeVFSProgressCallbackState *progress)
215{
216        return progress->progress_info->total_bytes_copied
217                >= progress->progress_info->bytes_total;
218}
219
220static int
221call_progress_often_internal (GnomeVFSProgressCallbackState *progress,
222                              GnomeVFSXferPhase phase,
223                              gint64 *next_time)
224{
225        int result;
226        gint64 now;
227
228        result = 1;
229        now = system_time ();
230
231        progress->progress_info->phase = phase;
232
233        if (progress->sync_callback != NULL) {
234                result = (* progress->sync_callback) (progress->progress_info, progress->user_data);
235        }
236
237        if (now < *next_time && !at_end (progress)) {
238                return result;
239        }
240
241        *next_time = now + progress->update_callback_period;
242        if (progress->update_callback != NULL) {
243                result = (* progress->update_callback) (progress->progress_info, progress->async_job_data);
244        }
245
246        return result;
247}
248
249static int
250call_progress_often (GnomeVFSProgressCallbackState *progress,
251                     GnomeVFSXferPhase phase)
252{
253        return call_progress_often_internal
254                (progress, phase, &progress->next_update_callback_time);
255}
256
257static int
258call_progress_with_uris_often (GnomeVFSProgressCallbackState *progress,
259                               const GnomeVFSURI *source_uri, const GnomeVFSURI *dest_uri,
260                               GnomeVFSXferPhase phase)
261{
262        progress_set_source_target_uris (progress, source_uri, dest_uri);
263        return call_progress_often_internal
264                (progress, phase, &progress->next_text_update_callback_time);
265}
266
267/* Handle an error condition according to `error_mode'.  Returns `TRUE' if the
268 * operation should be retried, `FALSE' otherwise.  `*result' will be set to
269 * the result value we want to be returned to the caller of the xfer
270 * function.
271 */
272static gboolean
273handle_error (GnomeVFSResult *result,
274              GnomeVFSProgressCallbackState *progress,
275              GnomeVFSXferErrorMode *error_mode,
276              gboolean *skip)
277{
278        GnomeVFSXferErrorAction action;
279
280        *skip = FALSE;
281
282        switch (*error_mode) {
283        case GNOME_VFS_XFER_ERROR_MODE_ABORT:
284                return FALSE;
285
286        case GNOME_VFS_XFER_ERROR_MODE_QUERY:
287                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR;
288                progress->progress_info->vfs_status = *result;
289                action = call_progress_with_current_names (progress, progress->progress_info->phase);
290                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
291
292                switch (action) {
293                case GNOME_VFS_XFER_ERROR_ACTION_RETRY:
294                        return TRUE;
295                case GNOME_VFS_XFER_ERROR_ACTION_ABORT:
296                        *result = GNOME_VFS_ERROR_INTERRUPTED;
297                        return FALSE;
298                case GNOME_VFS_XFER_ERROR_ACTION_SKIP:
299                        *result = GNOME_VFS_OK;
300                        *skip = TRUE;
301                        return FALSE;
302                }
303                break;
304        }
305
306        *skip = FALSE;
307        return FALSE;
308}
309
310/* This is conceptually similiar to the previous `handle_error()' function, but
311 * handles the overwrite case.
312 */
313static gboolean
314handle_overwrite (GnomeVFSResult *result,
315                  GnomeVFSProgressCallbackState *progress,
316                  GnomeVFSXferErrorMode *error_mode,
317                  GnomeVFSXferOverwriteMode *overwrite_mode,
318                  gboolean *replace,
319                  gboolean *skip)
320{
321        GnomeVFSXferOverwriteAction action;
322
323        switch (*overwrite_mode) {
324        case GNOME_VFS_XFER_OVERWRITE_MODE_ABORT:
325                *replace = FALSE;
326                *result = GNOME_VFS_ERROR_FILE_EXISTS;
327                *skip = FALSE;
328                return FALSE;
329        case GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE:
330                *replace = TRUE;
331                *skip = FALSE;
332                return TRUE;
333        case GNOME_VFS_XFER_OVERWRITE_MODE_SKIP:
334                *replace = FALSE;
335                *skip = TRUE;
336                return FALSE;
337        case GNOME_VFS_XFER_OVERWRITE_MODE_QUERY:
338                progress->progress_info->vfs_status = *result;
339                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE;
340                action = call_progress_with_current_names (progress, progress->progress_info->phase);
341                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
342
343                switch (action) {
344                case GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT:
345                        *replace = FALSE;
346                        *result = GNOME_VFS_ERROR_FILE_EXISTS;
347                        *skip = FALSE;
348                        return FALSE;
349                case GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE:
350                        *replace = TRUE;
351                        *skip = FALSE;
352                        return TRUE;
353                case GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE_ALL:
354                        *replace = TRUE;
355                        *overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE;
356                        *skip = FALSE;
357                        return TRUE;
358                case GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP:
359                        *replace = FALSE;
360                        *skip = TRUE;
361                        return FALSE;
362                case GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP_ALL:
363                        *replace = FALSE;
364                        *skip = TRUE;
365                        *overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_SKIP;
366                        return FALSE;
367                }
368        }
369
370        *replace = FALSE;
371        *skip = FALSE;
372        return FALSE;
373}
374
375static GnomeVFSResult
376remove_file (GnomeVFSURI *uri,
377             GnomeVFSProgressCallbackState *progress,
378             GnomeVFSXferOptions xfer_options,
379             GnomeVFSXferErrorMode *error_mode,
380             gboolean *skip)
381{
382        GnomeVFSResult result;
383        gboolean retry;
384
385        progress->progress_info->file_index++;
386        do {
387                retry = FALSE;
388                result = gnome_vfs_unlink_from_uri (uri);               
389                if (result != GNOME_VFS_OK) {
390                        retry = handle_error (&result, progress,
391                                              error_mode, skip);
392                } else {
393                        /* add some small size for each deleted item because delete overhead
394                         * does not depend on file/directory size
395                         */
396                        progress->progress_info->total_bytes_copied += DEFAULT_SIZE_OVERHEAD;
397
398                        if (call_progress_with_uris_often (progress, uri, NULL,
399                                                           GNOME_VFS_XFER_PHASE_DELETESOURCE)
400                                == GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
401                                result = GNOME_VFS_ERROR_INTERRUPTED;
402                                break;
403                        }
404                }
405
406
407        } while (retry);
408
409        return result;
410}
411
412static GnomeVFSResult
413empty_directory (GnomeVFSURI *uri,
414                 GnomeVFSProgressCallbackState *progress,
415                 GnomeVFSXferOptions xfer_options,
416                 GnomeVFSXferErrorMode *error_mode,
417                 gboolean *skip)
418{
419        GnomeVFSResult result;
420        GnomeVFSDirectoryHandle *directory_handle;
421        gboolean retry;
422
423
424        *skip = FALSE;
425        do {
426                result = gnome_vfs_directory_open_from_uri (&directory_handle, uri,
427                                                            GNOME_VFS_FILE_INFO_DEFAULT,
428                                                            NULL);
429                retry = FALSE;
430                if (result != GNOME_VFS_OK) {
431                        retry = handle_error (&result, progress,
432                                              error_mode, skip);
433                }
434        } while (retry);
435
436        if (result != GNOME_VFS_OK || *skip) {
437                return result;
438        }
439       
440        for (;;) {
441                GnomeVFSFileInfo *info;
442                GnomeVFSURI *item_uri;
443               
444                item_uri = NULL;
445                info = gnome_vfs_file_info_new ();
446                result = gnome_vfs_directory_read_next (directory_handle, info);
447                if (result != GNOME_VFS_OK) {
448                        gnome_vfs_file_info_unref (info);
449                        break;
450                }
451
452                /* Skip "." and "..".  */
453                if (strcmp (info->name, ".") != 0 && strcmp (info->name, "..") != 0) {
454
455                        item_uri = gnome_vfs_uri_append_file_name (uri, info->name);
456                       
457                        progress_set_source_target_uris (progress, item_uri, NULL);
458                       
459                        if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
460                                result = remove_directory (item_uri, TRUE,
461                                                           progress, xfer_options, error_mode,
462                                                           skip);
463                        } else {
464                                result = remove_file (item_uri, progress,
465                                                      xfer_options,
466                                                      error_mode,
467                                                      skip);
468                        }
469
470                }
471
472                gnome_vfs_file_info_unref (info);
473               
474                if (item_uri != NULL) {
475                        gnome_vfs_uri_unref (item_uri);
476                }
477               
478                if (result != GNOME_VFS_OK) {
479                        break;
480                }
481        }
482        gnome_vfs_directory_close (directory_handle);
483
484        if (result == GNOME_VFS_ERROR_EOF) {
485                result = GNOME_VFS_OK;
486        }
487
488        return result;
489}
490
491typedef struct {
492        const GnomeVFSURI *base_uri;
493        GList *uri_list;
494} PrependOneURIParams;
495
496static gboolean
497PrependOneURIToList (const gchar *rel_path, GnomeVFSFileInfo *info,
498        gboolean recursing_will_loop, gpointer cast_to_params, gboolean *recurse)
499{
500        PrependOneURIParams *params;
501
502        params = (PrependOneURIParams *)cast_to_params;
503        params->uri_list = g_list_prepend (params->uri_list, gnome_vfs_uri_append_string (
504                params->base_uri, rel_path));
505
506        if (recursing_will_loop) {
507                return FALSE;
508        }
509        *recurse = TRUE;
510        return TRUE;
511}
512
513static GnomeVFSResult
514non_recursive_empty_directory (GnomeVFSURI *directory_uri,
515                               GnomeVFSProgressCallbackState *progress,
516                               GnomeVFSXferOptions xfer_options,
517                               GnomeVFSXferErrorMode *error_mode,
518                               gboolean *skip)
519{
520        /* Used as a fallback when we run out of file descriptors during
521         * a deep recursive deletion.
522         * We instead compile a flat list of URIs, doing a recursion that does not
523         * keep directories open.
524         */
525
526        GList *uri_list;
527        GList *p;
528        GnomeVFSURI *uri;
529        GnomeVFSResult result;
530        GnomeVFSFileInfo *info;
531        PrependOneURIParams visit_params;
532
533        /* Build up the list so that deep items appear before their parents
534         * so that we can delete directories, children first.
535         */
536        visit_params.base_uri = directory_uri;
537        visit_params.uri_list = NULL;
538        result = gnome_vfs_directory_visit_uri (directory_uri, GNOME_VFS_FILE_INFO_DEFAULT,
539                NULL, GNOME_VFS_DIRECTORY_VISIT_SAMEFS | GNOME_VFS_DIRECTORY_VISIT_LOOPCHECK,
540                PrependOneURIToList, &visit_params);
541
542        uri_list = visit_params.uri_list;
543
544        if (result == GNOME_VFS_OK) {
545                for (p = uri_list; p != NULL; p = p->next) {
546
547                        uri = (GnomeVFSURI *)p->data;
548                        info = gnome_vfs_file_info_new ();
549                        result = gnome_vfs_get_file_info_uri (uri, info, GNOME_VFS_FILE_INFO_DEFAULT);
550                        progress_set_source_target_uris (progress, uri, NULL);
551                        if (result == GNOME_VFS_OK) {
552                                if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
553                                        result = remove_directory (uri, FALSE,
554                                                progress, xfer_options, error_mode, skip);
555                                } else {
556                                        result = remove_file (uri, progress,
557                                                xfer_options, error_mode, skip);
558                                }
559                        }
560                        gnome_vfs_file_info_unref (info);
561                }
562        }
563
564        gnome_vfs_uri_list_free (uri_list);
565
566        return result;
567}
568
569static GnomeVFSResult
570remove_directory (GnomeVFSURI *uri,
571                  gboolean recursive,
572                  GnomeVFSProgressCallbackState *progress,
573                  GnomeVFSXferOptions xfer_options,
574                  GnomeVFSXferErrorMode *error_mode,
575                  gboolean *skip)
576{
577        GnomeVFSResult result;
578        gboolean retry;
579
580        result = GNOME_VFS_OK;
581
582        if (recursive) {
583                result = empty_directory (uri, progress, xfer_options, error_mode, skip);
584                if (result == GNOME_VFS_ERROR_TOO_MANY_OPEN_FILES) {
585                        result = non_recursive_empty_directory (uri, progress, xfer_options,
586                                error_mode, skip);
587                }
588        }
589
590        if (result == GNOME_VFS_ERROR_EOF) {
591                result = GNOME_VFS_OK;
592        }
593
594        if (result == GNOME_VFS_OK) {
595                progress->progress_info->file_index++;
596
597                do {
598                        retry = FALSE;
599
600                        result = gnome_vfs_remove_directory_from_uri (uri);
601                        if (result != GNOME_VFS_OK) {
602                                retry = handle_error (&result, progress,
603                                                      error_mode, skip);
604                        } else {
605                                /* add some small size for each deleted item */
606                                progress->progress_info->total_bytes_copied += DEFAULT_SIZE_OVERHEAD;
607
608                                if (call_progress_with_uris_often (progress, uri, NULL,
609                                        GNOME_VFS_XFER_PHASE_DELETESOURCE)
610                                        == GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
611                                        result = GNOME_VFS_ERROR_INTERRUPTED;
612                                        break;
613                                }
614                        }
615
616                } while (retry);
617        }
618
619        return result;
620}
621
622/* iterates the list of names in a given directory, applies @callback on each,
623 * optionally recurses into directories
624 */
625static GnomeVFSResult
626gnome_vfs_visit_list (const GList *name_uri_list,
627                      GnomeVFSFileInfoOptions info_options,
628                      GnomeVFSDirectoryVisitOptions visit_options,
629                      gboolean recursive,
630                      GnomeVFSDirectoryVisitFunc callback,
631                      gpointer data)
632{
633        GnomeVFSResult result;
634        const GList *p;
635        GnomeVFSURI *uri;
636        GnomeVFSFileInfo *info;
637        gboolean tmp_recurse;
638       
639        result = GNOME_VFS_OK;
640
641        /* go through our list of items */
642        for (p = name_uri_list; p != NULL; p = p->next) {
643               
644                /* get the URI and VFSFileInfo for each */
645                uri = (GnomeVFSURI *)p->data;
646                info = gnome_vfs_file_info_new ();
647                result = gnome_vfs_get_file_info_uri (uri, info, info_options);
648               
649                if (result == GNOME_VFS_OK) {
650                        tmp_recurse = TRUE;
651                       
652                        /* call our callback on each item*/
653                        if (!callback (gnome_vfs_uri_get_path (uri), info, FALSE, data, &tmp_recurse)) {
654                                result = GNOME_VFS_ERROR_INTERRUPTED;
655                        }
656                       
657                        if (result == GNOME_VFS_OK
658                            && recursive
659                            && info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
660                                /* let gnome_vfs_directory_visit_uri call our callback
661                                 * recursively
662                                 */
663                                result = gnome_vfs_directory_visit_uri (uri, info_options,
664                                        NULL, visit_options, callback, data);
665                        }
666                }
667                gnome_vfs_file_info_unref (info);
668               
669                if (result != GNOME_VFS_OK) {
670                        break;
671                }
672        }
673        return result;
674}
675
676typedef struct {
677        GnomeVFSProgressCallbackState *progress;
678        GnomeVFSResult result;
679} CountEachFileSizeParams;
680
681/* iteratee for count_items_and_size */
682static gboolean
683count_each_file_size_one (const gchar *rel_path,
684                          GnomeVFSFileInfo *info,
685                          gboolean recursing_will_loop,
686                          gpointer data,
687                          gboolean *recurse)
688{
689        CountEachFileSizeParams *params;
690
691        params = (CountEachFileSizeParams *)data;
692
693        if (call_progress_often (params->progress, params->progress->progress_info->phase) == 0) {
694                /* progress callback requested to stop */
695                params->result = GNOME_VFS_ERROR_INTERRUPTED;
696                *recurse = FALSE;
697                return FALSE;
698        }
699
700        /* keep track of the item we are counting so we can correctly report errors */
701        progress_set_source_target (params->progress, rel_path, NULL);
702
703        /* count each file, folder, symlink */
704        params->progress->progress_info->files_total++;
705        if (info->type == GNOME_VFS_FILE_TYPE_REGULAR) {
706                /* add each file size */
707                params->progress->progress_info->bytes_total += info->size + DEFAULT_SIZE_OVERHEAD;
708        } else if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
709                /* add some small size for each directory */
710                params->progress->progress_info->bytes_total += DEFAULT_SIZE_OVERHEAD;
711        }
712
713        /* watch out for infinite recursion*/
714        if (recursing_will_loop) {
715                params->result = GNOME_VFS_ERROR_LOOP;
716                return FALSE;
717        }
718
719        *recurse = TRUE;
720
721        return TRUE;
722}
723
724/* calculate the number of items and their total size; used as a preflight
725 * before the transfer operation starts
726 */
727static GnomeVFSResult
728count_items_and_size (const GList *name_uri_list,
729                      GnomeVFSXferOptions xfer_options,
730                      GnomeVFSProgressCallbackState *progress,
731                      gboolean move,
732                      gboolean link)
733{
734        /*
735         * FIXME bugzilla.eazel.com 1200:
736         * Deal with errors here, respond to skip by pulling items from the name list
737         */
738        GnomeVFSFileInfoOptions info_options;
739        GnomeVFSDirectoryVisitOptions visit_options;
740        CountEachFileSizeParams each_params;
741
742        /* initialize the results */
743        progress->progress_info->files_total = 0;
744        progress->progress_info->bytes_total = 0;
745
746        /* set up the params for recursion */
747        visit_options = GNOME_VFS_DIRECTORY_VISIT_LOOPCHECK;
748        if (xfer_options & GNOME_VFS_XFER_SAMEFS)
749                visit_options |= GNOME_VFS_DIRECTORY_VISIT_SAMEFS;
750        each_params.progress = progress;
751        each_params.result = GNOME_VFS_OK;
752
753        if (xfer_options & GNOME_VFS_XFER_FOLLOW_LINKS) {
754                info_options = GNOME_VFS_FILE_INFO_FOLLOW_LINKS;
755        } else {
756                info_options = GNOME_VFS_FILE_INFO_DEFAULT;
757        }
758
759        return gnome_vfs_visit_list (name_uri_list, info_options,
760                visit_options, !link && !move && (xfer_options & GNOME_VFS_XFER_RECURSIVE) != 0,
761                count_each_file_size_one, &each_params);
762}
763
764/* calculate the number of items and their total size; used as a preflight
765 * before the transfer operation starts
766 */
767static GnomeVFSResult
768directory_add_items_and_size (GnomeVFSURI *dir_uri,
769                              GnomeVFSXferOptions xfer_options,
770                              GnomeVFSProgressCallbackState *progress)
771{
772        GnomeVFSFileInfoOptions info_options;
773        GnomeVFSDirectoryVisitOptions visit_options;
774        CountEachFileSizeParams each_params;
775
776        /* set up the params for recursion */
777        visit_options = GNOME_VFS_DIRECTORY_VISIT_LOOPCHECK;
778        if (xfer_options & GNOME_VFS_XFER_SAMEFS)
779                visit_options |= GNOME_VFS_DIRECTORY_VISIT_SAMEFS;
780        each_params.progress = progress;
781        each_params.result = GNOME_VFS_OK;
782
783        if (xfer_options & GNOME_VFS_XFER_FOLLOW_LINKS) {
784                info_options = GNOME_VFS_FILE_INFO_FOLLOW_LINKS;
785        } else {
786                info_options = GNOME_VFS_FILE_INFO_DEFAULT;
787        }
788
789        return gnome_vfs_directory_visit_uri (dir_uri, info_options, NULL,
790                visit_options, count_each_file_size_one, &each_params);
791
792}
793
794/* Compares the list of files about to be created by a transfer with
795 * any possible existing files with conflicting names in the target directory.
796 * Handles conflicts, optionaly removing the conflicting file/directory
797 */
798static GnomeVFSResult
799handle_name_conflicts (GList **source_uri_list,
800                       GList **target_uri_list,
801                       GnomeVFSXferOptions xfer_options,
802                       GnomeVFSXferErrorMode *error_mode,
803                       GnomeVFSXferOverwriteMode *overwrite_mode,
804                       GnomeVFSProgressCallbackState *progress)
805{
806        GnomeVFSResult result;
807        GList *source_item;
808        GList *target_item;
809
810        int conflict_count; /* values are 0, 1, many */
811       
812        result = GNOME_VFS_OK;
813        conflict_count = 0;
814
815        /* Go through the list of names, find out if there is 0, 1 or more conflicts. */
816        for (target_item = *target_uri_list; target_item != NULL;
817             target_item = target_item->next) {
818                if (gnome_vfs_uri_exists ((GnomeVFSURI *)target_item->data)) {
819                        conflict_count++;
820                        if (conflict_count > 1) {
821                                break;
822                        }
823                }
824        }
825
826        if (conflict_count == 0) {
827                /* No conflicts, we are done. */
828                return GNOME_VFS_OK;
829        }
830
831        /* Pass in the conflict count so that we can decide to present the Replace All
832         * for multiple conflicts.
833         */
834        progress->progress_info->duplicate_count = conflict_count;
835
836       
837        /* Go through the list of names again, present overwrite alerts for each. */
838        for (target_item = *target_uri_list, source_item = *source_uri_list;
839             target_item != NULL;) {
840                gboolean replace;
841                gboolean skip;
842                gboolean is_move_to_self;
843                GnomeVFSURI *uri, *source_uri;
844                GnomeVFSFileInfo *info;
845               
846                skip = FALSE;
847                source_uri = (GnomeVFSURI *)source_item->data;
848                uri = (GnomeVFSURI *)target_item->data;
849                is_move_to_self = (xfer_options & GNOME_VFS_XFER_REMOVESOURCE) != 0
850                        && gnome_vfs_uri_equal (source_uri, uri);
851                if (!is_move_to_self && gnome_vfs_uri_exists (uri)) {
852                        progress_set_source_target_uris (progress, source_uri, uri);
853                         
854                        /* no error getting info -- file exists, ask what to do */
855                        replace = handle_overwrite (&result, progress, error_mode,
856                                overwrite_mode, &replace, &skip);
857
858                        /* FIXME bugzilla.eazel.com 1207:
859                         * move items to Trash here
860                         */
861
862                        /* get rid of the conflicting file */
863                        if (replace) {
864                                info = gnome_vfs_file_info_new ();
865                                gnome_vfs_get_file_info_uri (uri, info, GNOME_VFS_FILE_INFO_DEFAULT);
866                                progress_set_source_target_uris (progress, uri, NULL);
867                                if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
868                                        remove_directory (uri, TRUE, progress,
869                                                xfer_options, error_mode, &skip);
870                                } else {
871                                        remove_file (uri, progress,
872                                                xfer_options, error_mode, &skip);
873                                }
874                                gnome_vfs_file_info_unref (info);
875                        }
876                }
877
878               
879                if (result != GNOME_VFS_OK) {
880                        break;
881                }
882
883                if (skip) {
884                        /* skipping a file, remove it's name from the source and target name
885                         * lists.
886                         */
887                        GList *source_item_to_remove;
888                        GList *target_item_to_remove;
889
890                        source_item_to_remove = source_item;
891                        target_item_to_remove = target_item;
892                       
893                        source_item = source_item->next;
894                        target_item = target_item->next;
895
896                        gnome_vfs_uri_unref ((GnomeVFSURI *)source_item_to_remove->data);
897                        gnome_vfs_uri_unref ((GnomeVFSURI *)target_item_to_remove->data);
898                        *source_uri_list = g_list_remove_link (*source_uri_list, source_item_to_remove);
899                        *target_uri_list = g_list_remove_link (*target_uri_list, target_item_to_remove);
900                       
901                        continue;
902                }
903
904
905                target_item = target_item->next;
906                source_item = source_item->next;
907        }
908
909        return result;
910}
911
912/* Create new directory. If GNOME_VFS_XFER_USE_UNIQUE_NAMES is set,
913 * return with an error if a name conflict occurs, else
914 * handle the overwrite.
915 * On success, opens the new directory
916 */
917static GnomeVFSResult
918create_directory (GnomeVFSURI *dir_uri,
919                  GnomeVFSDirectoryHandle **return_handle,
920                  GnomeVFSXferOptions xfer_options,
921                  GnomeVFSXferErrorMode *error_mode,
922                  GnomeVFSXferOverwriteMode *overwrite_mode,
923                  GnomeVFSProgressCallbackState *progress,
924                  gboolean *skip)
925{
926        GnomeVFSResult result;
927        gboolean retry;
928       
929        *skip = FALSE;
930        do {
931                retry = FALSE;
932
933                result = gnome_vfs_make_directory_for_uri (dir_uri, 0777);
934
935                if (result == GNOME_VFS_ERROR_FILE_EXISTS) {
936                        gboolean force_replace;
937
938                        if ((xfer_options & GNOME_VFS_XFER_USE_UNIQUE_NAMES) != 0) {
939                                /* just let the caller pass a unique name*/
940                                return result;
941                        }
942
943                        retry = handle_overwrite (&result,
944                                                  progress,
945                                                  error_mode,
946                                                  overwrite_mode,
947                                                  &force_replace,
948                                                  skip);
949
950                        if (*skip) {
951                                return GNOME_VFS_OK;
952                        }
953                        if (force_replace) {
954                                result = remove_directory (dir_uri, TRUE, progress,
955                                                           xfer_options, error_mode,
956                                                           skip);
957                        } else {
958                                result = GNOME_VFS_OK;
959                        }
960                }
961
962                if (result == GNOME_VFS_OK) {
963                        return gnome_vfs_directory_open_from_uri (return_handle,
964                                                                  dir_uri,
965                                                                  GNOME_VFS_FILE_INFO_DEFAULT,
966                                                                  NULL);
967                }
968                /* handle the error case */
969                retry = handle_error (&result, progress,
970                                      error_mode, skip);
971
972                if (*skip) {
973                        return GNOME_VFS_OK;
974                }
975
976        } while (retry);
977
978        return result;
979}
980
981/* Copy the data of a single file. */
982static GnomeVFSResult
983copy_file_data (GnomeVFSHandle *target_handle,
984                GnomeVFSHandle *source_handle,
985                GnomeVFSProgressCallbackState *progress,
986                GnomeVFSXferOptions xfer_options,
987                GnomeVFSXferErrorMode *error_mode,
988                guint block_size,
989                gboolean *skip)
990{
991        GnomeVFSResult result;
992        gpointer buffer;
993        const char *write_buffer;
994        GnomeVFSFileSize total_bytes_read;
995
996        *skip = FALSE;
997
998        if (call_progress_often (progress, GNOME_VFS_XFER_PHASE_COPYING) == 0) {
999                return GNOME_VFS_ERROR_INTERRUPTED;
1000        }
1001
1002        buffer = g_malloc (block_size);
1003        total_bytes_read = 0;
1004       
1005        do {
1006                GnomeVFSFileSize bytes_read;
1007                GnomeVFSFileSize bytes_to_write;
1008                GnomeVFSFileSize bytes_written;
1009                gboolean retry;
1010
1011                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
1012                progress->progress_info->vfs_status = GNOME_VFS_OK;
1013
1014                progress->progress_info->phase = GNOME_VFS_XFER_PHASE_READSOURCE;
1015
1016                do {
1017                        retry = FALSE;
1018
1019                        result = gnome_vfs_read (source_handle, buffer,
1020                                                 block_size, &bytes_read);
1021                        if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF)
1022                                retry = handle_error (&result, progress,
1023                                                      error_mode, skip);
1024                } while (retry);
1025
1026                if (result != GNOME_VFS_OK || bytes_read == 0 || *skip) {
1027                        break;
1028                }
1029
1030                total_bytes_read += total_bytes_read;
1031                bytes_to_write = bytes_read;
1032
1033                progress->progress_info->phase = GNOME_VFS_XFER_PHASE_WRITETARGET;
1034
1035                write_buffer = buffer;
1036                do {
1037                        retry = FALSE;
1038
1039                        result = gnome_vfs_write (target_handle, write_buffer,
1040                                                  bytes_to_write,
1041                                                  &bytes_written);
1042
1043                        if (result != GNOME_VFS_OK) {
1044                                retry = handle_error (&result, progress, error_mode, skip);
1045                        }
1046
1047                        bytes_to_write -= bytes_written;
1048                        write_buffer += bytes_written;
1049                } while ((result == GNOME_VFS_OK && bytes_to_write > 0) || retry);
1050
1051                progress->progress_info->phase = GNOME_VFS_XFER_PHASE_COPYING;
1052
1053                progress->progress_info->bytes_copied += bytes_read;
1054                progress->progress_info->total_bytes_copied += bytes_read;
1055
1056                if (call_progress_often (progress, GNOME_VFS_XFER_PHASE_COPYING) == 0) {
1057                        g_free (buffer);
1058                        return GNOME_VFS_ERROR_INTERRUPTED;
1059                }
1060
1061                if (*skip) {
1062                        break;
1063                }
1064
1065        } while (result == GNOME_VFS_OK);
1066
1067        if (result == GNOME_VFS_ERROR_EOF) {
1068                result = GNOME_VFS_OK;
1069        }
1070
1071        if (result == GNOME_VFS_OK) {
1072                /* tiny (0 - sized) files still present non-zero overhead during a copy, make sure
1073                 * we count at least a default amount for each file
1074                 */
1075                progress->progress_info->total_bytes_copied += DEFAULT_SIZE_OVERHEAD;
1076
1077                call_progress_often (progress, GNOME_VFS_XFER_PHASE_COPYING);
1078        }
1079
1080        g_free (buffer);
1081
1082        return result;
1083}
1084
1085static GnomeVFSResult
1086xfer_open_source (GnomeVFSHandle **source_handle,
1087                  GnomeVFSURI *source_uri,
1088                  GnomeVFSProgressCallbackState *progress,
1089                  GnomeVFSXferOptions xfer_options,
1090                  GnomeVFSXferErrorMode *error_mode,
1091                  gboolean *skip)
1092{
1093        GnomeVFSResult result;
1094        gboolean retry;
1095
1096        *skip = FALSE;
1097        do {
1098                retry = FALSE;
1099
1100                result = gnome_vfs_open_uri (source_handle, source_uri,
1101                                             GNOME_VFS_OPEN_READ);
1102
1103                if (result != GNOME_VFS_OK) {
1104                        retry = handle_error (&result, progress, error_mode, skip);
1105                }
1106        } while (retry);
1107
1108        return result;
1109}
1110
1111static GnomeVFSResult
1112xfer_create_target (GnomeVFSHandle **target_handle,
1113                    GnomeVFSURI *target_uri,
1114                    GnomeVFSProgressCallbackState *progress,
1115                    GnomeVFSXferOptions xfer_options,
1116                    GnomeVFSXferErrorMode *error_mode,
1117                    GnomeVFSXferOverwriteMode *overwrite_mode,
1118                    gboolean *skip)
1119{
1120        GnomeVFSResult result;
1121        gboolean retry;
1122        gboolean exclusive;
1123
1124        exclusive = (*overwrite_mode != GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE);
1125
1126        *skip = FALSE;
1127        do {
1128                retry = FALSE;
1129
1130                result = gnome_vfs_create_uri (target_handle, target_uri,
1131                                               GNOME_VFS_OPEN_WRITE,
1132                                               exclusive, 0666);
1133
1134                if (result == GNOME_VFS_ERROR_FILE_EXISTS) {
1135                        gboolean replace;
1136
1137                        retry = handle_overwrite (&result,
1138                                                  progress,
1139                                                  error_mode,
1140                                                  overwrite_mode,
1141                                                  &replace,
1142                                                  skip);
1143
1144                        if (replace) {
1145                                exclusive = FALSE;
1146                        }
1147
1148                } else if (result != GNOME_VFS_OK) {
1149                        retry = handle_error (&result,  progress, error_mode, skip);
1150                }
1151        } while (retry);
1152
1153        return result;
1154}
1155
1156static GnomeVFSResult
1157copy_symlink (GnomeVFSURI *source_uri,
1158              GnomeVFSURI *target_uri,
1159              const char *link_name,
1160              GnomeVFSProgressCallbackState *progress)
1161{
1162        GnomeVFSResult result;
1163       
1164        result = gnome_vfs_create_symbolic_link (target_uri, link_name);
1165       
1166        if (result == GNOME_VFS_OK && call_progress_with_uris_often (progress, source_uri,
1167                target_uri, GNOME_VFS_XFER_PHASE_OPENTARGET) == 0) {
1168                result = GNOME_VFS_ERROR_INTERRUPTED;
1169        }
1170        return result;
1171}
1172
1173static GnomeVFSResult
1174copy_file (GnomeVFSFileInfo *info, 
1175           GnomeVFSURI *source_uri,
1176           GnomeVFSURI *target_uri,
1177           GnomeVFSXferOptions xfer_options,
1178           GnomeVFSXferErrorMode *error_mode,
1179           GnomeVFSXferOverwriteMode *overwrite_mode,
1180           GnomeVFSProgressCallbackState *progress,
1181           gboolean *skip)
1182{
1183        GnomeVFSResult close_result, result;
1184        GnomeVFSHandle *source_handle, *target_handle;
1185        GnomeVFSSetFileInfoMask set_mask;
1186
1187        progress->progress_info->phase = GNOME_VFS_XFER_PHASE_OPENSOURCE;
1188        progress->progress_info->bytes_copied = 0;
1189        result = xfer_open_source (&source_handle, source_uri,
1190                                   progress, xfer_options,
1191                                   error_mode, skip);
1192        if (*skip) {
1193                return GNOME_VFS_OK;
1194        }
1195       
1196        if (result != GNOME_VFS_OK) {
1197                return result;
1198        }
1199
1200        progress->progress_info->phase = GNOME_VFS_XFER_PHASE_OPENTARGET;
1201        result = xfer_create_target (&target_handle, target_uri,
1202                                     progress, xfer_options,
1203                                     error_mode, overwrite_mode,
1204                                     skip);
1205
1206
1207        if (*skip) {
1208                gnome_vfs_close (source_handle);
1209                return GNOME_VFS_OK;
1210        }
1211        if (result != GNOME_VFS_OK) {
1212                gnome_vfs_close (source_handle);
1213                return result;
1214        }
1215
1216        if (call_progress_with_uris_often (progress, source_uri, target_uri,
1217                GNOME_VFS_XFER_PHASE_OPENTARGET) != GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
1218
1219                result = copy_file_data (target_handle, source_handle,
1220                                        progress, xfer_options, error_mode,
1221                                        /* use an arbitrary default block size of 4096
1222                                         * if one isn't available for this file system
1223                                         */
1224                                        (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_IO_BLOCK_SIZE)
1225                                                ? info->io_block_size : 4096,
1226                                        skip);
1227        }
1228
1229        if (result == GNOME_VFS_OK
1230                && call_progress_often (progress, GNOME_VFS_XFER_PHASE_CLOSETARGET) == 0) {
1231                result = GNOME_VFS_ERROR_INTERRUPTED;
1232        }
1233
1234        close_result = gnome_vfs_close (source_handle);
1235        if (result == GNOME_VFS_OK && close_result != GNOME_VFS_OK) {
1236                handle_error (&close_result, progress, error_mode, skip);
1237                return close_result;
1238        }
1239
1240        close_result = gnome_vfs_close (target_handle);
1241        if (result == GNOME_VFS_OK && close_result != GNOME_VFS_OK) {
1242                handle_error (&close_result, progress, error_mode, skip);
1243                return close_result;
1244        }
1245
1246        if (result == GNOME_VFS_OK) {
1247                /* FIXME the modules should ignore setting of permissions if
1248                 * "valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS" is clear
1249                 * for now, make sure permissions aren't set to 000
1250                 */
1251
1252                if ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS) == 0) {
1253                        set_mask = GNOME_VFS_SET_FILE_INFO_TIME;
1254                } else {
1255                        set_mask = GNOME_VFS_SET_FILE_INFO_PERMISSIONS
1256                                        | GNOME_VFS_SET_FILE_INFO_OWNER
1257                                        | GNOME_VFS_SET_FILE_INFO_TIME;
1258                }
1259
1260                /* ignore errors while setting file info attributes at this point */
1261                gnome_vfs_set_file_info_uri (target_uri, info, set_mask);
1262        }
1263
1264        if (*skip) {
1265                return GNOME_VFS_OK;
1266        }
1267
1268        return result;
1269}
1270
1271static GnomeVFSResult
1272copy_directory (GnomeVFSFileInfo *source_file_info,
1273                GnomeVFSURI *source_dir_uri,
1274                GnomeVFSURI *target_dir_uri,
1275                GnomeVFSXferOptions xfer_options,
1276                GnomeVFSXferErrorMode *error_mode,
1277                GnomeVFSXferOverwriteMode *overwrite_mode,
1278                GnomeVFSProgressCallbackState *progress,
1279                gboolean *skip)
1280{
1281        GnomeVFSResult result;
1282        GnomeVFSDirectoryHandle *source_directory_handle;
1283        GnomeVFSDirectoryHandle *dest_directory_handle;
1284        GnomeVFSSetFileInfoMask set_mask;
1285
1286        source_directory_handle = NULL;
1287        dest_directory_handle = NULL;
1288       
1289        result = gnome_vfs_directory_open_from_uri (&source_directory_handle, source_dir_uri,
1290                                                    GNOME_VFS_FILE_INFO_DEFAULT, NULL);
1291
1292        if (result != GNOME_VFS_OK) {
1293                return result;
1294        }
1295
1296        progress->progress_info->bytes_copied = 0;
1297        if (call_progress_with_uris_often (progress, source_dir_uri, target_dir_uri,
1298                               GNOME_VFS_XFER_PHASE_COPYING)
1299                == GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
1300                return GNOME_VFS_ERROR_INTERRUPTED;
1301        }
1302
1303        result = create_directory (target_dir_uri,
1304                                   &dest_directory_handle,
1305                                   xfer_options,
1306                                   error_mode,
1307                                   overwrite_mode,
1308                                   progress,
1309                                   skip);
1310
1311        if (*skip) {
1312                gnome_vfs_directory_close (source_directory_handle);
1313                return GNOME_VFS_OK;
1314        }
1315       
1316        if (result != GNOME_VFS_OK) {
1317                gnome_vfs_directory_close (source_directory_handle);
1318                return result;
1319        }
1320
1321        if (call_progress_with_uris_often (progress, source_dir_uri, target_dir_uri,
1322                                           GNOME_VFS_XFER_PHASE_OPENTARGET) != 0) {
1323
1324                progress->progress_info->total_bytes_copied += DEFAULT_SIZE_OVERHEAD;
1325                progress->progress_info->top_level_item = FALSE;
1326
1327                /* We do not deal with symlink loops here.
1328                 * That's OK because we don't follow symlinks.
1329                 */
1330                do {
1331                        GnomeVFSURI *source_uri;
1332                        GnomeVFSURI *dest_uri;
1333                        GnomeVFSFileInfo *info;
1334
1335                        source_uri = NULL;
1336                        dest_uri = NULL;
1337                        info = gnome_vfs_file_info_new ();
1338
1339                        result = gnome_vfs_directory_read_next (source_directory_handle, info);
1340                        if (result != GNOME_VFS_OK) {
1341                                gnome_vfs_file_info_unref (info);       
1342                                break;
1343                        }
1344                       
1345                        /* Skip "." and "..".  */
1346                        if (strcmp (info->name, ".") != 0 && strcmp (info->name, "..") != 0) {
1347
1348                                progress->progress_info->file_index++;
1349
1350                                source_uri = gnome_vfs_uri_append_file_name (source_dir_uri, info->name);
1351                                dest_uri = gnome_vfs_uri_append_file_name (target_dir_uri, info->name);
1352                               
1353                                if (info->type == GNOME_VFS_FILE_TYPE_REGULAR) {
1354                                        result = copy_file (info, source_uri, dest_uri,
1355                                                            xfer_options, error_mode, overwrite_mode,
1356                                                            progress, skip);
1357                                } else if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
1358                                        result = copy_directory (info, source_uri, dest_uri,
1359                                                                 xfer_options, error_mode, overwrite_mode,
1360                                                                 progress, skip);
1361                                } else if (info->type == GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK) {
1362                                        result = copy_symlink (source_uri, dest_uri, info->symlink_name,
1363                                                               progress);
1364                                }
1365                                /* just ignore all the other special file system objects here */
1366                        }
1367                       
1368                        gnome_vfs_file_info_unref (info);
1369                       
1370                        if (dest_uri != NULL) {
1371                                gnome_vfs_uri_unref (dest_uri);
1372                        }
1373                        if (source_uri != NULL) {
1374                                gnome_vfs_uri_unref (source_uri);
1375                        }
1376
1377                } while (result == GNOME_VFS_OK);
1378        }
1379
1380        if (result == GNOME_VFS_ERROR_EOF) {
1381                /* all is well, we just finished iterating the directory */
1382                result = GNOME_VFS_OK;
1383        }
1384
1385        gnome_vfs_directory_close (dest_directory_handle);
1386        gnome_vfs_directory_close (source_directory_handle);
1387
1388        if (result == GNOME_VFS_OK) {
1389                /* FIXME the modules should ignore setting of permissions if
1390                 * "valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS" is clear
1391                 * for now, make sure permissions aren't set to 000
1392                 */
1393
1394                if ((source_file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS) == 0) {
1395                        set_mask = GNOME_VFS_SET_FILE_INFO_TIME;
1396                } else {
1397                        set_mask = GNOME_VFS_SET_FILE_INFO_PERMISSIONS
1398                                        | GNOME_VFS_SET_FILE_INFO_OWNER
1399                                        | GNOME_VFS_SET_FILE_INFO_TIME;
1400                }
1401
1402                /* ignore errors while setting file info attributes at this point */
1403                gnome_vfs_set_file_info_uri (target_dir_uri, source_file_info, set_mask);
1404        }
1405
1406        return result;
1407}
1408
1409static GnomeVFSResult
1410copy_items (const GList *source_uri_list,
1411            const GList *target_uri_list,
1412            GnomeVFSXferOptions xfer_options,
1413            GnomeVFSXferErrorMode *error_mode,
1414            GnomeVFSXferOverwriteMode overwrite_mode,
1415            GnomeVFSProgressCallbackState *progress,
1416            GList **p_source_uris_copied_list)
1417{
1418        GnomeVFSResult result;
1419        const GList *source_item, *target_item;
1420       
1421        result = GNOME_VFS_OK;
1422
1423        /* go through the list of names */
1424        for (source_item = source_uri_list, target_item = target_uri_list; source_item != NULL;) {
1425
1426                GnomeVFSURI *source_uri;
1427                GnomeVFSURI *target_uri;
1428                GnomeVFSURI *target_dir_uri;
1429
1430                GnomeVFSFileInfo *info;
1431                gboolean skip;
1432                int count;
1433                int progress_result;
1434
1435                progress->progress_info->file_index++;
1436
1437                skip = FALSE;
1438                target_uri = NULL;
1439
1440                source_uri = (GnomeVFSURI *)source_item->data;
1441                target_dir_uri = gnome_vfs_uri_get_parent ((GnomeVFSURI *)target_item->data);
1442               
1443                /* get source URI and file info */
1444                info = gnome_vfs_file_info_new ();
1445                result = gnome_vfs_get_file_info_uri (source_uri, info,
1446                                                      GNOME_VFS_FILE_INFO_DEFAULT);
1447
1448                progress->progress_info->duplicate_name =
1449                        gnome_vfs_uri_extract_short_path_name
1450                        ((GnomeVFSURI *)target_item->data);
1451
1452                if (result == GNOME_VFS_OK) {
1453                        /* optionally keep trying until we hit a unique target name */
1454                        for (count = 1; ; count++) {
1455                                GnomeVFSXferOverwriteMode overwrite_mode_abort;
1456
1457                                target_uri = gnome_vfs_uri_append_string
1458                                        (target_dir_uri,
1459                                         progress->progress_info->duplicate_name);
1460
1461                                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
1462                                progress->progress_info->file_size = info->size;
1463                                progress->progress_info->bytes_copied = 0;
1464                                progress->progress_info->top_level_item = TRUE;
1465
1466                                if (call_progress_with_uris_often (progress, source_uri, target_uri,
1467                                                       GNOME_VFS_XFER_PHASE_COPYING) == 0) {
1468                                        result = GNOME_VFS_ERROR_INTERRUPTED;
1469                                }
1470
1471                                overwrite_mode_abort = GNOME_VFS_XFER_OVERWRITE_MODE_ABORT;
1472                               
1473                               
1474                                if (info->type == GNOME_VFS_FILE_TYPE_REGULAR) {
1475                                        result = copy_file (info, source_uri, target_uri,
1476                                                            xfer_options, error_mode,
1477                                                            &overwrite_mode_abort,
1478                                                            progress, &skip);
1479                                } else if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
1480                                        result = copy_directory (info, source_uri, target_uri,
1481                                                                 xfer_options, error_mode,
1482                                                                 &overwrite_mode_abort,
1483                                                                 progress, &skip);
1484                                } else if (info->type == GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK) {
1485                                        result = copy_symlink (source_uri, target_uri, info->symlink_name,
1486                                                               progress);
1487                                }
1488                                /* just ignore all the other special file system objects here */
1489
1490                                if (result == GNOME_VFS_OK && !skip) {
1491                                        /* Add to list of successfully copied files... */
1492                                        *p_source_uris_copied_list = g_list_prepend (*p_source_uris_copied_list, source_uri);
1493                                        gnome_vfs_uri_ref (source_uri);
1494                                }
1495
1496                                if (result != GNOME_VFS_ERROR_FILE_EXISTS) {
1497                                        /* whatever happened, it wasn't a name conflict */
1498                                        break;
1499                                }
1500
1501                                if (overwrite_mode != GNOME_VFS_XFER_OVERWRITE_MODE_QUERY
1502                                    || (xfer_options & GNOME_VFS_XFER_USE_UNIQUE_NAMES) == 0)
1503                                        break;
1504
1505                                /* pass in the current target name, progress will update it to
1506                                 * a new unique name such as 'foo (copy)' or 'bar (copy 2)'
1507                                 */
1508                                g_free (progress->progress_info->duplicate_name);
1509                                progress->progress_info->duplicate_name =
1510                                        gnome_vfs_uri_extract_short_path_name
1511                                        ((GnomeVFSURI *)target_item->data);
1512                                progress->progress_info->duplicate_count = count;
1513                                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE;
1514                                progress->progress_info->vfs_status = result;
1515                                progress_result = call_progress_uri (progress, source_uri, target_uri,
1516                                                       GNOME_VFS_XFER_PHASE_COPYING);
1517                                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
1518
1519                                if (progress_result == GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
1520                                        break;
1521                                }
1522
1523                                if (skip) {
1524                                        break;
1525                                }
1526                               
1527                                /* try again with new uri */
1528                                gnome_vfs_uri_unref (target_uri);
1529
1530                        }
1531                }
1532
1533                gnome_vfs_file_info_unref (info);
1534                g_free (progress->progress_info->duplicate_name);
1535
1536                if (result != GNOME_VFS_OK) {
1537                        break;
1538                }
1539
1540                gnome_vfs_uri_unref (target_dir_uri);
1541
1542                source_item = source_item->next;
1543                target_item = target_item->next;
1544
1545                g_assert ((source_item != NULL) == (target_item != NULL));
1546        }
1547
1548        return result;
1549}
1550
1551static GnomeVFSResult
1552move_items (const GList *source_uri_list,
1553            const GList *target_uri_list,
1554            GnomeVFSXferOptions xfer_options,
1555            GnomeVFSXferErrorMode *error_mode,
1556            GnomeVFSXferOverwriteMode *overwrite_mode,
1557            GnomeVFSProgressCallbackState *progress)
1558{
1559        GnomeVFSResult result;
1560        const GList *source_item, *target_item;
1561       
1562        result = GNOME_VFS_OK;
1563
1564        /* go through the list of names */
1565        for (source_item = source_uri_list, target_item = target_uri_list; source_item != NULL;) {
1566
1567                GnomeVFSURI *source_uri;
1568                GnomeVFSURI *target_uri;
1569                GnomeVFSURI *target_dir_uri;
1570                gboolean retry;
1571                gboolean skip;
1572                int conflict_count;
1573                int progress_result;
1574
1575                source_uri = (GnomeVFSURI *)source_item->data;
1576                target_dir_uri = gnome_vfs_uri_get_parent ((GnomeVFSURI *)target_item->data);
1577
1578                progress->progress_info->duplicate_name = 
1579                        gnome_vfs_uri_extract_short_path_name
1580                        ((GnomeVFSURI *)target_item->data);
1581
1582                skip = FALSE;
1583                conflict_count = 1;
1584
1585                do {
1586                        retry = FALSE;
1587                        target_uri = gnome_vfs_uri_append_string (target_dir_uri,
1588                                 progress->progress_info->duplicate_name);
1589
1590                        progress->progress_info->file_size = 0;
1591                        progress->progress_info->bytes_copied = 0;
1592                        progress_set_source_target_uris (progress, source_uri, target_uri);
1593                        progress->progress_info->top_level_item = TRUE;
1594
1595                        /* no matter what the replace mode, just overwrite the destination
1596                         * handle_name_conflicts took care of conflicting files
1597                         */
1598                        result = gnome_vfs_move_uri (source_uri, target_uri,
1599                                                     (xfer_options & GNOME_VFS_XFER_USE_UNIQUE_NAMES) != 0
1600                                                     ? GNOME_VFS_XFER_OVERWRITE_MODE_ABORT
1601                                                     : GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE);
1602
1603
1604                        if (result == GNOME_VFS_ERROR_FILE_EXISTS) {
1605                                /* deal with a name conflict -- ask the progress_callback for a better name */
1606                                g_free (progress->progress_info->duplicate_name);
1607                                progress->progress_info->duplicate_name =
1608                                        gnome_vfs_uri_extract_short_path_name
1609                                        ((GnomeVFSURI *)target_item->data);
1610                                progress->progress_info->duplicate_count = conflict_count;
1611                                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE;
1612                                progress->progress_info->vfs_status = result;
1613                                progress_result = call_progress_uri (progress, source_uri, target_uri,
1614                                                       GNOME_VFS_XFER_PHASE_COPYING);
1615                                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
1616
1617                                if (progress_result == GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
1618                                        gnome_vfs_uri_unref (target_uri);
1619                                        break;
1620                                }
1621                                conflict_count++;
1622                                result = GNOME_VFS_OK;
1623                                retry = TRUE;
1624                                continue;
1625                        }
1626
1627                        if (result != GNOME_VFS_OK) {
1628                                retry = handle_error (&result, progress, error_mode, &skip);
1629                        }
1630
1631                        if (result == GNOME_VFS_OK
1632                            && !skip
1633                            && call_progress_with_uris_often (progress, source_uri,
1634                                        target_uri, GNOME_VFS_XFER_PHASE_MOVING) == 0) {
1635                                result = GNOME_VFS_ERROR_INTERRUPTED;
1636                                gnome_vfs_uri_unref (target_uri);
1637                                break;
1638                        }
1639                        gnome_vfs_uri_unref (target_uri);
1640                } while (retry);
1641               
1642                gnome_vfs_uri_unref (target_dir_uri);
1643
1644                if (result != GNOME_VFS_OK && !skip)
1645                        break;
1646
1647                source_item = source_item->next;
1648                target_item = target_item->next;
1649                g_assert ((source_item != NULL) == (target_item != NULL));
1650        }
1651
1652        return result;
1653}
1654
1655static GnomeVFSResult
1656link_items (const GList *source_uri_list,
1657            const GList *target_uri_list,
1658            GnomeVFSXferOptions xfer_options,
1659            GnomeVFSXferErrorMode *error_mode,
1660            GnomeVFSXferOverwriteMode *overwrite_mode,
1661            GnomeVFSProgressCallbackState *progress)
1662{
1663        GnomeVFSResult result;
1664        const GList *source_item, *target_item;
1665        GnomeVFSURI *source_uri;
1666        GnomeVFSURI *target_dir_uri;
1667        GnomeVFSURI *target_uri;
1668        gboolean retry;
1669        gboolean skip;
1670        int conflict_count;
1671        int progress_result;
1672        char *source_reference;
1673
1674        result = GNOME_VFS_OK;
1675
1676        /* go through the list of names, create a link to each item */
1677        for (source_item = source_uri_list, target_item = target_uri_list; source_item != NULL;) {
1678
1679                source_uri = (GnomeVFSURI *)source_item->data;
1680                source_reference = gnome_vfs_uri_to_string (source_uri, GNOME_VFS_URI_HIDE_NONE);
1681
1682                target_dir_uri = gnome_vfs_uri_get_parent ((GnomeVFSURI *)target_item->data);
1683                progress->progress_info->duplicate_name =
1684                        gnome_vfs_uri_extract_short_path_name
1685                        ((GnomeVFSURI *)target_item->data);
1686
1687                skip = FALSE;
1688                conflict_count = 1;
1689
1690                do {
1691                        retry = FALSE;
1692                        target_uri = gnome_vfs_uri_append_string
1693                                (target_dir_uri,
1694                                 progress->progress_info->duplicate_name);
1695
1696                        progress->progress_info->file_size = 0;
1697                        progress->progress_info->bytes_copied = 0;
1698                        progress->progress_info->top_level_item = TRUE;
1699                        progress_set_source_target_uris (progress, source_uri, target_uri);
1700
1701                        /* no matter what the replace mode, just overwrite the destination
1702                         * handle_name_conflicts took care of conflicting files
1703                         */
1704                        result = gnome_vfs_create_symbolic_link (target_uri, source_reference);
1705                        if (result == GNOME_VFS_ERROR_FILE_EXISTS) {
1706                                /* deal with a name conflict -- ask the progress_callback for a better name */
1707                                g_free (progress->progress_info->duplicate_name);
1708                                progress->progress_info->duplicate_name =
1709                                        gnome_vfs_uri_extract_short_path_name
1710                                        ((GnomeVFSURI *)target_item->data);
1711                                progress->progress_info->duplicate_count = conflict_count;
1712                                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE;
1713                                progress->progress_info->vfs_status = result;
1714                                progress_result = call_progress_uri (progress, source_uri, target_uri,
1715                                                       GNOME_VFS_XFER_PHASE_COPYING);
1716                                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
1717
1718                                if (progress_result == GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
1719                                        gnome_vfs_uri_unref (target_uri);
1720                                        break;
1721                                }
1722                                conflict_count++;
1723                                result = GNOME_VFS_OK;
1724                                retry = TRUE;
1725                                continue;
1726                        }
1727                       
1728                        if (result != GNOME_VFS_OK) {
1729                                retry = handle_error (&result, progress, error_mode, &skip);
1730                        }
1731
1732                        if (result == GNOME_VFS_OK
1733                                && call_progress_with_uris_often (progress, source_uri,
1734                                                target_uri, GNOME_VFS_XFER_PHASE_OPENTARGET) == 0) {
1735                                result = GNOME_VFS_ERROR_INTERRUPTED;
1736                                gnome_vfs_uri_unref (target_uri);
1737                                break;
1738                        }
1739                        gnome_vfs_uri_unref (target_uri);
1740                } while (retry);
1741               
1742                gnome_vfs_uri_unref (target_dir_uri);
1743                g_free (source_reference);
1744
1745                if (result != GNOME_VFS_OK && !skip)
1746                        break;
1747
1748                source_item = source_item->next;
1749                target_item = target_item->next;
1750                g_assert ((source_item != NULL) == (target_item != NULL));
1751        }
1752
1753        return result;
1754}
1755
1756static GnomeVFSResult
1757gnome_vfs_xfer_empty_directories (const GList *trash_dir_uris,
1758                                  GnomeVFSXferErrorMode error_mode,
1759                                  GnomeVFSProgressCallbackState *progress)
1760{
1761        GnomeVFSResult result;
1762        const GList *p;
1763        gboolean skip;
1764
1765        result = GNOME_VFS_OK;
1766
1767                /* initialize the results */
1768        progress->progress_info->files_total = 0;
1769        progress->progress_info->bytes_total = 0;
1770        progress->progress_info->phase = GNOME_VFS_XFER_PHASE_COLLECTING;
1771
1772
1773        for (p = trash_dir_uris;  p != NULL; p = p->next) {
1774                result = directory_add_items_and_size (p->data,
1775                        GNOME_VFS_XFER_REMOVESOURCE | GNOME_VFS_XFER_RECURSIVE,
1776                        progress);
1777                if (result == GNOME_VFS_ERROR_TOO_MANY_OPEN_FILES) {
1778                        /* out of file descriptors -- we will deal with that */
1779                        result = GNOME_VFS_OK;
1780                        break;
1781                }
1782                /* set up a fake total size to represent the bulk of the operation
1783                 * -- we'll subtract a proportional value for every deletion
1784                 */
1785                progress->progress_info->bytes_total
1786                        = progress->progress_info->files_total * DEFAULT_SIZE_OVERHEAD;
1787        }
1788        call_progress (progress, GNOME_VFS_XFER_PHASE_READYTOGO);
1789        for (p = trash_dir_uris;  p != NULL; p = p->next) {
1790                result = empty_directory ((GnomeVFSURI *)p->data, progress,
1791                        GNOME_VFS_XFER_REMOVESOURCE | GNOME_VFS_XFER_RECURSIVE,
1792                        &error_mode, &skip);
1793                if (result == GNOME_VFS_ERROR_TOO_MANY_OPEN_FILES) {
1794                        result = non_recursive_empty_directory ((GnomeVFSURI *)p->data,
1795                                progress, GNOME_VFS_XFER_REMOVESOURCE | GNOME_VFS_XFER_RECURSIVE,
1796                                &error_mode, &skip);
1797                }
1798        }
1799
1800        return result;
1801}
1802
1803static GnomeVFSResult
1804gnome_vfs_xfer_delete_items_common (const GList *source_uri_list,
1805                                    GnomeVFSXferErrorMode error_mode,
1806                                    GnomeVFSXferOptions xfer_options,
1807                                    GnomeVFSProgressCallbackState *progress)
1808{
1809        GnomeVFSFileInfo *info;
1810        GnomeVFSResult result;
1811        GnomeVFSURI *uri;
1812        const GList *p;
1813        gboolean skip;
1814
1815        result = GNOME_VFS_OK;
1816       
1817        for (p = source_uri_list;  p != NULL; p = p->next) {
1818       
1819                skip = FALSE;
1820                /* get the URI and VFSFileInfo for each */
1821                uri = p->data;
1822
1823                info = gnome_vfs_file_info_new ();
1824                result = gnome_vfs_get_file_info_uri (uri, info,
1825                                                      GNOME_VFS_FILE_INFO_DEFAULT);
1826
1827                if (result != GNOME_VFS_OK) {
1828                        break;
1829                }
1830
1831                progress_set_source_target_uris (progress, uri, NULL);
1832                if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
1833                        remove_directory (uri, TRUE, progress, xfer_options,
1834                                          &error_mode, &skip);
1835                } else {
1836                        remove_file (uri, progress, xfer_options, &error_mode,
1837                                     &skip);
1838                }
1839        }
1840
1841        return result;
1842}
1843
1844static GnomeVFSResult
1845gnome_vfs_xfer_delete_items (const GList *source_uri_list,
1846                             GnomeVFSXferErrorMode error_mode,
1847                             GnomeVFSXferOptions xfer_options,
1848                             GnomeVFSProgressCallbackState *progress)
1849{
1850
1851        GnomeVFSResult result;
1852               
1853                /* initialize the results */
1854        progress->progress_info->files_total = 0;
1855        progress->progress_info->bytes_total = 0;
1856        call_progress (progress, GNOME_VFS_XFER_PHASE_COLLECTING);
1857
1858
1859        result = count_items_and_size (source_uri_list,
1860                GNOME_VFS_XFER_REMOVESOURCE | GNOME_VFS_XFER_RECURSIVE, progress, FALSE, FALSE);
1861
1862        /* When deleting, ignore the real file sizes, just count the same DEFAULT_SIZE_OVERHEAD
1863         * for each file.
1864         */
1865        progress->progress_info->bytes_total
1866                = progress->progress_info->files_total * DEFAULT_SIZE_OVERHEAD;
1867        if (result != GNOME_VFS_ERROR_INTERRUPTED) {
1868                call_progress (progress, GNOME_VFS_XFER_PHASE_READYTOGO);
1869                result = gnome_vfs_xfer_delete_items_common (source_uri_list,
1870                        error_mode, xfer_options, progress);
1871        }
1872
1873        return result;
1874}
1875
1876static GnomeVFSResult
1877gnome_vfs_new_directory_with_unique_name (const GnomeVFSURI *target_dir_uri,
1878                                          const char *name,
1879                                          GnomeVFSXferErrorMode error_mode,
1880                                          GnomeVFSXferOverwriteMode overwrite_mode,
1881                                          GnomeVFSProgressCallbackState *progress)
1882{
1883        GnomeVFSResult result;
1884        GnomeVFSURI *target_uri;
1885        GnomeVFSDirectoryHandle *dest_directory_handle;
1886        gboolean dummy;
1887        int progress_result;
1888        int conflict_count;
1889       
1890        dest_directory_handle = NULL;
1891        progress->progress_info->top_level_item = TRUE;
1892        progress->progress_info->duplicate_name = g_strdup (name);
1893
1894        for (conflict_count = 1; ; conflict_count++) {
1895
1896                target_uri = gnome_vfs_uri_append_string
1897                        (target_dir_uri,
1898                         progress->progress_info->duplicate_name);
1899                result = create_directory (target_uri,
1900                                           &dest_directory_handle,
1901                                           GNOME_VFS_XFER_USE_UNIQUE_NAMES,
1902                                           &error_mode,
1903                                           &overwrite_mode,
1904                                           progress,
1905                                           &dummy);
1906
1907                if (result != GNOME_VFS_ERROR_FILE_EXISTS
1908                        && result != GNOME_VFS_ERROR_NAME_TOO_LONG) {
1909                        break;
1910                }
1911
1912                /* deal with a name conflict -- ask the progress_callback for a better name */
1913                g_free (progress->progress_info->duplicate_name);
1914                progress->progress_info->duplicate_name = g_strdup (name);
1915                progress->progress_info->duplicate_count = conflict_count;
1916                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE;
1917                progress->progress_info->vfs_status = result;
1918                progress_result = call_progress_uri (progress, NULL, target_uri,
1919                                       GNOME_VFS_XFER_PHASE_COPYING);
1920                progress->progress_info->status = GNOME_VFS_XFER_PROGRESS_STATUS_OK;
1921
1922                if (progress_result == GNOME_VFS_XFER_OVERWRITE_ACTION_ABORT) {
1923                        break;
1924                }
1925
1926                gnome_vfs_uri_unref (target_uri);
1927        }
1928
1929        call_progress_uri (progress, NULL, target_uri,
1930                GNOME_VFS_XFER_PHASE_OPENTARGET);
1931
1932        if (dest_directory_handle != NULL) {
1933                gnome_vfs_directory_close (dest_directory_handle);
1934        }
1935
1936        gnome_vfs_uri_unref (target_uri);
1937        g_free (progress->progress_info->duplicate_name);
1938
1939        return result;
1940}
1941
1942static GnomeVFSResult
1943gnome_vfs_destination_is_writable (const GnomeVFSURI *uri)
1944{
1945        GnomeVFSURI *test_uri;
1946        GnomeVFSResult result;
1947        GnomeVFSHandle *handle;
1948
1949        if (!gnome_vfs_uri_is_local (uri)) {
1950                /* if destination is not local, do not test it for writability, just
1951                 * assume it's writable
1952                 */
1953                return GNOME_VFS_OK;
1954        }
1955
1956        /* test writability by creating and erasing a temporary file */
1957        test_uri = gnome_vfs_uri_append_file_name (uri, ".vfs-write.tmp");
1958        result = gnome_vfs_create_uri (&handle, test_uri, GNOME_VFS_OPEN_WRITE, TRUE, 0600);
1959
1960        if (result == GNOME_VFS_OK) {
1961                gnome_vfs_close (handle);
1962        }
1963       
1964        if (result == GNOME_VFS_OK || result == GNOME_VFS_ERROR_FILE_EXISTS) {
1965                gnome_vfs_unlink_from_uri (test_uri);
1966                result = GNOME_VFS_OK;
1967        }
1968       
1969        gnome_vfs_uri_unref (test_uri);
1970        return result;
1971}
1972
1973static GnomeVFSResult
1974gnome_vfs_xfer_uri_internal (const GList *source_uris,
1975                             const GList *target_uris,
1976                             GnomeVFSXferOptions xfer_options,
1977                             GnomeVFSXferErrorMode error_mode,
1978                             GnomeVFSXferOverwriteMode overwrite_mode,
1979                             GnomeVFSProgressCallbackState *progress)
1980{
1981        GnomeVFSResult result;
1982        GList *source_uri_list, *target_uri_list;
1983        GList *source_uri, *target_uri;
1984        GList *source_uri_list_copied;
1985        GnomeVFSURI *target_dir_uri;
1986        gboolean move, link;
1987        GnomeVFSFileSize free_bytes;
1988        GnomeVFSFileSize bytes_total;
1989        gulong files_total;
1990        gboolean skip;
1991       
1992        result = GNOME_VFS_OK;
1993        move = FALSE;
1994        link = FALSE;
1995        target_dir_uri = NULL;
1996        source_uri_list_copied = NULL;
1997
1998        /* Check and see if target is writable. Return error if it is not. */
1999        call_progress (progress, GNOME_VFS_XFER_CHECKING_DESTINATION);
2000        target_dir_uri = gnome_vfs_uri_get_parent ((GnomeVFSURI *)((GList *)target_uris)->data);
2001        result = gnome_vfs_destination_is_writable (target_dir_uri);
2002        progress_set_source_target_uris (progress, NULL, target_dir_uri);
2003        if (result != GNOME_VFS_OK) {
2004                handle_error (&result, progress, &error_mode, &skip);
2005                gnome_vfs_uri_unref (target_dir_uri);
2006                return result;
2007        }
2008
2009        move = (xfer_options & GNOME_VFS_XFER_REMOVESOURCE) != 0;
2010        link = (xfer_options & GNOME_VFS_XFER_LINK_ITEMS) != 0;
2011
2012        if (move && link) {
2013                return GNOME_VFS_ERROR_BAD_PARAMETERS;
2014        }
2015
2016        /* Create an owning list of source and destination uris.
2017         * We want to be able to remove items that we decide to skip during
2018         * name conflict check.
2019         */
2020        source_uri_list = gnome_vfs_uri_list_copy ((GList *)source_uris);
2021        target_uri_list = gnome_vfs_uri_list_copy ((GList *)target_uris);
2022
2023        if ((xfer_options & GNOME_VFS_XFER_USE_UNIQUE_NAMES) == 0) {
2024                /* see if moved files are on the same file system so that we decide to do
2025                 * a simple move or have to do a copy/remove
2026                 */
2027                for (source_uri = source_uri_list, target_uri = target_uri_list;
2028                        source_uri != NULL;
2029                        source_uri = source_uri->next, target_uri = target_uri->next) {
2030                        gboolean same_fs;
2031
2032                        g_assert (target_dir_uri != NULL);
2033
2034                        result = gnome_vfs_check_same_fs_uris ((GnomeVFSURI *)source_uri->data,
2035                                target_dir_uri, &same_fs);
2036
2037                        if (result != GNOME_VFS_OK) {
2038                                break;
2039                        }
2040
2041                        move &= same_fs;
2042                }
2043        }
2044
2045        if (target_dir_uri != NULL) {
2046                gnome_vfs_uri_unref (target_dir_uri);
2047                target_dir_uri = NULL;
2048        }
2049       
2050        if (result == GNOME_VFS_OK) {
2051                call_progress (progress, GNOME_VFS_XFER_PHASE_COLLECTING);
2052                result = count_items_and_size (source_uri_list, xfer_options, progress, move, link);
2053                if (result != GNOME_VFS_ERROR_INTERRUPTED) {
2054                        /* Ignore anything but interruptions here -- we will deal with the errors
2055                         * during the actual copy
2056                         */
2057                        result = GNOME_VFS_OK;
2058                }
2059        }
2060                       
2061        if (result == GNOME_VFS_OK) {
2062                /* Calculate free space on destination. If an error is returned, we have a non-local
2063                 * file system, so we just forge ahead and hope for the best
2064                 */                     
2065                target_dir_uri = gnome_vfs_uri_get_parent ((GnomeVFSURI *)target_uri_list->data);
2066                result = gnome_vfs_get_volume_free_space (target_dir_uri, &free_bytes);
2067
2068
2069                if (result == GNOME_VFS_OK) {
2070                        if (!move && progress->progress_info->bytes_total > free_bytes) {
2071                                result = GNOME_VFS_ERROR_NO_SPACE;
2072                                progress_set_source_target_uris (progress, NULL, target_dir_uri);
2073                                handle_error (&result, progress, &error_mode, &skip);
2074                        }
2075                } else {
2076                        /* Errors from gnome_vfs_get_volume_free_space should be ignored */
2077                        result = GNOME_VFS_OK;
2078                }
2079               
2080                if (target_dir_uri != NULL) {
2081                        gnome_vfs_uri_unref (target_dir_uri);
2082                        target_dir_uri = NULL;
2083                }
2084
2085                if (result != GNOME_VFS_OK) {
2086                        return result;
2087                }
2088                       
2089                if ((xfer_options & GNOME_VFS_XFER_USE_UNIQUE_NAMES) == 0) {
2090               
2091                        /* Save the preflight numbers, handle_name_conflicts would overwrite them */
2092                        bytes_total = progress->progress_info->bytes_total;
2093                        files_total = progress->progress_info->files_total;
2094
2095                        /* Set the preflight numbers to 0, we don't want to run progress on the
2096                         * removal of conflicting items.
2097                         */
2098                        progress->progress_info->bytes_total = 0;
2099                        progress->progress_info->files_total = 0;
2100                       
2101                        result = handle_name_conflicts (&source_uri_list, &target_uri_list,
2102                                                        xfer_options, &error_mode, &overwrite_mode,
2103                                                        progress);
2104                                                       
2105                        progress->progress_info->bytes_total = bytes_total;
2106                        progress->progress_info->files_total = files_total;
2107
2108                }
2109
2110                /* reset the preflight numbers */
2111                progress->progress_info->file_index = 0;
2112                progress->progress_info->total_bytes_copied = 0;
2113
2114                if (result != GNOME_VFS_OK) {
2115                        /* don't care about any results from handle_error */
2116                        handle_error (&result, progress, &error_mode, &skip);
2117
2118                        /* whatever error it was, we handled it */
2119                        result = GNOME_VFS_OK;
2120                } else {
2121                        call_progress (progress, GNOME_VFS_XFER_PHASE_READYTOGO);
2122
2123                        if (move) {
2124                                g_assert (!link);
2125                                result = move_items (source_uri_list, target_uri_list,
2126                                                     xfer_options, &error_mode, &overwrite_mode,
2127                                                     progress);
2128                        } else if (link) {
2129                                result = link_items (source_uri_list, target_uri_list,
2130                                                     xfer_options, &error_mode, &overwrite_mode,
2131                                                     progress);
2132                        } else {
2133                                result = copy_items (source_uri_list, target_uri_list,
2134                                                     xfer_options, &error_mode, overwrite_mode,
2135                                                     progress, &source_uri_list_copied);
2136                        }
2137                       
2138                        if (result == GNOME_VFS_OK) {
2139                                if (xfer_options & GNOME_VFS_XFER_REMOVESOURCE
2140                                    && ! (move || link)) {
2141                                        call_progress (progress, GNOME_VFS_XFER_PHASE_CLEANUP);
2142                                        result = gnome_vfs_xfer_delete_items_common (
2143                                                        source_uri_list_copied,
2144                                                        error_mode, xfer_options, progress);
2145                                }
2146                        }
2147                }
2148        }
2149
2150        gnome_vfs_uri_list_free (source_uri_list);
2151        gnome_vfs_uri_list_free (target_uri_list);
2152        gnome_vfs_uri_list_free (source_uri_list_copied);
2153
2154        return result;
2155}
2156
2157GnomeVFSResult
2158gnome_vfs_xfer_private (const GList *source_uri_list,
2159                        const GList *target_uri_list,
2160                        GnomeVFSXferOptions xfer_options,
2161                        GnomeVFSXferErrorMode error_mode,
2162                        GnomeVFSXferOverwriteMode overwrite_mode,
2163                        GnomeVFSXferProgressCallback progress_callback,
2164                        gpointer data,
2165                        GnomeVFSXferProgressCallback sync_progress_callback,
2166                        gpointer sync_progress_data)
2167{
2168        GnomeVFSProgressCallbackState progress_state;
2169        GnomeVFSXferProgressInfo progress_info;
2170        GnomeVFSURI *target_dir_uri;
2171        GnomeVFSResult result;
2172       
2173        init_progress (&progress_state, &progress_info);
2174        progress_state.sync_callback = sync_progress_callback;
2175        progress_state.user_data = sync_progress_data;
2176        progress_state.update_callback = progress_callback;
2177        progress_state.async_job_data = data;
2178
2179
2180        if ((xfer_options & GNOME_VFS_XFER_EMPTY_DIRECTORIES) != 0) {
2181                /* Directory empty operation (Empty Trash, etc.). */
2182                g_assert (source_uri_list != NULL);
2183                g_assert (target_uri_list == NULL);
2184               
2185                call_progress (&progress_state, GNOME_VFS_XFER_PHASE_INITIAL);
2186                result = gnome_vfs_xfer_empty_directories (source_uri_list, error_mode, &progress_state);
2187        } else if ((xfer_options & GNOME_VFS_XFER_DELETE_ITEMS) != 0) {
2188                /* Delete items operation */
2189                g_assert (source_uri_list != NULL);
2190                g_assert (target_uri_list == NULL);
2191                               
2192                call_progress (&progress_state, GNOME_VFS_XFER_PHASE_INITIAL);
2193                result = gnome_vfs_xfer_delete_items (source_uri_list,
2194                      error_mode, xfer_options, &progress_state);
2195        } else if ((xfer_options & GNOME_VFS_XFER_NEW_UNIQUE_DIRECTORY) != 0) {
2196                /* New directory create operation */
2197                g_assert (source_uri_list == NULL);
2198                g_assert (g_list_length ((GList *) target_uri_list) == 1);
2199
2200                target_dir_uri = gnome_vfs_uri_get_parent ((GnomeVFSURI *) target_uri_list->data);
2201                result = GNOME_VFS_ERROR_INVALID_URI;
2202                if (target_dir_uri != NULL) {
2203                        if (gnome_vfs_uri_get_basename ((GnomeVFSURI *) target_uri_list->data) != NULL) {
2204               
2205                                result = gnome_vfs_new_directory_with_unique_name (target_dir_uri,
2206                                        gnome_vfs_uri_get_basename ((GnomeVFSURI *) target_uri_list->data),
2207                                        error_mode, overwrite_mode, &progress_state);
2208                        }
2209                        gnome_vfs_uri_unref (target_dir_uri);
2210                }
2211        } else {
2212                /* Copy items operation */
2213                g_assert (source_uri_list != NULL);
2214                g_assert (target_uri_list != NULL);
2215                g_assert (g_list_length ((GList *)source_uri_list) == g_list_length ((GList *)target_uri_list));
2216
2217                call_progress (&progress_state, GNOME_VFS_XFER_PHASE_INITIAL);
2218                result = gnome_vfs_xfer_uri_internal (source_uri_list, target_uri_list,
2219                        xfer_options, error_mode, overwrite_mode, &progress_state);
2220        }
2221
2222        call_progress (&progress_state, GNOME_VFS_XFER_PHASE_COMPLETED);
2223        free_progress (&progress_info);
2224
2225        /* FIXME bugzilla.eazel.com 1218:
2226         *
2227         * The async job setup will try to call the callback function with the callback data
2228         * even though they are usually dead at this point because the callback detected
2229         * that we are giving up and cleaned up after itself.
2230         *
2231         * Should fix this in the async job call setup.
2232         *
2233         * For now just pretend everything worked well.
2234         *
2235         */
2236        result = GNOME_VFS_OK;
2237
2238        return result;
2239}
2240
2241GnomeVFSResult
2242gnome_vfs_xfer_uri_list (const GList *source_uri_list,
2243                         const GList *target_uri_list,
2244                         GnomeVFSXferOptions xfer_options,
2245                         GnomeVFSXferErrorMode error_mode,
2246                         GnomeVFSXferOverwriteMode overwrite_mode,
2247                         GnomeVFSXferProgressCallback progress_callback,
2248                         gpointer data)
2249{
2250        GnomeVFSProgressCallbackState progress_state;
2251        GnomeVFSXferProgressInfo progress_info;
2252        GnomeVFSResult result;
2253
2254        g_return_val_if_fail (source_uri_list != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
2255        g_return_val_if_fail (target_uri_list != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
2256        g_return_val_if_fail (progress_callback != NULL || error_mode != GNOME_VFS_XFER_ERROR_MODE_QUERY,
2257                              GNOME_VFS_ERROR_BAD_PARAMETERS); 
2258               
2259        init_progress (&progress_state, &progress_info);
2260        progress_state.sync_callback = progress_callback;
2261        progress_state.user_data = data;
2262
2263        call_progress (&progress_state, GNOME_VFS_XFER_PHASE_INITIAL);
2264
2265        result = gnome_vfs_xfer_uri_internal (source_uri_list, target_uri_list,
2266                xfer_options, error_mode, overwrite_mode, &progress_state);
2267
2268        call_progress (&progress_state, GNOME_VFS_XFER_PHASE_COMPLETED);
2269        free_progress (&progress_info);
2270
2271        return result;
2272}
2273
2274GnomeVFSResult 
2275gnome_vfs_xfer_uri (const GnomeVFSURI *source_uri,
2276                    const GnomeVFSURI *target_uri,
2277                    GnomeVFSXferOptions xfer_options,
2278                    GnomeVFSXferErrorMode error_mode,
2279                    GnomeVFSXferOverwriteMode overwrite_mode,
2280                    GnomeVFSXferProgressCallback progress_callback,
2281                    gpointer data)
2282{
2283        GList *source_uri_list, *target_uri_list;
2284        GnomeVFSResult result;
2285
2286        g_return_val_if_fail (source_uri != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
2287        g_return_val_if_fail (target_uri != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);     
2288        g_return_val_if_fail (progress_callback != NULL || error_mode != GNOME_VFS_XFER_ERROR_MODE_QUERY,
2289                              GNOME_VFS_ERROR_BAD_PARAMETERS); 
2290
2291        source_uri_list = g_list_append (NULL, (void *)source_uri);
2292        target_uri_list = g_list_append (NULL, (void *)target_uri);
2293
2294        result = gnome_vfs_xfer_uri_list (source_uri_list, target_uri_list,
2295                xfer_options, error_mode, overwrite_mode, progress_callback, data);
2296
2297        g_list_free (source_uri_list);
2298        g_list_free (target_uri_list);
2299
2300        return result;
2301}
2302
2303GnomeVFSResult
2304gnome_vfs_xfer_delete_list (const GList *uri_list,
2305                            GnomeVFSXferErrorMode error_mode,
2306                            GnomeVFSXferOptions xfer_options,
2307                            GnomeVFSXferProgressCallback
2308                                   progress_callback,
2309                            gpointer data)
2310{
2311        GnomeVFSProgressCallbackState progress_state;
2312        GnomeVFSXferProgressInfo progress_info;
2313        GnomeVFSResult result;
2314
2315        g_return_val_if_fail (uri_list != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
2316        g_return_val_if_fail (progress_callback != NULL || error_mode != GNOME_VFS_XFER_ERROR_MODE_QUERY,
2317                              GNOME_VFS_ERROR_BAD_PARAMETERS);
2318
2319        init_progress (&progress_state, &progress_info);
2320        progress_state.sync_callback = progress_callback;
2321        progress_state.user_data = data;
2322        call_progress (&progress_state, GNOME_VFS_XFER_PHASE_INITIAL);
2323
2324        result = gnome_vfs_xfer_delete_items (uri_list, error_mode, xfer_options,
2325                &progress_state);
2326       
2327        call_progress (&progress_state, GNOME_VFS_XFER_PHASE_COMPLETED);
2328        free_progress (&progress_info);
2329
2330        return result;
2331}
2332
Note: See TracBrowser for help on using the repository browser.