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-2022
  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-2022
  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/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 "filemanager.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)->rect.cols - 10)
 172 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.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 ! (defined __linux__ && (defined __GLIBC__ || defined __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  * |  [ ] Don't overwrite with zero length file               |   // 14
 396  * |  [ All ] [ Older ] [None] [ Smaller ] [ Size differs ]   |   // 15, 16, 17, 18, 19
 397  * +----------------------------------------------------------|
 398  * |                         [ Abort ]                        |   // 20
 399  * +----------------------------------------------------------+
 400  */
 401 
 402 static replace_action_t
 403 overwrite_query_dialog (file_op_context_t * ctx, enum OperationMode mode)
     /* [previous][next][first][last][top][bottom][index][help]  */
 404 {
 405 #define W(i) dlg_widgets[i].widget
 406 #define WX(i) W(i)->rect.x
 407 #define WY(i) W(i)->rect.y
 408 #define WCOLS(i) W(i)->rect.cols
 409 
 410 #define NEW_LABEL(i, text) \
 411     W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
 412 
 413 #define ADD_LABEL(i) \
 414     group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
 415                               g->current != NULL ? g->current->data : NULL)
 416 
 417 #define NEW_BUTTON(i) \
 418     W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
 419                                dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
 420 
 421 #define ADD_BUTTON(i) \
 422     group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
 423 
 424     /* dialog sizes */
 425     const int dlg_height = 17;
 426     int dlg_width = 60;
 427 
 428     struct
 429     {
 430         Widget *widget;
 431         const char *text;
 432         int y;
 433         int x;
 434         widget_pos_flags_t pos_flags;
 435         int value;              /* 0 for labels and checkbox */
 436     } dlg_widgets[] =
 437     {
 438         /* *INDENT-OFF* */
 439         /*  0 - label */
 440         { NULL, N_("New     :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
 441         /*  1 - label - name */
 442         { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
 443         /*  2 - label - size */
 444         { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
 445         /*  3 - label - date & time */
 446         { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
 447         /*  4 - label */
 448         { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
 449         /*  5 - label - name */
 450         { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
 451         /*  6 - label - size */
 452         { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
 453         /*  7 - label - date & time */
 454         { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
 455         /* --------------------------------------------------- */
 456         /*  8 - label */
 457         { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
 458         /*  9 - button */
 459         { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
 460         /* 10 - button */
 461         { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
 462         /* 11 - button */
 463         { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
 464         /* 12 - button */
 465         { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
 466         /* --------------------------------------------------- */
 467         /* 13 - label */
 468         { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
 469         /* 14 - checkbox */
 470         { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
 471         /* 15 - button */
 472         { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
 473         /* 16 - button */
 474         { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
 475         /* 17 - button */
 476         { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
 477         /* 18 - button */
 478         { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
 479         /* 19 - button */
 480         { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
 481         /* --------------------------------------------------- */
 482         /* 20 - button */
 483         { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
 484         /* *INDENT-ON* */
 485     };
 486 
 487     const int gap = 1;
 488 
 489     file_op_context_ui_t *ui = ctx->ui;
 490     Widget *wd;
 491     WGroup *g;
 492     const char *title;
 493 
 494     vfs_path_t *p;
 495     char *s1;
 496     const char *cs1;
 497     char s2[BUF_SMALL];
 498     int w, bw1, bw2;
 499     unsigned short i;
 500 
 501     gboolean do_append = FALSE, do_reget = FALSE;
 502     unsigned long yes_id, no_id;
 503     int result;
 504 
 505     if (mode == Foreground)
 506         title = _("File exists");
 507     else
 508         title = _("Background process: File exists");
 509 
 510 #ifdef ENABLE_NLS
 511     {
 512         const unsigned short num = G_N_ELEMENTS (dlg_widgets);
 513 
 514         for (i = 0; i < num; i++)
 515             if (dlg_widgets[i].text != NULL)
 516                 dlg_widgets[i].text = _(dlg_widgets[i].text);
 517     }
 518 #endif /* ENABLE_NLS */
 519 
 520     /* create widgets to get their real widths */
 521     /* new file */
 522     NEW_LABEL (0, dlg_widgets[0].text);
 523     /* new file name */
 524     p = vfs_path_from_str (ui->src_filename);
 525     s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
 526     NEW_LABEL (1, s1);
 527     vfs_path_free (p, TRUE);
 528     g_free (s1);
 529     /* new file size */
 530     size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
 531     NEW_LABEL (2, s2);
 532     /* new file modification date & time */
 533     cs1 = file_date (ui->src_stat->st_mtime);
 534     NEW_LABEL (3, cs1);
 535 
 536     /* existing file */
 537     NEW_LABEL (4, dlg_widgets[4].text);
 538     /* existing file name */
 539     p = vfs_path_from_str (ui->tgt_filename);
 540     s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
 541     NEW_LABEL (5, s1);
 542     vfs_path_free (p, TRUE);
 543     g_free (s1);
 544     /* existing file size */
 545     size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
 546     NEW_LABEL (6, s2);
 547     /* existing file modification date & time */
 548     cs1 = file_date (ui->dst_stat->st_mtime);
 549     NEW_LABEL (7, cs1);
 550 
 551     /* will "Append" and "Reget" buttons be in the dialog? */
 552     do_append = !S_ISDIR (ui->dst_stat->st_mode);
 553     do_reget = do_append && ctx->operation == OP_COPY && ui->dst_stat->st_size != 0
 554         && ui->src_stat->st_size > ui->dst_stat->st_size;
 555 
 556     NEW_LABEL (8, dlg_widgets[8].text);
 557     NEW_BUTTON (9);
 558     NEW_BUTTON (10);
 559     if (do_append)
 560         NEW_BUTTON (11);
 561     if (do_reget)
 562         NEW_BUTTON (12);
 563 
 564     NEW_LABEL (13, dlg_widgets[13].text);
 565     dlg_widgets[14].widget =
 566         WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
 567     for (i = 15; i <= 20; i++)
 568         NEW_BUTTON (i);
 569 
 570     /* place widgets */
 571     dlg_width -= 2 * (2 + gap); /* inside frame */
 572 
 573     /* perhaps longest line is buttons */
 574     bw1 = WCOLS (9) + gap + WCOLS (10);
 575     if (do_append)
 576         bw1 += gap + WCOLS (11);
 577     if (do_reget)
 578         bw1 += gap + WCOLS (12);
 579     dlg_width = MAX (dlg_width, bw1);
 580 
 581     bw2 = WCOLS (15);
 582     for (i = 16; i <= 19; i++)
 583         bw2 += gap + WCOLS (i);
 584     dlg_width = MAX (dlg_width, bw2);
 585 
 586     dlg_width = MAX (dlg_width, WCOLS (8));
 587     dlg_width = MAX (dlg_width, WCOLS (13));
 588     dlg_width = MAX (dlg_width, WCOLS (14));
 589 
 590     /* truncate file names */
 591     w = WCOLS (0) + gap + WCOLS (1);
 592     if (w > dlg_width)
 593     {
 594         WLabel *l = LABEL (W (1));
 595 
 596         w = dlg_width - gap - WCOLS (0);
 597         label_set_text (l, str_trunc (l->text, w));
 598     }
 599 
 600     w = WCOLS (4) + gap + WCOLS (5);
 601     if (w > dlg_width)
 602     {
 603         WLabel *l = LABEL (W (5));
 604 
 605         w = dlg_width - gap - WCOLS (4);
 606         label_set_text (l, str_trunc (l->text, w));
 607     }
 608 
 609     /* real dlalog width */
 610     dlg_width += 2 * (2 + gap);
 611 
 612     WX (1) = WX (0) + WCOLS (0) + gap;
 613     WX (5) = WX (4) + WCOLS (4) + gap;
 614 
 615     /* sizes: right alignment */
 616     WX (2) = dlg_width / 2 - WCOLS (2);
 617     WX (6) = dlg_width / 2 - WCOLS (6);
 618 
 619     w = dlg_width - (2 + gap);  /* right bound */
 620 
 621     /* date & time */
 622     WX (3) = w - WCOLS (3);
 623     WX (7) = w - WCOLS (7);
 624 
 625     /* buttons: center alignment */
 626     WX (9) = dlg_width / 2 - bw1 / 2;
 627     WX (10) = WX (9) + WCOLS (9) + gap;
 628     if (do_append)
 629         WX (11) = WX (10) + WCOLS (10) + gap;
 630     if (do_reget)
 631         WX (12) = WX (11) + WCOLS (11) + gap;
 632 
 633     WX (15) = dlg_width / 2 - bw2 / 2;
 634     for (i = 16; i <= 19; i++)
 635         WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
 636 
 637     /* TODO: write help (ticket #3970) */
 638     ui->replace_dlg =
 639         dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
 640                     "[Replace]", title);
 641     wd = WIDGET (ui->replace_dlg);
 642     g = GROUP (ui->replace_dlg);
 643 
 644     /* file info */
 645     for (i = 0; i <= 7; i++)
 646         ADD_LABEL (i);
 647     group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
 648 
 649     /* label & buttons */
 650     ADD_LABEL (8);              /* Overwrite this file? */
 651     yes_id = ADD_BUTTON (9);    /* Yes */
 652     no_id = ADD_BUTTON (10);    /* No */
 653     if (do_append)
 654         ADD_BUTTON (11);        /* Append */
 655     if (do_reget)
 656         ADD_BUTTON (12);        /* Reget */
 657     group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
 658 
 659     /* label & buttons */
 660     ADD_LABEL (13);             /* Overwrite all files? */
 661     group_add_widget (g, dlg_widgets[14].widget);
 662     for (i = 15; i <= 19; i++)
 663         ADD_BUTTON (i);
 664     group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
 665 
 666     ADD_BUTTON (20);            /* Abort */
 667 
 668     group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
 669 
 670     result = dlg_run (ui->replace_dlg);
 671 
 672     if (result != B_CANCEL)
 673         ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
 674 
 675     widget_destroy (wd);
 676 
 677     return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
 678 
 679 #undef ADD_BUTTON
 680 #undef NEW_BUTTON
 681 #undef ADD_LABEL
 682 #undef NEW_LABEL
 683 #undef WCOLS
 684 #undef WX
 685 #undef W
 686 }
 687 
 688 /* --------------------------------------------------------------------------------------------- */
 689 
 690 static gboolean
 691 is_wildcarded (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 692 {
 693     gboolean escaped = FALSE;
 694 
 695     for (; *p != '\0'; p++)
 696     {
 697         if (*p == '\\')
 698         {
 699             if (p[1] >= '1' && p[1] <= '9' && !escaped)
 700                 return TRUE;
 701             escaped = !escaped;
 702         }
 703         else
 704         {
 705             if ((*p == '*' || *p == '?') && !escaped)
 706                 return TRUE;
 707             escaped = FALSE;
 708         }
 709     }
 710     return FALSE;
 711 }
 712 
 713 /* --------------------------------------------------------------------------------------------- */
 714 
 715 static void
 716 place_progress_buttons (WDialog * h, gboolean suspended)
     /* [previous][next][first][last][top][bottom][index][help]  */
 717 {
 718     const size_t i = suspended ? 2 : 1;
 719     Widget *w = WIDGET (h);
 720     int buttons_width;
 721 
 722     buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
 723     buttons_width += progress_buttons[i].len;
 724     button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
 725 
 726     progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
 727     progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1;
 728     progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1;
 729 }
 730 
 731 /* --------------------------------------------------------------------------------------------- */
 732 
 733 static int
 734 progress_button_callback (WButton * button, int action)
     /* [previous][next][first][last][top][bottom][index][help]  */
 735 {
 736     (void) button;
 737     (void) action;
 738 
 739     /* don't close dialog in any case */
 740     return 0;
 741 }
 742 
 743 /* --------------------------------------------------------------------------------------------- */
 744 /*** public functions ****************************************************************************/
 745 /* --------------------------------------------------------------------------------------------- */
 746 
 747 FileProgressStatus
 748 check_progress_buttons (file_op_context_t * ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
 749 {
 750     int c;
 751     Gpm_Event event;
 752     file_op_context_ui_t *ui;
 753 
 754     if (ctx == NULL || ctx->ui == NULL)
 755         return FILE_CONT;
 756 
 757     ui = ctx->ui;
 758 
 759   get_event:
 760     event.x = -1;               /* Don't show the GPM cursor */
 761     c = tty_get_event (&event, FALSE, ctx->suspended);
 762     if (c == EV_NONE)
 763         return FILE_CONT;
 764 
 765     /* Reinitialize to avoid old values after events other than selecting a button */
 766     ui->op_dlg->ret_value = FILE_CONT;
 767 
 768     dlg_process_event (ui->op_dlg, c, &event);
 769     switch (ui->op_dlg->ret_value)
 770     {
 771     case FILE_SKIP:
 772         if (ctx->suspended)
 773         {
 774             /* redraw dialog in case of Skip after Suspend */
 775             place_progress_buttons (ui->op_dlg, FALSE);
 776             widget_draw (WIDGET (ui->op_dlg));
 777         }
 778         ctx->suspended = FALSE;
 779         return FILE_SKIP;
 780     case B_CANCEL:
 781     case FILE_ABORT:
 782         ctx->suspended = FALSE;
 783         return FILE_ABORT;
 784     case FILE_SUSPEND:
 785         ctx->suspended = !ctx->suspended;
 786         place_progress_buttons (ui->op_dlg, ctx->suspended);
 787         widget_draw (WIDGET (ui->op_dlg));
 788         MC_FALLTHROUGH;
 789     default:
 790         if (ctx->suspended)
 791             goto get_event;
 792         return FILE_CONT;
 793     }
 794 }
 795 
 796 /* --------------------------------------------------------------------------------------------- */
 797 /* {{{ File progress display routines */
 798 
 799 void
 800 file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
     /* [previous][next][first][last][top][bottom][index][help]  */
 801                            filegui_dialog_type_t dialog_type)
 802 {
 803     file_op_context_ui_t *ui;
 804     Widget *w;
 805     WGroup *g;
 806     int buttons_width;
 807     int dlg_width = 58, dlg_height = 17;
 808     int y = 2, x = 3;
 809     WRect r;
 810 
 811     if (ctx == NULL || ctx->ui != NULL)
 812         return;
 813 
 814 #ifdef ENABLE_NLS
 815     if (progress_buttons[0].len == -1)
 816     {
 817         size_t i;
 818 
 819         for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
 820             progress_buttons[i].text = _(progress_buttons[i].text);
 821     }
 822 #endif
 823 
 824     ctx->dialog_type = dialog_type;
 825     ctx->recursive_result = RECURSIVE_YES;
 826     ctx->ui = g_new0 (file_op_context_ui_t, 1);
 827 
 828     ui = ctx->ui;
 829     ui->replace_result = REPLACE_YES;
 830 
 831     ui->op_dlg =
 832         dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors, NULL,
 833                     NULL, NULL, op_names[ctx->operation]);
 834     w = WIDGET (ui->op_dlg);
 835     g = GROUP (ui->op_dlg);
 836 
 837     if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
 838     {
 839         ui->showing_eta = with_eta && ctx->progress_totals_computed;
 840         ui->showing_bps = with_eta;
 841 
 842         ui->src_file_label = label_new (y++, x, "");
 843         group_add_widget (g, ui->src_file_label);
 844 
 845         ui->src_file = label_new (y++, x, "");
 846         group_add_widget (g, ui->src_file);
 847 
 848         ui->tgt_file_label = label_new (y++, x, "");
 849         group_add_widget (g, ui->tgt_file_label);
 850 
 851         ui->tgt_file = label_new (y++, x, "");
 852         group_add_widget (g, ui->tgt_file);
 853 
 854         ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
 855         if (!classic_progressbar && (current_panel == right_panel))
 856             ui->progress_file_gauge->from_left_to_right = FALSE;
 857         group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
 858 
 859         ui->progress_file_label = label_new (y++, x, "");
 860         group_add_widget (g, ui->progress_file_label);
 861 
 862         if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
 863         {
 864             ui->total_bytes_label = hline_new (y++, -1, -1);
 865             group_add_widget (g, ui->total_bytes_label);
 866 
 867             if (ctx->progress_totals_computed)
 868             {
 869                 ui->progress_total_gauge =
 870                     gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
 871                 if (!classic_progressbar && (current_panel == right_panel))
 872                     ui->progress_total_gauge->from_left_to_right = FALSE;
 873                 group_add_widget_autopos (g, ui->progress_total_gauge,
 874                                           WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
 875             }
 876 
 877             ui->total_files_processed_label = label_new (y++, x, "");
 878             group_add_widget (g, ui->total_files_processed_label);
 879 
 880             ui->time_label = label_new (y++, x, "");
 881             group_add_widget (g, ui->time_label);
 882         }
 883     }
 884     else
 885     {
 886         ui->src_file = label_new (y++, x, "");
 887         group_add_widget (g, ui->src_file);
 888 
 889         ui->total_files_processed_label = label_new (y++, x, "");
 890         group_add_widget (g, ui->total_files_processed_label);
 891     }
 892 
 893     group_add_widget (g, hline_new (y++, -1, -1));
 894 
 895     progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
 896                                                 progress_buttons[0].flags, progress_buttons[0].text,
 897                                                 progress_button_callback));
 898     if (progress_buttons[0].len == -1)
 899         progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
 900 
 901     progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
 902                                                 progress_buttons[1].flags, progress_buttons[1].text,
 903                                                 progress_button_callback));
 904     if (progress_buttons[1].len == -1)
 905         progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
 906 
 907     if (progress_buttons[2].len == -1)
 908     {
 909         /* create and destroy button to get it length */
 910         progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
 911                                                     progress_buttons[2].flags,
 912                                                     progress_buttons[2].text,
 913                                                     progress_button_callback));
 914         progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
 915         widget_destroy (progress_buttons[2].w);
 916     }
 917     progress_buttons[2].w = progress_buttons[1].w;
 918 
 919     progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
 920                                                 progress_buttons[3].flags, progress_buttons[3].text,
 921                                                 NULL));
 922     if (progress_buttons[3].len == -1)
 923         progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
 924 
 925     group_add_widget (g, progress_buttons[0].w);
 926     group_add_widget (g, progress_buttons[1].w);
 927     group_add_widget (g, progress_buttons[3].w);
 928 
 929     buttons_width = 2 +
 930         progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
 931         progress_buttons[3].len;
 932 
 933     /* adjust dialog sizes  */
 934     r = w->rect;
 935     r.lines = y + 3;
 936     r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
 937     widget_set_size_rect (w, &r);
 938 
 939     place_progress_buttons (ui->op_dlg, FALSE);
 940 
 941     widget_select (progress_buttons[0].w);
 942 
 943     /* We will manage the dialog without any help, that's why
 944        we have to call dlg_init */
 945     dlg_init (ui->op_dlg);
 946 }
 947 
 948 /* --------------------------------------------------------------------------------------------- */
 949 
 950 void
 951 file_op_context_destroy_ui (file_op_context_t * ctx)
     /* [previous][next][first][last][top][bottom][index][help]  */
 952 {
 953     if (ctx != NULL && ctx->ui != NULL)
 954     {
 955         file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui;
 956 
 957         dlg_run_done (ui->op_dlg);
 958         widget_destroy (WIDGET (ui->op_dlg));
 959         MC_PTR_FREE (ctx->ui);
 960     }
 961 }
 962 
 963 /* --------------------------------------------------------------------------------------------- */
 964 /**
 965    show progressbar for file
 966  */
 967 
 968 void
 969 file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
     /* [previous][next][first][last][top][bottom][index][help]  */
 970                     const char *stalled_msg, gboolean force_update)
 971 {
 972     file_op_context_ui_t *ui;
 973 
 974     if (!verbose || ctx == NULL || ctx->ui == NULL)
 975         return;
 976 
 977     ui = ctx->ui;
 978 
 979     if (total == 0)
 980     {
 981         gauge_show (ui->progress_file_gauge, FALSE);
 982         return;
 983     }
 984 
 985     gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
 986     gauge_show (ui->progress_file_gauge, TRUE);
 987 
 988     if (!force_update)
 989         return;
 990 
 991     if (!ui->showing_eta || ctx->eta_secs <= 0.5)
 992         label_set_text (ui->progress_file_label, stalled_msg);
 993     else
 994     {
 995         char buffer2[BUF_TINY];
 996 
 997         file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
 998         if (ctx->bps == 0)
 999             label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
1000         else
1001         {
1002             char buffer3[BUF_TINY];
1003 
1004             file_bps_prepare_for_show (buffer3, ctx->bps);
1005             label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
1006         }
1007 
1008     }
1009 }
1010 
1011 /* --------------------------------------------------------------------------------------------- */
1012 
1013 void
1014 file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total)
     /* [previous][next][first][last][top][bottom][index][help]  */
1015 {
1016     file_op_context_ui_t *ui;
1017 
1018     if (ctx == NULL || ctx->ui == NULL)
1019         return;
1020 
1021     ui = ctx->ui;
1022 
1023     if (ui->total_files_processed_label == NULL)
1024         return;
1025 
1026     if (ctx->progress_totals_computed)
1027         label_set_textv (ui->total_files_processed_label, _("Files processed: %zu/%zu"), done,
1028                          total);
1029     else
1030         label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"), done);
1031 }
1032 
1033 /* --------------------------------------------------------------------------------------------- */
1034 
1035 void
1036 file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1037                           uintmax_t copied_bytes, gboolean show_summary)
1038 {
1039     char buffer2[BUF_TINY];
1040     char buffer3[BUF_TINY];
1041     file_op_context_ui_t *ui;
1042 
1043     if (ctx == NULL || ctx->ui == NULL)
1044         return;
1045 
1046     ui = ctx->ui;
1047 
1048     if (ui->progress_total_gauge != NULL)
1049     {
1050         if (ctx->progress_bytes == 0)
1051             gauge_show (ui->progress_total_gauge, FALSE);
1052         else
1053         {
1054             gauge_set_value (ui->progress_total_gauge, 1024,
1055                              (int) (1024 * copied_bytes / ctx->progress_bytes));
1056             gauge_show (ui->progress_total_gauge, TRUE);
1057         }
1058     }
1059 
1060     if (!show_summary && tctx->bps == 0)
1061         return;
1062 
1063     if (ui->time_label != NULL)
1064     {
1065         gint64 tv_current;
1066         char buffer4[BUF_TINY];
1067 
1068         tv_current = g_get_monotonic_time ();
1069         file_frmt_time (buffer2, (tv_current - tctx->transfer_start) / G_USEC_PER_SEC);
1070 
1071         if (ctx->progress_totals_computed)
1072         {
1073             file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
1074             if (tctx->bps == 0)
1075                 label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3);
1076             else
1077             {
1078                 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
1079                 label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
1080             }
1081         }
1082         else
1083         {
1084             if (tctx->bps == 0)
1085                 label_set_textv (ui->time_label, _("Time: %s"), buffer2);
1086             else
1087             {
1088                 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
1089                 label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4);
1090             }
1091         }
1092     }
1093 
1094     if (ui->total_bytes_label != NULL)
1095     {
1096         size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
1097 
1098         if (!ctx->progress_totals_computed)
1099             hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2);
1100         else
1101         {
1102             size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
1103             hline_set_textv (ui->total_bytes_label, _(" Total: %s/%s "), buffer2, buffer3);
1104         }
1105     }
1106 }
1107 
1108 /* }}} */
1109 
1110 /* --------------------------------------------------------------------------------------------- */
1111 
1112 void
1113 file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1114 {
1115     file_op_context_ui_t *ui;
1116 
1117     if (ctx == NULL || ctx->ui == NULL)
1118         return;
1119 
1120     ui = ctx->ui;
1121 
1122     if (vpath != NULL)
1123     {
1124         char *s;
1125 
1126         s = vfs_path_tokens_get (vpath, -1, 1);
1127         label_set_text (ui->src_file_label, _("Source"));
1128         label_set_text (ui->src_file, truncFileString (ui->op_dlg, s));
1129         g_free (s);
1130     }
1131     else
1132     {
1133         label_set_text (ui->src_file_label, "");
1134         label_set_text (ui->src_file, "");
1135     }
1136 }
1137 
1138 /* --------------------------------------------------------------------------------------------- */
1139 
1140 void
1141 file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1142 {
1143     file_op_context_ui_t *ui;
1144 
1145     if (ctx == NULL || ctx->ui == NULL)
1146         return;
1147 
1148     ui = ctx->ui;
1149 
1150     if (vpath != NULL)
1151     {
1152         label_set_text (ui->tgt_file_label, _("Target"));
1153         label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
1154     }
1155     else
1156     {
1157         label_set_text (ui->tgt_file_label, "");
1158         label_set_text (ui->tgt_file, "");
1159     }
1160 }
1161 
1162 /* --------------------------------------------------------------------------------------------- */
1163 
1164 gboolean
1165 file_progress_show_deleting (file_op_context_t * ctx, const char *s, size_t * count)
     /* [previous][next][first][last][top][bottom][index][help]  */
1166 {
1167     static gint64 timestamp = 0;
1168     /* update with 25 FPS rate */
1169     static const gint64 delay = G_USEC_PER_SEC / 25;
1170 
1171     gboolean ret;
1172 
1173     if (ctx == NULL || ctx->ui == NULL)
1174         return FALSE;
1175 
1176     ret = mc_time_elapsed (&timestamp, delay);
1177 
1178     if (ret)
1179     {
1180         file_op_context_ui_t *ui;
1181 
1182         ui = ctx->ui;
1183 
1184         if (ui->src_file_label != NULL)
1185             label_set_text (ui->src_file_label, _("Deleting"));
1186 
1187         label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
1188     }
1189 
1190     if (count != NULL)
1191         (*count)++;
1192 
1193     return ret;
1194 }
1195 
1196 /* --------------------------------------------------------------------------------------------- */
1197 
1198 FileProgressStatus
1199 file_progress_real_query_replace (file_op_context_t * ctx, enum OperationMode mode,
     /* [previous][next][first][last][top][bottom][index][help]  */
1200                                   const char *src, struct stat * src_stat,
1201                                   const char *dst, struct stat * dst_stat)
1202 {
1203     file_op_context_ui_t *ui;
1204     FileProgressStatus replace_with_zero;
1205 
1206     if (ctx == NULL || ctx->ui == NULL)
1207         return FILE_CONT;
1208 
1209     ui = ctx->ui;
1210 
1211     if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
1212         || ui->replace_result == REPLACE_APPEND)
1213     {
1214         ui->src_filename = src;
1215         ui->src_stat = src_stat;
1216         ui->tgt_filename = dst;
1217         ui->dst_stat = dst_stat;
1218         ui->replace_result = overwrite_query_dialog (ctx, mode);
1219     }
1220 
1221     replace_with_zero = (src_stat->st_size == 0
1222                          && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
1223 
1224     switch (ui->replace_result)
1225     {
1226     case REPLACE_OLDER:
1227         do_refresh ();
1228         if (src_stat->st_mtime > dst_stat->st_mtime)
1229             return replace_with_zero;
1230         else
1231             return FILE_SKIP;
1232 
1233     case REPLACE_SIZE:
1234         do_refresh ();
1235         if (src_stat->st_size == dst_stat->st_size)
1236             return FILE_SKIP;
1237         else
1238             return replace_with_zero;
1239 
1240     case REPLACE_SMALLER:
1241         do_refresh ();
1242         if (src_stat->st_size > dst_stat->st_size)
1243             return FILE_CONT;
1244         else
1245             return FILE_SKIP;
1246 
1247     case REPLACE_ALL:
1248         do_refresh ();
1249         return replace_with_zero;
1250 
1251     case REPLACE_REGET:
1252         /* Careful: we fall through and set do_append */
1253         ctx->do_reget = dst_stat->st_size;
1254         MC_FALLTHROUGH;
1255 
1256     case REPLACE_APPEND:
1257         ctx->do_append = TRUE;
1258         MC_FALLTHROUGH;
1259 
1260     case REPLACE_YES:
1261         do_refresh ();
1262         return FILE_CONT;
1263 
1264     case REPLACE_NO:
1265     case REPLACE_NONE:
1266         do_refresh ();
1267         return FILE_SKIP;
1268 
1269     case REPLACE_ABORT:
1270     default:
1271         return FILE_ABORT;
1272     }
1273 }
1274 
1275 /* --------------------------------------------------------------------------------------------- */
1276 
1277 char *
1278 file_mask_dialog (file_op_context_t * ctx, FileOperation operation, gboolean only_one,
     /* [previous][next][first][last][top][bottom][index][help]  */
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 = NULL;
1286     char *tmp;
1287     char *def_text_secure;
1288 
1289     if (ctx == NULL)
1290         return NULL;
1291 
1292     /* unselect checkbox if target filesystem doesn't support attributes */
1293     ctx->op_preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
1294     ctx->stable_symlinks = FALSE;
1295     *do_bg = FALSE;
1296 
1297     /* filter out a possible password from def_text */
1298     vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1299     tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1300     vfs_path_free (vpath, TRUE);
1301 
1302     if (source_easy_patterns)
1303         def_text_secure = strutils_glob_escape (tmp);
1304     else
1305         def_text_secure = strutils_regex_escape (tmp);
1306     g_free (tmp);
1307 
1308     if (only_one)
1309     {
1310         int format_len, text_len;
1311         int max_len;
1312 
1313         format_len = str_term_width1 (format);
1314         text_len = str_term_width1 (text);
1315         max_len = COLS - 2 - 6;
1316 
1317         if (format_len + text_len <= max_len)
1318         {
1319             fmd_xlen = format_len + text_len + 6;
1320             fmd_xlen = MAX (fmd_xlen, 68);
1321         }
1322         else
1323         {
1324             text = str_trunc ((const char *) text, max_len - format_len);
1325             fmd_xlen = max_len + 6;
1326         }
1327 
1328         g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1329     }
1330     else
1331     {
1332         fmd_xlen = COLS * 2 / 3;
1333         fmd_xlen = MAX (fmd_xlen, 68);
1334         g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1335     }
1336 
1337     {
1338         char *source_mask = NULL;
1339         char *orig_mask;
1340         int val;
1341         struct stat buf;
1342 
1343         quick_widget_t quick_widgets[] = {
1344             /* *INDENT-OFF* */
1345             QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
1346                                  "input-def", &source_mask, NULL, FALSE, FALSE,
1347                                  INPUT_COMPLETE_FILENAMES),
1348             QUICK_START_COLUMNS,
1349                 QUICK_SEPARATOR (FALSE),
1350             QUICK_NEXT_COLUMN,
1351                 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1352             QUICK_STOP_COLUMNS,
1353             QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir,
1354                                  NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1355             QUICK_SEPARATOR (TRUE),
1356             QUICK_START_COLUMNS,
1357                 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1358                 QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
1359             QUICK_NEXT_COLUMN,
1360                 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1361                 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1362             QUICK_STOP_COLUMNS,
1363             QUICK_START_BUTTONS (TRUE, TRUE),
1364                 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1365 #ifdef ENABLE_BACKGROUND
1366                 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1367 #endif /* ENABLE_BACKGROUND */
1368                 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1369             QUICK_END
1370             /* *INDENT-ON* */
1371         };
1372 
1373         WRect r = { -1, -1, 0, fmd_xlen };
1374 
1375         quick_dialog_t qdlg = {
1376             r, op_names[operation], "[Mask Copy/Rename]",
1377             quick_widgets, NULL, NULL
1378         };
1379 
1380         while (TRUE)
1381         {
1382             val = quick_dialog_skip (&qdlg, 4);
1383 
1384             if (val == B_CANCEL)
1385             {
1386                 g_free (def_text_secure);
1387                 return NULL;
1388             }
1389 
1390             ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
1391 
1392             if (ctx->op_preserve)
1393             {
1394                 ctx->preserve = TRUE;
1395                 ctx->umask_kill = 0777777;
1396                 ctx->preserve_uidgid = (geteuid () == 0);
1397             }
1398             else
1399             {
1400                 mode_t i2;
1401 
1402                 ctx->preserve = ctx->preserve_uidgid = FALSE;
1403                 i2 = umask (0);
1404                 umask (i2);
1405                 ctx->umask_kill = i2 ^ 0777777;
1406             }
1407 
1408             if (dest_dir == NULL || *dest_dir == '\0')
1409             {
1410                 g_free (def_text_secure);
1411                 g_free (source_mask);
1412                 g_free (dest_dir);
1413                 return NULL;
1414             }
1415 
1416             ctx->search_handle = mc_search_new (source_mask, NULL);
1417             if (ctx->search_handle != NULL)
1418                 break;
1419 
1420             message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
1421             MC_PTR_FREE (dest_dir);
1422             MC_PTR_FREE (source_mask);
1423         }
1424 
1425         g_free (def_text_secure);
1426         g_free (source_mask);
1427 
1428         ctx->search_handle->is_case_sensitive = TRUE;
1429         if (source_easy_patterns)
1430             ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1431         else
1432             ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1433 
1434         tmp = dest_dir;
1435         dest_dir = tilde_expand (tmp);
1436         g_free (tmp);
1437         vpath = vfs_path_from_str (dest_dir);
1438 
1439         ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1440         if (ctx->dest_mask == NULL)
1441             ctx->dest_mask = dest_dir;
1442         else
1443             ctx->dest_mask++;
1444 
1445         orig_mask = ctx->dest_mask;
1446 
1447         if (*ctx->dest_mask == '\0'
1448             || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1449                 && (!only_one
1450                     || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1451             || (ctx->dive_into_subdirs
1452                 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1453                     || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1454             ctx->dest_mask = g_strdup ("\\0");
1455         else
1456         {
1457             ctx->dest_mask = g_strdup (ctx->dest_mask);
1458             *orig_mask = '\0';
1459         }
1460 
1461         if (*dest_dir == '\0')
1462         {
1463             g_free (dest_dir);
1464             dest_dir = g_strdup ("./");
1465         }
1466 
1467         vfs_path_free (vpath, TRUE);
1468 
1469         if (val == B_USER)
1470             *do_bg = TRUE;
1471     }
1472 
1473     return dest_dir;
1474 }
1475 
1476 /* --------------------------------------------------------------------------------------------- */

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