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. check_progress_buttons
  13. file_op_context_create_ui
  14. file_op_context_destroy_ui
  15. file_progress_show
  16. file_progress_show_count
  17. file_progress_show_total
  18. file_progress_show_source
  19. file_progress_show_target
  20. file_progress_show_deleting
  21. file_progress_real_query_replace
  22. file_mask_dialog

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

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