root/src/filemanager/filegui.c

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

DEFINITIONS

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

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

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