Manual pages: mcmcdiffmceditmcview

root/src/filemanager/filegui.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. statfs
  2. statvfs_works
  3. filegui__check_attrs_on_fs
  4. file_frmt_time
  5. file_eta_prepare_for_show
  6. file_bps_prepare_for_show
  7. file_ui_op_dlg_callback
  8. overwrite_query_dialog
  9. is_wildcarded
  10. place_progress_buttons
  11. progress_button_callback
  12. file_op_context_new
  13. file_op_context_destroy
  14. file_progress_check_buttons
  15. file_progress_ui_create
  16. file_progress_ui_destroy
  17. file_progress_show
  18. file_progress_show_count
  19. file_progress_show_total
  20. file_progress_show_source
  21. file_progress_show_target
  22. file_progress_show_deleting
  23. file_progress_real_query_replace
  24. file_mask_dialog

   1 /*
   2    File management GUI for the text mode edition
   3 
   4    The copy code was based in GNU's cp, and was written by:
   5    Torbjorn Granlund, David MacKenzie, and Jim Meyering.
   6 
   7    The move code was based in GNU's mv, and was written by:
   8    Mike Parker and David MacKenzie.
   9 
  10    Janne Kukonlehto added much error recovery to them for being used
  11    in an interactive program.
  12 
  13    Copyright (C) 1994-2025
  14    Free Software Foundation, Inc.
  15 
  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-2023
  25 
  26    This file is part of the Midnight Commander.
  27 
  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.
  32 
  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.
  37 
  38    You should have received a copy of the GNU General Public License
  39    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  40  */
  41 
  42 /*
  43  * Please note that all dialogs used here must be safe for background
  44  * operations.
  45  */
  46 
  47 /** \file  filegui.c
  48  *  \brief Source: file management GUI for the text mode edition
  49  */
  50 
  51 /* {{{ Include files */
  52 
  53 #include <config.h>
  54 
  55 #include <errno.h>
  56 #include <ctype.h>
  57 #include <limits.h>  // INT_MAX
  58 #include <stdio.h>
  59 #include <string.h>
  60 #include <sys/types.h>
  61 #include <sys/stat.h>
  62 
  63 #if defined(USE_STATVFS)
  64 #include <sys/statvfs.h>
  65 #elif defined HAVE_SYS_VFS_H
  66 #include <sys/vfs.h>
  67 #elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H
  68 /* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
  69    It does have statvfs.h, but shouldn't use it, since it doesn't
  70    HAVE_STRUCT_STATVFS_F_BASETYPE.  So find a clean way to fix it.  */
  71 /* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
  72 #include <sys/param.h>
  73 #include <sys/mount.h>
  74 #elif defined HAVE_OS_H  // Haiku, also (obsolete) BeOS
  75 #include <fs_info.h>
  76 #endif
  77 
  78 #if defined(USE_STATVFS)
  79 #if !defined STAT_STATVFS && defined STAT_STATVFS64
  80 #define STRUCT_STATVFS struct statvfs64
  81 #define STATFS         statvfs64
  82 #else
  83 #define STRUCT_STATVFS struct statvfs
  84 #define STATFS         statvfs
  85 
  86 #if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)
  87 #include <sys/utsname.h>
  88 #include <sys/statfs.h>
  89 #define STAT_STATFS2_BSIZE 1
  90 #endif
  91 #endif
  92 
  93 #else
  94 #define STATFS         statfs
  95 #define STRUCT_STATVFS struct statfs
  96 #ifdef HAVE_OS_H  // Haiku, also (obsolete) BeOS
  97 /* BeOS has a statvfs function, but it does not return sensible values
  98    for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
  99    f_fstypename.  Use 'struct fs_info' instead.  */
 100 static int
 101 statfs (char const *filename, struct fs_info *buf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 102 {
 103     dev_t device;
 104 
 105     device = dev_for_path (filename);
 106 
 107     if (device < 0)
 108     {
 109         errno = (device == B_ENTRY_NOT_FOUND     ? ENOENT
 110                      : device == B_BAD_VALUE     ? EINVAL
 111                      : device == B_NAME_TOO_LONG ? ENAMETOOLONG
 112                      : device == B_NO_MEMORY     ? ENOMEM
 113                      : device == B_FILE_ERROR    ? EIO
 114                                                  : 0);
 115         return -1;
 116     }
 117     // If successful, buf->dev will be == device.
 118     return fs_stat_dev (device, buf);
 119 }
 120 
 121 #define STRUCT_STATVFS struct fs_info
 122 #else
 123 #define STRUCT_STATVFS struct statfs
 124 #endif
 125 #endif
 126 
 127 #ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
 128 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
 129 #else
 130 #if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
 131 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
 132 #elif defined HAVE_OS_H  // Haiku, also (obsolete) BeOS
 133 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
 134 #endif
 135 #endif
 136 
 137 #include <unistd.h>
 138 
 139 #include "lib/global.h"
 140 
 141 #include "lib/tty/key.h"  // tty_get_event
 142 #include "lib/mcconfig.h"
 143 #include "lib/search.h"
 144 #include "lib/vfs/vfs.h"
 145 #include "lib/strutil.h"
 146 #include "lib/timefmt.h"  // file_date()
 147 #include "lib/util.h"
 148 #include "lib/widget.h"
 149 
 150 #include "src/setup.h"  // verbose, safe_overwrite, copymove_persistent_ext2_attr
 151 
 152 #include "filemanager.h"
 153 
 154 #include "filegui.h"
 155 
 156 /* }}} */
 157 
 158 /*** global variables ****************************************************************************/
 159 
 160 gboolean classic_progressbar = TRUE;
 161 
 162 /*** file scope macro definitions ****************************************************************/
 163 
 164 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
 165 
 166 /*** file scope type declarations ****************************************************************/
 167 
 168 typedef enum
 169 {
 170     MSDOS_SUPER_MAGIC = 0x4d44,
 171     NTFS_SB_MAGIC = 0x5346544e,
 172     FUSE_MAGIC = 0x65735546,
 173     PROC_SUPER_MAGIC = 0x9fa0,
 174     SMB_SUPER_MAGIC = 0x517B,
 175     NCP_SUPER_MAGIC = 0x564c,
 176     USBDEVICE_SUPER_MAGIC = 0x9fa2
 177 } filegui_nonattrs_fs_t;
 178 
 179 // Used for button result values
 180 typedef enum
 181 {
 182     REPLACE_YES = B_USER,
 183     REPLACE_NO,
 184     REPLACE_APPEND,
 185     REPLACE_REGET,
 186     REPLACE_ALL,
 187     REPLACE_OLDER,
 188     REPLACE_NONE,
 189     REPLACE_SMALLER,
 190     REPLACE_SIZE,
 191     REPLACE_ABORT
 192 } replace_action_t;
 193 
 194 // UI and internal data required by a file operation context.
 195 typedef struct
 196 {
 197     // ETA and bps
 198     gboolean showing_eta;
 199     gboolean showing_bps;
 200 
 201     // Dialog and widgets for the operation progress window
 202     WDialog *op_dlg;
 203     // Source file: label and name
 204     WLabel *src_file_label;
 205     WLabel *src_file;
 206     // Target file: label and name
 207     WLabel *tgt_file_label;
 208     WLabel *tgt_file;
 209     WGauge *progress_file_gauge;
 210     WLabel *progress_file_label;
 211 
 212     WHLine *total_bytes_label;
 213     WGauge *progress_total_gauge;
 214     WLabel *total_files_processed_label;
 215     WLabel *time_label;
 216 
 217     // Query replace dialog
 218     WDialog *replace_dlg;
 219     const char *src_filename;
 220     const char *tgt_filename;
 221     replace_action_t replace_result;
 222     gboolean dont_overwrite_with_zero;
 223 
 224     struct stat *src_stat, *dst_stat;
 225 } file_progress_ui_t;
 226 
 227 /*** forward declarations (file scope functions) *************************************************/
 228 
 229 /*** file scope variables ************************************************************************/
 230 
 231 static struct
 232 {
 233     Widget *w;
 234     FileProgressStatus action;
 235     const char *text;
 236     button_flags_t flags;
 237     int width;
 238 } progress_buttons[] = {
 239     { NULL, FILE_SKIP, N_ ("&Skip"), NORMAL_BUTTON, -1 },
 240     { NULL, FILE_SUSPEND, N_ ("S&uspend"), NORMAL_BUTTON, -1 },
 241     { NULL, FILE_SUSPEND, N_ ("Con&tinue"), NORMAL_BUTTON, -1 },
 242     { NULL, FILE_ABORT, N_ ("&Abort"), NORMAL_BUTTON, -1 },
 243 };
 244 
 245 /* --------------------------------------------------------------------------------------------- */
 246 /*** file scope functions ************************************************************************/
 247 /* --------------------------------------------------------------------------------------------- */
 248 
 249 /* Return true if statvfs works.  This is false for statvfs on systems
 250    with GNU libc on Linux kernels before 2.6.36, which stats all
 251    preceding entries in /proc/mounts; that makes df hang if even one
 252    of the corresponding file systems is hard-mounted but not available.  */
 253 
 254 #if defined(USE_STATVFS) && !(!defined STAT_STATVFS && defined STAT_STATVFS64)
 255 static int
 256 statvfs_works (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 257 {
 258 #if !(defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
 259     return 1;
 260 #else
 261     static int statvfs_works_cache = -1;
 262     struct utsname name;
 263 
 264     if (statvfs_works_cache < 0)
 265         statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
 266     return statvfs_works_cache;
 267 #endif
 268 }
 269 #endif
 270 
 271 /* --------------------------------------------------------------------------------------------- */
 272 
 273 static gboolean
 274 filegui__check_attrs_on_fs (const char *fs_path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 275 {
 276     STRUCT_STATVFS stfs;
 277 
 278 #if defined(USE_STATVFS) && defined(STAT_STATVFS)
 279     if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
 280         return TRUE;
 281 #else
 282     if (STATFS (fs_path, &stfs) != 0)
 283         return TRUE;
 284 #endif
 285 
 286 #if (defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_TYPE))                                  \
 287     || (!defined(USE_STATVFS) && defined(HAVE_STRUCT_STATFS_F_TYPE))
 288     switch ((filegui_nonattrs_fs_t) stfs.f_type)
 289     {
 290     case MSDOS_SUPER_MAGIC:
 291     case NTFS_SB_MAGIC:
 292     case PROC_SUPER_MAGIC:
 293     case SMB_SUPER_MAGIC:
 294     case NCP_SUPER_MAGIC:
 295     case USBDEVICE_SUPER_MAGIC:
 296         return FALSE;
 297     default:
 298         break;
 299     }
 300 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
 301     if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
 302         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
 303         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
 304         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
 305         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
 306         || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
 307         return FALSE;
 308 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
 309     if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
 310         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
 311         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
 312         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
 313         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
 314         return FALSE;
 315 #endif
 316 
 317     return TRUE;
 318 }
 319 
 320 /* --------------------------------------------------------------------------------------------- */
 321 
 322 static void
 323 file_frmt_time (char *buffer, double eta_secs)
     /* [previous][next][first][last][top][bottom][index][help]  */
 324 {
 325     int eta_hours, eta_mins, eta_s;
 326 
 327     eta_hours = (int) (eta_secs / (60 * 60));
 328     eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
 329     eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
 330     g_snprintf (buffer, BUF_TINY, _ ("%d:%02d:%02d"), eta_hours, eta_mins, eta_s);
 331 }
 332 
 333 /* --------------------------------------------------------------------------------------------- */
 334 
 335 static void
 336 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
     /* [previous][next][first][last][top][bottom][index][help]  */
 337 {
 338     char _fmt_buff[BUF_TINY];
 339 
 340     if (eta_secs >= INT_MAX)
 341     {
 342         // Over 68 years
 343         g_strlcpy (_fmt_buff, "--", sizeof (_fmt_buff));
 344     }
 345     else
 346     {
 347         if (eta_secs <= 0.5)
 348         {
 349             if (!always_show)
 350             {
 351                 *buffer = '\0';
 352                 return;
 353             }
 354 
 355             eta_secs = 1.0;
 356         }
 357 
 358         file_frmt_time (_fmt_buff, eta_secs);
 359     }
 360 
 361     g_snprintf (buffer, BUF_TINY, _ ("ETA %s"), _fmt_buff);
 362 }
 363 
 364 /* --------------------------------------------------------------------------------------------- */
 365 
 366 static void
 367 file_bps_prepare_for_show (char *buffer, long bps)
     /* [previous][next][first][last][top][bottom][index][help]  */
 368 {
 369     if (bps > 1024 * 1024)
 370         g_snprintf (buffer, BUF_TINY, _ ("%.2f MB/s"), bps / (1024 * 1024.0));
 371     else if (bps > 1024)
 372         g_snprintf (buffer, BUF_TINY, _ ("%.2f KB/s"), bps / 1024.0);
 373     else if (bps > 1)
 374         g_snprintf (buffer, BUF_TINY, _ ("%ld B/s"), bps);
 375     else
 376         *buffer = '\0';
 377 }
 378 
 379 /* --------------------------------------------------------------------------------------------- */
 380 
 381 static cb_ret_t
 382 file_ui_op_dlg_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 383 {
 384     switch (msg)
 385     {
 386     case MSG_ACTION:
 387         // Do not close the dialog because the query dialog will be shown
 388         if (parm == CK_Cancel)
 389         {
 390             DIALOG (w)->ret_value = FILE_ABORT;  // for file_progress_check_buttons()
 391             return MSG_HANDLED;
 392         }
 393         return MSG_NOT_HANDLED;
 394 
 395     default:
 396         return dlg_default_callback (w, sender, msg, parm, data);
 397     }
 398 }
 399 
 400 /* --------------------------------------------------------------------------------------------- */
 401 
 402 /* The dialog layout:
 403  *
 404  * +---------------------- File exists -----------------------+
 405  * | New     : /path/to/original_file_name                    |   // 0, 1
 406  * |                    1234567             feb  4 2017 13:38 |   // 2, 3
 407  * | Existing: /path/to/target_file_name                      |   // 4, 5
 408  * |                 1234567890             feb  4 2017 13:37 |   // 6, 7
 409  * +----------------------------------------------------------+
 410  * |                   Overwrite this file?                   |   // 8
 411  * |            [ Yes ] [ No ] [ Append ] [ Reget ]           |   // 9, 10, 11, 12
 412  * +----------------------------------------------------------+
 413  * |                   Overwrite all files?                   |   // 13
 414  * |  [ ] Don't overwrite with zero length file               |   // 14
 415  * |  [ All ] [ Older ] [None] [ Smaller ] [ Size differs ]   |   // 15, 16, 17, 18, 19
 416  * +----------------------------------------------------------|
 417  * |                         [ Abort ]                        |   // 20
 418  * +----------------------------------------------------------+
 419  */
 420 
 421 static replace_action_t
 422 overwrite_query_dialog (file_op_context_t *ctx, enum OperationMode mode)
     /* [previous][next][first][last][top][bottom][index][help]  */
 423 {
 424 #define W(i)               dlg_widgets[i].widget
 425 #define WX(i)              W (i)->rect.x
 426 #define WY(i)              W (i)->rect.y
 427 #define WCOLS(i)           W (i)->rect.cols
 428 
 429 #define NEW_LABEL(i, text) W (i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
 430 
 431 #define ADD_LABEL(i)                                                                               \
 432     group_add_widget_autopos (g, W (i), dlg_widgets[i].pos_flags,                                  \
 433                               g->current != NULL ? g->current->data : NULL)
 434 
 435 #define NEW_BUTTON(i)                                                                              \
 436     W (i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, dlg_widgets[i].value,          \
 437                                 NORMAL_BUTTON, dlg_widgets[i].text, NULL))
 438 
 439 #define ADD_BUTTON(i)                                                                              \
 440     group_add_widget_autopos (g, W (i), dlg_widgets[i].pos_flags, g->current->data)
 441 
 442     // dialog sizes
 443     const int dlg_height = 17;
 444     int dlg_width = 60;
 445 
 446     struct
 447     {
 448         Widget *widget;
 449         const char *text;
 450         int y;
 451         int x;
 452         widget_pos_flags_t pos_flags;
 453         int value;  // 0 for labels and checkbox
 454     } dlg_widgets[] = {
 455         //  0 - label
 456         { NULL, N_ ("New     :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
 457         //  1 - label - name
 458         { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
 459         //  2 - label - size
 460         { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
 461         //  3 - label - date & time
 462         { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
 463         //  4 - label
 464         { NULL, N_ ("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
 465         //  5 - label - name
 466         { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
 467         //  6 - label - size
 468         { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
 469         //  7 - label - date & time
 470         { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
 471         // ---------------------------------------------------
 472         //  8 - label
 473         { NULL, N_ ("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
 474         //  9 - button
 475         { NULL, N_ ("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
 476         // 10 - button
 477         { NULL, N_ ("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
 478         // 11 - button
 479         { NULL, N_ ("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
 480         // 12 - button
 481         { NULL, N_ ("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
 482         // ---------------------------------------------------
 483         // 13 - label
 484         { NULL, N_ ("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
 485         // 14 - checkbox
 486         { NULL, N_ ("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
 487         // 15 - button
 488         { NULL, N_ ("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
 489         // 16 - button
 490         { NULL, N_ ("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
 491         // 17 - button
 492         { NULL, N_ ("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
 493         // 18 - button
 494         { NULL, N_ ("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
 495         // 19 - button
 496         { NULL, N_ ("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
 497         // ---------------------------------------------------
 498         // 20 - button
 499         { NULL, N_ ("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
 500     };
 501 
 502     const int gap = 1;
 503 
 504     file_progress_ui_t *ui = ctx->ui;
 505     Widget *wd;
 506     WGroup *g;
 507     const char *title;
 508 
 509     vfs_path_t *p;
 510     char *s1;
 511     const char *cs1;
 512     char s2[BUF_SMALL];
 513     int w, bw1, bw2;
 514     unsigned short i;
 515 
 516     gboolean do_append = FALSE, do_reget = FALSE;
 517     unsigned long yes_id, no_id;
 518     int result;
 519 
 520     const gint64 t = g_get_monotonic_time ();
 521 
 522     if (mode == Foreground)
 523         title = _ ("File exists");
 524     else
 525         title = _ ("Background process: File exists");
 526 
 527 #ifdef ENABLE_NLS
 528     {
 529         const unsigned short num = G_N_ELEMENTS (dlg_widgets);
 530 
 531         for (i = 0; i < num; i++)
 532             if (dlg_widgets[i].text != NULL)
 533                 dlg_widgets[i].text = _ (dlg_widgets[i].text);
 534     }
 535 #endif
 536 
 537     // create widgets to get their real widths
 538     // new file
 539     NEW_LABEL (0, dlg_widgets[0].text);
 540     // new file name
 541     p = vfs_path_from_str (ui->src_filename);
 542     s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
 543     NEW_LABEL (1, s1);
 544     vfs_path_free (p, TRUE);
 545     g_free (s1);
 546     // new file size
 547     size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
 548     NEW_LABEL (2, s2);
 549     // new file modification date & time
 550     cs1 = file_date (ui->src_stat->st_mtime);
 551     NEW_LABEL (3, cs1);
 552 
 553     // existing file
 554     NEW_LABEL (4, dlg_widgets[4].text);
 555     // existing file name
 556     p = vfs_path_from_str (ui->tgt_filename);
 557     s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
 558     NEW_LABEL (5, s1);
 559     vfs_path_free (p, TRUE);
 560     g_free (s1);
 561     // existing file size
 562     size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
 563     NEW_LABEL (6, s2);
 564     // existing file modification date & time
 565     cs1 = file_date (ui->dst_stat->st_mtime);
 566     NEW_LABEL (7, cs1);
 567 
 568     // will "Append" and "Reget" buttons be in the dialog?
 569     do_append = !S_ISDIR (ui->dst_stat->st_mode);
 570     do_reget =
 571         do_append && ui->dst_stat->st_size != 0 && ui->src_stat->st_size > ui->dst_stat->st_size;
 572 
 573     NEW_LABEL (8, dlg_widgets[8].text);
 574     NEW_BUTTON (9);
 575     NEW_BUTTON (10);
 576     if (do_append)
 577         NEW_BUTTON (11);
 578     if (do_reget)
 579         NEW_BUTTON (12);
 580 
 581     NEW_LABEL (13, dlg_widgets[13].text);
 582     dlg_widgets[14].widget =
 583         WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
 584     for (i = 15; i <= 20; i++)
 585         NEW_BUTTON (i);
 586 
 587     // place widgets
 588     dlg_width -= 2 * (2 + gap);  // inside frame
 589 
 590     // perhaps longest line is buttons
 591     bw1 = WCOLS (9) + gap + WCOLS (10);
 592     if (do_append)
 593         bw1 += gap + WCOLS (11);
 594     if (do_reget)
 595         bw1 += gap + WCOLS (12);
 596     dlg_width = MAX (dlg_width, bw1);
 597 
 598     bw2 = WCOLS (15);
 599     for (i = 16; i <= 19; i++)
 600         bw2 += gap + WCOLS (i);
 601     dlg_width = MAX (dlg_width, bw2);
 602 
 603     dlg_width = MAX (dlg_width, WCOLS (8));
 604     dlg_width = MAX (dlg_width, WCOLS (13));
 605     dlg_width = MAX (dlg_width, WCOLS (14));
 606 
 607     // truncate file names
 608     w = WCOLS (0) + gap + WCOLS (1);
 609     if (w > dlg_width)
 610     {
 611         WLabel *l = LABEL (W (1));
 612 
 613         w = dlg_width - gap - WCOLS (0);
 614         label_set_text (l, str_trunc (l->text, w));
 615     }
 616 
 617     w = WCOLS (4) + gap + WCOLS (5);
 618     if (w > dlg_width)
 619     {
 620         WLabel *l = LABEL (W (5));
 621 
 622         w = dlg_width - gap - WCOLS (4);
 623         label_set_text (l, str_trunc (l->text, w));
 624     }
 625 
 626     // real dialog width
 627     dlg_width += 2 * (2 + gap);
 628 
 629     WX (1) = WX (0) + WCOLS (0) + gap;
 630     WX (5) = WX (4) + WCOLS (4) + gap;
 631 
 632     // sizes: right alignment
 633     WX (2) = dlg_width / 2 - WCOLS (2);
 634     WX (6) = dlg_width / 2 - WCOLS (6);
 635 
 636     w = dlg_width - (2 + gap);  // right bound
 637 
 638     // date & time
 639     WX (3) = w - WCOLS (3);
 640     WX (7) = w - WCOLS (7);
 641 
 642     // buttons: center alignment
 643     WX (9) = dlg_width / 2 - bw1 / 2;
 644     WX (10) = WX (9) + WCOLS (9) + gap;
 645     if (do_append)
 646         WX (11) = WX (10) + WCOLS (10) + gap;
 647     if (do_reget)
 648         WX (12) = WX (11) + WCOLS (11) + gap;
 649 
 650     WX (15) = dlg_width / 2 - bw2 / 2;
 651     for (i = 16; i <= 19; i++)
 652         WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
 653 
 654     // TODO: write help (ticket #3970)
 655     ui->replace_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE,
 656                                   alarm_colors, NULL, NULL, "[Replace]", title);
 657     wd = WIDGET (ui->replace_dlg);
 658     g = GROUP (ui->replace_dlg);
 659 
 660     // file info
 661     for (i = 0; i <= 7; i++)
 662         ADD_LABEL (i);
 663     group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
 664 
 665     // label & buttons
 666     ADD_LABEL (8);            // Overwrite this file?
 667     yes_id = ADD_BUTTON (9);  // Yes
 668     no_id = ADD_BUTTON (10);  // No
 669     if (do_append)
 670         ADD_BUTTON (11);  // Append
 671     if (do_reget)
 672         ADD_BUTTON (12);  // Reget
 673     group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
 674 
 675     // label & buttons
 676     ADD_LABEL (13);  // Overwrite all files?
 677     group_add_widget (g, dlg_widgets[14].widget);
 678     for (i = 15; i <= 19; i++)
 679         ADD_BUTTON (i);
 680     group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
 681 
 682     ADD_BUTTON (20);  // Abort
 683 
 684     group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
 685 
 686     result = dlg_run (ui->replace_dlg);
 687 
 688     if (result != B_CANCEL)
 689         ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
 690 
 691     widget_destroy (wd);
 692 
 693     ctx->pauses += g_get_monotonic_time () - t;
 694 
 695     return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
 696 
 697 #undef ADD_BUTTON
 698 #undef NEW_BUTTON
 699 #undef ADD_LABEL
 700 #undef NEW_LABEL
 701 #undef WCOLS
 702 #undef WX
 703 #undef W
 704 }
 705 
 706 /* --------------------------------------------------------------------------------------------- */
 707 
 708 static gboolean
 709 is_wildcarded (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 710 {
 711     gboolean escaped = FALSE;
 712 
 713     for (; *p != '\0'; p++)
 714     {
 715         if (*p == '\\')
 716         {
 717             if (p[1] >= '1' && p[1] <= '9' && !escaped)
 718                 return TRUE;
 719             escaped = !escaped;
 720         }
 721         else
 722         {
 723             if ((*p == '*' || *p == '?') && !escaped)
 724                 return TRUE;
 725             escaped = FALSE;
 726         }
 727     }
 728     return FALSE;
 729 }
 730 
 731 /* --------------------------------------------------------------------------------------------- */
 732 
 733 static void
 734 place_progress_buttons (WDialog *h, const gboolean suspended)
     /* [previous][next][first][last][top][bottom][index][help]  */
 735 {
 736     const Widget *w = WIDGET (h);
 737 
 738     const size_t i = suspended ? 2 : 1;
 739     const int buttons_width =
 740         2 + progress_buttons[0].width + progress_buttons[3].width + progress_buttons[i].width;
 741 
 742     button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
 743 
 744     progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
 745     progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].width + 1;
 746     progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].width + 1;
 747 }
 748 
 749 /* --------------------------------------------------------------------------------------------- */
 750 
 751 static int
 752 progress_button_callback (MC_UNUSED WButton *button, MC_UNUSED int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
 753 {
 754     // don't close dialog in any case
 755     return 0;
 756 }
 757 
 758 /* --------------------------------------------------------------------------------------------- */
 759 /*** public functions ****************************************************************************/
 760 /* --------------------------------------------------------------------------------------------- */
 761 /**
 762  * \fn file_op_context_t * file_op_context_new (FileOperation op)
 763  * \param op file operation struct
 764  * \return The newly-created context, filled with the default file mask values.
 765  *
 766  * Creates a new file operation context with the default values.  If you later want
 767  * to have a user interface for this, call file_progress_ui_create().
 768  */
 769 
 770 file_op_context_t *
 771 file_op_context_new (const FileOperation op)
     /* [previous][next][first][last][top][bottom][index][help]  */
 772 {
 773     static gboolean i18n_flag = FALSE;
 774 
 775     file_op_context_t *ctx;
 776 
 777     if (!i18n_flag)
 778     {
 779         for (int i = G_N_ELEMENTS (op_names); i-- != 0;)
 780             op_names[i] = Q_ (op_names[i]);
 781         i18n_flag = TRUE;
 782     }
 783 
 784     ctx = g_new0 (file_op_context_t, 1);
 785     ctx->operation = op;
 786     ctx->preserve = TRUE;
 787     ctx->preserve_uidgid = (geteuid () == 0);
 788     ctx->umask_kill = (mode_t) (~0);
 789     ctx->erase_at_end = TRUE;
 790     ctx->do_reget = -1;
 791     ctx->stat_func = mc_lstat;
 792     ctx->ask_overwrite = TRUE;
 793 
 794     return ctx;
 795 }
 796 
 797 /* --------------------------------------------------------------------------------------------- */
 798 /**
 799  * \fn void file_op_context_destroy (file_op_context_t *ctx)
 800  * \param ctx The file operation context to destroy.
 801  *
 802  * Destroys the specified file operation context and its associated UI data, if
 803  * it exists.
 804  */
 805 
 806 void
 807 file_op_context_destroy (file_op_context_t *ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
 808 {
 809     if (ctx != NULL)
 810     {
 811         file_progress_ui_destroy (ctx);
 812         mc_search_free (ctx->search_handle);
 813         g_free (ctx);
 814     }
 815 }
 816 
 817 /* --------------------------------------------------------------------------------------------- */
 818 
 819 FileProgressStatus
 820 file_progress_check_buttons (file_op_context_t *ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
 821 {
 822     int c;
 823     Gpm_Event event;
 824 
 825     if (ctx == NULL || ctx->ui == NULL)
 826         return FILE_CONT;
 827 
 828     const file_progress_ui_t *ui = ctx->ui;
 829 
 830 get_event:
 831     event.x = -1;  // Don't show the GPM cursor
 832     c = tty_get_event (&event, FALSE, ctx->suspended);
 833     if (c == EV_NONE)
 834         return FILE_CONT;
 835 
 836     // Reinitialize to avoid old values after events other than selecting a button
 837     ui->op_dlg->ret_value = FILE_CONT;
 838 
 839     dlg_process_event (ui->op_dlg, c, &event);
 840     switch (ui->op_dlg->ret_value)
 841     {
 842     case FILE_SKIP:
 843         if (ctx->suspended)
 844         {
 845             // redraw dialog in case of Skip after Suspend
 846             place_progress_buttons (ui->op_dlg, FALSE);
 847             widget_draw (WIDGET (ui->op_dlg));
 848         }
 849         ctx->suspended = FALSE;
 850         return FILE_SKIP;
 851     case B_CANCEL:
 852     case FILE_ABORT:
 853         ctx->suspended = FALSE;
 854         return FILE_ABORT;
 855     case FILE_SUSPEND:
 856         ctx->suspended = !ctx->suspended;
 857         place_progress_buttons (ui->op_dlg, ctx->suspended);
 858         widget_draw (WIDGET (ui->op_dlg));
 859         MC_FALLTHROUGH;
 860     default:
 861         if (ctx->suspended)
 862             goto get_event;
 863         return FILE_CONT;
 864     }
 865 }
 866 
 867 /* --------------------------------------------------------------------------------------------- */
 868 /* {{{ File progress display routines */
 869 
 870 void
 871 file_progress_ui_create (file_op_context_t *ctx, gboolean with_eta,
     /* [previous][next][first][last][top][bottom][index][help]  */
 872                          filegui_dialog_type_t dialog_type)
 873 {
 874     file_progress_ui_t *ui;
 875     Widget *w;
 876     WGroup *g;
 877     int buttons_width;
 878     int dlg_width = 58, dlg_height = 17;
 879     int y = 2, x = 3;
 880     WRect r;
 881 
 882     if (ctx == NULL || ctx->ui != NULL)
 883         return;
 884 
 885 #ifdef ENABLE_NLS
 886     if (progress_buttons[0].width == -1)
 887     {
 888         size_t i;
 889 
 890         for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
 891             progress_buttons[i].text = _ (progress_buttons[i].text);
 892     }
 893 #endif
 894 
 895     ctx->dialog_type = dialog_type;
 896     ctx->recursive_result = RECURSIVE_YES;
 897     ctx->ui = g_new0 (file_progress_ui_t, 1);
 898 
 899     ui = ctx->ui;
 900     ui->replace_result = REPLACE_YES;
 901 
 902     ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
 903                              file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
 904     w = WIDGET (ui->op_dlg);
 905     g = GROUP (ui->op_dlg);
 906 
 907     if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
 908     {
 909         ui->showing_eta = with_eta && ctx->totals_computed;
 910         ui->showing_bps = with_eta;
 911 
 912         ui->src_file_label = label_new (y++, x, NULL);
 913         group_add_widget (g, ui->src_file_label);
 914 
 915         ui->src_file = label_new (y++, x, NULL);
 916         group_add_widget (g, ui->src_file);
 917 
 918         ui->tgt_file_label = label_new (y++, x, NULL);
 919         group_add_widget (g, ui->tgt_file_label);
 920 
 921         ui->tgt_file = label_new (y++, x, NULL);
 922         group_add_widget (g, ui->tgt_file);
 923 
 924         ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
 925         if (!classic_progressbar && (current_panel == right_panel))
 926             ui->progress_file_gauge->from_left_to_right = FALSE;
 927         group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
 928 
 929         ui->progress_file_label = label_new (y++, x, NULL);
 930         group_add_widget (g, ui->progress_file_label);
 931 
 932         if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
 933         {
 934             ui->total_bytes_label = hline_new (y++, -1, -1);
 935             group_add_widget (g, ui->total_bytes_label);
 936 
 937             if (ctx->totals_computed)
 938             {
 939                 ui->progress_total_gauge =
 940                     gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
 941                 if (!classic_progressbar && (current_panel == right_panel))
 942                     ui->progress_total_gauge->from_left_to_right = FALSE;
 943                 group_add_widget_autopos (g, ui->progress_total_gauge,
 944                                           WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
 945             }
 946 
 947             ui->total_files_processed_label = label_new (y++, x, NULL);
 948             group_add_widget (g, ui->total_files_processed_label);
 949 
 950             ui->time_label = label_new (y++, x, NULL);
 951             group_add_widget (g, ui->time_label);
 952         }
 953     }
 954     else
 955     {
 956         ui->src_file = label_new (y++, x, NULL);
 957         group_add_widget (g, ui->src_file);
 958 
 959         ui->total_files_processed_label = label_new (y++, x, NULL);
 960         group_add_widget (g, ui->total_files_processed_label);
 961     }
 962 
 963     group_add_widget (g, hline_new (y++, -1, -1));
 964 
 965     progress_buttons[0].w =
 966         WIDGET (button_new (y, 0, progress_buttons[0].action, progress_buttons[0].flags,
 967                             progress_buttons[0].text, progress_button_callback));
 968     if (progress_buttons[0].width == -1)
 969         progress_buttons[0].width = button_get_width (BUTTON (progress_buttons[0].w));
 970 
 971     progress_buttons[1].w =
 972         WIDGET (button_new (y, 0, progress_buttons[1].action, progress_buttons[1].flags,
 973                             progress_buttons[1].text, progress_button_callback));
 974     if (progress_buttons[1].width == -1)
 975         progress_buttons[1].width = button_get_width (BUTTON (progress_buttons[1].w));
 976 
 977     if (progress_buttons[2].width == -1)
 978     {
 979         // create and destroy button to get it length
 980         progress_buttons[2].w =
 981             WIDGET (button_new (y, 0, progress_buttons[2].action, progress_buttons[2].flags,
 982                                 progress_buttons[2].text, progress_button_callback));
 983         progress_buttons[2].width = button_get_width (BUTTON (progress_buttons[2].w));
 984         widget_destroy (progress_buttons[2].w);
 985     }
 986     progress_buttons[2].w = progress_buttons[1].w;
 987 
 988     progress_buttons[3].w =
 989         WIDGET (button_new (y, 0, progress_buttons[3].action, progress_buttons[3].flags,
 990                             progress_buttons[3].text, progress_button_callback));
 991     if (progress_buttons[3].width == -1)
 992         progress_buttons[3].width = button_get_width (BUTTON (progress_buttons[3].w));
 993 
 994     group_add_widget (g, progress_buttons[0].w);
 995     group_add_widget (g, progress_buttons[1].w);
 996     group_add_widget (g, progress_buttons[3].w);
 997 
 998     buttons_width = 2 + progress_buttons[0].width
 999         + MAX (progress_buttons[1].width, progress_buttons[2].width) + progress_buttons[3].width;
1000 
1001     // adjust dialog sizes
1002     r = w->rect;
1003     r.lines = y + 3;
1004     r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
1005     widget_set_size_rect (w, &r);
1006 
1007     place_progress_buttons (ui->op_dlg, FALSE);
1008 
1009     widget_select (progress_buttons[0].w);
1010 
1011     // we will manage the dialog without any help, that's why we have to call dlg_init
1012     dlg_init (ui->op_dlg);
1013 }
1014 
1015 /* --------------------------------------------------------------------------------------------- */
1016 
1017 void
1018 file_progress_ui_destroy (file_op_context_t *ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
1019 {
1020     if (ctx != NULL && ctx->ui != NULL)
1021     {
1022         const file_progress_ui_t *ui = ctx->ui;
1023 
1024         dlg_run_done (ui->op_dlg);
1025         widget_destroy (WIDGET (ui->op_dlg));
1026         MC_PTR_FREE (ctx->ui);
1027     }
1028 }
1029 
1030 /* --------------------------------------------------------------------------------------------- */
1031 
1032 void
1033 file_progress_show (file_op_context_t *ctx, off_t done, off_t total, const char *stalled_msg,
     /* [previous][next][first][last][top][bottom][index][help]  */
1034                     gboolean force_update)
1035 {
1036     if (ctx == NULL || ctx->ui == NULL)
1037         return;
1038 
1039     const file_progress_ui_t *ui = ctx->ui;
1040 
1041     if (total == 0)
1042     {
1043         gauge_show (ui->progress_file_gauge, FALSE);
1044         return;
1045     }
1046 
1047     gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
1048     gauge_show (ui->progress_file_gauge, TRUE);
1049 
1050     if (!force_update)
1051         return;
1052 
1053     if (!ui->showing_eta || ctx->eta_secs <= 0.5)
1054         label_set_text (ui->progress_file_label, stalled_msg);
1055     else
1056     {
1057         char buffer2[BUF_TINY];
1058 
1059         file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
1060         if (ctx->bps == 0)
1061             label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
1062         else
1063         {
1064             char buffer3[BUF_TINY];
1065 
1066             file_bps_prepare_for_show (buffer3, ctx->bps);
1067             label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
1068         }
1069     }
1070 }
1071 
1072 /* --------------------------------------------------------------------------------------------- */
1073 
1074 void
1075 file_progress_show_count (file_op_context_t *ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
1076 {
1077     if (ctx == NULL || ctx->ui == NULL)
1078         return;
1079 
1080     const file_progress_ui_t *ui = ctx->ui;
1081 
1082     if (ui->total_files_processed_label == NULL)
1083         return;
1084 
1085     if (ctx->totals_computed)
1086         label_set_textv (ui->total_files_processed_label, _ ("Files processed: %zu / %zu"),
1087                          ctx->total_progress_count, ctx->total_count);
1088     else
1089         label_set_textv (ui->total_files_processed_label, _ ("Files processed: %zu"),
1090                          ctx->total_progress_count);
1091 }
1092 
1093 /* --------------------------------------------------------------------------------------------- */
1094 
1095 void
1096 file_progress_show_total (file_op_context_t *ctx, uintmax_t copied_bytes, gint64 tv_current,
     /* [previous][next][first][last][top][bottom][index][help]  */
1097                           gboolean show_summary)
1098 {
1099     char buffer2[BUF_TINY];
1100     char buffer3[BUF_TINY];
1101 
1102     if (ctx == NULL || ctx->ui == NULL)
1103         return;
1104 
1105     const file_progress_ui_t *ui = ctx->ui;
1106 
1107     if (ui->progress_total_gauge != NULL)
1108     {
1109         if (ctx->total_bytes == 0)
1110             gauge_show (ui->progress_total_gauge, FALSE);
1111         else
1112         {
1113             gauge_set_value (ui->progress_total_gauge, 1024,
1114                              (int) (1024 * copied_bytes / ctx->total_bytes));
1115             gauge_show (ui->progress_total_gauge, TRUE);
1116         }
1117     }
1118 
1119     if (!show_summary && ctx->total_bps == 0)
1120         return;
1121 
1122     if (ui->time_label != NULL)
1123     {
1124         char buffer4[BUF_TINY];
1125 
1126         file_frmt_time (buffer2,
1127                         (tv_current - ctx->pauses - ctx->total_transfer_start) / G_USEC_PER_SEC);
1128 
1129         if (ctx->totals_computed)
1130         {
1131             file_eta_prepare_for_show (buffer3, ctx->total_eta_secs, TRUE);
1132             if (ctx->total_bps == 0)
1133                 label_set_textv (ui->time_label, _ ("Time: %s %s"), buffer2, buffer3);
1134             else
1135             {
1136                 file_bps_prepare_for_show (buffer4, ctx->total_bps);
1137                 label_set_textv (ui->time_label, _ ("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
1138             }
1139         }
1140         else
1141         {
1142             if (ctx->total_bps == 0)
1143                 label_set_textv (ui->time_label, _ ("Time: %s"), buffer2);
1144             else
1145             {
1146                 file_bps_prepare_for_show (buffer4, ctx->total_bps);
1147                 label_set_textv (ui->time_label, _ ("Time: %s (%s)"), buffer2, buffer4);
1148             }
1149         }
1150     }
1151 
1152     if (ui->total_bytes_label != NULL)
1153     {
1154         size_trunc_len (buffer2, 5, copied_bytes, 0, panels_options.kilobyte_si);
1155 
1156         if (!ctx->totals_computed)
1157             hline_set_textv (ui->total_bytes_label, _ (" Total: %s "), buffer2);
1158         else
1159         {
1160             size_trunc_len (buffer3, 5, ctx->total_bytes, 0, panels_options.kilobyte_si);
1161             hline_set_textv (ui->total_bytes_label, _ (" Total: %s / %s "), buffer2, buffer3);
1162         }
1163     }
1164 }
1165 
1166 /* --------------------------------------------------------------------------------------------- */
1167 
1168 void
1169 file_progress_show_source (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1170 {
1171     if (ctx == NULL || ctx->ui == NULL)
1172         return;
1173 
1174     const file_progress_ui_t *ui = ctx->ui;
1175 
1176     if (vpath != NULL)
1177     {
1178         label_set_text (ui->src_file_label, _ ("Source"));
1179         label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1180     }
1181     else
1182     {
1183         label_set_text (ui->src_file_label, NULL);
1184         label_set_text (ui->src_file, NULL);
1185     }
1186 }
1187 
1188 /* --------------------------------------------------------------------------------------------- */
1189 
1190 void
1191 file_progress_show_target (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1192 {
1193     if (ctx == NULL || ctx->ui == NULL)
1194         return;
1195 
1196     const file_progress_ui_t *ui = ctx->ui;
1197 
1198     if (vpath != NULL)
1199     {
1200         label_set_text (ui->tgt_file_label, _ ("Target"));
1201         label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1202     }
1203     else
1204     {
1205         label_set_text (ui->tgt_file_label, NULL);
1206         label_set_text (ui->tgt_file, NULL);
1207     }
1208 }
1209 
1210 /* --------------------------------------------------------------------------------------------- */
1211 
1212 gboolean
1213 file_progress_show_deleting (file_op_context_t *ctx, const vfs_path_t *vpath, size_t *count)
     /* [previous][next][first][last][top][bottom][index][help]  */
1214 {
1215     static gint64 timestamp = 0;
1216     const gint64 delay = G_USEC_PER_SEC / 25;  // update with 25 FPS rate
1217 
1218     if (ctx == NULL || ctx->ui == NULL)
1219         return FALSE;
1220 
1221     const gboolean ret = mc_time_elapsed (&timestamp, delay);
1222 
1223     if (ret)
1224     {
1225         file_progress_ui_t *ui;
1226         const char *s;
1227 
1228         ui = ctx->ui;
1229 
1230         if (ui->src_file_label != NULL)
1231             label_set_text (ui->src_file_label, _ ("Deleting"));
1232 
1233         s = vfs_path_as_str (vpath);
1234         label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1235     }
1236 
1237     if (count != NULL)
1238         (*count)++;
1239 
1240     return ret;
1241 }
1242 
1243 /* --------------------------------------------------------------------------------------------- */
1244 
1245 FileProgressStatus
1246 file_progress_real_query_replace (file_op_context_t *ctx, enum OperationMode mode, const char *src,
     /* [previous][next][first][last][top][bottom][index][help]  */
1247                                   struct stat *src_stat, const char *dst, struct stat *dst_stat)
1248 {
1249     file_progress_ui_t *ui;
1250     FileProgressStatus replace_with_zero;
1251 
1252     if (ctx == NULL || ctx->ui == NULL)
1253         return FILE_CONT;
1254 
1255     ui = ctx->ui;
1256 
1257     if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
1258         || ui->replace_result == REPLACE_APPEND)
1259     {
1260         ui->src_filename = src;
1261         ui->src_stat = src_stat;
1262         ui->tgt_filename = dst;
1263         ui->dst_stat = dst_stat;
1264         ui->replace_result = overwrite_query_dialog (ctx, mode);
1265     }
1266 
1267     replace_with_zero =
1268         (src_stat->st_size == 0 && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
1269 
1270     switch (ui->replace_result)
1271     {
1272     case REPLACE_OLDER:
1273         do_refresh ();
1274         if (src_stat->st_mtime > dst_stat->st_mtime)
1275             return replace_with_zero;
1276         else
1277             return FILE_SKIP;
1278 
1279     case REPLACE_SIZE:
1280         do_refresh ();
1281         if (src_stat->st_size == dst_stat->st_size)
1282             return FILE_SKIP;
1283         else
1284             return replace_with_zero;
1285 
1286     case REPLACE_SMALLER:
1287         do_refresh ();
1288         if (src_stat->st_size > dst_stat->st_size)
1289             return FILE_CONT;
1290         else
1291             return FILE_SKIP;
1292 
1293     case REPLACE_ALL:
1294         do_refresh ();
1295         return replace_with_zero;
1296 
1297     case REPLACE_REGET:
1298         // Careful: we fall through and set do_append
1299         ctx->do_reget = dst_stat->st_size;
1300         MC_FALLTHROUGH;
1301 
1302     case REPLACE_APPEND:
1303         ctx->do_append = TRUE;
1304         MC_FALLTHROUGH;
1305 
1306     case REPLACE_YES:
1307         do_refresh ();
1308         return FILE_CONT;
1309 
1310     case REPLACE_NO:
1311     case REPLACE_NONE:
1312         do_refresh ();
1313         return FILE_SKIP;
1314 
1315     case REPLACE_ABORT:
1316     default:
1317         return FILE_ABORT;
1318     }
1319 }
1320 
1321 /* --------------------------------------------------------------------------------------------- */
1322 
1323 char *
1324 file_mask_dialog (file_op_context_t *ctx, gboolean only_one, const char *format, const void *text,
     /* [previous][next][first][last][top][bottom][index][help]  */
1325                   const char *def_text, gboolean *do_bg)
1326 {
1327     gboolean preserve;
1328     size_t fmd_xlen;
1329     vfs_path_t *vpath;
1330     gboolean source_easy_patterns = easy_patterns;
1331     char fmd_buf[BUF_MEDIUM];
1332     char *dest_dir = NULL;
1333     char *tmp;
1334     char *def_text_secure;
1335 
1336     if (ctx == NULL)
1337         return NULL;
1338 
1339     // unselect checkbox if target filesystem doesn't support attributes
1340     preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
1341 
1342     ctx->stable_symlinks = FALSE;
1343     *do_bg = FALSE;
1344 
1345     // filter out a possible password from def_text
1346     vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1347     tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1348     vfs_path_free (vpath, TRUE);
1349 
1350     if (source_easy_patterns)
1351         def_text_secure = str_glob_escape (tmp);
1352     else
1353         def_text_secure = str_regex_escape (tmp);
1354     g_free (tmp);
1355 
1356     if (only_one)
1357     {
1358         int format_len, text_len;
1359         int max_len;
1360 
1361         format_len = str_term_width1 (format);
1362         text_len = str_term_width1 (text);
1363         max_len = COLS - 2 - 6;
1364 
1365         if (format_len + text_len <= max_len)
1366         {
1367             fmd_xlen = format_len + text_len + 6;
1368             fmd_xlen = MAX (fmd_xlen, 68);
1369         }
1370         else
1371         {
1372             text = str_trunc ((const char *) text, max_len - format_len);
1373             fmd_xlen = max_len + 6;
1374         }
1375 
1376         g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1377     }
1378     else
1379     {
1380         fmd_xlen = COLS * 2 / 3;
1381         fmd_xlen = MAX (fmd_xlen, 68);
1382         g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1383     }
1384 
1385     {
1386         char *source_mask = NULL;
1387         char *orig_mask;
1388         int val;
1389         struct stat buf;
1390 
1391         quick_widget_t quick_widgets[] = {
1392             // clang-format off
1393             QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
1394                                  "input-def", &source_mask, NULL, FALSE, FALSE,
1395                                  INPUT_COMPLETE_FILENAMES),
1396             QUICK_START_COLUMNS,
1397                 QUICK_SEPARATOR (FALSE),
1398             QUICK_NEXT_COLUMN,
1399                 QUICK_CHECKBOX (N_ ("&Using shell patterns"), &source_easy_patterns, NULL),
1400             QUICK_STOP_COLUMNS,
1401             QUICK_LABELED_INPUT (N_ ("to:"), input_label_above, def_text_secure, "input2",
1402                                  &dest_dir, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1403             QUICK_SEPARATOR (TRUE),
1404             QUICK_START_COLUMNS,
1405                 QUICK_CHECKBOX (N_ ("Follow &links"), &ctx->follow_links, NULL),
1406                 QUICK_CHECKBOX (N_ ("Preserve &attributes"), &preserve, NULL),
1407 #ifdef ENABLE_EXT2FS_ATTR
1408                 QUICK_CHECKBOX (N_ ("Preserve e&xt2 attributes"), &copymove_persistent_ext2_attr, NULL),
1409 #endif
1410             QUICK_NEXT_COLUMN,
1411                 QUICK_CHECKBOX (N_ ("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1412                 QUICK_CHECKBOX (N_ ("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1413             QUICK_STOP_COLUMNS,
1414             QUICK_START_BUTTONS (TRUE, TRUE),
1415                 QUICK_BUTTON (N_ ("&OK"), B_ENTER, NULL, NULL),
1416 #ifdef ENABLE_BACKGROUND
1417                 QUICK_BUTTON (N_ ("&Background"), B_USER, NULL, NULL),
1418 #endif
1419                 QUICK_BUTTON (N_ ("&Cancel"), B_CANCEL, NULL, NULL),
1420             QUICK_END,
1421             // clang-format on
1422         };
1423 
1424         WRect r = { -1, -1, 0, fmd_xlen };
1425 
1426         quick_dialog_t qdlg = {
1427             .rect = r,
1428             .title = op_names[ctx->operation],
1429             .help = "[Mask Copy/Rename]",
1430             .widgets = quick_widgets,
1431             .callback = NULL,
1432             .mouse_callback = NULL,
1433         };
1434 
1435         while (TRUE)
1436         {
1437             val = quick_dialog_skip (&qdlg, 4);
1438 
1439             if (val == B_CANCEL)
1440             {
1441                 g_free (def_text_secure);
1442                 return NULL;
1443             }
1444 
1445             ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
1446 
1447             if (preserve)
1448             {
1449                 ctx->preserve = TRUE;
1450                 ctx->umask_kill = (mode_t) (~0);
1451                 ctx->preserve_uidgid = (geteuid () == 0);
1452             }
1453             else
1454             {
1455                 mode_t i2;
1456 
1457                 ctx->preserve = ctx->preserve_uidgid = FALSE;
1458                 i2 = umask (0);
1459                 umask (i2);
1460                 ctx->umask_kill = i2 ^ ((mode_t) (~0));
1461             }
1462 
1463             if (*dest_dir == '\0')
1464             {
1465                 g_free (def_text_secure);
1466                 g_free (source_mask);
1467                 g_free (dest_dir);
1468                 return NULL;
1469             }
1470 
1471             ctx->search_handle = mc_search_new (source_mask, NULL);
1472             if (ctx->search_handle != NULL)
1473                 break;
1474 
1475             message (D_ERROR, MSG_ERROR, _ ("Invalid source pattern '%s'"), source_mask);
1476             MC_PTR_FREE (dest_dir);
1477             MC_PTR_FREE (source_mask);
1478         }
1479 
1480         g_free (def_text_secure);
1481         g_free (source_mask);
1482 
1483         ctx->search_handle->is_case_sensitive = TRUE;
1484         if (source_easy_patterns)
1485             ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1486         else
1487             ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1488 
1489         tmp = dest_dir;
1490         dest_dir = tilde_expand (tmp);
1491         g_free (tmp);
1492         vpath = vfs_path_from_str (dest_dir);
1493 
1494         ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1495         if (ctx->dest_mask == NULL)
1496             ctx->dest_mask = dest_dir;
1497         else
1498             ctx->dest_mask++;
1499 
1500         orig_mask = ctx->dest_mask;
1501 
1502         if (*ctx->dest_mask == '\0'
1503             || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1504                 && (!only_one || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1505             || (ctx->dive_into_subdirs
1506                 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1507                     || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1508             ctx->dest_mask = g_strdup ("\\0");
1509         else
1510         {
1511             ctx->dest_mask = g_strdup (ctx->dest_mask);
1512             *orig_mask = '\0';
1513         }
1514 
1515         if (*dest_dir == '\0')
1516         {
1517             g_free (dest_dir);
1518             dest_dir = g_strdup ("./");
1519         }
1520 
1521         vfs_path_free (vpath, TRUE);
1522 
1523         if (val == B_USER)
1524             *do_bg = TRUE;
1525     }
1526 
1527     return dest_dir;
1528 }
1529 
1530 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */