Manual pages: mcmcdiffmceditmcview

root/src/filemanager/filegui.c

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

DEFINITIONS

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

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

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