Get rid of g_return_* macros in file operation routines
[midnight-commander.git] / src / filemanager / filegui.c
blob6b4e12917c9b4020f459f3bca31340cf3ec83f94
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 add_widget_autopos (ui->replace_dlg, \
402 label_new (ypos, rd_widgets [i].xpos, buffer), \
403 rd_widgets [i].pos_flags, \
404 ui->replace_dlg->current != NULL ? ui->replace_dlg->current->data : NULL)
406 /* dialog sizes */
407 const int rd_ylen = 1;
408 int rd_xlen = 60;
409 int y = 2;
410 unsigned long yes_id;
412 struct
414 const char *text;
415 int ypos, xpos;
416 widget_pos_flags_t pos_flags;
417 int value; /* 0 for labels */
418 } rd_widgets[] =
420 /* *INDENT-OFF* */
421 /* 0 */
422 { N_("Target file already exists!"), 3, 4, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
423 /* 1 */
424 { "%s", 4, 4, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
425 /* 2 */ /* cannot use PRIuMAX here; %llu is used instead */
426 { N_("New : %s, size %llu"), 6, 4, WPOS_KEEP_DEFAULT, 0 },
427 /* 3 */ /* cannot use PRIuMAX here; %llu is used instead */
428 { N_("Existing: %s, size %llu"), 7, 4, WPOS_KEEP_DEFAULT, 0 },
429 /* 4 */
430 { N_("Overwrite this target?"), 9, 4, WPOS_KEEP_DEFAULT, 0 },
431 /* 5 */
432 { N_("&Yes"), 9, 28, WPOS_KEEP_DEFAULT, REPLACE_YES },
433 /* 6 */
434 { N_("&No"), 9, 37, WPOS_KEEP_DEFAULT, REPLACE_NO },
435 /* 7 */
436 { N_("A&ppend"), 9, 45, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
437 /* 8 */
438 { N_("&Reget"), 10, 28, WPOS_KEEP_DEFAULT, REPLACE_REGET },
439 /* 9 */
440 { N_("Overwrite all targets?"), 11, 4, WPOS_KEEP_DEFAULT, 0 },
441 /* 10 */
442 { N_("A&ll"), 11, 28, WPOS_KEEP_DEFAULT, REPLACE_ALWAYS },
443 /* 11 */
444 { N_("&Update"), 11, 36, WPOS_KEEP_DEFAULT, REPLACE_UPDATE },
445 /* 12 */
446 { N_("Non&e"), 11, 47, WPOS_KEEP_DEFAULT, REPLACE_NEVER },
447 /* 13 */
448 { N_("If &size differs"), 12, 28, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
449 /* 14 */
450 { N_("&Abort"), 14, 25, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
451 /* *INDENT-ON* */
454 const size_t num = G_N_ELEMENTS (rd_widgets);
455 int *widgets_len;
457 FileOpContextUI *ui = ctx->ui;
459 char buffer[BUF_SMALL];
460 const char *title;
461 vfs_path_t *stripped_vpath;
462 const char *stripped_name;
463 char *stripped_name_orig;
464 int result;
466 widgets_len = g_new0 (int, num);
468 if (mode == Foreground)
469 title = _("File exists");
470 else
471 title = _("Background process: File exists");
473 stripped_vpath = vfs_path_from_str (ui->replace_filename);
474 stripped_name = stripped_name_orig =
475 vfs_path_to_str_flags (stripped_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
476 vfs_path_free (stripped_vpath);
479 size_t i;
480 int l1, l2, l, row;
481 int stripped_name_len;
483 for (i = 0; i < num; i++)
485 #ifdef ENABLE_NLS
486 if (i != 1) /* skip filename */
487 rd_widgets[i].text = _(rd_widgets[i].text);
488 #endif /* ENABLE_NLS */
489 widgets_len[i] = str_term_width1 (rd_widgets[i].text);
493 * longest of "Overwrite..." labels
494 * (assume "Target date..." are short enough)
496 l1 = max (widgets_len[9], widgets_len[4]);
498 /* longest of button rows */
499 l = l2 = 0;
500 row = 0;
501 for (i = 1; i < num - 1; i++)
502 if (rd_widgets[i].value != 0)
504 if (row != rd_widgets[i].ypos)
506 row = rd_widgets[i].ypos;
507 l2 = max (l2, l);
508 l = 0;
510 l += widgets_len[i] + 4;
513 l2 = max (l2, l); /* last row */
514 rd_xlen = max (rd_xlen, l1 + l2 + 8);
515 /* rd_xlen = max (rd_xlen, str_term_width1 (title) + 2); */
516 stripped_name_len = str_term_width1 (stripped_name);
517 rd_xlen = max (rd_xlen, min (COLS, stripped_name_len + 8));
519 /* Now place widgets */
520 l1 += 5; /* start of first button in the row */
521 l = l1;
522 row = 0;
523 for (i = 2; i < num - 1; i++)
524 if (rd_widgets[i].value != 0)
526 if (row != rd_widgets[i].ypos)
528 row = rd_widgets[i].ypos;
529 l = l1;
531 rd_widgets[i].xpos = l;
532 l += widgets_len[i] + 4;
536 /* FIXME - missing help node */
537 ui->replace_dlg =
538 create_dlg (TRUE, 0, 0, rd_ylen, rd_xlen, alarm_colors, NULL, NULL, "[Replace]", title,
539 DLG_CENTER);
541 /* prompt */
542 ADD_RD_LABEL (0, "", "", y++);
543 /* file name */
544 ADD_RD_LABEL (1, str_trunc (stripped_name, rd_xlen - 8), "", y++);
546 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
548 /* source date and size */
549 ADD_RD_LABEL (2, file_date (ui->s_stat->st_mtime), (unsigned long long) ui->s_stat->st_size,
550 y++);
551 /* destination date and size */
552 ADD_RD_LABEL (3, file_date (ui->d_stat->st_mtime), (unsigned long long) ui->d_stat->st_size,
553 y++);
555 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
557 ADD_RD_LABEL (4, 0, 0, y); /* Overwrite this target? */
558 yes_id = ADD_RD_BUTTON (5, y); /* Yes */
559 ADD_RD_BUTTON (6, y); /* No */
561 /* "this target..." widgets */
562 if (!S_ISDIR (ui->d_stat->st_mode))
564 ADD_RD_BUTTON (7, y++); /* Append */
566 if ((ctx->operation == OP_COPY) && (ui->d_stat->st_size != 0)
567 && (ui->s_stat->st_size > ui->d_stat->st_size))
568 ADD_RD_BUTTON (8, y++); /* Reget */
571 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
573 ADD_RD_LABEL (9, 0, 0, y); /* Overwrite all targets? */
574 ADD_RD_BUTTON (10, y); /* All" */
575 ADD_RD_BUTTON (11, y); /* Update */
576 ADD_RD_BUTTON (12, y++); /* None */
577 ADD_RD_BUTTON (13, y++); /* If size differs */
579 add_widget (ui->replace_dlg, hline_new (y++, -1, -1));
581 ADD_RD_BUTTON (14, y); /* Abort */
583 dlg_set_size (ui->replace_dlg, y + 3, rd_xlen);
584 dlg_select_by_id (ui->replace_dlg, yes_id);
585 result = run_dlg (ui->replace_dlg);
586 destroy_dlg (ui->replace_dlg);
588 g_free (widgets_len);
589 g_free (stripped_name_orig);
591 return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
592 #undef ADD_RD_LABEL
593 #undef ADD_RD_BUTTON
596 /* --------------------------------------------------------------------------------------------- */
598 static gboolean
599 is_wildcarded (char *p)
601 for (; *p; p++)
603 if (*p == '*')
604 return TRUE;
605 if (*p == '\\' && p[1] >= '1' && p[1] <= '9')
606 return TRUE;
608 return FALSE;
611 /* --------------------------------------------------------------------------------------------- */
613 static void
614 place_progress_buttons (WDialog * h, gboolean suspended)
616 const size_t i = suspended ? 2 : 1;
617 Widget *w = WIDGET (h);
618 int buttons_width;
620 buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
621 buttons_width += progress_buttons[i].len;
622 button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
624 progress_buttons[0].w->x = w->x + (w->cols - buttons_width) / 2;
625 progress_buttons[i].w->x = progress_buttons[0].w->x + progress_buttons[0].len + 1;
626 progress_buttons[3].w->x = progress_buttons[i].w->x + progress_buttons[i].len + 1;
629 /* --------------------------------------------------------------------------------------------- */
631 static int
632 progress_button_callback (WButton * button, int action)
634 (void) button;
635 (void) action;
637 /* don't close dialog in any case */
638 return 0;
641 /* --------------------------------------------------------------------------------------------- */
642 /*** public functions ****************************************************************************/
643 /* --------------------------------------------------------------------------------------------- */
645 FileProgressStatus
646 check_progress_buttons (FileOpContext * ctx)
648 int c;
649 Gpm_Event event;
650 FileOpContextUI *ui;
652 if (ctx == NULL || ctx->ui == NULL)
653 return FILE_CONT;
655 ui = ctx->ui;
657 get_event:
658 event.x = -1; /* Don't show the GPM cursor */
659 c = tty_get_event (&event, FALSE, ctx->suspended);
660 if (c == EV_NONE)
661 return FILE_CONT;
663 /* Reinitialize to avoid old values after events other than selecting a button */
664 ui->op_dlg->ret_value = FILE_CONT;
666 dlg_process_event (ui->op_dlg, c, &event);
667 switch (ui->op_dlg->ret_value)
669 case FILE_SKIP:
670 if (ctx->suspended)
672 /* redraw dialog in case of Skip after Suspend */
673 place_progress_buttons (ui->op_dlg, FALSE);
674 dlg_redraw (ui->op_dlg);
676 ctx->suspended = FALSE;
677 return FILE_SKIP;
678 case B_CANCEL:
679 case FILE_ABORT:
680 ctx->suspended = FALSE;
681 return FILE_ABORT;
682 case FILE_SUSPEND:
683 ctx->suspended = !ctx->suspended;
684 place_progress_buttons (ui->op_dlg, ctx->suspended);
685 dlg_redraw (ui->op_dlg);
686 /* fallthrough */
687 default:
688 if (ctx->suspended)
689 goto get_event;
690 return FILE_CONT;
694 /* --------------------------------------------------------------------------------------------- */
695 /* {{{ File progress display routines */
697 void
698 file_op_context_create_ui (FileOpContext * ctx, gboolean with_eta,
699 filegui_dialog_type_t dialog_type)
701 FileOpContextUI *ui;
702 int buttons_width;
703 int dlg_width = 58, dlg_height = 17;
704 int y = 2, x = 3;
706 if (ctx == NULL || ctx->ui != NULL)
707 return;
709 #ifdef ENABLE_NLS
710 if (progress_buttons[0].len == -1)
712 size_t i;
714 for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
715 progress_buttons[i].text = _(progress_buttons[i].text);
717 #endif
719 ctx->dialog_type = dialog_type;
720 ctx->recursive_result = RECURSIVE_YES;
721 ctx->ui = g_new0 (FileOpContextUI, 1);
723 ui = ctx->ui;
724 ui->replace_result = REPLACE_YES;
725 ui->showing_eta = with_eta && ctx->progress_totals_computed;
726 ui->showing_bps = with_eta;
728 ui->op_dlg =
729 create_dlg (TRUE, 0, 0, dlg_height, dlg_width, dialog_colors, NULL, NULL, NULL,
730 op_names[ctx->operation], DLG_CENTER);
732 ui->file_label[0] = label_new (y++, x, "");
733 add_widget (ui->op_dlg, ui->file_label[0]);
735 ui->file_string[0] = label_new (y++, x, "");
736 add_widget (ui->op_dlg, ui->file_string[0]);
738 ui->file_label[1] = label_new (y++, x, "");
739 add_widget (ui->op_dlg, ui->file_label[1]);
741 ui->file_string[1] = label_new (y++, x, "");
742 add_widget (ui->op_dlg, ui->file_string[1]);
744 ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
745 if (!classic_progressbar && (current_panel == right_panel))
746 ui->progress_file_gauge->from_left_to_right = FALSE;
747 add_widget_autopos (ui->op_dlg, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
749 ui->progress_file_label = label_new (y++, x, "");
750 add_widget (ui->op_dlg, ui->progress_file_label);
752 if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
754 ui->total_bytes_label = hline_new (y++, -1, -1);
755 add_widget (ui->op_dlg, ui->total_bytes_label);
757 if (ctx->progress_totals_computed)
759 ui->progress_total_gauge =
760 gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
761 if (!classic_progressbar && (current_panel == right_panel))
762 ui->progress_total_gauge->from_left_to_right = FALSE;
763 add_widget_autopos (ui->op_dlg, ui->progress_total_gauge,
764 WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
767 ui->total_files_processed_label = label_new (y++, x, "");
768 add_widget (ui->op_dlg, ui->total_files_processed_label);
770 ui->time_label = label_new (y++, x, "");
771 add_widget (ui->op_dlg, ui->time_label);
774 add_widget (ui->op_dlg, hline_new (y++, -1, -1));
776 progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
777 progress_buttons[0].flags, progress_buttons[0].text,
778 progress_button_callback));
779 if (progress_buttons[0].len == -1)
780 progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
782 progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
783 progress_buttons[1].flags, progress_buttons[1].text,
784 progress_button_callback));
785 if (progress_buttons[1].len == -1)
786 progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
788 if (progress_buttons[2].len == -1)
790 /* create and destroy button to get it length */
791 progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
792 progress_buttons[2].flags,
793 progress_buttons[2].text,
794 progress_button_callback));
795 progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
796 send_message (progress_buttons[2].w, NULL, MSG_DESTROY, 0, NULL);
797 g_free (progress_buttons[2].w);
799 progress_buttons[2].w = progress_buttons[1].w;
801 progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
802 progress_buttons[3].flags, progress_buttons[3].text,
803 NULL));
804 if (progress_buttons[3].len == -1)
805 progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
807 add_widget (ui->op_dlg, progress_buttons[0].w);
808 add_widget (ui->op_dlg, progress_buttons[1].w);
809 add_widget (ui->op_dlg, progress_buttons[3].w);
811 buttons_width = 2 +
812 progress_buttons[0].len + max (progress_buttons[1].len, progress_buttons[2].len) +
813 progress_buttons[3].len;
815 /* adjust dialog sizes */
816 dlg_set_size (ui->op_dlg, y + 3, max (COLS * 2 / 3, buttons_width + 6));
818 place_progress_buttons (ui->op_dlg, FALSE);
820 dlg_select_widget (progress_buttons[0].w);
822 /* We will manage the dialog without any help, that's why
823 we have to call init_dlg */
824 init_dlg (ui->op_dlg);
827 /* --------------------------------------------------------------------------------------------- */
829 void
830 file_op_context_destroy_ui (FileOpContext * ctx)
832 if (ctx != NULL && ctx->ui != NULL)
834 FileOpContextUI *ui = (FileOpContextUI *) ctx->ui;
836 dlg_run_done (ui->op_dlg);
837 destroy_dlg (ui->op_dlg);
838 g_free (ui);
839 ctx->ui = NULL;
843 /* --------------------------------------------------------------------------------------------- */
845 show progressbar for file
848 void
849 file_progress_show (FileOpContext * ctx, off_t done, off_t total,
850 const char *stalled_msg, gboolean force_update)
852 FileOpContextUI *ui;
853 char buffer[BUF_TINY];
854 char buffer2[BUF_TINY];
855 char buffer3[BUF_TINY];
857 if (!verbose || ctx == NULL || ctx->ui == NULL)
858 return;
860 ui = ctx->ui;
862 if (total == 0)
864 gauge_show (ui->progress_file_gauge, 0);
865 return;
868 gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
869 gauge_show (ui->progress_file_gauge, 1);
871 if (!force_update)
872 return;
874 if (ui->showing_eta && ctx->eta_secs > 0.5)
876 file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
877 if (ctx->bps == 0)
878 g_snprintf (buffer, BUF_TINY, "%s %s", buffer2, stalled_msg);
879 else
881 file_bps_prepare_for_show (buffer3, ctx->bps);
882 g_snprintf (buffer, BUF_TINY, "%s (%s) %s", buffer2, buffer3, stalled_msg);
885 else
887 g_snprintf (buffer, BUF_TINY, "%s", stalled_msg);
890 label_set_text (ui->progress_file_label, buffer);
893 /* --------------------------------------------------------------------------------------------- */
895 void
896 file_progress_show_count (FileOpContext * ctx, size_t done, size_t total)
898 char buffer[BUF_TINY];
899 FileOpContextUI *ui;
901 if (ctx == NULL || ctx->ui == NULL)
902 return;
904 ui = ctx->ui;
905 if (ctx->progress_totals_computed)
906 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu/%zu"), done, total);
907 else
908 g_snprintf (buffer, BUF_TINY, _("Files processed: %zu"), done);
909 label_set_text (ui->total_files_processed_label, buffer);
912 /* --------------------------------------------------------------------------------------------- */
914 void
915 file_progress_show_total (FileOpTotalContext * tctx, FileOpContext * ctx, uintmax_t copied_bytes,
916 gboolean show_summary)
918 char buffer[BUF_TINY];
919 char buffer2[BUF_TINY];
920 char buffer3[BUF_TINY];
921 char buffer4[BUF_TINY];
922 struct timeval tv_current;
923 FileOpContextUI *ui;
925 if (ctx == NULL || ctx->ui == NULL)
926 return;
928 ui = ctx->ui;
930 if (ctx->progress_totals_computed)
932 if (ctx->progress_bytes == 0)
933 gauge_show (ui->progress_total_gauge, 0);
934 else
936 gauge_set_value (ui->progress_total_gauge, 1024,
937 (int) (1024 * copied_bytes / ctx->progress_bytes));
938 gauge_show (ui->progress_total_gauge, 1);
942 if (!show_summary && tctx->bps == 0)
943 return;
945 gettimeofday (&tv_current, NULL);
946 file_frmt_time (buffer2, tv_current.tv_sec - tctx->transfer_start.tv_sec);
948 if (ctx->progress_totals_computed)
950 file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
951 if (tctx->bps == 0)
952 g_snprintf (buffer, BUF_TINY, _("Time: %s %s"), buffer2, buffer3);
953 else
955 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
956 g_snprintf (buffer, BUF_TINY, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
959 else
961 if (tctx->bps == 0)
962 g_snprintf (buffer, BUF_TINY, _("Time: %s"), buffer2);
963 else
965 file_bps_prepare_for_show (buffer4, (long) tctx->bps);
966 g_snprintf (buffer, BUF_TINY, _("Time: %s (%s)"), buffer2, buffer4);
970 label_set_text (ui->time_label, buffer);
972 size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
973 if (!ctx->progress_totals_computed)
974 g_snprintf (buffer, BUF_TINY, _(" Total: %s "), buffer2);
975 else
977 size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
978 g_snprintf (buffer, BUF_TINY, _(" Total: %s/%s "), buffer2, buffer3);
981 hline_set_text (ui->total_bytes_label, buffer);
984 /* }}} */
986 /* --------------------------------------------------------------------------------------------- */
988 void
989 file_progress_show_source (FileOpContext * ctx, const vfs_path_t * s_vpath)
991 FileOpContextUI *ui;
993 if (ctx == NULL || ctx->ui == NULL)
994 return;
996 ui = ctx->ui;
998 if (s_vpath != NULL)
1000 char *s;
1002 s = vfs_path_tokens_get (s_vpath, -1, 1);
1003 label_set_text (ui->file_label[0], _("Source"));
1004 label_set_text (ui->file_string[0], truncFileString (ui->op_dlg, s));
1005 g_free (s);
1007 else
1009 label_set_text (ui->file_label[0], "");
1010 label_set_text (ui->file_string[0], "");
1014 /* --------------------------------------------------------------------------------------------- */
1016 void
1017 file_progress_show_target (FileOpContext * ctx, const vfs_path_t * s_vpath)
1019 FileOpContextUI *ui;
1021 if (ctx == NULL || ctx->ui == NULL)
1022 return;
1024 ui = ctx->ui;
1026 if (s_vpath != NULL)
1028 char *s;
1030 s = vfs_path_to_str (s_vpath);
1031 label_set_text (ui->file_label[1], _("Target"));
1032 label_set_text (ui->file_string[1], truncFileStringSecure (ui->op_dlg, s));
1033 g_free (s);
1035 else
1037 label_set_text (ui->file_label[1], "");
1038 label_set_text (ui->file_string[1], "");
1042 /* --------------------------------------------------------------------------------------------- */
1044 void
1045 file_progress_show_deleting (FileOpContext * ctx, const char *s)
1047 FileOpContextUI *ui;
1049 if (ctx == NULL || ctx->ui == NULL)
1050 return;
1052 ui = ctx->ui;
1053 label_set_text (ui->file_label[0], _("Deleting"));
1054 label_set_text (ui->file_label[0], truncFileStringSecure (ui->op_dlg, s));
1057 /* --------------------------------------------------------------------------------------------- */
1059 FileProgressStatus
1060 file_progress_real_query_replace (FileOpContext * ctx,
1061 enum OperationMode mode, const char *destname,
1062 struct stat *_s_stat, struct stat *_d_stat)
1064 FileOpContextUI *ui;
1066 if (ctx == NULL || ctx->ui == NULL)
1067 return FILE_CONT;
1069 ui = ctx->ui;
1071 if (ui->replace_result < REPLACE_ALWAYS)
1073 ui->replace_filename = destname;
1074 ui->s_stat = _s_stat;
1075 ui->d_stat = _d_stat;
1076 ui->replace_result = overwrite_query_dialog (ctx, mode);
1079 switch (ui->replace_result)
1081 case REPLACE_UPDATE:
1082 do_refresh ();
1083 if (_s_stat->st_mtime > _d_stat->st_mtime)
1084 return FILE_CONT;
1085 else
1086 return FILE_SKIP;
1088 case REPLACE_SIZE:
1089 do_refresh ();
1090 if (_s_stat->st_size == _d_stat->st_size)
1091 return FILE_SKIP;
1092 else
1093 return FILE_CONT;
1095 case REPLACE_REGET:
1096 /* Careful: we fall through and set do_append */
1097 ctx->do_reget = _d_stat->st_size;
1099 case REPLACE_APPEND:
1100 ctx->do_append = TRUE;
1102 case REPLACE_YES:
1103 case REPLACE_ALWAYS:
1104 do_refresh ();
1105 return FILE_CONT;
1106 case REPLACE_NO:
1107 case REPLACE_NEVER:
1108 do_refresh ();
1109 return FILE_SKIP;
1110 case REPLACE_ABORT:
1111 default:
1112 return FILE_ABORT;
1116 /* --------------------------------------------------------------------------------------------- */
1118 char *
1119 file_mask_dialog (FileOpContext * ctx, FileOperation operation,
1120 gboolean only_one,
1121 const char *format, const void *text, const char *def_text, gboolean * do_bg)
1123 size_t fmd_xlen;
1124 vfs_path_t *vpath;
1125 int source_easy_patterns = easy_patterns;
1126 char fmd_buf[BUF_MEDIUM];
1127 char *dest_dir, *tmp;
1128 char *def_text_secure;
1130 if (ctx == NULL)
1131 return NULL;
1133 /* unselect checkbox if target filesystem don't support attributes */
1134 ctx->op_preserve = filegui__check_attrs_on_fs (def_text);
1135 ctx->stable_symlinks = FALSE;
1136 *do_bg = FALSE;
1138 /* filter out a possible password from def_text */
1139 vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
1140 tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
1141 vfs_path_free (vpath);
1143 if (source_easy_patterns)
1144 def_text_secure = strutils_glob_escape (tmp);
1145 else
1146 def_text_secure = strutils_regex_escape (tmp);
1147 g_free (tmp);
1149 if (only_one)
1151 int format_len, text_len;
1152 int max_len;
1154 format_len = str_term_width1 (format);
1155 text_len = str_term_width1 (text);
1156 max_len = COLS - 2 - 6;
1158 if (format_len + text_len <= max_len)
1160 fmd_xlen = format_len + text_len + 6;
1161 fmd_xlen = max (fmd_xlen, 68);
1163 else
1165 text = str_trunc ((const char *) text, max_len - format_len);
1166 fmd_xlen = max_len + 6;
1169 g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
1171 else
1173 fmd_xlen = COLS * 2 / 3;
1174 fmd_xlen = max (fmd_xlen, 68);
1175 g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
1179 char *source_mask, *orig_mask;
1180 int val;
1181 struct stat buf;
1183 quick_widget_t quick_widgets[] = {
1184 /* *INDENT-OFF* */
1185 QUICK_LABELED_INPUT (fmd_buf, input_label_above,
1186 easy_patterns ? "*" : "^(.*)$", "input-def", &source_mask,
1187 NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1188 QUICK_START_COLUMNS,
1189 QUICK_SEPARATOR (FALSE),
1190 QUICK_NEXT_COLUMN,
1191 QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
1192 QUICK_STOP_COLUMNS,
1193 QUICK_LABELED_INPUT (N_("to:"), input_label_above,
1194 def_text_secure, "input2", &dest_dir, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
1195 QUICK_SEPARATOR (TRUE),
1196 QUICK_START_COLUMNS,
1197 QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
1198 QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
1199 QUICK_NEXT_COLUMN,
1200 QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
1201 QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
1202 QUICK_STOP_COLUMNS,
1203 QUICK_START_BUTTONS (TRUE, TRUE),
1204 QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
1205 #ifdef ENABLE_BACKGROUND
1206 QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
1207 #endif /* ENABLE_BACKGROUND */
1208 QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1209 QUICK_END
1210 /* *INDENT-ON* */
1213 quick_dialog_t qdlg = {
1214 -1, -1, fmd_xlen,
1215 op_names[operation], "[Mask Copy/Rename]",
1216 quick_widgets, NULL, NULL
1219 ask_file_mask:
1220 val = quick_dialog_skip (&qdlg, 4);
1222 if (val == B_CANCEL)
1224 g_free (def_text_secure);
1225 return NULL;
1228 if (ctx->follow_links)
1229 ctx->stat_func = mc_stat;
1230 else
1231 ctx->stat_func = mc_lstat;
1233 if (ctx->op_preserve)
1235 ctx->preserve = TRUE;
1236 ctx->umask_kill = 0777777;
1237 ctx->preserve_uidgid = (geteuid () == 0);
1239 else
1241 int i2;
1243 ctx->preserve = ctx->preserve_uidgid = FALSE;
1244 i2 = umask (0);
1245 umask (i2);
1246 ctx->umask_kill = i2 ^ 0777777;
1249 if ((dest_dir == NULL) || (*dest_dir == '\0'))
1251 g_free (def_text_secure);
1252 g_free (source_mask);
1253 return dest_dir;
1256 ctx->search_handle = mc_search_new (source_mask, -1);
1258 if (ctx->search_handle == NULL)
1260 message (D_ERROR, MSG_ERROR, _("Invalid source pattern `%s'"), source_mask);
1261 g_free (dest_dir);
1262 g_free (source_mask);
1263 goto ask_file_mask;
1266 g_free (def_text_secure);
1267 g_free (source_mask);
1269 ctx->search_handle->is_case_sensitive = TRUE;
1270 if (source_easy_patterns)
1271 ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
1272 else
1273 ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
1275 tmp = dest_dir;
1276 dest_dir = tilde_expand (tmp);
1277 g_free (tmp);
1278 vpath = vfs_path_from_str (dest_dir);
1280 ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
1281 if (ctx->dest_mask == NULL)
1282 ctx->dest_mask = dest_dir;
1283 else
1284 ctx->dest_mask++;
1285 orig_mask = ctx->dest_mask;
1286 if (*ctx->dest_mask == '\0'
1287 || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
1288 && (!only_one
1289 || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
1290 || (ctx->dive_into_subdirs
1291 && ((!only_one && !is_wildcarded (ctx->dest_mask))
1292 || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
1293 ctx->dest_mask = g_strdup ("\\0");
1294 else
1296 ctx->dest_mask = g_strdup (ctx->dest_mask);
1297 *orig_mask = '\0';
1299 if (*dest_dir == '\0')
1301 g_free (dest_dir);
1302 dest_dir = g_strdup ("./");
1304 vfs_path_free (vpath);
1305 if (val == B_USER)
1306 *do_bg = TRUE;
1309 return dest_dir;
1312 /* --------------------------------------------------------------------------------------------- */