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

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