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-2024
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-2023
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 #elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
86 #if ! defined STAT_STATVFS && defined STAT_STATVFS64
87 #define STRUCT_STATVFS struct statvfs64
88 #define STATFS statvfs64
90 #define STRUCT_STATVFS struct statvfs
91 #define STATFS statvfs
93 #if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)
94 #include <sys/utsname.h>
95 #include <sys/statfs.h>
96 #define STAT_STATFS2_BSIZE 1
101 #define STATFS statfs
102 #define STRUCT_STATVFS struct statfs
103 #ifdef HAVE_OS_H /* Haiku, also (obsolete) BeOS */
104 /* BeOS has a statvfs function, but it does not return sensible values
105 for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
106 f_fstypename. Use 'struct fs_info' instead. */
108 statfs (char const *filename
, struct fs_info
*buf
)
112 device
= dev_for_path (filename
);
116 errno
= (device
== B_ENTRY_NOT_FOUND
? ENOENT
117 : device
== B_BAD_VALUE
? EINVAL
118 : device
== B_NAME_TOO_LONG
? ENAMETOOLONG
119 : device
== B_NO_MEMORY
? ENOMEM
: device
== B_FILE_ERROR
? EIO
: 0);
122 /* If successful, buf->dev will be == device. */
123 return fs_stat_dev (device
, buf
);
126 #define STRUCT_STATVFS struct fs_info
128 #define STRUCT_STATVFS struct statfs
132 #ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
133 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
135 #if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
136 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
137 #elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
138 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
144 #include "lib/global.h"
146 #include "lib/tty/key.h" /* tty_get_event */
147 #include "lib/mcconfig.h"
148 #include "lib/search.h"
149 #include "lib/vfs/vfs.h"
150 #include "lib/strutil.h"
151 #include "lib/timefmt.h" /* file_date() */
152 #include "lib/util.h"
153 #include "lib/widget.h"
155 #include "src/setup.h" /* verbose, safe_overwrite */
157 #include "filemanager.h"
158 #include "fileopctx.h" /* FILE_CONT */
164 /*** global variables ****************************************************************************/
166 gboolean classic_progressbar
= TRUE
;
168 /*** file scope macro definitions ****************************************************************/
170 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
172 /*** file scope type declarations ****************************************************************/
176 MSDOS_SUPER_MAGIC
= 0x4d44,
177 NTFS_SB_MAGIC
= 0x5346544e,
178 FUSE_MAGIC
= 0x65735546,
179 PROC_SUPER_MAGIC
= 0x9fa0,
180 SMB_SUPER_MAGIC
= 0x517B,
181 NCP_SUPER_MAGIC
= 0x564c,
182 USBDEVICE_SUPER_MAGIC
= 0x9fa2
183 } filegui_nonattrs_fs_t
;
186 /* Used for button result values */
189 REPLACE_YES
= B_USER
,
201 /* This structure describes the UI and internal data required by a file
207 gboolean showing_eta
;
208 gboolean showing_bps
;
210 /* Dialog and widgets for the operation progress window */
212 /* Source file: label and name */
213 WLabel
*src_file_label
;
215 /* Target file: label and name */
216 WLabel
*tgt_file_label
;
219 WGauge
*progress_file_gauge
;
220 WLabel
*progress_file_label
;
222 WGauge
*progress_total_gauge
;
224 WLabel
*total_files_processed_label
;
226 WHLine
*total_bytes_label
;
228 /* Query replace dialog */
229 WDialog
*replace_dlg
;
230 const char *src_filename
;
231 const char *tgt_filename
;
232 replace_action_t replace_result
;
233 gboolean dont_overwrite_with_zero
;
235 struct stat
*src_stat
, *dst_stat
;
236 } file_op_context_ui_t
;
238 /*** forward declarations (file scope functions) *************************************************/
240 /*** file scope variables ************************************************************************/
245 FileProgressStatus action
;
247 button_flags_t flags
;
249 } progress_buttons
[] = {
251 { NULL
, FILE_SKIP
, N_("&Skip"), NORMAL_BUTTON
, -1 },
252 { NULL
, FILE_SUSPEND
, N_("S&uspend"), NORMAL_BUTTON
, -1 },
253 { NULL
, FILE_SUSPEND
, N_("Con&tinue"), NORMAL_BUTTON
, -1 },
254 { NULL
, FILE_ABORT
, N_("&Abort"), NORMAL_BUTTON
, -1 }
258 /* --------------------------------------------------------------------------------------------- */
259 /*** file scope functions ************************************************************************/
260 /* --------------------------------------------------------------------------------------------- */
262 /* Return true if statvfs works. This is false for statvfs on systems
263 with GNU libc on Linux kernels before 2.6.36, which stats all
264 preceding entries in /proc/mounts; that makes df hang if even one
265 of the corresponding file systems is hard-mounted but not available. */
267 #if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
271 #if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
274 static int statvfs_works_cache
= -1;
277 if (statvfs_works_cache
< 0)
278 statvfs_works_cache
= (uname (&name
) == 0 && 0 <= str_verscmp (name
.release
, "2.6.36"));
279 return statvfs_works_cache
;
284 /* --------------------------------------------------------------------------------------------- */
287 filegui__check_attrs_on_fs (const char *fs_path
)
291 #if USE_STATVFS && defined(STAT_STATVFS)
292 if (statvfs_works () && statvfs (fs_path
, &stfs
) != 0)
295 if (STATFS (fs_path
, &stfs
) != 0)
299 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
300 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
301 switch ((filegui_nonattrs_fs_t
) stfs
.f_type
)
303 case MSDOS_SUPER_MAGIC
:
305 case PROC_SUPER_MAGIC
:
306 case SMB_SUPER_MAGIC
:
307 case NCP_SUPER_MAGIC
:
308 case USBDEVICE_SUPER_MAGIC
:
313 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
314 if (strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "msdos") == 0
315 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "msdosfs") == 0
316 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "ntfs") == 0
317 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "procfs") == 0
318 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "smbfs") == 0
319 || strstr (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "fusefs") != NULL
)
321 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
322 if (strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "pcfs") == 0
323 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "ntfs") == 0
324 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "proc") == 0
325 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "smbfs") == 0
326 || strcmp (stfs
.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
, "fuse") == 0)
333 /* --------------------------------------------------------------------------------------------- */
336 file_frmt_time (char *buffer
, double eta_secs
)
338 int eta_hours
, eta_mins
, eta_s
;
340 eta_hours
= (int) (eta_secs
/ (60 * 60));
341 eta_mins
= (int) ((eta_secs
- (eta_hours
* 60 * 60)) / 60);
342 eta_s
= (int) (eta_secs
- (eta_hours
* 60 * 60 + eta_mins
* 60));
343 g_snprintf (buffer
, BUF_TINY
, _("%d:%02d:%02d"), eta_hours
, eta_mins
, eta_s
);
346 /* --------------------------------------------------------------------------------------------- */
349 file_eta_prepare_for_show (char *buffer
, double eta_secs
, gboolean always_show
)
351 char _fmt_buff
[BUF_TINY
];
364 file_frmt_time (_fmt_buff
, eta_secs
);
365 g_snprintf (buffer
, BUF_TINY
, _("ETA %s"), _fmt_buff
);
368 /* --------------------------------------------------------------------------------------------- */
371 file_bps_prepare_for_show (char *buffer
, long bps
)
373 if (bps
> 1024 * 1024)
374 g_snprintf (buffer
, BUF_TINY
, _("%.2f MB/s"), bps
/ (1024 * 1024.0));
376 g_snprintf (buffer
, BUF_TINY
, _("%.2f KB/s"), bps
/ 1024.0);
378 g_snprintf (buffer
, BUF_TINY
, _("%ld B/s"), bps
);
383 /* --------------------------------------------------------------------------------------------- */
386 file_ui_op_dlg_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
391 /* Do not close the dialog because the query dialog will be shown */
392 if (parm
== CK_Cancel
)
394 DIALOG (w
)->ret_value
= FILE_ABORT
; /* for check_progress_buttons() */
397 return MSG_NOT_HANDLED
;
400 return dlg_default_callback (w
, sender
, msg
, parm
, data
);
404 /* --------------------------------------------------------------------------------------------- */
406 /* The dialog layout:
408 * +---------------------- File exists -----------------------+
409 * | New : /path/to/original_file_name | // 0, 1
410 * | 1234567 feb 4 2017 13:38 | // 2, 3
411 * | Existing: /path/to/target_file_name | // 4, 5
412 * | 1234567890 feb 4 2017 13:37 | // 6, 7
413 * +----------------------------------------------------------+
414 * | Overwrite this file? | // 8
415 * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12
416 * +----------------------------------------------------------+
417 * | Overwrite all files? | // 13
418 * | [ ] Don't overwrite with zero length file | // 14
419 * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19
420 * +----------------------------------------------------------|
421 * | [ Abort ] | // 20
422 * +----------------------------------------------------------+
425 static replace_action_t
426 overwrite_query_dialog (file_op_context_t
*ctx
, enum OperationMode mode
)
428 #define W(i) dlg_widgets[i].widget
429 #define WX(i) W(i)->rect.x
430 #define WY(i) W(i)->rect.y
431 #define WCOLS(i) W(i)->rect.cols
433 #define NEW_LABEL(i, text) \
434 W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
436 #define ADD_LABEL(i) \
437 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
438 g->current != NULL ? g->current->data : NULL)
440 #define NEW_BUTTON(i) \
441 W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
442 dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
444 #define ADD_BUTTON(i) \
445 group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
448 const int dlg_height
= 17;
457 widget_pos_flags_t pos_flags
;
458 int value
; /* 0 for labels and checkbox */
462 { NULL
, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT
, 0 },
463 /* 1 - label - name */
464 { NULL
, NULL
, 2, 14, WPOS_KEEP_DEFAULT
, 0 },
465 /* 2 - label - size */
466 { NULL
, NULL
, 3, 3, WPOS_KEEP_DEFAULT
, 0 },
467 /* 3 - label - date & time */
468 { NULL
, NULL
, 3, 43, WPOS_KEEP_TOP
| WPOS_KEEP_RIGHT
, 0 },
470 { NULL
, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT
, 0 },
471 /* 5 - label - name */
472 { NULL
, NULL
, 4, 14, WPOS_KEEP_DEFAULT
, 0 },
473 /* 6 - label - size */
474 { NULL
, NULL
, 5, 3, WPOS_KEEP_DEFAULT
, 0 },
475 /* 7 - label - date & time */
476 { NULL
, NULL
, 5, 43, WPOS_KEEP_TOP
| WPOS_KEEP_RIGHT
, 0 },
477 /* --------------------------------------------------- */
479 { NULL
, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP
| WPOS_CENTER_HORZ
, 0 },
481 { NULL
, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT
, REPLACE_YES
},
483 { NULL
, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT
, REPLACE_NO
},
485 { NULL
, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT
, REPLACE_APPEND
},
487 { NULL
, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT
, REPLACE_REGET
},
488 /* --------------------------------------------------- */
490 { NULL
, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP
| WPOS_CENTER_HORZ
, 0 },
492 { NULL
, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT
, 0 },
494 { NULL
, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT
, REPLACE_ALL
},
496 { NULL
, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT
, REPLACE_OLDER
},
498 { NULL
, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT
, REPLACE_NONE
},
500 { NULL
, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT
, REPLACE_SMALLER
},
502 { NULL
, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT
, REPLACE_SIZE
},
503 /* --------------------------------------------------- */
505 { NULL
, N_("&Abort"), 14, 27, WPOS_KEEP_TOP
| WPOS_CENTER_HORZ
, REPLACE_ABORT
}
511 file_op_context_ui_t
*ui
= ctx
->ui
;
523 gboolean do_append
= FALSE
, do_reget
= FALSE
;
524 unsigned long yes_id
, no_id
;
527 if (mode
== Foreground
)
528 title
= _("File exists");
530 title
= _("Background process: File exists");
534 const unsigned short num
= G_N_ELEMENTS (dlg_widgets
);
536 for (i
= 0; i
< num
; i
++)
537 if (dlg_widgets
[i
].text
!= NULL
)
538 dlg_widgets
[i
].text
= _(dlg_widgets
[i
].text
);
540 #endif /* ENABLE_NLS */
542 /* create widgets to get their real widths */
544 NEW_LABEL (0, dlg_widgets
[0].text
);
546 p
= vfs_path_from_str (ui
->src_filename
);
547 s1
= vfs_path_to_str_flags (p
, 0, VPF_STRIP_HOME
| VPF_STRIP_PASSWORD
);
549 vfs_path_free (p
, TRUE
);
552 size_trunc_len (s2
, sizeof (s2
), ui
->src_stat
->st_size
, 0, panels_options
.kilobyte_si
);
554 /* new file modification date & time */
555 cs1
= file_date (ui
->src_stat
->st_mtime
);
559 NEW_LABEL (4, dlg_widgets
[4].text
);
560 /* existing file name */
561 p
= vfs_path_from_str (ui
->tgt_filename
);
562 s1
= vfs_path_to_str_flags (p
, 0, VPF_STRIP_HOME
| VPF_STRIP_PASSWORD
);
564 vfs_path_free (p
, TRUE
);
566 /* existing file size */
567 size_trunc_len (s2
, sizeof (s2
), ui
->dst_stat
->st_size
, 0, panels_options
.kilobyte_si
);
569 /* existing file modification date & time */
570 cs1
= file_date (ui
->dst_stat
->st_mtime
);
573 /* will "Append" and "Reget" buttons be in the dialog? */
574 do_append
= !S_ISDIR (ui
->dst_stat
->st_mode
);
575 do_reget
= do_append
&& ui
->dst_stat
->st_size
!= 0
576 && ui
->src_stat
->st_size
> ui
->dst_stat
->st_size
;
578 NEW_LABEL (8, dlg_widgets
[8].text
);
586 NEW_LABEL (13, dlg_widgets
[13].text
);
587 dlg_widgets
[14].widget
=
588 WIDGET (check_new (dlg_widgets
[14].y
, dlg_widgets
[14].x
, FALSE
, dlg_widgets
[14].text
));
589 for (i
= 15; i
<= 20; i
++)
593 dlg_width
-= 2 * (2 + gap
); /* inside frame */
595 /* perhaps longest line is buttons */
596 bw1
= WCOLS (9) + gap
+ WCOLS (10);
598 bw1
+= gap
+ WCOLS (11);
600 bw1
+= gap
+ WCOLS (12);
601 dlg_width
= MAX (dlg_width
, bw1
);
604 for (i
= 16; i
<= 19; i
++)
605 bw2
+= gap
+ WCOLS (i
);
606 dlg_width
= MAX (dlg_width
, bw2
);
608 dlg_width
= MAX (dlg_width
, WCOLS (8));
609 dlg_width
= MAX (dlg_width
, WCOLS (13));
610 dlg_width
= MAX (dlg_width
, WCOLS (14));
612 /* truncate file names */
613 w
= WCOLS (0) + gap
+ WCOLS (1);
616 WLabel
*l
= LABEL (W (1));
618 w
= dlg_width
- gap
- WCOLS (0);
619 label_set_text (l
, str_trunc (l
->text
, w
));
622 w
= WCOLS (4) + gap
+ WCOLS (5);
625 WLabel
*l
= LABEL (W (5));
627 w
= dlg_width
- gap
- WCOLS (4);
628 label_set_text (l
, str_trunc (l
->text
, w
));
631 /* real dlalog width */
632 dlg_width
+= 2 * (2 + gap
);
634 WX (1) = WX (0) + WCOLS (0) + gap
;
635 WX (5) = WX (4) + WCOLS (4) + gap
;
637 /* sizes: right alignment */
638 WX (2) = dlg_width
/ 2 - WCOLS (2);
639 WX (6) = dlg_width
/ 2 - WCOLS (6);
641 w
= dlg_width
- (2 + gap
); /* right bound */
644 WX (3) = w
- WCOLS (3);
645 WX (7) = w
- WCOLS (7);
647 /* buttons: center alignment */
648 WX (9) = dlg_width
/ 2 - bw1
/ 2;
649 WX (10) = WX (9) + WCOLS (9) + gap
;
651 WX (11) = WX (10) + WCOLS (10) + gap
;
653 WX (12) = WX (11) + WCOLS (11) + gap
;
655 WX (15) = dlg_width
/ 2 - bw2
/ 2;
656 for (i
= 16; i
<= 19; i
++)
657 WX (i
) = WX (i
- 1) + WCOLS (i
- 1) + gap
;
659 /* TODO: write help (ticket #3970) */
661 dlg_create (TRUE
, 0, 0, dlg_height
, dlg_width
, WPOS_CENTER
, FALSE
, alarm_colors
, NULL
, NULL
,
663 wd
= WIDGET (ui
->replace_dlg
);
664 g
= GROUP (ui
->replace_dlg
);
667 for (i
= 0; i
<= 7; i
++)
669 group_add_widget (g
, hline_new (WY (7) - wd
->rect
.y
+ 1, -1, -1));
671 /* label & buttons */
672 ADD_LABEL (8); /* Overwrite this file? */
673 yes_id
= ADD_BUTTON (9); /* Yes */
674 no_id
= ADD_BUTTON (10); /* No */
676 ADD_BUTTON (11); /* Append */
678 ADD_BUTTON (12); /* Reget */
679 group_add_widget (g
, hline_new (WY (10) - wd
->rect
.y
+ 1, -1, -1));
681 /* label & buttons */
682 ADD_LABEL (13); /* Overwrite all files? */
683 group_add_widget (g
, dlg_widgets
[14].widget
);
684 for (i
= 15; i
<= 19; i
++)
686 group_add_widget (g
, hline_new (WY (19) - wd
->rect
.y
+ 1, -1, -1));
688 ADD_BUTTON (20); /* Abort */
690 group_select_widget_by_id (g
, safe_overwrite
? no_id
: yes_id
);
692 result
= dlg_run (ui
->replace_dlg
);
694 if (result
!= B_CANCEL
)
695 ui
->dont_overwrite_with_zero
= CHECK (dlg_widgets
[14].widget
)->state
;
699 return (result
== B_CANCEL
) ? REPLACE_ABORT
: (replace_action_t
) result
;
710 /* --------------------------------------------------------------------------------------------- */
713 is_wildcarded (const char *p
)
715 gboolean escaped
= FALSE
;
717 for (; *p
!= '\0'; p
++)
721 if (p
[1] >= '1' && p
[1] <= '9' && !escaped
)
727 if ((*p
== '*' || *p
== '?') && !escaped
)
735 /* --------------------------------------------------------------------------------------------- */
738 place_progress_buttons (WDialog
*h
, gboolean suspended
)
740 const size_t i
= suspended
? 2 : 1;
741 Widget
*w
= WIDGET (h
);
744 buttons_width
= 2 + progress_buttons
[0].len
+ progress_buttons
[3].len
;
745 buttons_width
+= progress_buttons
[i
].len
;
746 button_set_text (BUTTON (progress_buttons
[i
].w
), progress_buttons
[i
].text
);
748 progress_buttons
[0].w
->rect
.x
= w
->rect
.x
+ (w
->rect
.cols
- buttons_width
) / 2;
749 progress_buttons
[i
].w
->rect
.x
= progress_buttons
[0].w
->rect
.x
+ progress_buttons
[0].len
+ 1;
750 progress_buttons
[3].w
->rect
.x
= progress_buttons
[i
].w
->rect
.x
+ progress_buttons
[i
].len
+ 1;
753 /* --------------------------------------------------------------------------------------------- */
756 progress_button_callback (WButton
*button
, int action
)
761 /* don't close dialog in any case */
765 /* --------------------------------------------------------------------------------------------- */
766 /*** public functions ****************************************************************************/
767 /* --------------------------------------------------------------------------------------------- */
770 check_progress_buttons (file_op_context_t
*ctx
)
774 file_op_context_ui_t
*ui
;
776 if (ctx
== NULL
|| ctx
->ui
== NULL
)
782 event
.x
= -1; /* Don't show the GPM cursor */
783 c
= tty_get_event (&event
, FALSE
, ctx
->suspended
);
787 /* Reinitialize to avoid old values after events other than selecting a button */
788 ui
->op_dlg
->ret_value
= FILE_CONT
;
790 dlg_process_event (ui
->op_dlg
, c
, &event
);
791 switch (ui
->op_dlg
->ret_value
)
796 /* redraw dialog in case of Skip after Suspend */
797 place_progress_buttons (ui
->op_dlg
, FALSE
);
798 widget_draw (WIDGET (ui
->op_dlg
));
800 ctx
->suspended
= FALSE
;
804 ctx
->suspended
= FALSE
;
807 ctx
->suspended
= !ctx
->suspended
;
808 place_progress_buttons (ui
->op_dlg
, ctx
->suspended
);
809 widget_draw (WIDGET (ui
->op_dlg
));
818 /* --------------------------------------------------------------------------------------------- */
819 /* {{{ File progress display routines */
822 file_op_context_create_ui (file_op_context_t
*ctx
, gboolean with_eta
,
823 filegui_dialog_type_t dialog_type
)
825 file_op_context_ui_t
*ui
;
829 int dlg_width
= 58, dlg_height
= 17;
833 if (ctx
== NULL
|| ctx
->ui
!= NULL
)
837 if (progress_buttons
[0].len
== -1)
841 for (i
= 0; i
< G_N_ELEMENTS (progress_buttons
); i
++)
842 progress_buttons
[i
].text
= _(progress_buttons
[i
].text
);
846 ctx
->dialog_type
= dialog_type
;
847 ctx
->recursive_result
= RECURSIVE_YES
;
848 ctx
->ui
= g_new0 (file_op_context_ui_t
, 1);
851 ui
->replace_result
= REPLACE_YES
;
853 ui
->op_dlg
= dlg_create (TRUE
, 0, 0, dlg_height
, dlg_width
, WPOS_CENTER
, FALSE
, dialog_colors
,
854 file_ui_op_dlg_callback
, NULL
, NULL
, op_names
[ctx
->operation
]);
855 w
= WIDGET (ui
->op_dlg
);
856 g
= GROUP (ui
->op_dlg
);
858 if (dialog_type
!= FILEGUI_DIALOG_DELETE_ITEM
)
860 ui
->showing_eta
= with_eta
&& ctx
->progress_totals_computed
;
861 ui
->showing_bps
= with_eta
;
863 ui
->src_file_label
= label_new (y
++, x
, NULL
);
864 group_add_widget (g
, ui
->src_file_label
);
866 ui
->src_file
= label_new (y
++, x
, NULL
);
867 group_add_widget (g
, ui
->src_file
);
869 ui
->tgt_file_label
= label_new (y
++, x
, NULL
);
870 group_add_widget (g
, ui
->tgt_file_label
);
872 ui
->tgt_file
= label_new (y
++, x
, NULL
);
873 group_add_widget (g
, ui
->tgt_file
);
875 ui
->progress_file_gauge
= gauge_new (y
++, x
+ 3, dlg_width
- (x
+ 3) * 2, FALSE
, 100, 0);
876 if (!classic_progressbar
&& (current_panel
== right_panel
))
877 ui
->progress_file_gauge
->from_left_to_right
= FALSE
;
878 group_add_widget_autopos (g
, ui
->progress_file_gauge
, WPOS_KEEP_TOP
| WPOS_KEEP_HORZ
, NULL
);
880 ui
->progress_file_label
= label_new (y
++, x
, NULL
);
881 group_add_widget (g
, ui
->progress_file_label
);
883 if (verbose
&& dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
885 ui
->total_bytes_label
= hline_new (y
++, -1, -1);
886 group_add_widget (g
, ui
->total_bytes_label
);
888 if (ctx
->progress_totals_computed
)
890 ui
->progress_total_gauge
=
891 gauge_new (y
++, x
+ 3, dlg_width
- (x
+ 3) * 2, FALSE
, 100, 0);
892 if (!classic_progressbar
&& (current_panel
== right_panel
))
893 ui
->progress_total_gauge
->from_left_to_right
= FALSE
;
894 group_add_widget_autopos (g
, ui
->progress_total_gauge
,
895 WPOS_KEEP_TOP
| WPOS_KEEP_HORZ
, NULL
);
898 ui
->total_files_processed_label
= label_new (y
++, x
, NULL
);
899 group_add_widget (g
, ui
->total_files_processed_label
);
901 ui
->time_label
= label_new (y
++, x
, NULL
);
902 group_add_widget (g
, ui
->time_label
);
907 ui
->src_file
= label_new (y
++, x
, NULL
);
908 group_add_widget (g
, ui
->src_file
);
910 ui
->total_files_processed_label
= label_new (y
++, x
, NULL
);
911 group_add_widget (g
, ui
->total_files_processed_label
);
914 group_add_widget (g
, hline_new (y
++, -1, -1));
916 progress_buttons
[0].w
= WIDGET (button_new (y
, 0, progress_buttons
[0].action
,
917 progress_buttons
[0].flags
, progress_buttons
[0].text
,
918 progress_button_callback
));
919 if (progress_buttons
[0].len
== -1)
920 progress_buttons
[0].len
= button_get_len (BUTTON (progress_buttons
[0].w
));
922 progress_buttons
[1].w
= WIDGET (button_new (y
, 0, progress_buttons
[1].action
,
923 progress_buttons
[1].flags
, progress_buttons
[1].text
,
924 progress_button_callback
));
925 if (progress_buttons
[1].len
== -1)
926 progress_buttons
[1].len
= button_get_len (BUTTON (progress_buttons
[1].w
));
928 if (progress_buttons
[2].len
== -1)
930 /* create and destroy button to get it length */
931 progress_buttons
[2].w
= WIDGET (button_new (y
, 0, progress_buttons
[2].action
,
932 progress_buttons
[2].flags
,
933 progress_buttons
[2].text
,
934 progress_button_callback
));
935 progress_buttons
[2].len
= button_get_len (BUTTON (progress_buttons
[2].w
));
936 widget_destroy (progress_buttons
[2].w
);
938 progress_buttons
[2].w
= progress_buttons
[1].w
;
940 progress_buttons
[3].w
= WIDGET (button_new (y
, 0, progress_buttons
[3].action
,
941 progress_buttons
[3].flags
, progress_buttons
[3].text
,
942 progress_button_callback
));
943 if (progress_buttons
[3].len
== -1)
944 progress_buttons
[3].len
= button_get_len (BUTTON (progress_buttons
[3].w
));
946 group_add_widget (g
, progress_buttons
[0].w
);
947 group_add_widget (g
, progress_buttons
[1].w
);
948 group_add_widget (g
, progress_buttons
[3].w
);
951 progress_buttons
[0].len
+ MAX (progress_buttons
[1].len
, progress_buttons
[2].len
) +
952 progress_buttons
[3].len
;
954 /* adjust dialog sizes */
957 r
.cols
= MAX (COLS
* 2 / 3, buttons_width
+ 6);
958 widget_set_size_rect (w
, &r
);
960 place_progress_buttons (ui
->op_dlg
, FALSE
);
962 widget_select (progress_buttons
[0].w
);
964 /* We will manage the dialog without any help, that's why
965 we have to call dlg_init */
966 dlg_init (ui
->op_dlg
);
969 /* --------------------------------------------------------------------------------------------- */
972 file_op_context_destroy_ui (file_op_context_t
*ctx
)
974 if (ctx
!= NULL
&& ctx
->ui
!= NULL
)
976 file_op_context_ui_t
*ui
= (file_op_context_ui_t
*) ctx
->ui
;
978 dlg_run_done (ui
->op_dlg
);
979 widget_destroy (WIDGET (ui
->op_dlg
));
980 MC_PTR_FREE (ctx
->ui
);
984 /* --------------------------------------------------------------------------------------------- */
986 show progressbar for file
990 file_progress_show (file_op_context_t
*ctx
, off_t done
, off_t total
,
991 const char *stalled_msg
, gboolean force_update
)
993 file_op_context_ui_t
*ui
;
995 if (ctx
== NULL
|| ctx
->ui
== NULL
)
1002 gauge_show (ui
->progress_file_gauge
, FALSE
);
1006 gauge_set_value (ui
->progress_file_gauge
, 1024, (int) (1024 * done
/ total
));
1007 gauge_show (ui
->progress_file_gauge
, TRUE
);
1012 if (!ui
->showing_eta
|| ctx
->eta_secs
<= 0.5)
1013 label_set_text (ui
->progress_file_label
, stalled_msg
);
1016 char buffer2
[BUF_TINY
];
1018 file_eta_prepare_for_show (buffer2
, ctx
->eta_secs
, FALSE
);
1020 label_set_textv (ui
->progress_file_label
, "%s %s", buffer2
, stalled_msg
);
1023 char buffer3
[BUF_TINY
];
1025 file_bps_prepare_for_show (buffer3
, ctx
->bps
);
1026 label_set_textv (ui
->progress_file_label
, "%s (%s) %s", buffer2
, buffer3
, stalled_msg
);
1032 /* --------------------------------------------------------------------------------------------- */
1035 file_progress_show_count (file_op_context_t
*ctx
, size_t done
, size_t total
)
1037 file_op_context_ui_t
*ui
;
1039 if (ctx
== NULL
|| ctx
->ui
== NULL
)
1044 if (ui
->total_files_processed_label
== NULL
)
1047 if (ctx
->progress_totals_computed
)
1048 label_set_textv (ui
->total_files_processed_label
, _("Files processed: %zu / %zu"), done
,
1051 label_set_textv (ui
->total_files_processed_label
, _("Files processed: %zu"), done
);
1054 /* --------------------------------------------------------------------------------------------- */
1057 file_progress_show_total (file_op_total_context_t
*tctx
, file_op_context_t
*ctx
,
1058 uintmax_t copied_bytes
, gboolean show_summary
)
1060 char buffer2
[BUF_TINY
];
1061 char buffer3
[BUF_TINY
];
1062 file_op_context_ui_t
*ui
;
1064 if (ctx
== NULL
|| ctx
->ui
== NULL
)
1069 if (ui
->progress_total_gauge
!= NULL
)
1071 if (ctx
->progress_bytes
== 0)
1072 gauge_show (ui
->progress_total_gauge
, FALSE
);
1075 gauge_set_value (ui
->progress_total_gauge
, 1024,
1076 (int) (1024 * copied_bytes
/ ctx
->progress_bytes
));
1077 gauge_show (ui
->progress_total_gauge
, TRUE
);
1081 if (!show_summary
&& tctx
->bps
== 0)
1084 if (ui
->time_label
!= NULL
)
1087 char buffer4
[BUF_TINY
];
1089 tv_current
= g_get_monotonic_time ();
1090 file_frmt_time (buffer2
, (tv_current
- tctx
->transfer_start
) / G_USEC_PER_SEC
);
1092 if (ctx
->progress_totals_computed
)
1094 file_eta_prepare_for_show (buffer3
, tctx
->eta_secs
, TRUE
);
1096 label_set_textv (ui
->time_label
, _("Time: %s %s"), buffer2
, buffer3
);
1099 file_bps_prepare_for_show (buffer4
, (long) tctx
->bps
);
1100 label_set_textv (ui
->time_label
, _("Time: %s %s (%s)"), buffer2
, buffer3
, buffer4
);
1106 label_set_textv (ui
->time_label
, _("Time: %s"), buffer2
);
1109 file_bps_prepare_for_show (buffer4
, (long) tctx
->bps
);
1110 label_set_textv (ui
->time_label
, _("Time: %s (%s)"), buffer2
, buffer4
);
1115 if (ui
->total_bytes_label
!= NULL
)
1117 size_trunc_len (buffer2
, 5, tctx
->copied_bytes
, 0, panels_options
.kilobyte_si
);
1119 if (!ctx
->progress_totals_computed
)
1120 hline_set_textv (ui
->total_bytes_label
, _(" Total: %s "), buffer2
);
1123 size_trunc_len (buffer3
, 5, ctx
->progress_bytes
, 0, panels_options
.kilobyte_si
);
1124 hline_set_textv (ui
->total_bytes_label
, _(" Total: %s / %s "), buffer2
, buffer3
);
1131 /* --------------------------------------------------------------------------------------------- */
1134 file_progress_show_source (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1136 file_op_context_ui_t
*ui
;
1138 if (ctx
== NULL
|| ctx
->ui
== NULL
)
1145 label_set_text (ui
->src_file_label
, _("Source"));
1146 label_set_text (ui
->src_file
, truncFileStringSecure (ui
->op_dlg
, vfs_path_as_str (vpath
)));
1150 label_set_text (ui
->src_file_label
, NULL
);
1151 label_set_text (ui
->src_file
, NULL
);
1155 /* --------------------------------------------------------------------------------------------- */
1158 file_progress_show_target (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1160 file_op_context_ui_t
*ui
;
1162 if (ctx
== NULL
|| ctx
->ui
== NULL
)
1169 label_set_text (ui
->tgt_file_label
, _("Target"));
1170 label_set_text (ui
->tgt_file
, truncFileStringSecure (ui
->op_dlg
, vfs_path_as_str (vpath
)));
1174 label_set_text (ui
->tgt_file_label
, NULL
);
1175 label_set_text (ui
->tgt_file
, NULL
);
1179 /* --------------------------------------------------------------------------------------------- */
1182 file_progress_show_deleting (file_op_context_t
*ctx
, const vfs_path_t
*vpath
, size_t *count
)
1184 static gint64 timestamp
= 0;
1185 /* update with 25 FPS rate */
1186 static const gint64 delay
= G_USEC_PER_SEC
/ 25;
1190 if (ctx
== NULL
|| ctx
->ui
== NULL
)
1193 ret
= mc_time_elapsed (×tamp
, delay
);
1197 file_op_context_ui_t
*ui
;
1202 if (ui
->src_file_label
!= NULL
)
1203 label_set_text (ui
->src_file_label
, _("Deleting"));
1205 s
= vfs_path_as_str (vpath
);
1206 label_set_text (ui
->src_file
, truncFileStringSecure (ui
->op_dlg
, s
));
1215 /* --------------------------------------------------------------------------------------------- */
1218 file_progress_real_query_replace (file_op_context_t
*ctx
, enum OperationMode mode
,
1219 const char *src
, struct stat
*src_stat
,
1220 const char *dst
, struct stat
*dst_stat
)
1222 file_op_context_ui_t
*ui
;
1223 FileProgressStatus replace_with_zero
;
1225 if (ctx
== NULL
|| ctx
->ui
== NULL
)
1230 if (ui
->replace_result
== REPLACE_YES
|| ui
->replace_result
== REPLACE_NO
1231 || ui
->replace_result
== REPLACE_APPEND
)
1233 ui
->src_filename
= src
;
1234 ui
->src_stat
= src_stat
;
1235 ui
->tgt_filename
= dst
;
1236 ui
->dst_stat
= dst_stat
;
1237 ui
->replace_result
= overwrite_query_dialog (ctx
, mode
);
1240 replace_with_zero
= (src_stat
->st_size
== 0
1241 && ui
->dont_overwrite_with_zero
) ? FILE_SKIP
: FILE_CONT
;
1243 switch (ui
->replace_result
)
1247 if (src_stat
->st_mtime
> dst_stat
->st_mtime
)
1248 return replace_with_zero
;
1254 if (src_stat
->st_size
== dst_stat
->st_size
)
1257 return replace_with_zero
;
1259 case REPLACE_SMALLER
:
1261 if (src_stat
->st_size
> dst_stat
->st_size
)
1268 return replace_with_zero
;
1271 /* Careful: we fall through and set do_append */
1272 ctx
->do_reget
= dst_stat
->st_size
;
1275 case REPLACE_APPEND
:
1276 ctx
->do_append
= TRUE
;
1294 /* --------------------------------------------------------------------------------------------- */
1297 file_mask_dialog (file_op_context_t
*ctx
, gboolean only_one
, const char *format
, const void *text
,
1298 const char *def_text
, gboolean
*do_bg
)
1303 gboolean source_easy_patterns
= easy_patterns
;
1304 char fmd_buf
[BUF_MEDIUM
];
1305 char *dest_dir
= NULL
;
1307 char *def_text_secure
;
1312 /* unselect checkbox if target filesystem doesn't support attributes */
1313 preserve
= copymove_persistent_attr
&& filegui__check_attrs_on_fs (def_text
);
1315 ctx
->stable_symlinks
= FALSE
;
1318 /* filter out a possible password from def_text */
1319 vpath
= vfs_path_from_str_flags (def_text
, only_one
? VPF_NO_CANON
: VPF_NONE
);
1320 tmp
= vfs_path_to_str_flags (vpath
, 0, VPF_STRIP_PASSWORD
);
1321 vfs_path_free (vpath
, TRUE
);
1323 if (source_easy_patterns
)
1324 def_text_secure
= str_glob_escape (tmp
);
1326 def_text_secure
= str_regex_escape (tmp
);
1331 int format_len
, text_len
;
1334 format_len
= str_term_width1 (format
);
1335 text_len
= str_term_width1 (text
);
1336 max_len
= COLS
- 2 - 6;
1338 if (format_len
+ text_len
<= max_len
)
1340 fmd_xlen
= format_len
+ text_len
+ 6;
1341 fmd_xlen
= MAX (fmd_xlen
, 68);
1345 text
= str_trunc ((const char *) text
, max_len
- format_len
);
1346 fmd_xlen
= max_len
+ 6;
1349 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, (const char *) text
);
1353 fmd_xlen
= COLS
* 2 / 3;
1354 fmd_xlen
= MAX (fmd_xlen
, 68);
1355 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, *(const int *) text
);
1359 char *source_mask
= NULL
;
1364 quick_widget_t quick_widgets
[] = {
1366 QUICK_LABELED_INPUT (fmd_buf
, input_label_above
, easy_patterns
? "*" : "^(.*)$",
1367 "input-def", &source_mask
, NULL
, FALSE
, FALSE
,
1368 INPUT_COMPLETE_FILENAMES
),
1369 QUICK_START_COLUMNS
,
1370 QUICK_SEPARATOR (FALSE
),
1372 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns
, NULL
),
1374 QUICK_LABELED_INPUT (N_("to:"), input_label_above
, def_text_secure
, "input2", &dest_dir
,
1375 NULL
, FALSE
, FALSE
, INPUT_COMPLETE_FILENAMES
),
1376 QUICK_SEPARATOR (TRUE
),
1377 QUICK_START_COLUMNS
,
1378 QUICK_CHECKBOX (N_("Follow &links"), &ctx
->follow_links
, NULL
),
1379 QUICK_CHECKBOX (N_("Preserve &attributes"), &preserve
, NULL
),
1381 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx
->dive_into_subdirs
, NULL
),
1382 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx
->stable_symlinks
, NULL
),
1384 QUICK_START_BUTTONS (TRUE
, TRUE
),
1385 QUICK_BUTTON (N_("&OK"), B_ENTER
, NULL
, NULL
),
1386 #ifdef ENABLE_BACKGROUND
1387 QUICK_BUTTON (N_("&Background"), B_USER
, NULL
, NULL
),
1388 #endif /* ENABLE_BACKGROUND */
1389 QUICK_BUTTON (N_("&Cancel"), B_CANCEL
, NULL
, NULL
),
1394 WRect r
= { -1, -1, 0, fmd_xlen
};
1396 quick_dialog_t qdlg
= {
1397 r
, op_names
[ctx
->operation
], "[Mask Copy/Rename]",
1398 quick_widgets
, NULL
, NULL
1403 val
= quick_dialog_skip (&qdlg
, 4);
1405 if (val
== B_CANCEL
)
1407 g_free (def_text_secure
);
1411 ctx
->stat_func
= ctx
->follow_links
? mc_stat
: mc_lstat
;
1415 ctx
->preserve
= TRUE
;
1416 ctx
->umask_kill
= (mode_t
) (~0);
1417 ctx
->preserve_uidgid
= (geteuid () == 0);
1423 ctx
->preserve
= ctx
->preserve_uidgid
= FALSE
;
1426 ctx
->umask_kill
= i2
^ ((mode_t
) (~0));
1429 if (*dest_dir
== '\0')
1431 g_free (def_text_secure
);
1432 g_free (source_mask
);
1437 ctx
->search_handle
= mc_search_new (source_mask
, NULL
);
1438 if (ctx
->search_handle
!= NULL
)
1441 message (D_ERROR
, MSG_ERROR
, _("Invalid source pattern '%s'"), source_mask
);
1442 MC_PTR_FREE (dest_dir
);
1443 MC_PTR_FREE (source_mask
);
1446 g_free (def_text_secure
);
1447 g_free (source_mask
);
1449 ctx
->search_handle
->is_case_sensitive
= TRUE
;
1450 if (source_easy_patterns
)
1451 ctx
->search_handle
->search_type
= MC_SEARCH_T_GLOB
;
1453 ctx
->search_handle
->search_type
= MC_SEARCH_T_REGEX
;
1456 dest_dir
= tilde_expand (tmp
);
1458 vpath
= vfs_path_from_str (dest_dir
);
1460 ctx
->dest_mask
= strrchr (dest_dir
, PATH_SEP
);
1461 if (ctx
->dest_mask
== NULL
)
1462 ctx
->dest_mask
= dest_dir
;
1466 orig_mask
= ctx
->dest_mask
;
1468 if (*ctx
->dest_mask
== '\0'
1469 || (!ctx
->dive_into_subdirs
&& !is_wildcarded (ctx
->dest_mask
)
1471 || (mc_stat (vpath
, &buf
) == 0 && S_ISDIR (buf
.st_mode
))))
1472 || (ctx
->dive_into_subdirs
1473 && ((!only_one
&& !is_wildcarded (ctx
->dest_mask
))
1474 || (only_one
&& mc_stat (vpath
, &buf
) == 0 && S_ISDIR (buf
.st_mode
)))))
1475 ctx
->dest_mask
= g_strdup ("\\0");
1478 ctx
->dest_mask
= g_strdup (ctx
->dest_mask
);
1482 if (*dest_dir
== '\0')
1485 dest_dir
= g_strdup ("./");
1488 vfs_path_free (vpath
, TRUE
);
1497 /* --------------------------------------------------------------------------------------------- */