1d90b251d3cf7983c6a8412dbb13b06e16b302a4
[midnight-commander.git] / src / filemanager / filegui.c
blob1d90b251d3cf7983c6a8412dbb13b06e16b302a4
1 /*
2 File management GUI for the text mode edition
4 The copy code was based in GNU's cp, and was written by:
5 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
7 The move code was based in GNU's mv, and was written by:
8 Mike Parker and David MacKenzie.
10 Janne Kukonlehto added much error recovery to them for being used
11 in an interactive program.
13 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
14 2004, 2005, 2006, 2007, 2009, 2011, 2012, 2013
15 The Free Software Foundation, Inc.
17 Written by:
18 Janne Kukonlehto, 1994, 1995
19 Fred Leeflang, 1994, 1995
20 Miguel de Icaza, 1994, 1995, 1996
21 Jakub Jelinek, 1995, 1996
22 Norbert Warmuth, 1997
23 Pavel Machek, 1998
24 Slava Zanko, 2009-2012
25 Andrew Borodin <aborodin@vmail.ru>, 2009, 2010, 2011, 2012, 2013
27 This file is part of the Midnight Commander.
29 The Midnight Commander is free software: you can redistribute it
30 and/or modify it under the terms of the GNU General Public License as
31 published by the Free Software Foundation, either version 3 of the License,
32 or (at your option) any later version.
34 The Midnight Commander is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
39 You should have received a copy of the GNU General Public License
40 along with this program. If not, see <http://www.gnu.org/licenses/>.
44 * Please note that all dialogs used here must be safe for background
45 * operations.
48 /** \file filegui.c
49 * \brief Source: file management GUI for the text mode edition
52 /* {{{ Include files */
54 #include <config.h>
56 /* Keep this conditional in sync with the similar conditional in m4.include/mc-get-fs-info. */
57 #if ((STAT_STATVFS || STAT_STATVFS64) \
58 && (HAVE_STRUCT_STATVFS_F_BASETYPE || HAVE_STRUCT_STATVFS_F_FSTYPENAME \
59 || (! HAVE_STRUCT_STATFS_F_FSTYPENAME)))
60 #define USE_STATVFS 1
61 #else
62 #define USE_STATVFS 0
63 #endif
65 #include <errno.h>
66 #include <ctype.h>
67 #include <stdio.h>
68 #include <string.h>
69 #include <sys/types.h>
70 #include <sys/stat.h>
72 #if USE_STATVFS
73 #include <sys/statvfs.h>
74 #elif HAVE_SYS_VFS_H
75 #include <sys/vfs.h>
76 #elif HAVE_SYS_MOUNT_H && HAVE_SYS_PARAM_H
77 /* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
78 It does have statvfs.h, but shouldn't use it, since it doesn't
79 HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
80 /* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
81 #include <sys/param.h>
82 #include <sys/mount.h>
83 #if HAVE_NFS_NFS_CLNT_H && HAVE_NFS_VFS_H
84 /* Ultrix 4.4 needs these for the declaration of struct statfs. */
85 #include <netinet/in.h>
86 #include <nfs/nfs_clnt.h>
87 #include <nfs/vfs.h>
88 #endif
89 #elif HAVE_OS_H /* BeOS */
90 #include <fs_info.h>
91 #endif
93 #if USE_STATVFS
94 #if ! STAT_STATVFS && STAT_STATVFS64
95 #define STRUCT_STATVFS struct statvfs64
96 #define STATFS statvfs64
97 #else
98 #define STRUCT_STATVFS struct statvfs
99 #define STATFS statvfs
100 /* Return true if statvfs works. This is false for statvfs on systems
101 with GNU libc on Linux kernels before 2.6.36, which stats all
102 preceding entries in /proc/mounts; that makes df hang if even one
103 of the corresponding file systems is hard-mounted but not available. */
104 #if ! (__linux__ && (__GLIBC__ || __UCLIBC__))
105 static int
106 statvfs_works (void)
108 return 1;
110 #else
111 #include <string.h> /* for strverscmp */
112 #include <sys/utsname.h>
113 #include <sys/statfs.h>
114 #define STAT_STATFS2_BSIZE 1
116 static int
117 statvfs_works (void)
119 static int statvfs_works_cache = -1;
120 struct utsname name;
122 if (statvfs_works_cache < 0)
123 statvfs_works_cache = (uname (&name) == 0 && 0 <= strverscmp (name.release, "2.6.36"));
124 return statvfs_works_cache;
126 #endif
127 #endif
128 #else
129 #define STATFS statfs
130 #define STRUCT_STATVFS struct statfs
131 #if HAVE_OS_H /* BeOS */
132 /* BeOS has a statvfs function, but it does not return sensible values
133 for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
134 f_fstypename. Use 'struct fs_info' instead. */
135 static int
136 statfs (char const *filename, struct fs_info *buf)
138 dev_t device = dev_for_path (filename);
140 if (device < 0)
142 errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
143 : device == B_BAD_VALUE ? EINVAL
144 : device == B_NAME_TOO_LONG ? ENAMETOOLONG
145 : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0);
146 return -1;
148 /* If successful, buf->dev will be == device. */
149 return fs_stat_dev (device, buf);
152 #define STRUCT_STATVFS struct fs_info
153 #else
154 #define STRUCT_STATVFS struct statfs
155 #endif
156 #endif
158 #if HAVE_STRUCT_STATVFS_F_BASETYPE
159 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
160 #else
161 #if HAVE_STRUCT_STATVFS_F_FSTYPENAME || HAVE_STRUCT_STATFS_F_FSTYPENAME
162 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
163 #elif HAVE_OS_H /* BeOS */
164 #define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
165 #endif
166 #endif
168 #include <unistd.h>
170 #include "lib/global.h"
172 #include "lib/tty/key.h" /* tty_get_event */
173 #include "lib/mcconfig.h"
174 #include "lib/search.h"
175 #include "lib/vfs/vfs.h"
176 #include "lib/strescape.h"
177 #include "lib/strutil.h"
178 #include "lib/timefmt.h" /* file_date() */
179 #include "lib/util.h"
180 #include "lib/widget.h"
182 #include "src/setup.h" /* verbose */
184 #include "midnight.h"
185 #include "fileopctx.h" /* FILE_CONT */
187 #include "filegui.h"
189 /* }}} */
191 /*** global variables ****************************************************************************/
193 int classic_progressbar = 1;
195 /*** file scope macro definitions ****************************************************************/
197 /* Hack: the vfs code should not rely on this */
198 #define WITH_FULL_PATHS 1
200 #define truncFileString(dlg, s) str_trunc (s, WIDGET (dlg)->cols - 10)
201 #define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->cols - 10)
203 /*** file scope type declarations ****************************************************************/
205 /* *INDENT-OFF* */
206 typedef enum {
207 MSDOS_SUPER_MAGIC = 0x4d44,
208 NTFS_SB_MAGIC = 0x5346544e,
209 FUSE_MAGIC = 0x65735546,
210 PROC_SUPER_MAGIC = 0x9fa0,
211 SMB_SUPER_MAGIC = 0x517B,
212 NCP_SUPER_MAGIC = 0x564c,
213 USBDEVICE_SUPER_MAGIC = 0x9fa2
214 } filegui_nonattrs_fs_t;
215 /* *INDENT-ON* */
217 /* Used for button result values */
218 typedef enum
220 REPLACE_YES = B_USER,
221 REPLACE_NO,
222 REPLACE_APPEND,
223 REPLACE_ALWAYS,
224 REPLACE_UPDATE,
225 REPLACE_NEVER,
226 REPLACE_ABORT,
227 REPLACE_SIZE,
228 REPLACE_REGET
229 } replace_action_t;
231 /* This structure describes the UI and internal data required by a file
232 * operation context.
234 typedef struct
236 /* ETA and bps */
237 gboolean showing_eta;
238 gboolean showing_bps;
240 /* Dialog and widgets for the operation progress window */
241 WDialog *op_dlg;
242 WLabel *file_string[2];
243 WLabel *file_label[2];
244 WGauge *progress_file_gauge;
245 WLabel *progress_file_label;
247 WGauge *progress_total_gauge;
249 WLabel *total_files_processed_label;
250 WLabel *time_label;
251 WHLine *total_bytes_label;
253 /* Query replace dialog */
254 WDialog *replace_dlg;
255 const char *replace_filename;
256 replace_action_t replace_result;
258 struct stat *s_stat, *d_stat;
259 } FileOpContextUI;
261 /*** file scope variables ************************************************************************/
263 struct
265 Widget *w;
266 FileProgressStatus action;
267 const char *text;
268 button_flags_t flags;
269 int len;
270 } progress_buttons[] =
272 /* *INDENT-OFF* */
273 { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
274 { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
275 { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
276 { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
277 /* *INDENT-ON* */
280 /* --------------------------------------------------------------------------------------------- */
281 /*** file scope functions ************************************************************************/
282 /* --------------------------------------------------------------------------------------------- */
284 static gboolean
285 filegui__check_attrs_on_fs (const char *fs_path)
287 STRUCT_STATVFS stfs;
289 if (!setup_copymove_persistent_attr)
290 return FALSE;
292 #if USE_STATVFS && defined(STAT_STATVFS)
293 if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
294 return TRUE;
295 #else
296 if (STATFS (fs_path, &stfs) != 0)
297 return TRUE;
298 #endif
300 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
301 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
302 switch ((filegui_nonattrs_fs_t) stfs.f_type)
304 case MSDOS_SUPER_MAGIC:
305 case NTFS_SB_MAGIC:
306 case PROC_SUPER_MAGIC:
307 case SMB_SUPER_MAGIC:
308 case NCP_SUPER_MAGIC:
309 case USBDEVICE_SUPER_MAGIC:
310 return FALSE;
311 default:
312 break;
314 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
315 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
316 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
317 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
318 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
319 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
320 || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
321 return FALSE;
322 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
323 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
324 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
325 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
326 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
327 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
328 return FALSE;
329 #endif
331 return TRUE;
334 /* --------------------------------------------------------------------------------------------- */
336 static void
337 file_frmt_time (char *buffer, double eta_secs)
339 int eta_hours, eta_mins, eta_s;
340 eta_hours = eta_secs / (60 * 60);
341 eta_mins = (eta_secs - (eta_hours * 60 * 60)) / 60;
342 eta_s = eta_secs - (eta_hours * 60 * 60 + eta_mins * 60);
343 g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s);
346 /* --------------------------------------------------------------------------------------------- */
348 static void
349 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
351 char _fmt_buff[BUF_TINY];
352 if (eta_secs <= 0.5 && !always_show)
354 *buffer = '\0';
355 return;
357 if (eta_secs <= 0.5)
358 eta_secs = 1;
359 file_frmt_time (_fmt_buff, eta_secs);
360 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
363 /* --------------------------------------------------------------------------------------------- */
365 static void
366 file_bps_prepare_for_show (char *buffer, long bps)
368 if (bps > 1024 * 1024)
370 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
372 else if (bps > 1024)
374 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
376 else if (bps > 1)
378 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
380 else
381 *buffer = '\0';
384 /* --------------------------------------------------------------------------------------------- */
386 * FIXME: probably it is better to replace this with quick dialog machinery,
387 * but actually I'm not familiar with it and have not much time :(
388 * alex
390 static replace_action_t
391 overwrite_query_dialog (FileOpContext * ctx, enum OperationMode mode)
393 #define ADD_RD_BUTTON(i, ypos) \
394 add_widget_autopos (ui->replace_dlg, \
395 button_new (ypos, rd_widgets [i].xpos, rd_widgets [i].value, \
396 NORMAL_BUTTON, rd_widgets [i].text, NULL), \
397 rd_widgets [i].pos_flags, ui->replace_dlg->current->data)
399 #define ADD_RD_LABEL(i, p1, p2, ypos) \
400 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2); \
401 label2 = WIDGET (label_new (ypos, rd_widgets [i].xpos, buffer)); \
402 add_widget_autopos (ui->replace_dlg, label2, rd_widgets [i].pos_flags, \
403 ui->replace_dlg->current != NULL ? ui->replace_dlg->current->data : NULL)
405 /* dialog sizes */
406 const int rd_ylen = 1;
407 int rd_xlen = 60;
408 int y = 2;
409 unsigned long yes_id;
411 struct
413 const char *text;
414 int ypos, xpos;
415 widget_pos_flags_t pos_flags;
416 int value; /* 0 for labels */
417 } rd_widgets[] =
419 /* *INDENT-OFF* */
420 /* 0 */
421 { N_("Target file already exists!"), 3, 4, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
422 /* 1 */
423 { "%s", 4, 4, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
424 /* 2 */
425 { N_("New : %s, size %s"), 6, 4, WPOS_KEEP_DEFAULT, 0 },
426 /* 3 */
427 { N_("Existing: %s, size %s"), 7, 4, WPOS_KEEP_DEFAULT, 0 },
428 /* 4 */
429 { N_("Overwrite this target?"), 9, 4, WPOS_KEEP_DEFAULT, 0 },
430 /* 5 */
431 { N_("&Yes"), 9, 28, WPOS_KEEP_DEFAULT, REPLACE_YES },
432 /* 6 */
433 { N_("&No"), 9, 37, WPOS_KEEP_DEFAULT, REPLACE_NO },
434 /* 7 */
435 { N_("A&ppend"), 9, 45, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
436 /* 8 */
437 { N_("&Reget"), 10, 28, WPOS_KEEP_DEFAULT, REPLACE_REGET },
438 /* 9 */
439 { N_("Overwrite all targets?"), 11, 4, WPOS_KEEP_DEFAULT, 0 },
440 /* 10 */
441 { N_("A&ll"), 11, 28, WPOS_KEEP_DEFAULT, REPLACE_ALWAYS },
442 /* 11 */
443 { N_("&Update"), 11, 36, WPOS_KEEP_DEFAULT, REPLACE_UPDATE },
444 /* 12 */
445 { N_("Non&e"), 11, 47, WPOS_KEEP_DEFAULT, REPLACE_NEVER },
446 /* 13 */
447 { N_("If &size differs"), 12, 28, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
448 /* 14 */
449 { N_("&Abort"), 14, 25, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
450 /* *INDENT-ON* */
453 const size_t num = G_N_ELEMENTS (rd_widgets);
454 int *widgets_len;
456 FileOpContextUI *ui = ctx->ui;
458 char buffer[BUF_SMALL];
459 char fsize_buffer[BUF_SMALL];
460 Widget *label1, *label2;
461 const char *title;
462 vfs_path_t *stripped_vpath;
463 const char *stripped_name;
464 char *stripped_name_orig;
465 int result;
467 widgets_len = g_new0 (int, num);
469 if (mode == Foreground)
470 title = _("File exists");
471 else
472 title = _("Background process: File exists");
474 stripped_vpath = vfs_path_from_str (ui->replace_filename);
475 stripped_name = stripped_name_orig =
476 vfs_path_to_str_flags (stripped_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
477 vfs_path_free (stripped_vpath);
480 size_t i;
481 int l1, l2, l, row;
482 int stripped_name_len;
484 for (i = 0; i < num; i++)
486 #ifdef ENABLE_NLS
487 if (i != 1) /* skip filename */
488 rd_widgets[i].text = _(rd_widgets[i].text);
489 #endif /* ENABLE_NLS */
490 widgets_len[i] = str_term_width1 (rd_widgets[i].text);
494 * longest of "Overwrite..." labels
495 * (assume "Target date..." are short enough)
497 l1 = max (widgets_len[9], widgets_len[4]);
499 /* longest of button rows */
500 l = l2 = 0;
501 row = 0;
502 for (i = 1; i < num - 1; i++)
503 if (rd_widgets[i].value != 0)
505 if (row != rd_widgets[i].ypos)
507 row = rd_widgets[i].ypos;
508 l2 = max (l2, l);
509 l = 0;
511 l += widgets_len[i] + 4;
514 l2 = max (l2, l); /* last row */
515 rd_xlen = max (rd_xlen, l1 + l2 + 8);
516 /* rd_xlen = max (rd_xlen, str_term_width1 (title) + 2); */
517 stripped_name_len = str_term_width1 (stripped_name);
518 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
520 /* Now place widgets */
521 l1 += 5; /* start of first button in the row */
522 l = l1;
523 row = 0;
524 for (i = 2; i < num - 1; i++)
525 if (rd_widgets[i].value != 0)
527 if (row != rd_widgets[i].ypos)
529 row = rd_widgets[i].ypos;
530 l = l1;
532 rd_widgets[i].xpos = l;
533 l += widgets_len[i] + 4;
537 /* FIXME - missing help node */
538 ui->replace_dlg =
539 create_dlg (TRUE, 0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, NULL, "[Replace]", title,
540 DLG_CENTER);
542 /* prompt */
543 ADD_RD_LABEL (0, "", "", y++);
544 /* file name */
545 ADD_RD_LABEL (1, "", "", y++);
546 label1 = label2;
548 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
550 /* source date and size */
551 size_trunc_len (fsize_buffer, sizeof (fsize_buffer), ui->s_stat->st_size, -1, panels_options.kilobyte_si);
552 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime), fsize_buffer, y++);
553 rd_xlen = max (rd_xlen, label2->cols + 8);
554 /* destination date and size */
555 size_trunc_len (fsize_buffer, sizeof (fsize_buffer), ui->d_stat->st_size, -1, panels_options.kilobyte_si);
556 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime), fsize_buffer, y++);
557 rd_xlen = max (rd_xlen, label2->cols + 8);
559 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
561 ADD_RD_LABEL (4, 0, 0, y); /* Overwrite this target? */
562 yes_id = ADD_RD_BUTTON (5, y); /* Yes */
563 ADD_RD_BUTTON (6, y); /* No */
565 /* "this target..." widgets */
566 if (!S_ISDIR (ui->d_stat->st_mode))
568 ADD_RD_BUTTON (7, y++); /* Append */
570 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
571 && (ui->s_stat->st_size > ui->d_stat->st_size))
572 ADD_RD_BUTTON (8, y++); /* Reget */
575 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
577 ADD_RD_LABEL (9, 0, 0, y); /* Overwrite all targets? */
578 ADD_RD_BUTTON (10, y); /* All" */
579 ADD_RD_BUTTON (11, y); /* Update */
580 ADD_RD_BUTTON (12, y++); /* None */
581 ADD_RD_BUTTON (13, y++); /* If size differs */
583 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
585 ADD_RD_BUTTON (14, y); /* Abort */
587 label_set_text (LABEL (label1), str_trunc (stripped_name, rd_xlen - 8));
588 dlg_set_size (ui->replace_dlg, y + 3, rd_xlen);
589 dlg_select_by_id (ui->replace_dlg, yes_id);
590 result = run_dlg (ui->replace_dlg);
591 destroy_dlg (ui->replace_dlg);
593 g_free (widgets_len);
594 g_free (stripped_name_orig);
596 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
597 #undef ADD_RD_LABEL
598 #undef ADD_RD_BUTTON
601 /* --------------------------------------------------------------------------------------------- */
603 static gboolean
604 is_wildcarded (char *p)
606 for (; *p; p++)
608 if (*p == '*')
609 return TRUE;
610 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
611 return TRUE;
613 return FALSE;
616 /* --------------------------------------------------------------------------------------------- */
618 static void
619 place_progress_buttons (WDialog * h, gboolean suspended)
621 const size_t i = suspended ? 2 : 1;
622 Widget *w = WIDGET (h);
623 int buttons_width;
625 buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
626 buttons_width += progress_buttons[i].len;
627 button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
629 progress_buttons[0].w->x = w->x + (w->cols - buttons_width) / 2;
630 progress_buttons[i].w->x = progress_buttons[0].w->x + progress_buttons[0].len + 1;
631 progress_buttons[3].w->x = progress_buttons[i].w->x + progress_buttons[i].len + 1;
634 /* --------------------------------------------------------------------------------------------- */
636 static int
637 progress_button_callback (WButton * button, int action)
639 (void) button;
640 (void) action;
642 /* don't close dialog in any case */
643 return 0;
646 /* --------------------------------------------------------------------------------------------- */
647 /*** public functions ****************************************************************************/
648 /* --------------------------------------------------------------------------------------------- */
650 FileProgressStatus
651 check_progress_buttons (FileOpContext * ctx)
653 int c;
654 Gpm_Event event;
655 FileOpContextUI *ui;
657 if (ctx == NULL || ctx->ui == NULL)
658 return FILE_CONT;
660 ui = ctx->ui;
662 get_event:
663 event.x = -1; /* Don't show the GPM cursor */
664 c = tty_get_event (&event, FALSE, ctx->suspended);
665 if (c == EV_NONE)
666 return FILE_CONT;
668 /* Reinitialize to avoid old values after events other than selecting a button */
669 ui->op_dlg->ret_value = FILE_CONT;
671 dlg_process_event (ui->op_dlg, c, &event);
672 switch (ui->op_dlg->ret_value)
674 case FILE_SKIP:
675 if (ctx->suspended)
677 /* redraw dialog in case of Skip after Suspend */
678 place_progress_buttons (ui->op_dlg, FALSE);
679 dlg_redraw (ui->op_dlg);
681 ctx->suspended = FALSE;
682 return FILE_SKIP;
683 case B_CANCEL:
684 case FILE_ABORT:
685 ctx->suspended = FALSE;
686 return FILE_ABORT;
687 case FILE_SUSPEND:
688 ctx->suspended = !ctx->suspended;
689 place_progress_buttons (ui->op_dlg, ctx->suspended);
690 dlg_redraw (ui->op_dlg);
691 /* fallthrough */
692 default:
693 if (ctx->suspended)
694 goto get_event;
695 return FILE_CONT;
699 /* --------------------------------------------------------------------------------------------- */
700 /* {{{ File progress display routines */
702 void
703 file_op_context_create_ui (FileOpContext * ctx, gboolean with_eta,
704 filegui_dialog_type_t dialog_type)
706 FileOpContextUI *ui;
707 int buttons_width;
708 int dlg_width = 58, dlg_height = 17;
709 int y = 2, x = 3;
711 if (ctx == NULL || ctx->ui != NULL)
712 return;
714 #ifdef ENABLE_NLS
715 if (progress_buttons[0].len == -1)
717 size_t i;
719 for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
720 progress_buttons[i].text = _(progress_buttons[i].text);
722 #endif
724 ctx->dialog_type = dialog_type;
725 ctx->recursive_result = RECURSIVE_YES;
726 ctx->ui = g_new0 (FileOpContextUI, 1);
728 ui = ctx->ui;
729 ui->replace_result = REPLACE_YES;
730 ui->showing_eta = with_eta && ctx->progress_totals_computed;
731 ui->showing_bps = with_eta;
733 ui->op_dlg =
734 create_dlg (TRUE, 0, 0, dlg_height, dlg_width, dialog_colors, NULL, NULL, NULL,
735 op_names[ctx->operation], DLG_CENTER);
737 ui->file_label[0] = label_new (y++, x, "");
738 add_widget (ui->op_dlg, ui->file_label[0]);
740 ui->file_string[0] = label_new (y++, x, "");
741 add_widget (ui->op_dlg, ui->file_string[0]);
743 ui->file_label[1] = label_new (y++, x, "");
744 add_widget (ui->op_dlg, ui->file_label[1]);
746 ui->file_string[1] = label_new (y++, x, "");
747 add_widget (ui->op_dlg, ui->file_string[1]);
749 ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
750 if (!classic_progressbar && (current_panel == right_panel))
751 ui->progress_file_gauge->from_left_to_right = FALSE;
752 add_widget_autopos (ui->op_dlg, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
754 ui->progress_file_label = label_new (y++, x, "");
755 add_widget (ui->op_dlg, ui->progress_file_label);
757 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
759 ui->total_bytes_label = hline_new (y++, -1, -1);
760 add_widget (ui->op_dlg, ui->total_bytes_label);
762 if (ctx->progress_totals_computed)
764 ui->progress_total_gauge =
765 gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
766 if (!classic_progressbar && (current_panel == right_panel))
767 ui->progress_total_gauge->from_left_to_right = FALSE;
768 add_widget_autopos (ui->op_dlg, ui->progress_total_gauge,
769 WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
772 ui->total_files_processed_label = label_new (y++, x, "");
773 add_widget (ui->op_dlg, ui->total_files_processed_label);
775 ui->time_label = label_new (y++, x, "");
776 add_widget (ui->op_dlg, ui->time_label);
779 add_widget (ui->op_dlg, hline_new (y++, -1, -1));
781 progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
782 progress_buttons[0].flags, progress_buttons[0].text,
783 progress_button_callback));
784 if (progress_buttons[0].len == -1)
785 progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
787 progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
788 progress_buttons[1].flags, progress_buttons[1].text,
789 progress_button_callback));
790 if (progress_buttons[1].len == -1)
791 progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
793 if (progress_buttons[2].len == -1)
795 /* create and destroy button to get it length */
796 progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
797 progress_buttons[2].flags,
798 progress_buttons[2].text,
799 progress_button_callback));
800 progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
801 send_message (progress_buttons[2].w, NULL, MSG_DESTROY, 0, NULL);
802 g_free (progress_buttons[2].w);
804 progress_buttons[2].w = progress_buttons[1].w;
806 progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
807 progress_buttons[3].flags, progress_buttons[3].text,
808 NULL));
809 if (progress_buttons[3].len == -1)
810 progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
812 add_widget (ui->op_dlg, progress_buttons[0].w);
813 add_widget (ui->op_dlg, progress_buttons[1].w);
814 add_widget (ui->op_dlg, progress_buttons[3].w);
816 buttons_width = 2 +
817 progress_buttons[0].len + max (progress_buttons[1].len, progress_buttons[2].len) +
818 progress_buttons[3].len;
820 /* adjust dialog sizes */
821 dlg_set_size (ui->op_dlg, y + 3, max (COLS * 2 / 3, buttons_width + 6));
823 place_progress_buttons (ui->op_dlg, FALSE);
825 dlg_select_widget (progress_buttons[0].w);
827 /* We will manage the dialog without any help, that's why
828 we have to call init_dlg */
829 init_dlg (ui->op_dlg);
832 /* --------------------------------------------------------------------------------------------- */
834 void
835 file_op_context_destroy_ui (FileOpContext * ctx)
837 if (ctx != NULL && ctx->ui != NULL)
839 FileOpContextUI *ui = (FileOpContextUI *) ctx->ui;
841 dlg_run_done (ui->op_dlg);
842 destroy_dlg (ui->op_dlg);
843 g_free (ui);
844 ctx->ui = NULL;
848 /* --------------------------------------------------------------------------------------------- */
850 show progressbar for file
853 void
854 file_progress_show (FileOpContext * ctx, off_t done, off_t total,
855 const char *stalled_msg, gboolean force_update)
857 FileOpContextUI *ui;
858 char buffer[BUF_TINY];
859 char buffer2[BUF_TINY];
860 char buffer3[BUF_TINY];
862 if (!verbose || ctx == NULL || ctx->ui == NULL)
863 return;
865 ui = ctx->ui;
867 if (total == 0)
869 gauge_show (ui->progress_file_gauge, 0);
870 return;
873 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
874 gauge_show (ui->progress_file_gauge, 1);
876 if (!force_update)
877 return;
879 if (ui->showing_eta && ctx->eta_secs > 0.5)
881 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
882 if (ctx->bps == 0)
883 g_snprintf (buffer, BUF_TINY, "%s %s", buffer2, stalled_msg);
884 else
886 file_bps_prepare_for_show (buffer3, ctx->bps);
887 g_snprintf (buffer, BUF_TINY, "%s (%s) %s", buffer2, buffer3, stalled_msg);
890 else
892 g_snprintf (buffer, BUF_TINY, "%s", stalled_msg);
895 label_set_text (ui->progress_file_label, buffer);
898 /* --------------------------------------------------------------------------------------------- */
900 void
901 file_progress_show_count (FileOpContext * ctx, size_t done, size_t total)
903 char buffer[BUF_TINY];
904 FileOpContextUI *ui;
906 if (ctx == NULL || ctx->ui == NULL)
907 return;
909 ui = ctx->ui;
910 if (ctx->progress_totals_computed)
911 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu/%zu"), done, total);
912 else
913 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu"), done);
914 label_set_text (ui->total_files_processed_label, buffer);
917 /* --------------------------------------------------------------------------------------------- */
919 void
920 file_progress_show_total (FileOpTotalContext * tctx, FileOpContext * ctx, uintmax_t copied_bytes,
921 gboolean show_summary)
923 char buffer[BUF_TINY];
924 char buffer2[BUF_TINY];
925 char buffer3[BUF_TINY];
926 char buffer4[BUF_TINY];
927 struct timeval tv_current;
928 FileOpContextUI *ui;
930 if (ctx == NULL || ctx->ui == NULL)
931 return;
933 ui = ctx->ui;
935 if (ctx->progress_totals_computed)
937 if (ctx->progress_bytes == 0)
938 gauge_show (ui->progress_total_gauge, 0);
939 else
941 gauge_set_value (ui->progress_total_gauge, 1024,
942 (int) (1024 * copied_bytes / ctx->progress_bytes));
943 gauge_show (ui->progress_total_gauge, 1);
947 if (!show_summary && tctx->bps == 0)
948 return;
950 gettimeofday (&tv_current, NULL);
951 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
953 if (ctx->progress_totals_computed)
955 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
956 if (tctx->bps == 0)
957 g_snprintf (buffer, BUF_TINY, _("Time: %s %s"), buffer2, buffer3);
958 else
960 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
961 g_snprintf (buffer, BUF_TINY, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
964 else
966 if (tctx->bps == 0)
967 g_snprintf (buffer, BUF_TINY, _("Time: %s"), buffer2);
968 else
970 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
971 g_snprintf (buffer, BUF_TINY, _("Time: %s (%s)"), buffer2, buffer4);
975 label_set_text (ui->time_label, buffer);
977 size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
978 if (!ctx->progress_totals_computed)
979 g_snprintf (buffer, BUF_TINY, _(" Total: %s "), buffer2);
980 else
982 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
983 g_snprintf (buffer, BUF_TINY, _(" Total: %s/%s "), buffer2, buffer3);
986 hline_set_text (ui->total_bytes_label, buffer);
989 /* }}} */
991 /* --------------------------------------------------------------------------------------------- */
993 void
994 file_progress_show_source (FileOpContext * ctx, const vfs_path_t * s_vpath)
996 FileOpContextUI *ui;
998 if (ctx == NULL || ctx->ui == NULL)
999 return;
1001 ui = ctx->ui;
1003 if (s_vpath != NULL)
1005 char *s;
1007 s = vfs_path_tokens_get (s_vpath, -1, 1);
1008 label_set_text (ui->file_label[0], _("Source"));
1009 label_set_text (ui->file_string[0], truncFileString (ui->op_dlg, s));
1010 g_free (s);
1012 else
1014 label_set_text (ui->file_label[0], "");
1015 label_set_text (ui->file_string[0], "");
1019 /* --------------------------------------------------------------------------------------------- */
1021 void
1022 file_progress_show_target (FileOpContext * ctx, const vfs_path_t * s_vpath)
1024 FileOpContextUI *ui;
1026 if (ctx == NULL || ctx->ui == NULL)
1027 return;
1029 ui = ctx->ui;
1031 if (s_vpath != NULL)
1033 char *s;
1035 s = vfs_path_to_str (s_vpath);
1036 label_set_text (ui->file_label[1], _("Target"));
1037 label_set_text (ui->file_string[1], truncFileStringSecure (ui->op_dlg, s));
1038 g_free (s);
1040 else
1042 label_set_text (ui->file_label[1], "");
1043 label_set_text (ui->file_string[1], "");
1047 /* --------------------------------------------------------------------------------------------- */
1049 void
1050 file_progress_show_deleting (FileOpContext * ctx, const char *s)
1052 FileOpContextUI *ui;
1054 if (ctx == NULL || ctx->ui == NULL)
1055 return;
1057 ui = ctx->ui;
1058 label_set_text (ui->file_label[0], _("Deleting"));
1059 label_set_text (ui->file_label[0], truncFileStringSecure (ui->op_dlg, s));
1062 /* --------------------------------------------------------------------------------------------- */
1064 FileProgressStatus
1065 file_progress_real_query_replace (FileOpContext * ctx,
1066 enum OperationMode mode, const char *destname,
1067 struct stat *_s_stat, struct stat *_d_stat)
1069 FileOpContextUI *ui;
1071 if (ctx == NULL || ctx->ui == NULL)
1072 return FILE_CONT;
1074 ui = ctx->ui;
1076 if (ui->replace_result < REPLACE_ALWAYS)
1078 ui->replace_filename = destname;
1079 ui->s_stat = _s_stat;
1080 ui->d_stat = _d_stat;
1081 ui->replace_result = overwrite_query_dialog (ctx, mode);
1084 switch (ui->replace_result)
1086 case REPLACE_UPDATE:
1087 do_refresh ();
1088 if (_s_stat->st_mtime > _d_stat->st_mtime)
1089 return FILE_CONT;
1090 else
1091 return FILE_SKIP;
1093 case REPLACE_SIZE:
1094 do_refresh ();
1095 if (_s_stat->st_size == _d_stat->st_size)
1096 return FILE_SKIP;
1097 else
1098 return FILE_CONT;
1100 case REPLACE_REGET:
1101 /* Careful: we fall through and set do_append */
1102 ctx->do_reget = _d_stat->st_size;
1104 case REPLACE_APPEND:
1105 ctx->do_append = TRUE;
1107 case REPLACE_YES:
1108 case REPLACE_ALWAYS:
1109 do_refresh ();
1110 return FILE_CONT;
1111 case REPLACE_NO:
1112 case REPLACE_NEVER:
1113 do_refresh ();
1114 return FILE_SKIP;
1115 case REPLACE_ABORT:
1116 default:
1117 return FILE_ABORT;
1121 /* --------------------------------------------------------------------------------------------- */
1123 char *
1124 file_mask_dialog (FileOpContext * ctx, FileOperation operation,
1125 gboolean only_one,
1126 const char *format, const void *text, const char *def_text, gboolean * do_bg)
1128 size_t fmd_xlen;
1129 vfs_path_t *vpath;
1130 int source_easy_patterns = easy_patterns;
1131 char fmd_buf[BUF_MEDIUM];
1132 char *dest_dir, *tmp;
1133 char *def_text_secure;
1135 if (ctx == NULL)
1136 return NULL;
1138 /* unselect checkbox if target filesystem don't support attributes */
1139 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
1140 ctx->stable_symlinks = FALSE;
1141 *do_bg = FALSE;
1143 /* filter out a possible password from def_text */
1144 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1145 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1146 vfs_path_free (vpath);
1148 if (source_easy_patterns)
1149 def_text_secure = strutils_glob_escape (tmp);
1150 else
1151 def_text_secure = strutils_regex_escape (tmp);
1152 g_free (tmp);
1154 if (only_one)
1156 int format_len, text_len;
1157 int max_len;
1159 format_len = str_term_width1 (format);
1160 text_len = str_term_width1 (text);
1161 max_len = COLS - 2 - 6;
1163 if (format_len + text_len <= max_len)
1165 fmd_xlen = format_len + text_len + 6;
1166 fmd_xlen = max (fmd_xlen, 68);
1168 else
1170 text = str_trunc ((const char *) text, max_len - format_len);
1171 fmd_xlen = max_len + 6;
1174 g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1176 else
1178 fmd_xlen = COLS * 2 / 3;
1179 fmd_xlen = max (fmd_xlen, 68);
1180 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1184 char *source_mask, *orig_mask;
1185 int val;
1186 struct stat buf;
1188 quick_widget_t quick_widgets[] = {
1189 /* *INDENT-OFF* */
1190 QUICK_LABELED_INPUT (fmd_buf, input_label_above,
1191 easy_patterns ? "*" : "^(.*)$", "input-def", &source_mask,
1192 NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1193 QUICK_START_COLUMNS,
1194 QUICK_SEPARATOR (FALSE),
1195 QUICK_NEXT_COLUMN,
1196 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1197 QUICK_STOP_COLUMNS,
1198 QUICK_LABELED_INPUT (N_("to:"), input_label_above,
1199 def_text_secure, "input2", &dest_dir, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1200 QUICK_SEPARATOR (TRUE),
1201 QUICK_START_COLUMNS,
1202 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1203 QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
1204 QUICK_NEXT_COLUMN,
1205 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1206 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1207 QUICK_STOP_COLUMNS,
1208 QUICK_START_BUTTONS (TRUE, TRUE),
1209 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1210 #ifdef ENABLE_BACKGROUND
1211 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1212 #endif /* ENABLE_BACKGROUND */
1213 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1214 QUICK_END
1215 /* *INDENT-ON* */
1218 quick_dialog_t qdlg = {
1219 -1, -1, fmd_xlen,
1220 op_names[operation], "[Mask Copy/Rename]",
1221 quick_widgets, NULL, NULL
1224 ask_file_mask:
1225 val = quick_dialog_skip (&qdlg, 4);
1227 if (val == B_CANCEL)
1229 g_free (def_text_secure);
1230 return NULL;
1233 if (ctx->follow_links)
1234 ctx->stat_func = mc_stat;
1235 else
1236 ctx->stat_func = mc_lstat;
1238 if (ctx->op_preserve)
1240 ctx->preserve = TRUE;
1241 ctx->umask_kill = 0777777;
1242 ctx->preserve_uidgid = (geteuid () == 0);
1244 else
1246 int i2;
1248 ctx->preserve = ctx->preserve_uidgid = FALSE;
1249 i2 = umask (0);
1250 umask (i2);
1251 ctx->umask_kill = i2 ^ 0777777;
1254 if ((dest_dir == NULL) || (*dest_dir == '\0'))
1256 g_free (def_text_secure);
1257 g_free (source_mask);
1258 return dest_dir;
1261 ctx->search_handle = mc_search_new (source_mask, -1);
1263 if (ctx->search_handle == NULL)
1265 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"), source_mask);
1266 g_free (dest_dir);
1267 g_free (source_mask);
1268 goto ask_file_mask;
1271 g_free (def_text_secure);
1272 g_free (source_mask);
1274 ctx->search_handle->is_case_sensitive = TRUE;
1275 if (source_easy_patterns)
1276 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1277 else
1278 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1280 tmp = dest_dir;
1281 dest_dir = tilde_expand (tmp);
1282 g_free (tmp);
1283 vpath = vfs_path_from_str (dest_dir);
1285 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1286 if (ctx->dest_mask == NULL)
1287 ctx->dest_mask = dest_dir;
1288 else
1289 ctx->dest_mask++;
1290 orig_mask = ctx->dest_mask;
1291 if (*ctx->dest_mask == '\0'
1292 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1293 && (!only_one
1294 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1295 || (ctx->dive_into_subdirs
1296 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1297 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1298 ctx->dest_mask = g_strdup ("\\0");
1299 else
1301 ctx->dest_mask = g_strdup (ctx->dest_mask);
1302 *orig_mask = '\0';
1304 if (*dest_dir == '\0')
1306 g_free (dest_dir);
1307 dest_dir = g_strdup ("./");
1309 vfs_path_free (vpath);
1310 if (val == B_USER)
1311 *do_bg = TRUE;
1314 return dest_dir;
1317 /* --------------------------------------------------------------------------------------------- */