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

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