Manual pages: mc • mcdiff • mcedit • mcview

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

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