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

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