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.
17 Janne Kukonlehto, 1994, 1995
18 Fred Leeflang, 1994, 1995
19 Miguel de Icaza, 1994, 1995, 1996
20 Jakub Jelinek, 1995, 1996
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
48 * \brief Source: file management GUI for the text mode edition
51 /* {{{ Include files */
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)))
67 #include <sys/types.h>
71 #include <sys/statvfs.h>
72 #elif defined HAVE_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>
87 #elif defined HAVE_OS_H /* BeOS */
92 #if ! defined STAT_STATVFS && defined STAT_STATVFS64
93 #define STRUCT_STATVFS struct statvfs64
94 #define STATFS statvfs64
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
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. */
114 statfs (char const *filename
, struct fs_info
*buf
)
116 dev_t device
= dev_for_path (filename
);
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);
126 /* If successful, buf->dev will be == device. */
127 return fs_stat_dev (device
, buf
);
130 #define STRUCT_STATVFS struct fs_info
132 #define STRUCT_STATVFS struct statfs
136 #ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
137 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
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
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 */
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 ****************************************************************/
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
;
195 /* Used for button result values */
198 REPLACE_YES
= B_USER
,
209 /* This structure describes the UI and internal data required by a file
215 gboolean showing_eta
;
216 gboolean showing_bps
;
218 /* Dialog and widgets for the operation progress window */
220 /* Source file: label and name */
221 WLabel
*src_file_label
;
223 /* Target file: label and name */
224 WLabel
*tgt_file_label
;
227 WGauge
*progress_file_gauge
;
228 WLabel
*progress_file_label
;
230 WGauge
*progress_total_gauge
;
232 WLabel
*total_files_processed_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 ************************************************************************/
249 FileProgressStatus action
;
251 button_flags_t flags
;
253 } progress_buttons
[] =
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 }
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)
276 #if ! (__linux__ && (__GLIBC__ || __UCLIBC__))
279 static int statvfs_works_cache
= -1;
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
;
289 /* --------------------------------------------------------------------------------------------- */
291 filegui__check_attrs_on_fs (const char *fs_path
)
295 if (!setup_copymove_persistent_attr
)
298 #if USE_STATVFS && defined(STAT_STATVFS)
299 if (statvfs_works () && statvfs (fs_path
, &stfs
) != 0)
302 if (STATFS (fs_path
, &stfs
) != 0)
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
:
312 case PROC_SUPER_MAGIC
:
313 case SMB_SUPER_MAGIC
:
314 case NCP_SUPER_MAGIC
:
315 case USBDEVICE_SUPER_MAGIC
:
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
)
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)
340 /* --------------------------------------------------------------------------------------------- */
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 /* --------------------------------------------------------------------------------------------- */
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
)
365 file_frmt_time (_fmt_buff
, eta_secs
);
366 g_snprintf (buffer
, BUF_TINY
, _("ETA %s"), _fmt_buff
);
369 /* --------------------------------------------------------------------------------------------- */
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));
380 g_snprintf (buffer
, BUF_TINY
, _("%.2f KB/s"), bps
/ 1024.0);
384 g_snprintf (buffer
, BUF_TINY
, _("%ld B/s"), bps
);
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 :(
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)
412 const int rd_ylen
= 1;
415 unsigned long yes_id
;
421 widget_pos_flags_t pos_flags
;
422 int value
; /* 0 for labels */
427 { N_("Target file already exists!"), 3, 4, WPOS_KEEP_TOP
| WPOS_CENTER_HORZ
, 0 },
429 { "%s", 4, 4, WPOS_KEEP_TOP
| WPOS_CENTER_HORZ
, 0 },
431 { N_("New : %s, size %s"), 6, 4, WPOS_KEEP_DEFAULT
, 0 },
433 { N_("Existing: %s, size %s"), 7, 4, WPOS_KEEP_DEFAULT
, 0 },
435 { N_("Overwrite this target?"), 9, 4, WPOS_KEEP_DEFAULT
, 0 },
437 { N_("&Yes"), 9, 28, WPOS_KEEP_DEFAULT
, REPLACE_YES
},
439 { N_("&No"), 9, 37, WPOS_KEEP_DEFAULT
, REPLACE_NO
},
441 { N_("A&ppend"), 9, 45, WPOS_KEEP_DEFAULT
, REPLACE_APPEND
},
443 { N_("&Reget"), 10, 28, WPOS_KEEP_DEFAULT
, REPLACE_REGET
},
445 { N_("Overwrite all targets?"), 11, 4, WPOS_KEEP_DEFAULT
, 0 },
447 { N_("A&ll"), 11, 28, WPOS_KEEP_DEFAULT
, REPLACE_ALWAYS
},
449 { N_("&Update"), 11, 36, WPOS_KEEP_DEFAULT
, REPLACE_UPDATE
},
451 { N_("Non&e"), 11, 47, WPOS_KEEP_DEFAULT
, REPLACE_NEVER
},
453 { N_("If &size differs"), 12, 28, WPOS_KEEP_DEFAULT
, REPLACE_SIZE
},
455 { N_("&Abort"), 14, 25, WPOS_KEEP_TOP
| WPOS_CENTER_HORZ
, REPLACE_ABORT
}
459 const size_t num
= G_N_ELEMENTS (rd_widgets
);
462 file_op_context_ui_t
*ui
= ctx
->ui
;
464 char buffer
[BUF_SMALL
];
465 char fsize_buffer
[BUF_SMALL
];
466 Widget
*label1
, *label2
;
468 vfs_path_t
*stripped_vpath
;
469 const char *stripped_name
;
470 char *stripped_name_orig
;
473 widgets_len
= g_new0 (int, num
);
475 if (mode
== Foreground
)
476 title
= _("File exists");
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
);
488 int stripped_name_len
;
490 for (i
= 0; i
< num
; i
++)
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 */
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
;
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 */
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
;
538 rd_widgets
[i
].xpos
= l
;
539 l
+= widgets_len
[i
] + 4;
543 /* FIXME - missing help node */
545 dlg_create (TRUE
, 0, 0, rd_ylen
, rd_xlen
, WPOS_CENTER
, FALSE
, alarm_colors
, NULL
, NULL
,
549 ADD_RD_LABEL (0, "", "", y
++);
551 ADD_RD_LABEL (1, "", "", y
++);
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
;
609 /* --------------------------------------------------------------------------------------------- */
612 is_wildcarded (const char *p
)
614 gboolean escaped
= FALSE
;
619 if (p
[1] >= '1' && p
[1] <= '9' && !escaped
)
625 if ((*p
== '*' || *p
== '?') && !escaped
)
633 /* --------------------------------------------------------------------------------------------- */
636 place_progress_buttons (WDialog
* h
, gboolean suspended
)
638 const size_t i
= suspended
? 2 : 1;
639 Widget
*w
= WIDGET (h
);
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 /* --------------------------------------------------------------------------------------------- */
654 progress_button_callback (WButton
* button
, int action
)
659 /* don't close dialog in any case */
663 /* --------------------------------------------------------------------------------------------- */
664 /*** public functions ****************************************************************************/
665 /* --------------------------------------------------------------------------------------------- */
668 check_progress_buttons (file_op_context_t
* ctx
)
672 file_op_context_ui_t
*ui
;
674 if (ctx
== NULL
|| ctx
->ui
== NULL
)
680 event
.x
= -1; /* Don't show the GPM cursor */
681 c
= tty_get_event (&event
, FALSE
, ctx
->suspended
);
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
)
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
;
702 ctx
->suspended
= FALSE
;
705 ctx
->suspended
= !ctx
->suspended
;
706 place_progress_buttons (ui
->op_dlg
, ctx
->suspended
);
707 dlg_redraw (ui
->op_dlg
);
716 /* --------------------------------------------------------------------------------------------- */
717 /* {{{ File progress display routines */
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
;
725 int dlg_width
= 58, dlg_height
= 17;
728 if (ctx
== NULL
|| ctx
->ui
!= NULL
)
732 if (progress_buttons
[0].len
== -1)
736 for (i
= 0; i
< G_N_ELEMENTS (progress_buttons
); i
++)
737 progress_buttons
[i
].text
= _(progress_buttons
[i
].text
);
741 ctx
->dialog_type
= dialog_type
;
742 ctx
->recursive_result
= RECURSIVE_YES
;
743 ctx
->ui
= g_new0 (file_op_context_ui_t
, 1);
746 ui
->replace_result
= REPLACE_YES
;
749 dlg_create (TRUE
, 0, 0, dlg_height
, dlg_width
, WPOS_CENTER
, FALSE
, dialog_colors
, NULL
,
750 NULL
, NULL
, op_names
[ctx
->operation
]);
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
,
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
);
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
,
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
);
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 /* --------------------------------------------------------------------------------------------- */
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
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
)
896 gauge_show (ui
->progress_file_gauge
, 0);
900 gauge_set_value (ui
->progress_file_gauge
, 1024, (int) (1024 * done
/ total
));
901 gauge_show (ui
->progress_file_gauge
, 1);
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
);
912 g_snprintf (buffer
, sizeof (buffer
), "%s %s", buffer2
, stalled_msg
);
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
);
923 g_snprintf (buffer
, sizeof (buffer
), "%s", stalled_msg
);
926 label_set_text (ui
->progress_file_label
, buffer
);
929 /* --------------------------------------------------------------------------------------------- */
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
)
941 if (ui
->total_files_processed_label
== NULL
)
944 if (ctx
->progress_totals_computed
)
945 g_snprintf (buffer
, sizeof (buffer
), _("Files processed: %zu/%zu"), done
, total
);
947 g_snprintf (buffer
, sizeof (buffer
), _("Files processed: %zu"), done
);
948 label_set_text (ui
->total_files_processed_label
, buffer
);
951 /* --------------------------------------------------------------------------------------------- */
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
)
967 if (ui
->progress_total_gauge
!= NULL
)
969 if (ctx
->progress_bytes
== 0)
970 gauge_show (ui
->progress_total_gauge
, 0);
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)
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
);
994 g_snprintf (buffer
, sizeof (buffer
), _("Time: %s %s"), buffer2
, buffer3
);
998 file_bps_prepare_for_show (buffer4
, (long) tctx
->bps
);
999 g_snprintf (buffer
, sizeof (buffer
), _("Time: %s %s (%s)"), buffer2
, buffer3
,
1006 g_snprintf (buffer
, sizeof (buffer
), _("Time: %s"), buffer2
);
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
);
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
);
1034 /* --------------------------------------------------------------------------------------------- */
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
)
1046 if (s_vpath
!= NULL
)
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
));
1057 label_set_text (ui
->src_file_label
, "");
1058 label_set_text (ui
->src_file
, "");
1062 /* --------------------------------------------------------------------------------------------- */
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
)
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
)));
1082 label_set_text (ui
->tgt_file_label
, "");
1083 label_set_text (ui
->tgt_file
, "");
1087 /* --------------------------------------------------------------------------------------------- */
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
)
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
));
1108 /* --------------------------------------------------------------------------------------------- */
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
)
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
:
1134 if (_s_stat
->st_mtime
> _d_stat
->st_mtime
)
1141 if (_s_stat
->st_size
== _d_stat
->st_size
)
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
;
1154 case REPLACE_ALWAYS
:
1167 /* --------------------------------------------------------------------------------------------- */
1170 file_mask_dialog (file_op_context_t
* ctx
, FileOperation operation
,
1172 const char *format
, const void *text
, const char *def_text
, gboolean
* do_bg
)
1176 int source_easy_patterns
= easy_patterns
;
1177 char fmd_buf
[BUF_MEDIUM
];
1178 char *dest_dir
, *tmp
;
1179 char *def_text_secure
;
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
;
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
);
1197 def_text_secure
= strutils_regex_escape (tmp
);
1202 int format_len
, text_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);
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
);
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
;
1234 quick_widget_t quick_widgets
[] = {
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
),
1242 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns
, NULL
),
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
),
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
),
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
),
1264 quick_dialog_t qdlg
= {
1266 op_names
[operation
], "[Mask Copy/Rename]",
1267 quick_widgets
, NULL
, NULL
1271 val
= quick_dialog_skip (&qdlg
, 4);
1273 if (val
== B_CANCEL
)
1275 g_free (def_text_secure
);
1279 if (ctx
->follow_links
)
1280 ctx
->stat_func
= mc_stat
;
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);
1294 ctx
->preserve
= ctx
->preserve_uidgid
= FALSE
;
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
);
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
);
1313 g_free (source_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
;
1324 ctx
->search_handle
->search_type
= MC_SEARCH_T_REGEX
;
1327 dest_dir
= tilde_expand (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
;
1336 orig_mask
= ctx
->dest_mask
;
1337 if (*ctx
->dest_mask
== '\0'
1338 || (!ctx
->dive_into_subdirs
&& !is_wildcarded (ctx
->dest_mask
)
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");
1347 ctx
->dest_mask
= g_strdup (ctx
->dest_mask
);
1350 if (*dest_dir
== '\0')
1353 dest_dir
= g_strdup ("./");
1355 vfs_path_free (vpath
);
1363 /* --------------------------------------------------------------------------------------------- */