(ftpfs_file_store): type accuracy and simplify conditions.
[midnight-commander.git] / src / filemanager / filegui.c
blobd447279e245c66e22dcd2b1537644e82e2c3806f
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
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, 2009-2012
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(ui, s) str_trunc (s, 52)
201 #define truncFileStringSecure(ui, s) path_trunc (s, 52)
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 Dlg_head *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 WLabel *total_bytes_label;
253 /* Query replace dialog */
254 Dlg_head *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 /*** file scope functions ************************************************************************/
264 /* --------------------------------------------------------------------------------------------- */
266 static gboolean
267 filegui__check_attrs_on_fs (const char *fs_path)
269 STRUCT_STATVFS stfs;
271 if (!setup_copymove_persistent_attr)
272 return FALSE;
274 #if USE_STATVFS && defined(STAT_STATVFS)
275 if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
276 return TRUE;
277 #else
278 if (STATFS (fs_path, &stfs) != 0)
279 return TRUE;
280 #endif
282 #if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
283 (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
284 switch ((filegui_nonattrs_fs_t) stfs.f_type)
286 case MSDOS_SUPER_MAGIC:
287 case NTFS_SB_MAGIC:
288 case PROC_SUPER_MAGIC:
289 case SMB_SUPER_MAGIC:
290 case NCP_SUPER_MAGIC:
291 case USBDEVICE_SUPER_MAGIC:
292 return FALSE;
293 default:
294 break;
296 #elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
297 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
298 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
299 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
300 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
301 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
302 || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
303 return FALSE;
304 #elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
305 if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
306 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
307 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
308 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
309 || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
310 return FALSE;
311 #endif
313 return TRUE;
316 /* --------------------------------------------------------------------------------------------- */
318 static void
319 file_frmt_time (char *buffer, double eta_secs)
321 int eta_hours, eta_mins, eta_s;
322 eta_hours = eta_secs / (60 * 60);
323 eta_mins = (eta_secs - (eta_hours * 60 * 60)) / 60;
324 eta_s = eta_secs - (eta_hours * 60 * 60 + eta_mins * 60);
325 g_snprintf (buffer, BUF_TINY, _("%d:%02d.%02d"), eta_hours, eta_mins, eta_s);
328 /* --------------------------------------------------------------------------------------------- */
330 static void
331 file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
333 char _fmt_buff[BUF_TINY];
334 if (eta_secs <= 0.5 && !always_show)
336 *buffer = '\0';
337 return;
339 if (eta_secs <= 0.5)
340 eta_secs = 1;
341 file_frmt_time (_fmt_buff, eta_secs);
342 g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
345 /* --------------------------------------------------------------------------------------------- */
347 static void
348 file_bps_prepare_for_show (char *buffer, long bps)
350 if (bps > 1024 * 1024)
352 g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
354 else if (bps > 1024)
356 g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
358 else if (bps > 1)
360 g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
362 else
363 *buffer = '\0';
366 /* --------------------------------------------------------------------------------------------- */
368 * FIXME: probably it is better to replace this with quick dialog machinery,
369 * but actually I'm not familiar with it and have not much time :(
370 * alex
372 static replace_action_t
373 overwrite_query_dialog (FileOpContext * ctx, enum OperationMode mode)
375 #define ADD_RD_BUTTON(i) \
376 add_widget (ui->replace_dlg, \
377 button_new (rd_widgets [i].ypos, rd_widgets [i].xpos, rd_widgets [i].value, \
378 NORMAL_BUTTON, rd_widgets [i].text, 0))
380 #define ADD_RD_LABEL(i, p1, p2) \
381 g_snprintf (buffer, sizeof (buffer), rd_widgets [i].text, p1, p2); \
382 add_widget (ui->replace_dlg, label_new (rd_widgets [i].ypos, rd_widgets [i].xpos, buffer))
384 /* dialog sizes */
385 const int rd_ylen = 17;
386 int rd_xlen = 60;
388 struct
390 const char *text;
391 int ypos, xpos;
392 int value; /* 0 for labels */
393 } rd_widgets[] =
395 /* *INDENT-OFF* */
396 /* 0 */
397 { N_("Target file already exists!"), 3, 4, 0 },
398 /* 1 */
399 { "%s", 4, 4, 0 },
400 /* 2 */ /* cannot use PRIuMAX here; %llu is used instead */
401 { N_("Source date: %s, size %llu"), 6, 4, 0 },
402 /* 3 */ /* cannot use PRIuMAX here; %llu is used instead */
403 { N_("Target date: %s, size %llu"), 7, 4, 0 },
404 /* 4 */
405 { N_("&Abort"), 14, 25, REPLACE_ABORT },
406 /* 5 */
407 { N_("If &size differs"), 12, 28, REPLACE_SIZE },
408 /* 6 */
409 { N_("Non&e"), 11, 47, REPLACE_NEVER },
410 /* 7 */
411 { N_("&Update"), 11, 36, REPLACE_UPDATE },
412 /* 8 */
413 { N_("A&ll"), 11, 28, REPLACE_ALWAYS },
414 /* 9 */
415 { N_("Overwrite all targets?"), 11, 4, 0 },
416 /* 10 */
417 { N_("&Reget"), 10, 28, REPLACE_REGET },
418 /* 11 */
419 { N_("A&ppend"), 9, 45, REPLACE_APPEND },
420 /* 12 */
421 { N_("&No"), 9, 37, REPLACE_NO },
422 /* 13 */
423 { N_("&Yes"), 9, 28, REPLACE_YES },
424 /* 14 */
425 { N_("Overwrite this target?"), 9, 4, 0 }
426 /* *INDENT-ON* */
429 const int num = sizeof (rd_widgets) / sizeof (rd_widgets[0]);
430 int *widgets_len;
432 FileOpContextUI *ui = ctx->ui;
434 char buffer[BUF_SMALL];
435 const char *title;
436 int stripped_name_len;
437 vfs_path_t *stripped_vpath;
438 const char *stripped_name;
439 char *stripped_name_orig;
440 int result;
442 widgets_len = g_new0 (int, num);
444 if (mode == Foreground)
445 title = _("File exists");
446 else
447 title = _("Background process: File exists");
449 stripped_vpath = vfs_path_from_str (ui->replace_filename);
450 stripped_name = stripped_name_orig =
451 vfs_path_to_str_flags (stripped_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
452 vfs_path_free (stripped_vpath);
453 stripped_name_len = str_term_width1 (stripped_name);
456 int i, l1, l2, l, row;
458 for (i = 0; i < num; i++)
460 #ifdef ENABLE_NLS
461 if (i != 1) /* skip filename */
462 rd_widgets[i].text = _(rd_widgets[i].text);
463 #endif /* ENABLE_NLS */
464 widgets_len[i] = str_term_width1 (rd_widgets[i].text);
468 * longest of "Overwrite..." labels
469 * (assume "Target date..." are short enough)
471 l1 = max (widgets_len[9], widgets_len[14]);
473 /* longest of button rows */
474 i = num;
475 for (row = l = l2 = 0; i--;)
476 if (rd_widgets[i].value != 0)
478 if (row != rd_widgets[i].ypos)
480 row = rd_widgets[i].ypos;
481 l2 = max (l2, l);
482 l = 0;
484 l += widgets_len[i] + 4;
487 l2 = max (l2, l); /* last row */
488 rd_xlen = max (rd_xlen, l1 + l2 + 8);
489 rd_xlen = max (rd_xlen, str_term_width1 (title) + 2);
490 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
492 /* Now place widgets */
493 l1 += 5; /* start of first button in the row */
494 i = num;
495 for (l = l1, row = 0; --i > 1;)
496 if (rd_widgets[i].value != 0)
498 if (row != rd_widgets[i].ypos)
500 row = rd_widgets[i].ypos;
501 l = l1;
503 rd_widgets[i].xpos = l;
504 l += widgets_len[i] + 4;
507 /* Abort button is centered */
508 rd_widgets[4].xpos = (rd_xlen - widgets_len[4] - 3) / 2;
511 /* FIXME - missing help node */
512 ui->replace_dlg =
513 create_dlg (TRUE, 0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, NULL, "[Replace]",
514 title, DLG_CENTER | DLG_REVERSE);
516 /* prompt -- centered */
517 add_widget (ui->replace_dlg,
518 label_new (rd_widgets[0].ypos, (rd_xlen - widgets_len[0]) / 2, rd_widgets[0].text));
519 /* file name -- centered */
520 stripped_name = str_trunc (stripped_name, rd_xlen - 8);
521 stripped_name_len = str_term_width1 (stripped_name);
522 add_widget (ui->replace_dlg,
523 label_new (rd_widgets[1].ypos, (rd_xlen - stripped_name_len) / 2, stripped_name));
525 /* source date and size */
526 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime), (unsigned long long) ui->s_stat->st_size);
527 /* destination date and size */
528 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime), (unsigned long long) ui->d_stat->st_size);
530 ADD_RD_BUTTON (4); /* Abort */
531 ADD_RD_BUTTON (5); /* If size differs */
532 ADD_RD_BUTTON (6); /* None */
533 ADD_RD_BUTTON (7); /* Update */
534 ADD_RD_BUTTON (8); /* All" */
535 ADD_RD_LABEL (9, 0, 0); /* Overwrite all targets? */
537 /* "this target..." widgets */
538 if (!S_ISDIR (ui->d_stat->st_mode))
540 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
541 && (ui->s_stat->st_size > ui->d_stat->st_size))
542 ADD_RD_BUTTON (10); /* Reget */
544 ADD_RD_BUTTON (11); /* Append */
546 ADD_RD_BUTTON (12); /* No */
547 ADD_RD_BUTTON (13); /* Yes */
548 ADD_RD_LABEL (14, 0, 0); /* Overwrite this target? */
550 result = run_dlg (ui->replace_dlg);
551 destroy_dlg (ui->replace_dlg);
553 g_free (widgets_len);
554 g_free (stripped_name_orig);
556 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
557 #undef ADD_RD_LABEL
558 #undef ADD_RD_BUTTON
561 /* --------------------------------------------------------------------------------------------- */
563 static gboolean
564 is_wildcarded (char *p)
566 for (; *p; p++)
568 if (*p == '*')
569 return TRUE;
570 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
571 return TRUE;
573 return FALSE;
576 /* --------------------------------------------------------------------------------------------- */
577 /*** public functions ****************************************************************************/
578 /* --------------------------------------------------------------------------------------------- */
580 FileProgressStatus
581 check_progress_buttons (FileOpContext * ctx)
583 int c;
584 Gpm_Event event;
585 FileOpContextUI *ui;
587 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
589 ui = ctx->ui;
591 event.x = -1; /* Don't show the GPM cursor */
592 c = tty_get_event (&event, FALSE, FALSE);
593 if (c == EV_NONE)
594 return FILE_CONT;
596 /* Reinitialize to avoid old values after events other than
597 selecting a button */
598 ui->op_dlg->ret_value = FILE_CONT;
600 dlg_process_event (ui->op_dlg, c, &event);
601 switch (ui->op_dlg->ret_value)
603 case FILE_SKIP:
604 return FILE_SKIP;
605 case B_CANCEL:
606 case FILE_ABORT:
607 return FILE_ABORT;
608 default:
609 return FILE_CONT;
614 /* --------------------------------------------------------------------------------------------- */
615 /* {{{ File progress display routines */
617 void
618 file_op_context_create_ui_without_init (FileOpContext * ctx, gboolean with_eta,
619 filegui_dialog_type_t dialog_type)
621 FileOpContextUI *ui;
622 const char *abort_button_label = N_("&Abort");
623 const char *skip_button_label = N_("&Skip");
624 int abort_button_width, skip_button_width, buttons_width;
625 int dlg_width, dlg_height;
627 g_return_if_fail (ctx != NULL);
628 g_return_if_fail (ctx->ui == NULL);
630 #ifdef ENABLE_NLS
631 abort_button_label = _(abort_button_label);
632 skip_button_label = _(skip_button_label);
633 #endif
635 abort_button_width = str_term_width1 (abort_button_label) + 3;
636 skip_button_width = str_term_width1 (skip_button_label) + 3;
637 buttons_width = abort_button_width + skip_button_width + 2;
639 dlg_width = max (58, buttons_width + 6);
640 dlg_height = 17; /* to make compiler happy :) */
642 ui = g_new0 (FileOpContextUI, 1);
643 ctx->ui = ui;
645 ctx->dialog_type = dialog_type;
647 switch (dialog_type)
649 case FILEGUI_DIALOG_ONE_ITEM:
650 dlg_height = verbose ? 12 : 10;
651 break;
652 case FILEGUI_DIALOG_MULTI_ITEM:
653 dlg_height = !verbose ? 10 : file_op_compute_totals ? 17 : 15;
654 break;
655 case FILEGUI_DIALOG_DELETE_ITEM:
656 dlg_height = 7;
657 break;
660 ctx->recursive_result = RECURSIVE_YES;
662 ui->replace_result = REPLACE_YES;
663 ui->showing_eta = with_eta && file_op_compute_totals;
664 ui->showing_bps = with_eta;
666 ui->op_dlg =
667 create_dlg (TRUE, 0, 0, dlg_height, dlg_width,
668 dialog_colors, NULL, NULL, NULL, op_names[ctx->operation],
669 DLG_CENTER | DLG_REVERSE);
671 add_widget (ui->op_dlg,
672 button_new (dlg_height - 3, dlg_width / 2 + 1, FILE_ABORT,
673 NORMAL_BUTTON, abort_button_label, NULL));
674 add_widget (ui->op_dlg,
675 button_new (dlg_height - 3, dlg_width / 2 - 1 - skip_button_width, FILE_SKIP,
676 NORMAL_BUTTON, skip_button_label, NULL));
678 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
680 int dy = file_op_compute_totals ? 2 : 0;
682 if (file_op_compute_totals)
683 add_widget (ui->op_dlg, ui->progress_total_gauge =
684 gauge_new (7 + dy, 3 + 3, 0, 100, 0));
686 add_widget (ui->op_dlg, ui->total_files_processed_label = label_new (9 + dy, 3, ""));
688 add_widget (ui->op_dlg, ui->time_label = label_new (10 + dy, 3, ""));
690 add_widget (ui->op_dlg, ui->total_bytes_label = label_new (8, 3 + 15, ""));
691 add_widget (ui->op_dlg, hline_new (8, 1, dlg_width - 2));
694 add_widget (ui->op_dlg, ui->progress_file_label = label_new (7, 3, ""));
696 add_widget (ui->op_dlg, ui->progress_file_gauge = gauge_new (6, 3 + 3, 0, 100, 0));
698 add_widget (ui->op_dlg, ui->file_string[1] = label_new (5, 3, ""));
700 add_widget (ui->op_dlg, ui->file_label[1] = label_new (4, 3, ""));
701 add_widget (ui->op_dlg, ui->file_string[0] = label_new (3, 3, ""));
702 add_widget (ui->op_dlg, ui->file_label[0] = label_new (2, 3, ""));
704 if ((right_panel == current_panel) && !classic_progressbar)
706 ui->progress_file_gauge->from_left_to_right = FALSE;
707 if (verbose && file_op_compute_totals && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
708 ui->progress_total_gauge->from_left_to_right = FALSE;
712 /* --------------------------------------------------------------------------------------------- */
714 void
715 file_op_context_create_ui (FileOpContext * ctx, gboolean with_eta,
716 filegui_dialog_type_t dialog_type)
718 FileOpContextUI *ui;
720 g_return_if_fail (ctx != NULL);
721 g_return_if_fail (ctx->ui == NULL);
723 file_op_context_create_ui_without_init (ctx, with_eta, dialog_type);
724 ui = ctx->ui;
726 /* We will manage the dialog without any help, that's why
727 we have to call init_dlg */
728 init_dlg (ui->op_dlg);
731 /* --------------------------------------------------------------------------------------------- */
733 void
734 file_op_context_destroy_ui (FileOpContext * ctx)
736 g_return_if_fail (ctx != NULL);
738 if (ctx->ui != NULL)
740 FileOpContextUI *ui = (FileOpContextUI *) ctx->ui;
742 dlg_run_done (ui->op_dlg);
743 destroy_dlg (ui->op_dlg);
744 g_free (ui);
745 ctx->ui = NULL;
749 /* --------------------------------------------------------------------------------------------- */
751 show progressbar for file
754 void
755 file_progress_show (FileOpContext * ctx, off_t done, off_t total,
756 const char *stalled_msg, gboolean force_update)
758 FileOpContextUI *ui;
759 char buffer[BUF_TINY];
760 char buffer2[BUF_TINY];
761 char buffer3[BUF_TINY];
763 if (!verbose)
764 return;
766 g_return_if_fail (ctx != NULL);
767 g_return_if_fail (ctx->ui != NULL);
769 ui = ctx->ui;
771 if (total == 0)
773 gauge_show (ui->progress_file_gauge, 0);
774 return;
777 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
778 gauge_show (ui->progress_file_gauge, 1);
780 if (!force_update)
781 return;
783 if (ui->showing_eta && ctx->eta_secs > 0.5)
785 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
786 if (ctx->bps == 0)
787 g_snprintf (buffer, BUF_TINY, "%s %s", buffer2, stalled_msg);
788 else
790 file_bps_prepare_for_show (buffer3, ctx->bps);
791 g_snprintf (buffer, BUF_TINY, "%s (%s) %s", buffer2, buffer3, stalled_msg);
794 else
796 g_snprintf (buffer, BUF_TINY, "%s", stalled_msg);
799 label_set_text (ui->progress_file_label, buffer);
802 /* --------------------------------------------------------------------------------------------- */
804 void
805 file_progress_show_count (FileOpContext * ctx, size_t done, size_t total)
807 char buffer[BUF_TINY];
808 FileOpContextUI *ui;
810 g_return_if_fail (ctx != NULL);
811 g_return_if_fail (ctx->ui != NULL);
813 ui = ctx->ui;
814 if (file_op_compute_totals)
815 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu/%zu"), done, total);
816 else
817 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu"), done);
818 label_set_text (ui->total_files_processed_label, buffer);
821 /* --------------------------------------------------------------------------------------------- */
823 void
824 file_progress_show_total (FileOpTotalContext * tctx, FileOpContext * ctx, uintmax_t copied_bytes,
825 gboolean show_summary)
827 char buffer[BUF_TINY];
828 char buffer2[BUF_TINY];
829 char buffer3[BUF_TINY];
830 char buffer4[BUF_TINY];
831 struct timeval tv_current;
832 FileOpContextUI *ui;
834 g_return_if_fail (ctx != NULL);
835 g_return_if_fail (ctx->ui != NULL);
837 ui = ctx->ui;
839 if (file_op_compute_totals)
841 if (ctx->progress_bytes == 0)
842 gauge_show (ui->progress_total_gauge, 0);
843 else
845 gauge_set_value (ui->progress_total_gauge, 1024,
846 (int) (1024 * copied_bytes / ctx->progress_bytes));
847 gauge_show (ui->progress_total_gauge, 1);
851 if (!show_summary && tctx->bps == 0)
852 return;
854 gettimeofday (&tv_current, NULL);
855 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
857 if (file_op_compute_totals)
859 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
860 if (tctx->bps == 0)
861 g_snprintf (buffer, BUF_TINY, _("Time: %s %s"), buffer2, buffer3);
862 else
864 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
865 g_snprintf (buffer, BUF_TINY, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
868 else
870 if (tctx->bps == 0)
871 g_snprintf (buffer, BUF_TINY, _("Time: %s"), buffer2);
872 else
874 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
875 g_snprintf (buffer, BUF_TINY, _("Time: %s (%s)"), buffer2, buffer4);
879 label_set_text (ui->time_label, buffer);
881 size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
882 if (!file_op_compute_totals)
883 g_snprintf (buffer, BUF_TINY, _(" Total: %s "), buffer2);
884 else
886 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
887 g_snprintf (buffer, BUF_TINY, _(" Total: %s/%s "), buffer2, buffer3);
890 label_set_text (ui->total_bytes_label, buffer);
893 /* }}} */
895 /* --------------------------------------------------------------------------------------------- */
897 void
898 file_progress_show_source (FileOpContext * ctx, const vfs_path_t * s_vpath)
900 FileOpContextUI *ui;
902 g_return_if_fail (ctx != NULL);
903 g_return_if_fail (ctx->ui != NULL);
905 ui = ctx->ui;
907 if (s_vpath != NULL)
909 char *s;
911 s = vfs_path_tokens_get (s_vpath, -1, 1);
912 label_set_text (ui->file_label[0], _("Source"));
913 label_set_text (ui->file_string[0], truncFileString (ui, s));
914 g_free (s);
916 else
918 label_set_text (ui->file_label[0], "");
919 label_set_text (ui->file_string[0], "");
923 /* --------------------------------------------------------------------------------------------- */
925 void
926 file_progress_show_target (FileOpContext * ctx, const vfs_path_t * s_vpath)
928 FileOpContextUI *ui;
930 g_return_if_fail (ctx != NULL);
931 g_return_if_fail (ctx->ui != NULL);
933 ui = ctx->ui;
935 if (s_vpath != NULL)
937 char *s;
939 s = vfs_path_to_str (s_vpath);
940 label_set_text (ui->file_label[1], _("Target"));
941 label_set_text (ui->file_string[1], truncFileStringSecure (ui, s));
942 g_free (s);
944 else
946 label_set_text (ui->file_label[1], "");
947 label_set_text (ui->file_string[1], "");
951 /* --------------------------------------------------------------------------------------------- */
953 void
954 file_progress_show_deleting (FileOpContext * ctx, const char *s)
956 FileOpContextUI *ui;
958 g_return_if_fail (ctx != NULL);
959 g_return_if_fail (ctx->ui != NULL);
961 ui = ctx->ui;
962 label_set_text (ui->file_label[0], _("Deleting"));
963 label_set_text (ui->file_label[0], truncFileStringSecure (ui, s));
966 /* --------------------------------------------------------------------------------------------- */
968 FileProgressStatus
969 file_progress_real_query_replace (FileOpContext * ctx,
970 enum OperationMode mode, const char *destname,
971 struct stat *_s_stat, struct stat *_d_stat)
973 FileOpContextUI *ui;
975 g_return_val_if_fail (ctx != NULL, FILE_CONT);
976 g_return_val_if_fail (ctx->ui != NULL, FILE_CONT);
978 ui = ctx->ui;
980 if (ui->replace_result < REPLACE_ALWAYS)
982 ui->replace_filename = destname;
983 ui->s_stat = _s_stat;
984 ui->d_stat = _d_stat;
985 ui->replace_result = overwrite_query_dialog (ctx, mode);
988 switch (ui->replace_result)
990 case REPLACE_UPDATE:
991 do_refresh ();
992 if (_s_stat->st_mtime > _d_stat->st_mtime)
993 return FILE_CONT;
994 else
995 return FILE_SKIP;
997 case REPLACE_SIZE:
998 do_refresh ();
999 if (_s_stat->st_size == _d_stat->st_size)
1000 return FILE_SKIP;
1001 else
1002 return FILE_CONT;
1004 case REPLACE_REGET:
1005 /* Careful: we fall through and set do_append */
1006 ctx->do_reget = _d_stat->st_size;
1008 case REPLACE_APPEND:
1009 ctx->do_append = TRUE;
1011 case REPLACE_YES:
1012 case REPLACE_ALWAYS:
1013 do_refresh ();
1014 return FILE_CONT;
1015 case REPLACE_NO:
1016 case REPLACE_NEVER:
1017 do_refresh ();
1018 return FILE_SKIP;
1019 case REPLACE_ABORT:
1020 default:
1021 return FILE_ABORT;
1025 /* --------------------------------------------------------------------------------------------- */
1027 char *
1028 file_mask_dialog (FileOpContext * ctx, FileOperation operation,
1029 gboolean only_one,
1030 const char *format, const void *text, const char *def_text, gboolean * do_bg)
1032 const size_t FMDY = 13;
1033 const size_t FMDX = 68;
1034 size_t fmd_xlen;
1036 /* buttons */
1037 const size_t gap = 1;
1038 size_t b0_len, b2_len;
1039 size_t b1_len = 0;
1041 int source_easy_patterns = easy_patterns;
1042 size_t i, len;
1043 char fmd_buf[BUF_MEDIUM];
1044 char *source_mask, *orig_mask, *dest_dir, *tmp;
1045 char *def_text_secure;
1046 int val;
1048 QuickWidget fmd_widgets[] = {
1049 /* 0 */ QUICK_BUTTON (42, 64, 10, FMDY, N_("&Cancel"), B_CANCEL, NULL),
1050 #ifdef ENABLE_BACKGROUND
1051 /* 1 */ QUICK_BUTTON (25, 64, 10, FMDY, N_("&Background"), B_USER, NULL),
1052 #define OFFSET 0
1053 #else
1054 #define OFFSET 1
1055 #endif /* ENABLE_BACKGROUND */
1056 /* 2 - OFFSET */
1057 QUICK_BUTTON (14, FMDX, 10, FMDY, N_("&OK"), B_ENTER, NULL),
1058 /* 3 - OFFSET */
1059 QUICK_CHECKBOX (42, FMDX, 8, FMDY, N_("&Stable Symlinks"), &ctx->stable_symlinks),
1060 /* 4 - OFFSET */
1061 QUICK_CHECKBOX (31, FMDX, 7, FMDY, N_("Di&ve into subdir if exists"),
1062 &ctx->dive_into_subdirs),
1063 /* 5 - OFFSET */
1064 QUICK_CHECKBOX (3, FMDX, 8, FMDY, N_("Preserve &attributes"), &ctx->op_preserve),
1065 /* 6 - OFFSET */
1066 QUICK_CHECKBOX (3, FMDX, 7, FMDY, N_("Follow &links"), &ctx->follow_links),
1067 /* 7 - OFFSET */
1068 QUICK_INPUT (3, FMDX, 6, FMDY, "", 58, 0, "input2", &dest_dir),
1069 /* 8 - OFFSET */
1070 QUICK_LABEL (3, FMDX, 5, FMDY, N_("to:")),
1071 /* 9 - OFFSET */
1072 QUICK_CHECKBOX (37, FMDX, 4, FMDY, N_("&Using shell patterns"), &source_easy_patterns),
1073 /* 10 - OFFSET */
1074 QUICK_INPUT (3, FMDX, 3, FMDY, easy_patterns ? "*" : "^(.*)$", 58, 0, "input-def",
1075 &source_mask),
1076 /* 11 - OFFSET */
1077 QUICK_LABEL (3, FMDX, 2, FMDY, fmd_buf),
1078 QUICK_END
1081 g_return_val_if_fail (ctx != NULL, NULL);
1083 #ifdef ENABLE_NLS
1084 /* buttons */
1085 for (i = 0; i <= 2 - OFFSET; i++)
1086 fmd_widgets[i].u.button.text = _(fmd_widgets[i].u.button.text);
1088 /* checkboxes */
1089 for (i = 3 - OFFSET; i <= 9 - OFFSET; i++)
1090 if (i != 7 - OFFSET)
1091 fmd_widgets[i].u.checkbox.text = _(fmd_widgets[i].u.checkbox.text);
1092 #endif /* !ENABLE_NLS */
1094 fmd_xlen = max (FMDX, (size_t) COLS * 2 / 3);
1096 len = str_term_width1 (fmd_widgets[6 - OFFSET].u.checkbox.text)
1097 + str_term_width1 (fmd_widgets[4 - OFFSET].u.checkbox.text) + 15;
1098 fmd_xlen = max (fmd_xlen, len);
1100 len = str_term_width1 (fmd_widgets[5 - OFFSET].u.checkbox.text)
1101 + str_term_width1 (fmd_widgets[3 - OFFSET].u.checkbox.text) + 15;
1102 fmd_xlen = max (fmd_xlen, len);
1104 /* buttons */
1105 b2_len = str_term_width1 (fmd_widgets[2 - OFFSET].u.button.text) + 6 + gap; /* OK */
1106 #ifdef ENABLE_BACKGROUND
1107 b1_len = str_term_width1 (fmd_widgets[1].u.button.text) + 4 + gap; /* Background */
1108 #endif
1109 b0_len = str_term_width1 (fmd_widgets[0].u.button.text) + 4; /* Cancel */
1110 len = b0_len + b1_len + b2_len;
1111 fmd_xlen = min (max (fmd_xlen, len + 6), (size_t) COLS);
1113 if (only_one)
1115 int flen;
1117 flen = str_term_width1 (format);
1118 i = fmd_xlen - flen - 4; /* FIXME */
1119 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc ((const char *) text, i));
1121 else
1123 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1124 fmd_xlen = max (fmd_xlen, (size_t) str_term_width1 (fmd_buf) + 6);
1127 for (i = sizeof (fmd_widgets) / sizeof (fmd_widgets[0]); i > 0;)
1128 fmd_widgets[--i].x_divisions = fmd_xlen;
1130 i = (fmd_xlen - len) / 2;
1131 /* OK button */
1132 fmd_widgets[2 - OFFSET].relative_x = i;
1133 i += b2_len;
1134 #ifdef ENABLE_BACKGROUND
1135 /* Background button */
1136 fmd_widgets[1].relative_x = i;
1137 i += b1_len;
1138 #endif
1139 /* Cancel button */
1140 fmd_widgets[0].relative_x = i;
1142 #define chkbox_xpos(i) \
1143 fmd_widgets [i].relative_x = fmd_xlen - str_term_width1 (fmd_widgets [i].u.checkbox.text) - 6
1144 chkbox_xpos (3 - OFFSET);
1145 chkbox_xpos (4 - OFFSET);
1146 chkbox_xpos (9 - OFFSET);
1147 #undef chkbox_xpos
1149 /* inputs */
1150 fmd_widgets[7 - OFFSET].u.input.len = fmd_widgets[10 - OFFSET].u.input.len = fmd_xlen - 6;
1152 /* unselect checkbox if target filesystem don't support attributes */
1153 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
1155 /* filter out a possible password from def_text */
1157 vfs_path_t *vpath;
1159 vpath = vfs_path_from_str_flags (def_text, (only_one) ? VPF_NO_CANON : VPF_NONE);
1160 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1161 vfs_path_free (vpath);
1163 if (source_easy_patterns)
1164 def_text_secure = strutils_glob_escape (tmp);
1165 else
1166 def_text_secure = strutils_regex_escape (tmp);
1167 g_free (tmp);
1169 /* destination */
1170 fmd_widgets[7 - OFFSET].u.input.text = def_text_secure;
1172 ctx->stable_symlinks = FALSE;
1173 *do_bg = FALSE;
1176 struct stat buf;
1177 vfs_path_t *vpath;
1179 QuickDialog Quick_input = {
1180 fmd_xlen, FMDY, -1, -1, op_names[operation],
1181 "[Mask Copy/Rename]", fmd_widgets, NULL, NULL, TRUE
1184 ask_file_mask:
1185 val = quick_dialog_skip (&Quick_input, 4);
1187 if (val == B_CANCEL)
1189 g_free (def_text_secure);
1190 return NULL;
1193 if (ctx->follow_links)
1194 ctx->stat_func = mc_stat;
1195 else
1196 ctx->stat_func = mc_lstat;
1198 if (ctx->op_preserve)
1200 ctx->preserve = TRUE;
1201 ctx->umask_kill = 0777777;
1202 ctx->preserve_uidgid = (geteuid () == 0);
1204 else
1206 int i2;
1207 ctx->preserve = ctx->preserve_uidgid = FALSE;
1208 i2 = umask (0);
1209 umask (i2);
1210 ctx->umask_kill = i2 ^ 0777777;
1213 if ((dest_dir == NULL) || (*dest_dir == '\0'))
1215 g_free (def_text_secure);
1216 g_free (source_mask);
1217 return dest_dir;
1220 ctx->search_handle = mc_search_new (source_mask, -1);
1222 if (ctx->search_handle == NULL)
1224 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"), source_mask);
1225 g_free (dest_dir);
1226 g_free (source_mask);
1227 goto ask_file_mask;
1230 g_free (def_text_secure);
1231 g_free (source_mask);
1233 ctx->search_handle->is_case_sensitive = TRUE;
1234 if (source_easy_patterns)
1235 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1236 else
1237 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1239 tmp = dest_dir;
1240 dest_dir = tilde_expand (tmp);
1241 g_free (tmp);
1242 vpath = vfs_path_from_str (dest_dir);
1244 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1245 if (ctx->dest_mask == NULL)
1246 ctx->dest_mask = dest_dir;
1247 else
1248 ctx->dest_mask++;
1249 orig_mask = ctx->dest_mask;
1250 if (*ctx->dest_mask == '\0'
1251 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1252 && (!only_one
1253 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1254 || (ctx->dive_into_subdirs
1255 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1256 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1257 ctx->dest_mask = g_strdup ("\\0");
1258 else
1260 ctx->dest_mask = g_strdup (ctx->dest_mask);
1261 *orig_mask = '\0';
1263 if (!*dest_dir)
1265 g_free (dest_dir);
1266 dest_dir = g_strdup ("./");
1268 vfs_path_free (vpath);
1269 if (val == B_USER)
1270 *do_bg = TRUE;
1273 return dest_dir;
1276 /* --------------------------------------------------------------------------------------------- */