root/src/filemanager/filegui.c

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

DEFINITIONS

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

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

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