Remove min() and max() macros. Use MIN() and MAX() macros from GLib.
[midnight-commander.git] / src / filemanager / filegui.c
blobda3e13ff837e05093fa9a76c78a502aaa1efb0a6
1 /*
2 File management GUI for the text mode edition
4 The copy code was based in GNU's cp, and was written by:
5 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
7 The move code was based in GNU's mv, and was written by:
8 Mike Parker and David MacKenzie.
10 Janne Kukonlehto added much error recovery to them for being used
11 in an interactive program.
13 Copyright (C) 1994-2016
14 Free Software Foundation, Inc.
16 Written by:
17 Janne Kukonlehto, 1994, 1995
18 Fred Leeflang, 1994, 1995
19 Miguel de Icaza, 1994, 1995, 1996
20 Jakub Jelinek, 1995, 1996
21 Norbert Warmuth, 1997
22 Pavel Machek, 1998
23 Slava Zanko, 2009, 2010, 2011, 2012, 2013
24 Andrew Borodin <aborodin@vmail.ru>, 2009, 2010, 2011, 2012, 2013
26 This file is part of the Midnight Commander.
28 The Midnight Commander is free software: you can redistribute it
29 and/or modify it under the terms of the GNU General Public License as
30 published by the Free Software Foundation, either version 3 of the License,
31 or (at your option) any later version.
33 The Midnight Commander is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
38 You should have received a copy of the GNU General Public License
39 along with this program. If not, see <http://www.gnu.org/licenses/>.
43 * Please note that all dialogs used here must be safe for background
44 * operations.
47 /** \file filegui.c
48 * \brief Source: file management GUI for the text mode edition
51 /* {{{ Include files */
53 #include <config.h>
55 #if ((defined STAT_STATVFS || defined STAT_STATVFS64) \
56 && (defined HAVE_STRUCT_STATVFS_F_BASETYPE || defined HAVE_STRUCT_STATVFS_F_FSTYPENAME \
57 || (! defined HAVE_STRUCT_STATFS_F_FSTYPENAME)))
58 #define USE_STATVFS 1
59 #else
60 #define USE_STATVFS 0
61 #endif
63 #include <errno.h>
64 #include <ctype.h>
65 #include <stdio.h>
66 #include <string.h>
67 #include <sys/types.h>
68 #include <sys/stat.h>
70 #if USE_STATVFS
71 #include <sys/statvfs.h>
72 #elif defined HAVE_SYS_VFS_H
73 #include <sys/vfs.h>
74 #elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H
75 /* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
76 It does have statvfs.h, but shouldn't use it, since it doesn't
77 HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
78 /* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
79 #include <sys/param.h>
80 #include <sys/mount.h>
81 #if defined HAVE_NFS_NFS_CLNT_H && defined HAVE_NFS_VFS_H
82 /* Ultrix 4.4 needs these for the declaration of struct statfs. */
83 #include <netinet/in.h>
84 #include <nfs/nfs_clnt.h>
85 #include <nfs/vfs.h>
86 #endif
87 #elif defined HAVE_OS_H /* BeOS */
88 #include <fs_info.h>
89 #endif
91 #if USE_STATVFS
92 #if ! defined STAT_STATVFS && defined STAT_STATVFS64
93 #define STRUCT_STATVFS struct statvfs64
94 #define STATFS statvfs64
95 #else
96 #define STRUCT_STATVFS struct statvfs
97 #define STATFS statvfs
99 #if __linux__ && (__GLIBC__ || __UCLIBC__)
100 #include <sys/utsname.h>
101 #include <sys/statfs.h>
102 #define STAT_STATFS2_BSIZE 1
103 #endif
104 #endif
106 #else
107 #define STATFS statfs
108 #define STRUCT_STATVFS struct statfs
109 #ifdef HAVE_OS_H /* BeOS */
110 /* BeOS has a statvfs function, but it does not return sensible values
111 for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
112 f_fstypename. Use 'struct fs_info' instead. */
113 static int
114 statfs (char const *filename, struct fs_info *buf)
116 dev_t device = dev_for_path (filename);
118 if (device < 0)
120 errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
121 : device == B_BAD_VALUE ? EINVAL
122 : device == B_NAME_TOO_LONG ? ENAMETOOLONG
123 : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0);
124 return -1;
126 /* If successful, buf->dev will be == device. */
127 return fs_stat_dev (device, buf);
130 #define STRUCT_STATVFS struct fs_info
131 #else
132 #define STRUCT_STATVFS struct statfs
133 #endif
134 #endif
136 #ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
137 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
138 #else
139 #if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
140 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
141 #elif defined HAVE_OS_H /* BeOS */
142 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
143 #endif
144 #endif
146 #include <unistd.h>
148 #include "lib/global.h"
150 #include "lib/tty/key.h" /* tty_get_event */
151 #include "lib/mcconfig.h"
152 #include "lib/search.h"
153 #include "lib/vfs/vfs.h"
154 #include "lib/strescape.h"
155 #include "lib/strutil.h"
156 #include "lib/timefmt.h" /* file_date() */
157 #include "lib/util.h"
158 #include "lib/widget.h"
160 #include "src/setup.h" /* verbose */
162 #include "midnight.h"
163 #include "fileopctx.h" /* FILE_CONT */
165 #include "filegui.h"
167 /* }}} */
169 /*** global variables ****************************************************************************/
171 int classic_progressbar = 1;
173 /*** file scope macro definitions ****************************************************************/
175 /* Hack: the vfs code should not rely on this */
176 #define WITH_FULL_PATHS 1
178 #define truncFileString(dlg, s) str_trunc (s, WIDGET (dlg)->cols - 10)
179 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->cols - 10)
181 /*** file scope type declarations ****************************************************************/
183 /* *INDENT-OFF* */
184 typedef enum {
185 MSDOS_SUPER_MAGIC = 0x4d44,
186 NTFS_SB_MAGIC = 0x5346544e,
187 FUSE_MAGIC = 0x65735546,
188 PROC_SUPER_MAGIC = 0x9fa0,
189 SMB_SUPER_MAGIC = 0x517B,
190 NCP_SUPER_MAGIC = 0x564c,
191 USBDEVICE_SUPER_MAGIC = 0x9fa2
192 } filegui_nonattrs_fs_t;
193 /* *INDENT-ON* */
195 /* Used for button result values */
196 typedef enum
198 REPLACE_YES = B_USER,
199 REPLACE_NO,
200 REPLACE_APPEND,
201 REPLACE_ALWAYS,
202 REPLACE_UPDATE,
203 REPLACE_NEVER,
204 REPLACE_ABORT,
205 REPLACE_SIZE,
206 REPLACE_REGET
207 } replace_action_t;
209 /* This structure describes the UI and internal data required by a file
210 * operation context.
212 typedef struct
214 /* ETA and bps */
215 gboolean showing_eta;
216 gboolean showing_bps;
218 /* Dialog and widgets for the operation progress window */
219 WDialog *op_dlg;
220 /* Source file: label and name */
221 WLabel *src_file_label;
222 WLabel *src_file;
223 /* Target file: label and name */
224 WLabel *tgt_file_label;
225 WLabel *tgt_file;
227 WGauge *progress_file_gauge;
228 WLabel *progress_file_label;
230 WGauge *progress_total_gauge;
232 WLabel *total_files_processed_label;
233 WLabel *time_label;
234 WHLine *total_bytes_label;
236 /* Query replace dialog */
237 WDialog *replace_dlg;
238 const char *replace_filename;
239 replace_action_t replace_result;
241 struct stat *s_stat, *d_stat;
242 } file_op_context_ui_t;
244 /*** file scope variables ************************************************************************/
246 static struct
248 Widget *w;
249 FileProgressStatus action;
250 const char *text;
251 button_flags_t flags;
252 int len;
253 } progress_buttons[] =
255 /* *INDENT-OFF* */
256 { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
257 { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
258 { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
259 { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
260 /* *INDENT-ON* */
263 /* --------------------------------------------------------------------------------------------- */
264 /*** file scope functions ************************************************************************/
265 /* --------------------------------------------------------------------------------------------- */
267 /* Return true if statvfs works. This is false for statvfs on systems
268 with GNU libc on Linux kernels before 2.6.36, which stats all
269 preceding entries in /proc/mounts; that makes df hang if even one
270 of the corresponding file systems is hard-mounted but not available. */
272 #if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
273 static int
274 statvfs_works (void)
276 #if ! (__linux__ && (__GLIBC__ || __UCLIBC__))
277 return 1;
278 #else
279 static int statvfs_works_cache = -1;
280 struct utsname name;
282 if (statvfs_works_cache < 0)
283 statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
284 return statvfs_works_cache;
285 #endif
287 #endif
289 /* --------------------------------------------------------------------------------------------- */
290 static gboolean
291 filegui__check_attrs_on_fs (const char *fs_path)
293 STRUCT_STATVFS stfs;
295 if (!setup_copymove_persistent_attr)
296 return FALSE;
298 #if USE_STATVFS && defined(STAT_STATVFS)
299 if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
300 return TRUE;
301 #else
302 if (STATFS (fs_path, &stfs) != 0)
303 return TRUE;
304 #endif
306 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
307 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
308 switch ((filegui_nonattrs_fs_t) stfs.f_type)
310 case MSDOS_SUPER_MAGIC:
311 case NTFS_SB_MAGIC:
312 case PROC_SUPER_MAGIC:
313 case SMB_SUPER_MAGIC:
314 case NCP_SUPER_MAGIC:
315 case USBDEVICE_SUPER_MAGIC:
316 return FALSE;
317 default:
318 break;
320 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
321 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
322 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
323 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
324 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
325 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
326 || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
327 return FALSE;
328 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
329 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
330 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
331 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
332 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
333 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
334 return FALSE;
335 #endif
337 return TRUE;
340 /* --------------------------------------------------------------------------------------------- */
342 static void
343 file_frmt_time (char *buffer, double eta_secs)
345 int eta_hours, eta_mins, eta_s;
346 eta_hours = (int) (eta_secs / (60 * 60));
347 eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
348 eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
349 g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s);
352 /* --------------------------------------------------------------------------------------------- */
354 static void
355 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
357 char _fmt_buff[BUF_TINY];
358 if (eta_secs <= 0.5 && !always_show)
360 *buffer = '\0';
361 return;
363 if (eta_secs <= 0.5)
364 eta_secs = 1;
365 file_frmt_time (_fmt_buff, eta_secs);
366 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
369 /* --------------------------------------------------------------------------------------------- */
371 static void
372 file_bps_prepare_for_show (char *buffer, long bps)
374 if (bps > 1024 * 1024)
376 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
378 else if (bps > 1024)
380 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
382 else if (bps > 1)
384 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
386 else
387 *buffer = '\0';
390 /* --------------------------------------------------------------------------------------------- */
392 * FIXME: probably it is better to replace this with quick dialog machinery,
393 * but actually I'm not familiar with it and have not much time :(
394 * alex
396 static replace_action_t
397 overwrite_query_dialog (file_op_context_t * ctx, enum OperationMode mode)
399 #define ADD_RD_BUTTON(i, ypos) \
400 add_widget_autopos (ui->replace_dlg, \
401 button_new (ypos, rd_widgets [i].xpos, rd_widgets [i].value, \
402 NORMAL_BUTTON, rd_widgets [i].text, NULL), \
403 rd_widgets [i].pos_flags, ui->replace_dlg->current->data)
405 #define ADD_RD_LABEL(i, p1, p2, ypos) \
406 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2); \
407 label2 = WIDGET (label_new (ypos, rd_widgets [i].xpos, buffer)); \
408 add_widget_autopos (ui->replace_dlg, label2, rd_widgets [i].pos_flags, \
409 ui->replace_dlg->current != NULL ? ui->replace_dlg->current->data : NULL)
411 /* dialog sizes */
412 const int rd_ylen = 1;
413 int rd_xlen = 60;
414 int y = 2;
415 unsigned long yes_id;
417 struct
419 const char *text;
420 int ypos, xpos;
421 widget_pos_flags_t pos_flags;
422 int value; /* 0 for labels */
423 } rd_widgets[] =
425 /* *INDENT-OFF* */
426 /* 0 */
427 { N_("Target file already exists!"), 3, 4, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
428 /* 1 */
429 { "%s", 4, 4, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
430 /* 2 */
431 { N_("New : %s, size %s"), 6, 4, WPOS_KEEP_DEFAULT, 0 },
432 /* 3 */
433 { N_("Existing: %s, size %s"), 7, 4, WPOS_KEEP_DEFAULT, 0 },
434 /* 4 */
435 { N_("Overwrite this target?"), 9, 4, WPOS_KEEP_DEFAULT, 0 },
436 /* 5 */
437 { N_("&Yes"), 9, 28, WPOS_KEEP_DEFAULT, REPLACE_YES },
438 /* 6 */
439 { N_("&No"), 9, 37, WPOS_KEEP_DEFAULT, REPLACE_NO },
440 /* 7 */
441 { N_("A&ppend"), 9, 45, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
442 /* 8 */
443 { N_("&Reget"), 10, 28, WPOS_KEEP_DEFAULT, REPLACE_REGET },
444 /* 9 */
445 { N_("Overwrite all targets?"), 11, 4, WPOS_KEEP_DEFAULT, 0 },
446 /* 10 */
447 { N_("A&ll"), 11, 28, WPOS_KEEP_DEFAULT, REPLACE_ALWAYS },
448 /* 11 */
449 { N_("&Update"), 11, 36, WPOS_KEEP_DEFAULT, REPLACE_UPDATE },
450 /* 12 */
451 { N_("Non&e"), 11, 47, WPOS_KEEP_DEFAULT, REPLACE_NEVER },
452 /* 13 */
453 { N_("If &size differs"), 12, 28, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
454 /* 14 */
455 { N_("&Abort"), 14, 25, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
456 /* *INDENT-ON* */
459 const size_t num = G_N_ELEMENTS (rd_widgets);
460 int *widgets_len;
462 file_op_context_ui_t *ui = ctx->ui;
464 char buffer[BUF_SMALL];
465 char fsize_buffer[BUF_SMALL];
466 Widget *label1, *label2;
467 const char *title;
468 vfs_path_t *stripped_vpath;
469 const char *stripped_name;
470 char *stripped_name_orig;
471 int result;
473 widgets_len = g_new0 (int, num);
475 if (mode == Foreground)
476 title = _("File exists");
477 else
478 title = _("Background process: File exists");
480 stripped_vpath = vfs_path_from_str (ui->replace_filename);
481 stripped_name = stripped_name_orig =
482 vfs_path_to_str_flags (stripped_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
483 vfs_path_free (stripped_vpath);
486 size_t i;
487 int l1, l2, l, row;
488 int stripped_name_len;
490 for (i = 0; i < num; i++)
492 #ifdef ENABLE_NLS
493 if (i != 1) /* skip filename */
494 rd_widgets[i].text = _(rd_widgets[i].text);
495 #endif /* ENABLE_NLS */
496 widgets_len[i] = str_term_width1 (rd_widgets[i].text);
500 * longest of "Overwrite..." labels
501 * (assume "Target date..." are short enough)
503 l1 = MAX (widgets_len[9], widgets_len[4]);
505 /* longest of button rows */
506 l = l2 = 0;
507 row = 0;
508 for (i = 1; i < num - 1; i++)
509 if (rd_widgets[i].value != 0)
511 if (row != rd_widgets[i].ypos)
513 row = rd_widgets[i].ypos;
514 l2 = MAX (l2, l);
515 l = 0;
517 l += widgets_len[i] + 4;
520 l2 = MAX (l2, l); /* last row */
521 rd_xlen = MAX (rd_xlen, l1 + l2 + 8);
522 /* rd_xlen = MAX (rd_xlen, str_term_width1 (title) + 2); */
523 stripped_name_len = str_term_width1 (stripped_name);
524 rd_xlen = MAX (rd_xlen, MIN (COLS, stripped_name_len + 8));
526 /* Now place widgets */
527 l1 += 5; /* start of first button in the row */
528 l = l1;
529 row = 0;
530 for (i = 2; i < num - 1; i++)
531 if (rd_widgets[i].value != 0)
533 if (row != rd_widgets[i].ypos)
535 row = rd_widgets[i].ypos;
536 l = l1;
538 rd_widgets[i].xpos = l;
539 l += widgets_len[i] + 4;
543 /* FIXME - missing help node */
544 ui->replace_dlg =
545 dlg_create (TRUE, 0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, NULL, "[Replace]", title,
546 DLG_CENTER);
548 /* prompt */
549 ADD_RD_LABEL (0, "", "", y++);
550 /* file name */
551 ADD_RD_LABEL (1, "", "", y++);
552 label1 = label2;
554 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
556 /* source date and size */
557 size_trunc_len (fsize_buffer, sizeof (fsize_buffer), ui->s_stat->st_size, 0,
558 panels_options.kilobyte_si);
559 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime), fsize_buffer, y++);
560 rd_xlen = MAX (rd_xlen, label2->cols + 8);
561 /* destination date and size */
562 size_trunc_len (fsize_buffer, sizeof (fsize_buffer), ui->d_stat->st_size, 0,
563 panels_options.kilobyte_si);
564 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime), fsize_buffer, y++);
565 rd_xlen = MAX (rd_xlen, label2->cols + 8);
567 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
569 ADD_RD_LABEL (4, 0, 0, y); /* Overwrite this target? */
570 yes_id = ADD_RD_BUTTON (5, y); /* Yes */
571 ADD_RD_BUTTON (6, y); /* No */
573 /* "this target..." widgets */
574 if (!S_ISDIR (ui->d_stat->st_mode))
576 ADD_RD_BUTTON (7, y++); /* Append */
578 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
579 && (ui->s_stat->st_size > ui->d_stat->st_size))
580 ADD_RD_BUTTON (8, y++); /* Reget */
583 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
585 ADD_RD_LABEL (9, 0, 0, y); /* Overwrite all targets? */
586 ADD_RD_BUTTON (10, y); /* All" */
587 ADD_RD_BUTTON (11, y); /* Update */
588 ADD_RD_BUTTON (12, y++); /* None */
589 ADD_RD_BUTTON (13, y++); /* If size differs */
591 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
593 ADD_RD_BUTTON (14, y); /* Abort */
595 label_set_text (LABEL (label1), str_trunc (stripped_name, rd_xlen - 8));
596 dlg_set_size (ui->replace_dlg, y + 3, rd_xlen);
597 dlg_select_by_id (ui->replace_dlg, yes_id);
598 result = dlg_run (ui->replace_dlg);
599 dlg_destroy (ui->replace_dlg);
601 g_free (widgets_len);
602 g_free (stripped_name_orig);
604 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
605 #undef ADD_RD_LABEL
606 #undef ADD_RD_BUTTON
609 /* --------------------------------------------------------------------------------------------- */
611 static gboolean
612 is_wildcarded (const char *p)
614 gboolean escaped = FALSE;
615 for (; *p; p++)
617 if (*p == '\\')
619 if (p[1] >= '1' && p[1] <= '9' && !escaped)
620 return TRUE;
621 escaped = !escaped;
623 else
625 if ((*p == '*' || *p == '?') && !escaped)
626 return TRUE;
627 escaped = FALSE;
630 return FALSE;
633 /* --------------------------------------------------------------------------------------------- */
635 static void
636 place_progress_buttons (WDialog * h, gboolean suspended)
638 const size_t i = suspended ? 2 : 1;
639 Widget *w = WIDGET (h);
640 int buttons_width;
642 buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
643 buttons_width += progress_buttons[i].len;
644 button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
646 progress_buttons[0].w->x = w->x + (w->cols - buttons_width) / 2;
647 progress_buttons[i].w->x = progress_buttons[0].w->x + progress_buttons[0].len + 1;
648 progress_buttons[3].w->x = progress_buttons[i].w->x + progress_buttons[i].len + 1;
651 /* --------------------------------------------------------------------------------------------- */
653 static int
654 progress_button_callback (WButton * button, int action)
656 (void) button;
657 (void) action;
659 /* don't close dialog in any case */
660 return 0;
663 /* --------------------------------------------------------------------------------------------- */
664 /*** public functions ****************************************************************************/
665 /* --------------------------------------------------------------------------------------------- */
667 FileProgressStatus
668 check_progress_buttons (file_op_context_t * ctx)
670 int c;
671 Gpm_Event event;
672 file_op_context_ui_t *ui;
674 if (ctx == NULL || ctx->ui == NULL)
675 return FILE_CONT;
677 ui = ctx->ui;
679 get_event:
680 event.x = -1; /* Don't show the GPM cursor */
681 c = tty_get_event (&event, FALSE, ctx->suspended);
682 if (c == EV_NONE)
683 return FILE_CONT;
685 /* Reinitialize to avoid old values after events other than selecting a button */
686 ui->op_dlg->ret_value = FILE_CONT;
688 dlg_process_event (ui->op_dlg, c, &event);
689 switch (ui->op_dlg->ret_value)
691 case FILE_SKIP:
692 if (ctx->suspended)
694 /* redraw dialog in case of Skip after Suspend */
695 place_progress_buttons (ui->op_dlg, FALSE);
696 dlg_redraw (ui->op_dlg);
698 ctx->suspended = FALSE;
699 return FILE_SKIP;
700 case B_CANCEL:
701 case FILE_ABORT:
702 ctx->suspended = FALSE;
703 return FILE_ABORT;
704 case FILE_SUSPEND:
705 ctx->suspended = !ctx->suspended;
706 place_progress_buttons (ui->op_dlg, ctx->suspended);
707 dlg_redraw (ui->op_dlg);
708 /* fallthrough */
709 default:
710 if (ctx->suspended)
711 goto get_event;
712 return FILE_CONT;
716 /* --------------------------------------------------------------------------------------------- */
717 /* {{{ File progress display routines */
719 void
720 file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
721 filegui_dialog_type_t dialog_type)
723 file_op_context_ui_t *ui;
724 int buttons_width;
725 int dlg_width = 58, dlg_height = 17;
726 int y = 2, x = 3;
728 if (ctx == NULL || ctx->ui != NULL)
729 return;
731 #ifdef ENABLE_NLS
732 if (progress_buttons[0].len == -1)
734 size_t i;
736 for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
737 progress_buttons[i].text = _(progress_buttons[i].text);
739 #endif
741 ctx->dialog_type = dialog_type;
742 ctx->recursive_result = RECURSIVE_YES;
743 ctx->ui = g_new0 (file_op_context_ui_t, 1);
745 ui = ctx->ui;
746 ui->replace_result = REPLACE_YES;
748 ui->op_dlg =
749 dlg_create (TRUE, 0, 0, dlg_height, dlg_width, dialog_colors, NULL, NULL, NULL,
750 op_names[ctx->operation], DLG_CENTER);
752 if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
754 ui->showing_eta = with_eta && ctx->progress_totals_computed;
755 ui->showing_bps = with_eta;
757 ui->src_file_label = label_new (y++, x, "");
758 add_widget (ui->op_dlg, ui->src_file_label);
760 ui->src_file = label_new (y++, x, "");
761 add_widget (ui->op_dlg, ui->src_file);
763 ui->tgt_file_label = label_new (y++, x, "");
764 add_widget (ui->op_dlg, ui->tgt_file_label);
766 ui->tgt_file = label_new (y++, x, "");
767 add_widget (ui->op_dlg, ui->tgt_file);
769 ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
770 if (!classic_progressbar && (current_panel == right_panel))
771 ui->progress_file_gauge->from_left_to_right = FALSE;
772 add_widget_autopos (ui->op_dlg, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ,
773 NULL);
775 ui->progress_file_label = label_new (y++, x, "");
776 add_widget (ui->op_dlg, ui->progress_file_label);
778 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
780 ui->total_bytes_label = hline_new (y++, -1, -1);
781 add_widget (ui->op_dlg, ui->total_bytes_label);
783 if (ctx->progress_totals_computed)
785 ui->progress_total_gauge =
786 gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
787 if (!classic_progressbar && (current_panel == right_panel))
788 ui->progress_total_gauge->from_left_to_right = FALSE;
789 add_widget_autopos (ui->op_dlg, ui->progress_total_gauge,
790 WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
793 ui->total_files_processed_label = label_new (y++, x, "");
794 add_widget (ui->op_dlg, ui->total_files_processed_label);
796 ui->time_label = label_new (y++, x, "");
797 add_widget (ui->op_dlg, ui->time_label);
800 else
802 ui->src_file = label_new (y++, x, "");
803 add_widget (ui->op_dlg, ui->src_file);
805 ui->total_files_processed_label = label_new (y++, x, "");
806 add_widget (ui->op_dlg, ui->total_files_processed_label);
809 add_widget (ui->op_dlg, hline_new (y++, -1, -1));
811 progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
812 progress_buttons[0].flags, progress_buttons[0].text,
813 progress_button_callback));
814 if (progress_buttons[0].len == -1)
815 progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
817 progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
818 progress_buttons[1].flags, progress_buttons[1].text,
819 progress_button_callback));
820 if (progress_buttons[1].len == -1)
821 progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
823 if (progress_buttons[2].len == -1)
825 /* create and destroy button to get it length */
826 progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
827 progress_buttons[2].flags,
828 progress_buttons[2].text,
829 progress_button_callback));
830 progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
831 send_message (progress_buttons[2].w, NULL, MSG_DESTROY, 0, NULL);
832 g_free (progress_buttons[2].w);
834 progress_buttons[2].w = progress_buttons[1].w;
836 progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
837 progress_buttons[3].flags, progress_buttons[3].text,
838 NULL));
839 if (progress_buttons[3].len == -1)
840 progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
842 add_widget (ui->op_dlg, progress_buttons[0].w);
843 add_widget (ui->op_dlg, progress_buttons[1].w);
844 add_widget (ui->op_dlg, progress_buttons[3].w);
846 buttons_width = 2 +
847 progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
848 progress_buttons[3].len;
850 /* adjust dialog sizes */
851 dlg_set_size (ui->op_dlg, y + 3, MAX (COLS * 2 / 3, buttons_width + 6));
853 place_progress_buttons (ui->op_dlg, FALSE);
855 dlg_select_widget (progress_buttons[0].w);
857 /* We will manage the dialog without any help, that's why
858 we have to call dlg_init */
859 dlg_init (ui->op_dlg);
862 /* --------------------------------------------------------------------------------------------- */
864 void
865 file_op_context_destroy_ui (file_op_context_t * ctx)
867 if (ctx != NULL && ctx->ui != NULL)
869 file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui;
871 dlg_run_done (ui->op_dlg);
872 dlg_destroy (ui->op_dlg);
873 MC_PTR_FREE (ctx->ui);
877 /* --------------------------------------------------------------------------------------------- */
879 show progressbar for file
882 void
883 file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
884 const char *stalled_msg, gboolean force_update)
886 file_op_context_ui_t *ui;
887 char buffer[BUF_TINY];
889 if (!verbose || ctx == NULL || ctx->ui == NULL)
890 return;
892 ui = ctx->ui;
894 if (total == 0)
896 gauge_show (ui->progress_file_gauge, 0);
897 return;
900 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
901 gauge_show (ui->progress_file_gauge, 1);
903 if (!force_update)
904 return;
906 if (ui->showing_eta && ctx->eta_secs > 0.5)
908 char buffer2[BUF_TINY];
910 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
911 if (ctx->bps == 0)
912 g_snprintf (buffer, sizeof (buffer), "%s %s", buffer2, stalled_msg);
913 else
915 char buffer3[BUF_TINY];
917 file_bps_prepare_for_show (buffer3, ctx->bps);
918 g_snprintf (buffer, sizeof (buffer), "%s (%s) %s", buffer2, buffer3, stalled_msg);
921 else
923 g_snprintf (buffer, sizeof (buffer), "%s", stalled_msg);
926 label_set_text (ui->progress_file_label, buffer);
929 /* --------------------------------------------------------------------------------------------- */
931 void
932 file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total)
934 char buffer[BUF_TINY];
935 file_op_context_ui_t *ui;
937 if (ctx == NULL || ctx->ui == NULL)
938 return;
940 ui = ctx->ui;
941 if (ui->total_files_processed_label == NULL)
942 return;
944 if (ctx->progress_totals_computed)
945 g_snprintf (buffer, sizeof (buffer), _("Files processed: %zu/%zu"), done, total);
946 else
947 g_snprintf (buffer, sizeof (buffer), _("Files processed: %zu"), done);
948 label_set_text (ui->total_files_processed_label, buffer);
951 /* --------------------------------------------------------------------------------------------- */
953 void
954 file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
955 uintmax_t copied_bytes, gboolean show_summary)
957 char buffer[BUF_TINY];
958 char buffer2[BUF_TINY];
959 char buffer3[BUF_TINY];
960 file_op_context_ui_t *ui;
962 if (ctx == NULL || ctx->ui == NULL)
963 return;
965 ui = ctx->ui;
967 if (ui->progress_total_gauge != NULL)
969 if (ctx->progress_bytes == 0)
970 gauge_show (ui->progress_total_gauge, 0);
971 else
973 gauge_set_value (ui->progress_total_gauge, 1024,
974 (int) (1024 * copied_bytes / ctx->progress_bytes));
975 gauge_show (ui->progress_total_gauge, 1);
979 if (!show_summary && tctx->bps == 0)
980 return;
982 if (ui->time_label != NULL)
984 struct timeval tv_current;
985 char buffer4[BUF_TINY];
987 gettimeofday (&tv_current, NULL);
988 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
990 if (ctx->progress_totals_computed)
992 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
993 if (tctx->bps == 0)
994 g_snprintf (buffer, sizeof (buffer), _("Time: %s %s"), buffer2, buffer3);
995 else
998 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
999 g_snprintf (buffer, sizeof (buffer), _("Time: %s %s (%s)"), buffer2, buffer3,
1000 buffer4);
1003 else
1005 if (tctx->bps == 0)
1006 g_snprintf (buffer, sizeof (buffer), _("Time: %s"), buffer2);
1007 else
1009 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
1010 g_snprintf (buffer, sizeof (buffer), _("Time: %s (%s)"), buffer2, buffer4);
1014 label_set_text (ui->time_label, buffer);
1017 if (ui->total_bytes_label != NULL)
1019 size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
1020 if (!ctx->progress_totals_computed)
1021 g_snprintf (buffer, sizeof (buffer), _(" Total: %s "), buffer2);
1022 else
1024 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
1025 g_snprintf (buffer, sizeof (buffer), _(" Total: %s/%s "), buffer2, buffer3);
1028 hline_set_text (ui->total_bytes_label, buffer);
1032 /* }}} */
1034 /* --------------------------------------------------------------------------------------------- */
1036 void
1037 file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * s_vpath)
1039 file_op_context_ui_t *ui;
1041 if (ctx == NULL || ctx->ui == NULL)
1042 return;
1044 ui = ctx->ui;
1046 if (s_vpath != NULL)
1048 char *s;
1050 s = vfs_path_tokens_get (s_vpath, -1, 1);
1051 label_set_text (ui->src_file_label, _("Source"));
1052 label_set_text (ui->src_file, truncFileString (ui->op_dlg, s));
1053 g_free (s);
1055 else
1057 label_set_text (ui->src_file_label, "");
1058 label_set_text (ui->src_file, "");
1062 /* --------------------------------------------------------------------------------------------- */
1064 void
1065 file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * s_vpath)
1067 file_op_context_ui_t *ui;
1069 if (ctx == NULL || ctx->ui == NULL)
1070 return;
1072 ui = ctx->ui;
1074 if (s_vpath != NULL)
1076 label_set_text (ui->tgt_file_label, _("Target"));
1077 label_set_text (ui->tgt_file,
1078 truncFileStringSecure (ui->op_dlg, vfs_path_as_str (s_vpath)));
1080 else
1082 label_set_text (ui->tgt_file_label, "");
1083 label_set_text (ui->tgt_file, "");
1087 /* --------------------------------------------------------------------------------------------- */
1089 void
1090 file_progress_show_deleting (file_op_context_t * ctx, const char *s, size_t * count)
1092 file_op_context_ui_t *ui;
1094 if (ctx == NULL || ctx->ui == NULL)
1095 return;
1097 ui = ctx->ui;
1099 if (ui->src_file_label != NULL)
1100 label_set_text (ui->src_file_label, _("Deleting"));
1102 label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1104 if (count != NULL)
1105 (*count)++;
1108 /* --------------------------------------------------------------------------------------------- */
1110 FileProgressStatus
1111 file_progress_real_query_replace (file_op_context_t * ctx,
1112 enum OperationMode mode, const char *destname,
1113 struct stat *_s_stat, struct stat *_d_stat)
1115 file_op_context_ui_t *ui;
1117 if (ctx == NULL || ctx->ui == NULL)
1118 return FILE_CONT;
1120 ui = ctx->ui;
1122 if (ui->replace_result < REPLACE_ALWAYS)
1124 ui->replace_filename = destname;
1125 ui->s_stat = _s_stat;
1126 ui->d_stat = _d_stat;
1127 ui->replace_result = overwrite_query_dialog (ctx, mode);
1130 switch (ui->replace_result)
1132 case REPLACE_UPDATE:
1133 do_refresh ();
1134 if (_s_stat->st_mtime > _d_stat->st_mtime)
1135 return FILE_CONT;
1136 else
1137 return FILE_SKIP;
1139 case REPLACE_SIZE:
1140 do_refresh ();
1141 if (_s_stat->st_size == _d_stat->st_size)
1142 return FILE_SKIP;
1143 else
1144 return FILE_CONT;
1146 case REPLACE_REGET:
1147 /* Careful: we fall through and set do_append */
1148 ctx->do_reget = _d_stat->st_size;
1150 case REPLACE_APPEND:
1151 ctx->do_append = TRUE;
1153 case REPLACE_YES:
1154 case REPLACE_ALWAYS:
1155 do_refresh ();
1156 return FILE_CONT;
1157 case REPLACE_NO:
1158 case REPLACE_NEVER:
1159 do_refresh ();
1160 return FILE_SKIP;
1161 case REPLACE_ABORT:
1162 default:
1163 return FILE_ABORT;
1167 /* --------------------------------------------------------------------------------------------- */
1169 char *
1170 file_mask_dialog (file_op_context_t * ctx, FileOperation operation,
1171 gboolean only_one,
1172 const char *format, const void *text, const char *def_text, gboolean * do_bg)
1174 size_t fmd_xlen;
1175 vfs_path_t *vpath;
1176 int source_easy_patterns = easy_patterns;
1177 char fmd_buf[BUF_MEDIUM];
1178 char *dest_dir, *tmp;
1179 char *def_text_secure;
1181 if (ctx == NULL)
1182 return NULL;
1184 /* unselect checkbox if target filesystem don't support attributes */
1185 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
1186 ctx->stable_symlinks = FALSE;
1187 *do_bg = FALSE;
1189 /* filter out a possible password from def_text */
1190 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1191 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1192 vfs_path_free (vpath);
1194 if (source_easy_patterns)
1195 def_text_secure = strutils_glob_escape (tmp);
1196 else
1197 def_text_secure = strutils_regex_escape (tmp);
1198 g_free (tmp);
1200 if (only_one)
1202 int format_len, text_len;
1203 int max_len;
1205 format_len = str_term_width1 (format);
1206 text_len = str_term_width1 (text);
1207 max_len = COLS - 2 - 6;
1209 if (format_len + text_len <= max_len)
1211 fmd_xlen = format_len + text_len + 6;
1212 fmd_xlen = MAX (fmd_xlen, 68);
1214 else
1216 text = str_trunc ((const char *) text, max_len - format_len);
1217 fmd_xlen = max_len + 6;
1220 g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1222 else
1224 fmd_xlen = COLS * 2 / 3;
1225 fmd_xlen = MAX (fmd_xlen, 68);
1226 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1230 char *source_mask, *orig_mask;
1231 int val;
1232 struct stat buf;
1234 quick_widget_t quick_widgets[] = {
1235 /* *INDENT-OFF* */
1236 QUICK_LABELED_INPUT (fmd_buf, input_label_above,
1237 easy_patterns ? "*" : "^(.*)$", "input-def", &source_mask,
1238 NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1239 QUICK_START_COLUMNS,
1240 QUICK_SEPARATOR (FALSE),
1241 QUICK_NEXT_COLUMN,
1242 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1243 QUICK_STOP_COLUMNS,
1244 QUICK_LABELED_INPUT (N_("to:"), input_label_above,
1245 def_text_secure, "input2", &dest_dir, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1246 QUICK_SEPARATOR (TRUE),
1247 QUICK_START_COLUMNS,
1248 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1249 QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
1250 QUICK_NEXT_COLUMN,
1251 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1252 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1253 QUICK_STOP_COLUMNS,
1254 QUICK_START_BUTTONS (TRUE, TRUE),
1255 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1256 #ifdef ENABLE_BACKGROUND
1257 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1258 #endif /* ENABLE_BACKGROUND */
1259 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1260 QUICK_END
1261 /* *INDENT-ON* */
1264 quick_dialog_t qdlg = {
1265 -1, -1, fmd_xlen,
1266 op_names[operation], "[Mask Copy/Rename]",
1267 quick_widgets, NULL, NULL
1270 ask_file_mask:
1271 val = quick_dialog_skip (&qdlg, 4);
1273 if (val == B_CANCEL)
1275 g_free (def_text_secure);
1276 return NULL;
1279 if (ctx->follow_links)
1280 ctx->stat_func = mc_stat;
1281 else
1282 ctx->stat_func = mc_lstat;
1284 if (ctx->op_preserve)
1286 ctx->preserve = TRUE;
1287 ctx->umask_kill = 0777777;
1288 ctx->preserve_uidgid = (geteuid () == 0);
1290 else
1292 mode_t i2;
1294 ctx->preserve = ctx->preserve_uidgid = FALSE;
1295 i2 = umask (0);
1296 umask (i2);
1297 ctx->umask_kill = i2 ^ 0777777;
1300 if ((dest_dir == NULL) || (*dest_dir == '\0'))
1302 g_free (def_text_secure);
1303 g_free (source_mask);
1304 return dest_dir;
1307 ctx->search_handle = mc_search_new (source_mask, NULL);
1309 if (ctx->search_handle == NULL)
1311 message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
1312 g_free (dest_dir);
1313 g_free (source_mask);
1314 goto ask_file_mask;
1317 g_free (def_text_secure);
1318 g_free (source_mask);
1320 ctx->search_handle->is_case_sensitive = TRUE;
1321 if (source_easy_patterns)
1322 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1323 else
1324 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1326 tmp = dest_dir;
1327 dest_dir = tilde_expand (tmp);
1328 g_free (tmp);
1329 vpath = vfs_path_from_str (dest_dir);
1331 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1332 if (ctx->dest_mask == NULL)
1333 ctx->dest_mask = dest_dir;
1334 else
1335 ctx->dest_mask++;
1336 orig_mask = ctx->dest_mask;
1337 if (*ctx->dest_mask == '\0'
1338 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1339 && (!only_one
1340 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1341 || (ctx->dive_into_subdirs
1342 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1343 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1344 ctx->dest_mask = g_strdup ("\\0");
1345 else
1347 ctx->dest_mask = g_strdup (ctx->dest_mask);
1348 *orig_mask = '\0';
1350 if (*dest_dir == '\0')
1352 g_free (dest_dir);
1353 dest_dir = g_strdup ("./");
1355 vfs_path_free (vpath);
1356 if (val == B_USER)
1357 *do_bg = TRUE;
1360 return dest_dir;
1363 /* --------------------------------------------------------------------------------------------- */