Manual pages: mcmcdiffmceditmcview

root/src/filemanager/filegui.c

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

DEFINITIONS

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

   1 /*
   2    File management GUI for the text mode edition
   3 
   4    The copy code was based in GNU's cp, and was written by:
   5    Torbjorn Granlund, David MacKenzie, and Jim Meyering.
   6 
   7    The move code was based in GNU's mv, and was written by:
   8    Mike Parker and David MacKenzie.
   9 
  10    Janne Kukonlehto added much error recovery to them for being used
  11    in an interactive program.
  12 
  13    Copyright (C) 1994-2025
  14    Free Software Foundation, Inc.
  15 
  16    Written by:
  17    Janne Kukonlehto, 1994, 1995
  18    Fred Leeflang, 1994, 1995
  19    Miguel de Icaza, 1994, 1995, 1996
  20    Jakub Jelinek, 1995, 1996
  21    Norbert Warmuth, 1997
  22    Pavel Machek, 1998
  23    Slava Zanko, 2009, 2010, 2011, 2012, 2013
  24    Andrew Borodin <aborodin@vmail.ru>, 2009-2023
  25 
  26    This file is part of the Midnight Commander.
  27 
  28    The Midnight Commander is free software: you can redistribute it
  29    and/or modify it under the terms of the GNU General Public License as
  30    published by the Free Software Foundation, either version 3 of the License,
  31    or (at your option) any later version.
  32 
  33    The Midnight Commander is distributed in the hope that it will be useful,
  34    but WITHOUT ANY WARRANTY; without even the implied warranty of
  35    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  36    GNU General Public License for more details.
  37 
  38    You should have received a copy of the GNU General Public License
  39    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  40  */
  41 
  42 /*
  43  * Please note that all dialogs used here must be safe for background
  44  * operations.
  45  */
  46 
  47 /** \file  filegui.c
  48  *  \brief Source: file management GUI for the text mode edition
  49  */
  50 
  51 /* {{{ Include files */
  52 
  53 #include <config.h>
  54 
  55 #include <errno.h>
  56 #include <ctype.h>
  57 #include <limits.h>  // INT_MAX
  58 #include <stdio.h>
  59 #include <string.h>
  60 #include <sys/types.h>
  61 #include <sys/stat.h>
  62 
  63 #if defined(USE_STATVFS)
  64 #include <sys/statvfs.h>
  65 #elif defined HAVE_SYS_VFS_H
  66 #include <sys/vfs.h>
  67 #elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H
  68 /* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
  69    It does have statvfs.h, but shouldn't use it, since it doesn't
  70    HAVE_STRUCT_STATVFS_F_BASETYPE.  So find a clean way to fix it.  */
  71 /* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
  72 #include <sys/param.h>
  73 #include <sys/mount.h>
  74 #elif defined HAVE_OS_H  // Haiku, also (obsolete) BeOS
  75 #include <fs_info.h>
  76 #endif
  77 
  78 #if defined(USE_STATVFS)
  79 #if !defined STAT_STATVFS && defined STAT_STATVFS64
  80 #define STRUCT_STATVFS struct statvfs64
  81 #define STATFS         statvfs64
  82 #else
  83 #define STRUCT_STATVFS struct statvfs
  84 #define STATFS         statvfs
  85 
  86 #if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)
  87 #include <sys/utsname.h>
  88 #include <sys/statfs.h>
  89 #define STAT_STATFS2_BSIZE 1
  90 #endif
  91 #endif
  92 
  93 #else
  94 #define STATFS         statfs
  95 #define STRUCT_STATVFS struct statfs
  96 #ifdef HAVE_OS_H  // Haiku, also (obsolete) BeOS
  97 /* BeOS has a statvfs function, but it does not return sensible values
  98    for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
  99    f_fstypename.  Use 'struct fs_info' instead.  */
 100 static int
 101 statfs (char const *filename, struct fs_info *buf)
     /* [previous][next][first][last][top][bottom][index][help]  */
 102 {
 103     dev_t device;
 104 
 105     device = dev_for_path (filename);
 106 
 107     if (device < 0)
 108     {
 109         errno = (device == B_ENTRY_NOT_FOUND     ? ENOENT
 110                      : device == B_BAD_VALUE     ? EINVAL
 111                      : device == B_NAME_TOO_LONG ? ENAMETOOLONG
 112                      : device == B_NO_MEMORY     ? ENOMEM
 113                      : device == B_FILE_ERROR    ? EIO
 114                                                  : 0);
 115         return -1;
 116     }
 117     // If successful, buf->dev will be == device.
 118     return fs_stat_dev (device, buf);
 119 }
 120 
 121 #define STRUCT_STATVFS struct fs_info
 122 #else
 123 #define STRUCT_STATVFS struct statfs
 124 #endif
 125 #endif
 126 
 127 #ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
 128 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
 129 #else
 130 #if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
 131 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
 132 #elif defined HAVE_OS_H  // Haiku, also (obsolete) BeOS
 133 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
 134 #endif
 135 #endif
 136 
 137 #include <unistd.h>
 138 
 139 #include "lib/global.h"
 140 
 141 #include "lib/tty/key.h"  // tty_get_event
 142 #include "lib/mcconfig.h"
 143 #include "lib/search.h"
 144 #include "lib/vfs/vfs.h"
 145 #include "lib/strutil.h"
 146 #include "lib/timefmt.h"  // file_date()
 147 #include "lib/util.h"
 148 #include "lib/widget.h"
 149 
 150 #include "src/setup.h"  // verbose, safe_overwrite, copymove_persistent_ext2_attr
 151 
 152 #include "filemanager.h"
 153 
 154 #include "filegui.h"
 155 
 156 /* }}} */
 157 
 158 /*** global variables ****************************************************************************/
 159 
 160 gboolean classic_progressbar = TRUE;
 161 
 162 /*** file scope macro definitions ****************************************************************/
 163 
 164 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
 165 
 166 /*** file scope type declarations ****************************************************************/
 167 
 168 typedef enum
 169 {
 170     MSDOS_SUPER_MAGIC = 0x4d44,
 171     NTFS_SB_MAGIC = 0x5346544e,
 172     FUSE_MAGIC = 0x65735546,
 173     PROC_SUPER_MAGIC = 0x9fa0,
 174     SMB_SUPER_MAGIC = 0x517B,
 175     NCP_SUPER_MAGIC = 0x564c,
 176     USBDEVICE_SUPER_MAGIC = 0x9fa2
 177 } filegui_nonattrs_fs_t;
 178 
 179 // Used for button result values
 180 typedef enum
 181 {
 182     REPLACE_YES = B_USER,
 183     REPLACE_NO,
 184     REPLACE_APPEND,
 185     REPLACE_REGET,
 186     REPLACE_ALL,
 187     REPLACE_OLDER,
 188     REPLACE_NONE,
 189     REPLACE_SMALLER,
 190     REPLACE_SIZE,
 191     REPLACE_ABORT
 192 } replace_action_t;
 193 
 194 // UI and internal data required by a file operation context.
 195 typedef struct
 196 {
 197     // ETA and bps
 198     gboolean showing_eta;
 199     gboolean showing_bps;
 200 
 201     // Dialog and widgets for the operation progress window
 202     WDialog *op_dlg;
 203     // Source file: label and name
 204     WLabel *src_file_label;
 205     WLabel *src_file;
 206     // Target file: label and name
 207     WLabel *tgt_file_label;
 208     WLabel *tgt_file;
 209     WGauge *progress_file_gauge;
 210     WLabel *progress_file_label;
 211 
 212     WHLine *total_bytes_label;
 213     WGauge *progress_total_gauge;
 214     WLabel *total_files_processed_label;
 215     WLabel *time_label;
 216 
 217     // Query replace dialog
 218     WDialog *replace_dlg;
 219     const char *src_filename;
 220     const char *tgt_filename;
 221     replace_action_t replace_result;
 222     gboolean dont_overwrite_with_zero;
 223 
 224     struct stat *src_stat, *dst_stat;
 225 } file_progress_ui_t;
 226 
 227 /*** forward declarations (file scope functions) *************************************************/
 228 
 229 /*** file scope variables ************************************************************************/
 230 
 231 static struct
 232 {
 233     Widget *w;
 234     FileProgressStatus action;
 235     const char *text;
 236     button_flags_t flags;
 237     int width;
 238 } progress_buttons[] = {
 239     { NULL, FILE_SKIP, N_ ("&Skip"), NORMAL_BUTTON, -1 },
 240     { NULL, FILE_SUSPEND, N_ ("S&uspend"), NORMAL_BUTTON, -1 },
 241     { NULL, FILE_SUSPEND, N_ ("Con&tinue"), NORMAL_BUTTON, -1 },
 242     { NULL, FILE_ABORT, N_ ("&Abort"), NORMAL_BUTTON, -1 },
 243 };
 244 
 245 /* --------------------------------------------------------------------------------------------- */
 246 /*** file scope functions ************************************************************************/
 247 /* --------------------------------------------------------------------------------------------- */
 248 
 249 /* Return true if statvfs works.  This is false for statvfs on systems
 250    with GNU libc on Linux kernels before 2.6.36, which stats all
 251    preceding entries in /proc/mounts; that makes df hang if even one
 252    of the corresponding file systems is hard-mounted but not available.  */
 253 
 254 #if defined(USE_STATVFS) && !(!defined STAT_STATVFS && defined STAT_STATVFS64)
 255 static int
 256 statvfs_works (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 257 {
 258 #if !(defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
 259     return 1;
 260 #else
 261     static int statvfs_works_cache = -1;
 262     struct utsname name;
 263 
 264     if (statvfs_works_cache < 0)
 265         statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
 266     return statvfs_works_cache;
 267 #endif
 268 }
 269 #endif
 270 
 271 /* --------------------------------------------------------------------------------------------- */
 272 
 273 static gboolean
 274 filegui__check_attrs_on_fs (const char *fs_path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 275 {
 276     STRUCT_STATVFS stfs;
 277 
 278 #if defined(USE_STATVFS) && defined(STAT_STATVFS)
 279     if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
 280         return TRUE;
 281 #else
 282     if (STATFS (fs_path, &stfs) != 0)
 283         return TRUE;
 284 #endif
 285 
 286 #if (defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_TYPE))                                  \
 287     || (!defined(USE_STATVFS) && defined(HAVE_STRUCT_STATFS_F_TYPE))
 288     switch ((filegui_nonattrs_fs_t) stfs.f_type)
 289     {
 290     case MSDOS_SUPER_MAGIC:
 291     case NTFS_SB_MAGIC:
 292     case PROC_SUPER_MAGIC:
 293     case SMB_SUPER_MAGIC:
 294     case NCP_SUPER_MAGIC:
 295     case USBDEVICE_SUPER_MAGIC:
 296         return FALSE;
 297     default:
 298         break;
 299     }
 300 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
 301     if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
 302         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
 303         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
 304         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
 305         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
 306         || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
 307         return FALSE;
 308 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
 309     if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
 310         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
 311         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
 312         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
 313         || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
 314         return FALSE;
 315 #endif
 316 
 317     return TRUE;
 318 }
 319 
 320 /* --------------------------------------------------------------------------------------------- */
 321 
 322 static void
 323 file_frmt_time (char *buffer, double eta_secs)
     /* [previous][next][first][last][top][bottom][index][help]  */
 324 {
 325     int eta_hours, eta_mins, eta_s;
 326 
 327     eta_hours = (int) (eta_secs / (60 * 60));
 328     eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
 329     eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
 330     g_snprintf (buffer, BUF_TINY, _ ("%d:%02d:%02d"), eta_hours, eta_mins, eta_s);
 331 }
 332 
 333 /* --------------------------------------------------------------------------------------------- */
 334 
 335 static void
 336 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
     /* [previous][next][first][last][top][bottom][index][help]  */
 337 {
 338     char _fmt_buff[BUF_TINY];
 339 
 340     if (eta_secs >= INT_MAX)
 341     {
 342         // Over 68 years
 343         g_strlcpy (_fmt_buff, "--", sizeof (_fmt_buff));
 344     }
 345     else
 346     {
 347         if (eta_secs <= 0.5)
 348         {
 349             if (!always_show)
 350             {
 351                 *buffer = '\0';
 352                 return;
 353             }
 354 
 355             eta_secs = 1.0;
 356         }
 357 
 358         file_frmt_time (_fmt_buff, eta_secs);
 359     }
 360 
 361     g_snprintf (buffer, BUF_TINY, _ ("ETA %s"), _fmt_buff);
 362 }
 363 
 364 /* --------------------------------------------------------------------------------------------- */
 365 
 366 static void
 367 file_bps_prepare_for_show (char *buffer, long bps)
     /* [previous][next][first][last][top][bottom][index][help]  */
 368 {
 369     if (bps > 1024 * 1024)
 370         g_snprintf (buffer, BUF_TINY, _ ("%.2f MB/s"), bps / (1024 * 1024.0));
 371     else if (bps > 1024)
 372         g_snprintf (buffer, BUF_TINY, _ ("%.2f KB/s"), bps / 1024.0);
 373     else if (bps > 1)
 374         g_snprintf (buffer, BUF_TINY, _ ("%ld B/s"), bps);
 375     else
 376         *buffer = '\0';
 377 }
 378 
 379 /* --------------------------------------------------------------------------------------------- */
 380 
 381 static cb_ret_t
 382 file_ui_op_dlg_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 383 {
 384     switch (msg)
 385     {
 386     case MSG_ACTION:
 387         // Do not close the dialog because the query dialog will be shown
 388         if (parm == CK_Cancel)
 389         {
 390             DIALOG (w)->ret_value = FILE_ABORT;  // for file_progress_check_buttons()
 391             return MSG_HANDLED;
 392         }
 393         return MSG_NOT_HANDLED;
 394 
 395     default:
 396         return dlg_default_callback (w, sender, msg, parm, data);
 397     }
 398 }
 399 
 400 /* --------------------------------------------------------------------------------------------- */
 401 
 402 /* The dialog layout:
 403  *
 404  * +---------------------- File exists -----------------------+
 405  * | New     : /path/to/original_file_name                    |   // 0, 1
 406  * |                    1234567             feb  4 2017 13:38 |   // 2, 3
 407  * | Existing: /path/to/target_file_name                      |   // 4, 5
 408  * |                 1234567890             feb  4 2017 13:37 |   // 6, 7
 409  * +----------------------------------------------------------+
 410  * |                   Overwrite this file?                   |   // 8
 411  * |            [ Yes ] [ No ] [ Append ] [ Reget ]           |   // 9, 10, 11, 12
 412  * +----------------------------------------------------------+
 413  * |                   Overwrite all files?                   |   // 13
 414  * |  [ ] Don't overwrite with zero length file               |   // 14
 415  * |  [ All ] [ Older ] [None] [ Smaller ] [ Size differs ]   |   // 15, 16, 17, 18, 19
 416  * +----------------------------------------------------------|
 417  * |                         [ Abort ]                        |   // 20
 418  * +----------------------------------------------------------+
 419  */
 420 
 421 static replace_action_t
 422 overwrite_query_dialog (file_op_context_t *ctx, enum OperationMode mode)
     /* [previous][next][first][last][top][bottom][index][help]  */
 423 {
 424 #define W(i)               dlg_widgets[i].widget
 425 #define WX(i)              W (i)->rect.x
 426 #define WY(i)              W (i)->rect.y
 427 #define WCOLS(i)           W (i)->rect.cols
 428 
 429 #define NEW_LABEL(i, text) W (i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
 430 
 431 #define ADD_LABEL(i)                                                                               \
 432     group_add_widget_autopos (g, W (i), dlg_widgets[i].pos_flags,                                  \
 433                               g->current != NULL ? g->current->data : NULL)
 434 
 435 #define NEW_BUTTON(i)                                                                              \
 436     W (i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, dlg_widgets[i].value,          \
 437                                 NORMAL_BUTTON, dlg_widgets[i].text, NULL))
 438 
 439 #define ADD_BUTTON(i)                                                                              \
 440     group_add_widget_autopos (g, W (i), dlg_widgets[i].pos_flags, g->current->data)
 441 
 442     // dialog sizes
 443     const int dlg_height = 17;
 444     int dlg_width = 60;
 445 
 446     struct
 447     {
 448         Widget *widget;
 449         const char *text;
 450         int y;
 451         int x;
 452         widget_pos_flags_t pos_flags;
 453         int value;  // 0 for labels and checkbox
 454     } dlg_widgets[] = {
 455         //  0 - label
 456         { NULL, _ ("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 
 872     if (ctx == NULL || ctx->ui != NULL)
 873         return;
 874 
 875 #ifdef ENABLE_NLS
 876     if (progress_buttons[0].width == -1)
 877     {
 878         size_t i;
 879 
 880         for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
 881             progress_buttons[i].text = _ (progress_buttons[i].text);
 882     }
 883 #endif
 884 
 885     ctx->dialog_type = dialog_type;
 886     ctx->recursive_result = RECURSIVE_YES;
 887     ctx->ui = g_new0 (file_progress_ui_t, 1);
 888 
 889     ui = ctx->ui;
 890     ui->replace_result = REPLACE_YES;
 891 
 892     ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
 893                              file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
 894     w = WIDGET (ui->op_dlg);
 895     g = GROUP (ui->op_dlg);
 896 
 897     if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
 898     {
 899         ui->showing_eta = with_eta && ctx->totals_computed;
 900         ui->showing_bps = with_eta;
 901 
 902         ui->src_file_label = label_new (y++, x, NULL);
 903         group_add_widget (g, ui->src_file_label);
 904 
 905         ui->src_file = label_new (y++, x, NULL);
 906         group_add_widget (g, ui->src_file);
 907 
 908         ui->tgt_file_label = label_new (y++, x, NULL);
 909         group_add_widget (g, ui->tgt_file_label);
 910 
 911         ui->tgt_file = label_new (y++, x, NULL);
 912         group_add_widget (g, ui->tgt_file);
 913 
 914         ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
 915         if (!classic_progressbar && (current_panel == right_panel))
 916             ui->progress_file_gauge->from_left_to_right = FALSE;
 917         group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
 918 
 919         ui->progress_file_label = label_new (y++, x, NULL);
 920         group_add_widget (g, ui->progress_file_label);
 921 
 922         if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
 923         {
 924             ui->total_bytes_label = hline_new (y++, -1, -1);
 925             group_add_widget (g, ui->total_bytes_label);
 926 
 927             if (ctx->totals_computed)
 928             {
 929                 ui->progress_total_gauge =
 930                     gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
 931                 if (!classic_progressbar && (current_panel == right_panel))
 932                     ui->progress_total_gauge->from_left_to_right = FALSE;
 933                 group_add_widget_autopos (g, ui->progress_total_gauge,
 934                                           WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
 935             }
 936 
 937             ui->total_files_processed_label = label_new (y++, x, NULL);
 938             group_add_widget (g, ui->total_files_processed_label);
 939 
 940             ui->time_label = label_new (y++, x, NULL);
 941             group_add_widget (g, ui->time_label);
 942         }
 943     }
 944     else
 945     {
 946         ui->src_file = label_new (y++, x, NULL);
 947         group_add_widget (g, ui->src_file);
 948 
 949         ui->total_files_processed_label = label_new (y++, x, NULL);
 950         group_add_widget (g, ui->total_files_processed_label);
 951     }
 952 
 953     group_add_widget (g, hline_new (y++, -1, -1));
 954 
 955     progress_buttons[0].w =
 956         WIDGET (button_new (y, 0, progress_buttons[0].action, progress_buttons[0].flags,
 957                             progress_buttons[0].text, progress_button_callback));
 958     if (progress_buttons[0].width == -1)
 959         progress_buttons[0].width = button_get_width (BUTTON (progress_buttons[0].w));
 960 
 961     progress_buttons[1].w =
 962         WIDGET (button_new (y, 0, progress_buttons[1].action, progress_buttons[1].flags,
 963                             progress_buttons[1].text, progress_button_callback));
 964     if (progress_buttons[1].width == -1)
 965         progress_buttons[1].width = button_get_width (BUTTON (progress_buttons[1].w));
 966 
 967     if (progress_buttons[2].width == -1)
 968     {
 969         // create and destroy button to get it length
 970         progress_buttons[2].w =
 971             WIDGET (button_new (y, 0, progress_buttons[2].action, progress_buttons[2].flags,
 972                                 progress_buttons[2].text, progress_button_callback));
 973         progress_buttons[2].width = button_get_width (BUTTON (progress_buttons[2].w));
 974         widget_destroy (progress_buttons[2].w);
 975     }
 976     progress_buttons[2].w = progress_buttons[1].w;
 977 
 978     progress_buttons[3].w =
 979         WIDGET (button_new (y, 0, progress_buttons[3].action, progress_buttons[3].flags,
 980                             progress_buttons[3].text, progress_button_callback));
 981     if (progress_buttons[3].width == -1)
 982         progress_buttons[3].width = button_get_width (BUTTON (progress_buttons[3].w));
 983 
 984     group_add_widget (g, progress_buttons[0].w);
 985     group_add_widget (g, progress_buttons[1].w);
 986     group_add_widget (g, progress_buttons[3].w);
 987 
 988     buttons_width = 2 + progress_buttons[0].width
 989         + MAX (progress_buttons[1].width, progress_buttons[2].width) + progress_buttons[3].width;
 990 
 991     // adjust dialog sizes
 992     r = w->rect;
 993     r.lines = y + 3;
 994     r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
 995     widget_set_size_rect (w, &r);
 996 
 997     place_progress_buttons (ui->op_dlg, FALSE);
 998 
 999     widget_select (progress_buttons[0].w);
1000 
1001     // we will manage the dialog without any help, that's why we have to call dlg_init
1002     dlg_init (ui->op_dlg);
1003 }
1004 
1005 /* --------------------------------------------------------------------------------------------- */
1006 
1007 void
1008 file_progress_ui_destroy (file_op_context_t *ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
1009 {
1010     if (ctx != NULL && ctx->ui != NULL)
1011     {
1012         const file_progress_ui_t *ui = ctx->ui;
1013 
1014         dlg_run_done (ui->op_dlg);
1015         widget_destroy (WIDGET (ui->op_dlg));
1016         MC_PTR_FREE (ctx->ui);
1017     }
1018 }
1019 
1020 /* --------------------------------------------------------------------------------------------- */
1021 
1022 void
1023 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]  */
1024                     gboolean force_update)
1025 {
1026     if (ctx == NULL || ctx->ui == NULL)
1027         return;
1028 
1029     const file_progress_ui_t *ui = ctx->ui;
1030 
1031     if (total == 0)
1032     {
1033         gauge_show (ui->progress_file_gauge, FALSE);
1034         return;
1035     }
1036 
1037     gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
1038     gauge_show (ui->progress_file_gauge, TRUE);
1039 
1040     if (!force_update)
1041         return;
1042 
1043     if (!ui->showing_eta || ctx->eta_secs <= 0.5)
1044         label_set_text (ui->progress_file_label, stalled_msg);
1045     else
1046     {
1047         char buffer2[BUF_TINY];
1048 
1049         file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
1050         if (ctx->bps == 0)
1051             label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
1052         else
1053         {
1054             char buffer3[BUF_TINY];
1055 
1056             file_bps_prepare_for_show (buffer3, ctx->bps);
1057             label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
1058         }
1059     }
1060 }
1061 
1062 /* --------------------------------------------------------------------------------------------- */
1063 
1064 void
1065 file_progress_show_count (file_op_context_t *ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
1066 {
1067     if (ctx == NULL || ctx->ui == NULL)
1068         return;
1069 
1070     const file_progress_ui_t *ui = ctx->ui;
1071 
1072     if (ui->total_files_processed_label == NULL)
1073         return;
1074 
1075     if (ctx->totals_computed)
1076         label_set_textv (ui->total_files_processed_label, _ ("Files processed: %zu / %zu"),
1077                          ctx->total_progress_count, ctx->total_count);
1078     else
1079         label_set_textv (ui->total_files_processed_label, _ ("Files processed: %zu"),
1080                          ctx->total_progress_count);
1081 }
1082 
1083 /* --------------------------------------------------------------------------------------------- */
1084 
1085 void
1086 file_progress_show_total (file_op_context_t *ctx, uintmax_t copied_bytes, gint64 tv_current,
     /* [previous][next][first][last][top][bottom][index][help]  */
1087                           gboolean show_summary)
1088 {
1089     char buffer2[BUF_TINY];
1090     char buffer3[BUF_TINY];
1091 
1092     if (ctx == NULL || ctx->ui == NULL)
1093         return;
1094 
1095     const file_progress_ui_t *ui = ctx->ui;
1096 
1097     if (ui->progress_total_gauge != NULL)
1098     {
1099         if (ctx->total_bytes == 0)
1100             gauge_show (ui->progress_total_gauge, FALSE);
1101         else
1102         {
1103             gauge_set_value (ui->progress_total_gauge, 1024,
1104                              (int) (1024 * copied_bytes / ctx->total_bytes));
1105             gauge_show (ui->progress_total_gauge, TRUE);
1106         }
1107     }
1108 
1109     if (!show_summary && ctx->total_bps == 0)
1110         return;
1111 
1112     if (ui->time_label != NULL)
1113     {
1114         char buffer4[BUF_TINY];
1115 
1116         file_frmt_time (buffer2,
1117                         (tv_current - ctx->pauses - ctx->total_transfer_start) / G_USEC_PER_SEC);
1118 
1119         if (ctx->totals_computed)
1120         {
1121             file_eta_prepare_for_show (buffer3, ctx->total_eta_secs, TRUE);
1122             if (ctx->total_bps == 0)
1123                 label_set_textv (ui->time_label, _ ("Time: %s %s"), buffer2, buffer3);
1124             else
1125             {
1126                 file_bps_prepare_for_show (buffer4, ctx->total_bps);
1127                 label_set_textv (ui->time_label, _ ("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
1128             }
1129         }
1130         else
1131         {
1132             if (ctx->total_bps == 0)
1133                 label_set_textv (ui->time_label, _ ("Time: %s"), buffer2);
1134             else
1135             {
1136                 file_bps_prepare_for_show (buffer4, ctx->total_bps);
1137                 label_set_textv (ui->time_label, _ ("Time: %s (%s)"), buffer2, buffer4);
1138             }
1139         }
1140     }
1141 
1142     if (ui->total_bytes_label != NULL)
1143     {
1144         size_trunc_len (buffer2, 5, copied_bytes, 0, panels_options.kilobyte_si);
1145 
1146         if (!ctx->totals_computed)
1147             hline_set_textv (ui->total_bytes_label, _ (" Total: %s "), buffer2);
1148         else
1149         {
1150             size_trunc_len (buffer3, 5, ctx->total_bytes, 0, panels_options.kilobyte_si);
1151             hline_set_textv (ui->total_bytes_label, _ (" Total: %s / %s "), buffer2, buffer3);
1152         }
1153     }
1154 }
1155 
1156 /* --------------------------------------------------------------------------------------------- */
1157 
1158 void
1159 file_progress_show_source (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1160 {
1161     if (ctx == NULL || ctx->ui == NULL)
1162         return;
1163 
1164     const file_progress_ui_t *ui = ctx->ui;
1165 
1166     if (vpath != NULL)
1167     {
1168         label_set_text (ui->src_file_label, _ ("Source"));
1169         label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1170     }
1171     else
1172     {
1173         label_set_text (ui->src_file_label, NULL);
1174         label_set_text (ui->src_file, NULL);
1175     }
1176 }
1177 
1178 /* --------------------------------------------------------------------------------------------- */
1179 
1180 void
1181 file_progress_show_target (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1182 {
1183     if (ctx == NULL || ctx->ui == NULL)
1184         return;
1185 
1186     const file_progress_ui_t *ui = ctx->ui;
1187 
1188     if (vpath != NULL)
1189     {
1190         label_set_text (ui->tgt_file_label, _ ("Target"));
1191         label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1192     }
1193     else
1194     {
1195         label_set_text (ui->tgt_file_label, NULL);
1196         label_set_text (ui->tgt_file, NULL);
1197     }
1198 }
1199 
1200 /* --------------------------------------------------------------------------------------------- */
1201 
1202 gboolean
1203 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]  */
1204 {
1205     static gint64 timestamp = 0;
1206     const gint64 delay = G_USEC_PER_SEC / 25;  // update with 25 FPS rate
1207 
1208     if (ctx == NULL || ctx->ui == NULL)
1209         return FALSE;
1210 
1211     const gboolean ret = mc_time_elapsed (&timestamp, delay);
1212 
1213     if (ret)
1214     {
1215         file_progress_ui_t *ui;
1216         const char *s;
1217 
1218         ui = ctx->ui;
1219 
1220         if (ui->src_file_label != NULL)
1221             label_set_text (ui->src_file_label, _ ("Deleting"));
1222 
1223         s = vfs_path_as_str (vpath);
1224         label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1225     }
1226 
1227     if (count != NULL)
1228         (*count)++;
1229 
1230     return ret;
1231 }
1232 
1233 /* --------------------------------------------------------------------------------------------- */
1234 
1235 FileProgressStatus
1236 file_progress_real_query_replace (file_op_context_t *ctx, enum OperationMode mode, const char *src,
     /* [previous][next][first][last][top][bottom][index][help]  */
1237                                   struct stat *src_stat, const char *dst, struct stat *dst_stat)
1238 {
1239     file_progress_ui_t *ui;
1240     FileProgressStatus replace_with_zero;
1241 
1242     if (ctx == NULL || ctx->ui == NULL)
1243         return FILE_CONT;
1244 
1245     ui = ctx->ui;
1246 
1247     if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
1248         || ui->replace_result == REPLACE_APPEND)
1249     {
1250         ui->src_filename = src;
1251         ui->src_stat = src_stat;
1252         ui->tgt_filename = dst;
1253         ui->dst_stat = dst_stat;
1254         ui->replace_result = overwrite_query_dialog (ctx, mode);
1255     }
1256 
1257     replace_with_zero =
1258         (src_stat->st_size == 0 && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
1259 
1260     switch (ui->replace_result)
1261     {
1262     case REPLACE_OLDER:
1263         do_refresh ();
1264         if (src_stat->st_mtime > dst_stat->st_mtime)
1265             return replace_with_zero;
1266         else
1267             return FILE_SKIP;
1268 
1269     case REPLACE_SIZE:
1270         do_refresh ();
1271         if (src_stat->st_size == dst_stat->st_size)
1272             return FILE_SKIP;
1273         else
1274             return replace_with_zero;
1275 
1276     case REPLACE_SMALLER:
1277         do_refresh ();
1278         if (src_stat->st_size > dst_stat->st_size)
1279             return FILE_CONT;
1280         else
1281             return FILE_SKIP;
1282 
1283     case REPLACE_ALL:
1284         do_refresh ();
1285         return replace_with_zero;
1286 
1287     case REPLACE_REGET:
1288         // Careful: we fall through and set do_append
1289         ctx->do_reget = dst_stat->st_size;
1290         MC_FALLTHROUGH;
1291 
1292     case REPLACE_APPEND:
1293         ctx->do_append = TRUE;
1294         MC_FALLTHROUGH;
1295 
1296     case REPLACE_YES:
1297         do_refresh ();
1298         return FILE_CONT;
1299 
1300     case REPLACE_NO:
1301     case REPLACE_NONE:
1302         do_refresh ();
1303         return FILE_SKIP;
1304 
1305     case REPLACE_ABORT:
1306     default:
1307         return FILE_ABORT;
1308     }
1309 }
1310 
1311 /* --------------------------------------------------------------------------------------------- */
1312 
1313 char *
1314 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]  */
1315                   const char *def_text, gboolean *do_bg)
1316 {
1317     gboolean preserve;
1318     size_t fmd_xlen;
1319     vfs_path_t *vpath;
1320     gboolean source_easy_patterns = easy_patterns;
1321     char fmd_buf[BUF_MEDIUM];
1322     char *dest_dir = NULL;
1323     char *tmp;
1324     char *def_text_secure;
1325 
1326     if (ctx == NULL)
1327         return NULL;
1328 
1329     // unselect checkbox if target filesystem doesn't support attributes
1330     preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
1331 
1332     ctx->stable_symlinks = FALSE;
1333     *do_bg = FALSE;
1334 
1335     // filter out a possible password from def_text
1336     vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1337     tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1338     vfs_path_free (vpath, TRUE);
1339 
1340     if (source_easy_patterns)
1341         def_text_secure = str_glob_escape (tmp);
1342     else
1343         def_text_secure = str_regex_escape (tmp);
1344     g_free (tmp);
1345 
1346     if (only_one)
1347     {
1348         int format_len, text_len;
1349         int max_len;
1350 
1351         format_len = str_term_width1 (format);
1352         text_len = str_term_width1 (text);
1353         max_len = COLS - 2 - 6;
1354 
1355         if (format_len + text_len <= max_len)
1356         {
1357             fmd_xlen = format_len + text_len + 6;
1358             fmd_xlen = MAX (fmd_xlen, 68);
1359         }
1360         else
1361         {
1362             text = str_trunc ((const char *) text, max_len - format_len);
1363             fmd_xlen = max_len + 6;
1364         }
1365 
1366         g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1367     }
1368     else
1369     {
1370         fmd_xlen = COLS * 2 / 3;
1371         fmd_xlen = MAX (fmd_xlen, 68);
1372         g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1373     }
1374 
1375     {
1376         char *source_mask = NULL;
1377         char *orig_mask;
1378         int val;
1379         struct stat buf;
1380 
1381         quick_widget_t quick_widgets[] = {
1382             // clang-format off
1383             QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
1384                                  "input-def", &source_mask, NULL, FALSE, FALSE,
1385                                  INPUT_COMPLETE_FILENAMES),
1386             QUICK_START_COLUMNS,
1387                 QUICK_SEPARATOR (FALSE),
1388             QUICK_NEXT_COLUMN,
1389                 QUICK_CHECKBOX (_ ("&Using shell patterns"), &source_easy_patterns, NULL),
1390             QUICK_STOP_COLUMNS,
1391             QUICK_LABELED_INPUT (_ ("to:"), input_label_above, def_text_secure, "input2",
1392                                  &dest_dir, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1393             QUICK_SEPARATOR (TRUE),
1394             QUICK_START_COLUMNS,
1395                 QUICK_CHECKBOX (_ ("Follow &links"), &ctx->follow_links, NULL),
1396                 QUICK_CHECKBOX (_ ("Preserve &attributes"), &preserve, NULL),
1397 #ifdef ENABLE_EXT2FS_ATTR
1398                 QUICK_CHECKBOX (_ ("Preserve e&xt2 attributes"), &copymove_persistent_ext2_attr, NULL),
1399 #endif
1400             QUICK_NEXT_COLUMN,
1401                 QUICK_CHECKBOX (_ ("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1402                 QUICK_CHECKBOX (_ ("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1403             QUICK_STOP_COLUMNS,
1404             QUICK_START_BUTTONS (TRUE, TRUE),
1405                 QUICK_BUTTON (_ ("&OK"), B_ENTER, NULL, NULL),
1406 #ifdef ENABLE_BACKGROUND
1407                 QUICK_BUTTON (_ ("&Background"), B_USER, NULL, NULL),
1408 #endif
1409                 QUICK_BUTTON (_ ("&Cancel"), B_CANCEL, NULL, NULL),
1410             QUICK_END,
1411             // clang-format on
1412         };
1413 
1414         WRect r = { -1, -1, 0, fmd_xlen };
1415 
1416         quick_dialog_t qdlg = {
1417             .rect = r,
1418             .title = op_names[ctx->operation],
1419             .help = "[Mask Copy/Rename]",
1420             .widgets = quick_widgets,
1421             .callback = NULL,
1422             .mouse_callback = NULL,
1423         };
1424 
1425         while (TRUE)
1426         {
1427             val = quick_dialog_skip (&qdlg, 4);
1428 
1429             if (val == B_CANCEL)
1430             {
1431                 g_free (def_text_secure);
1432                 return NULL;
1433             }
1434 
1435             ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
1436 
1437             if (preserve)
1438             {
1439                 ctx->preserve = TRUE;
1440                 ctx->umask_kill = (mode_t) (~0);
1441                 ctx->preserve_uidgid = (geteuid () == 0);
1442             }
1443             else
1444             {
1445                 mode_t i2;
1446 
1447                 ctx->preserve = ctx->preserve_uidgid = FALSE;
1448                 i2 = umask (0);
1449                 umask (i2);
1450                 ctx->umask_kill = i2 ^ ((mode_t) (~0));
1451             }
1452 
1453             if (*dest_dir == '\0')
1454             {
1455                 g_free (def_text_secure);
1456                 g_free (source_mask);
1457                 g_free (dest_dir);
1458                 return NULL;
1459             }
1460 
1461             ctx->search_handle = mc_search_new (source_mask, NULL);
1462             if (ctx->search_handle != NULL)
1463                 break;
1464 
1465             message (D_ERROR, MSG_ERROR, _ ("Invalid source pattern '%s'"), source_mask);
1466             MC_PTR_FREE (dest_dir);
1467             MC_PTR_FREE (source_mask);
1468         }
1469 
1470         g_free (def_text_secure);
1471         g_free (source_mask);
1472 
1473         ctx->search_handle->is_case_sensitive = TRUE;
1474         if (source_easy_patterns)
1475             ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1476         else
1477             ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1478 
1479         tmp = dest_dir;
1480         dest_dir = tilde_expand (tmp);
1481         g_free (tmp);
1482         vpath = vfs_path_from_str (dest_dir);
1483 
1484         ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1485         if (ctx->dest_mask == NULL)
1486             ctx->dest_mask = dest_dir;
1487         else
1488             ctx->dest_mask++;
1489 
1490         orig_mask = ctx->dest_mask;
1491 
1492         if (*ctx->dest_mask == '\0'
1493             || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1494                 && (!only_one || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1495             || (ctx->dive_into_subdirs
1496                 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1497                     || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1498             ctx->dest_mask = g_strdup ("\\0");
1499         else
1500         {
1501             ctx->dest_mask = g_strdup (ctx->dest_mask);
1502             *orig_mask = '\0';
1503         }
1504 
1505         if (*dest_dir == '\0')
1506         {
1507             g_free (dest_dir);
1508             dest_dir = g_strdup ("./");
1509         }
1510 
1511         vfs_path_free (vpath, TRUE);
1512 
1513         if (val == B_USER)
1514             *do_bg = TRUE;
1515     }
1516 
1517     return dest_dir;
1518 }
1519 
1520 /* --------------------------------------------------------------------------------------------- */

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