1 /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 */
19
20 #include "fops_common.h"
21
22 #include <regex.h>
23
24 #include <sys/stat.h> /* stat umask() */
25 #include <sys/types.h> /* mode_t */
26 #include <fcntl.h>
27 #include <unistd.h> /* unlink() */
28
29 #ifdef _WIN32
30 #include <windows.h>
31 #include <shellapi.h>
32 #endif
33
34 #include <assert.h> /* assert() */
35 #include <errno.h> /* errno */
36 #include <stddef.h> /* NULL size_t */
37 #include <stdint.h> /* uint64_t */
38 #include <stdio.h> /* snprintf() */
39 #include <stdlib.h> /* calloc() free() malloc() realloc() strtol() */
40 #include <string.h> /* memcmp() memset() strcat() strcmp() strdup() strlen() */
41 #include <time.h> /* clock_gettime() */
42
43 #include "cfg/config.h"
44 #include "compat/dtype.h"
45 #include "compat/fs_limits.h"
46 #include "compat/os.h"
47 #include "int/vim.h"
48 #include "io/ioeta.h"
49 #include "io/ionotif.h"
50 #include "modes/dialogs/msg_dialog.h"
51 #include "modes/modes.h"
52 #include "ui/cancellation.h"
53 #include "ui/fileview.h"
54 #include "ui/statusbar.h"
55 #include "ui/ui.h"
56 #include "utils/cancellation.h"
57 #ifdef _WIN32
58 #include "utils/env.h"
59 #endif
60 #include "utils/fs.h"
61 #include "utils/fsdata.h"
62 #include "utils/macros.h"
63 #include "utils/path.h"
64 #include "utils/str.h"
65 #include "utils/string_array.h"
66 #include "utils/utils.h"
67 #include "background.h"
68 #include "filelist.h"
69 #include "flist_pos.h"
70 #include "flist_sel.h"
71 #include "ops.h"
72 #include "running.h"
73 #include "status.h"
74 #include "trash.h"
75 #include "types.h"
76 #include "undo.h"
77
78 /* 10 to the power of number of digits after decimal point to take into account
79 * on progress percentage counting. */
80 #define IO_PRECISION 10
81
82 /* Maximum value of progress_data_t::last_progress. */
83 #define IO_MAX_PROGRESS (100*(IO_PRECISION))
84
85 /* Key used to switch to progress dialog. */
86 #define IO_DETAILS_KEY 'i'
87
88 /* Object for auxiliary information related to progress of operations in
89 * io_progress_changed() handler. */
90 typedef struct
91 {
92 int bg; /* Whether this is background operation. */
93 union
94 {
95 ops_t *ops; /* Information for foreground operation. */
96 bg_op_t *bg_op; /* Information for background operation. */
97 };
98
99 int last_progress; /* Progress of the operation during previous call.
100 Range is [-1; IO_MAX_PROGRESS]. */
101 IoPs last_stage; /* Stage of the operation during previous call. */
102
103 char *progress_bar; /* String of progress bar. */
104 int progress_bar_value; /* Value of progress bar during previous call. */
105 int progress_bar_max; /* Width of progress bar during previous call. */
106
107 /* State of rate calculation. */
108 long long last_calc_time; /* Time of last rate calculation. */
109 uint64_t last_seen_byte; /* Position at the time of last call. */
110 uint64_t rate; /* Rate in bytes per millisecond. */
111 char *rate_str; /* Rate formatted as a string. */
112
113 /* Whether progress is displayed in a dialog, rather than on status bar. */
114 int dialog;
115
116 int width; /* Maximum reached width of the dialog. */
117 }
118 progress_data_t;
119
120 static void io_progress_changed(const io_progress_t *state);
121 static int calc_io_progress(const io_progress_t *state, int *skip);
122 static void update_io_rate(progress_data_t *pdata, const ioeta_estim_t *estim);
123 static void update_progress_bar(progress_data_t *pdata, const ioeta_estim_t *estim);
124 static void io_progress_fg(const io_progress_t *state, int progress);
125 static void io_progress_fg_sb(const io_progress_t *state, int progress);
126 static void io_progress_bg(const io_progress_t *state, int progress);
127 static char * format_file_progress(const ioeta_estim_t *estim, int precision);
128 static void format_pretty_path(const char base_dir[], const char path[],
129 char pretty[], size_t pretty_size);
130 static int is_file_name_changed(const char old[], const char new[]);
131 static int ui_cancellation_hook(void *arg);
132 static progress_data_t * alloc_progress_data(int bg, void *info);
133 static long long time_in_ms(void);
134
135 line_prompt_func fops_line_prompt;
136 options_prompt_func fops_options_prompt;
137
138 void
fops_init(line_prompt_func line_func,options_prompt_func options_func)139 fops_init(line_prompt_func line_func, options_prompt_func options_func)
140 {
141 fops_line_prompt = line_func;
142 fops_options_prompt = options_func;
143 ionotif_register(&io_progress_changed);
144 }
145
146 /* I/O operation update callback. */
147 static void
io_progress_changed(const io_progress_t * state)148 io_progress_changed(const io_progress_t *state)
149 {
150 const ioeta_estim_t *const estim = state->estim;
151 progress_data_t *const pdata = estim->param;
152
153 int redraw = 0;
154 int progress, skip;
155
156 progress = calc_io_progress(state, &skip);
157 if(skip)
158 {
159 return;
160 }
161
162 /* Don't query for scheduled redraw or input for background operations. */
163 if(!pdata->bg)
164 {
165 redraw = (stats_update_fetch() != UT_NONE);
166
167 if(!pdata->dialog)
168 {
169 if(ui_char_pressed(IO_DETAILS_KEY))
170 {
171 pdata->dialog = 1;
172 ui_sb_clear();
173 }
174 }
175 }
176
177 /* Do nothing if progress change is small, but force update on stage
178 * change or redraw request. */
179 if(progress == pdata->last_progress &&
180 state->stage == pdata->last_stage && !redraw)
181 {
182 return;
183 }
184
185 pdata->last_stage = state->stage;
186
187 if(progress >= 0)
188 {
189 pdata->last_progress = progress;
190 }
191
192 if(redraw)
193 {
194 modes_redraw();
195 }
196
197 if(pdata->bg)
198 {
199 io_progress_bg(state, progress);
200 }
201 else
202 {
203 io_progress_fg(state, progress);
204 }
205 }
206
207 /* Calculates current IO operation progress. *skip will be set to non-zero
208 * value to indicate that progress change is irrelevant. Returns progress in
209 * the range [-1; IO_MAX_PROGRESS], where -1 means "unknown". */
210 static int
calc_io_progress(const io_progress_t * state,int * skip)211 calc_io_progress(const io_progress_t *state, int *skip)
212 {
213 const ioeta_estim_t *const estim = state->estim;
214 progress_data_t *const pdata = estim->param;
215
216 *skip = 0;
217 if(state->stage == IO_PS_ESTIMATING)
218 {
219 return estim->total_items/IO_PRECISION;
220 }
221 else if(estim->total_bytes == 0)
222 {
223 if(estim->total_items == 0)
224 {
225 return 0;
226 }
227 /* When files are empty, use their number for progress counting. */
228 return (estim->current_item*IO_MAX_PROGRESS)/estim->total_items;
229 }
230 else if(pdata->last_progress >= IO_MAX_PROGRESS &&
231 estim->current_byte == estim->total_bytes)
232 {
233 /* Special handling for unknown total size. */
234 ++pdata->last_progress;
235 if(pdata->last_progress%IO_PRECISION != 0)
236 {
237 *skip = 1;
238 }
239 return -1;
240 }
241 else
242 {
243 return (estim->current_byte*IO_MAX_PROGRESS)/estim->total_bytes;
244 }
245 }
246
247 /* Updates rate of operation. */
248 static void
update_io_rate(progress_data_t * pdata,const ioeta_estim_t * estim)249 update_io_rate(progress_data_t *pdata, const ioeta_estim_t *estim)
250 {
251 long long current_time_ms = time_in_ms();
252 long long elapsed_time_ms = current_time_ms - pdata->last_calc_time;
253
254 if(elapsed_time_ms == 0 ||
255 (pdata->last_seen_byte != 0 && elapsed_time_ms < 3000))
256 {
257 /* Rate is updated initially and then once in 3000 milliseconds. */
258 return;
259 }
260
261 uint64_t bytes_difference = estim->current_byte - pdata->last_seen_byte;
262 pdata->rate = bytes_difference/elapsed_time_ms;
263 pdata->last_calc_time = current_time_ms;
264 pdata->last_seen_byte = estim->current_byte;
265
266 char rate_str[64];
267 (void)friendly_size_notation(pdata->rate*1000, sizeof(rate_str) - 8,
268 rate_str);
269 strcat(rate_str, "/s");
270 replace_string(&pdata->rate_str, rate_str);
271 }
272
273 /* Updates progress bar of operation. */
274 static void
update_progress_bar(progress_data_t * pdata,const ioeta_estim_t * estim)275 update_progress_bar(progress_data_t *pdata, const ioeta_estim_t *estim)
276 {
277 int max_width = pdata->width - 6;
278 if(max_width <= 0)
279 {
280 return;
281 }
282
283 int value = (pdata->last_progress*max_width)/IO_MAX_PROGRESS;
284 if(value == pdata->progress_bar_value && max_width == pdata->progress_bar_max)
285 {
286 return;
287 }
288
289 pdata->progress_bar_value = value;
290 pdata->progress_bar_max = max_width;
291
292 pdata->progress_bar = malloc(max_width + 3);
293
294 pdata->progress_bar[0] = '[';
295 memset(pdata->progress_bar + 1, '=', value);
296 memset(pdata->progress_bar + 1 + value, ' ', max_width - value);
297 pdata->progress_bar[1 + max_width] = ']';
298 pdata->progress_bar[1 + max_width + 1] = '\0';
299 }
300
301 /* Takes care of progress for foreground operations. */
302 static void
io_progress_fg(const io_progress_t * state,int progress)303 io_progress_fg(const io_progress_t *state, int progress)
304 {
305 char current_size_str[64];
306 char total_size_str[64];
307 char src_path[PATH_MAX + 1];
308 const char *title, *ctrl_msg;
309 const char *target_name;
310 char *as_part;
311 const char *item_name;
312 int item_num;
313
314 const ioeta_estim_t *const estim = state->estim;
315 progress_data_t *const pdata = estim->param;
316 ops_t *const ops = pdata->ops;
317
318 if(!pdata->dialog)
319 {
320 io_progress_fg_sb(state, progress);
321 return;
322 }
323
324 (void)friendly_size_notation(estim->total_bytes, sizeof(total_size_str),
325 total_size_str);
326
327 copy_str(src_path, sizeof(src_path), replace_home_part(estim->item));
328 remove_last_path_component(src_path);
329
330 title = ops_describe(ops);
331 ctrl_msg = "Press Ctrl-C to cancel";
332 if(state->stage == IO_PS_ESTIMATING)
333 {
334 char pretty_path[PATH_MAX + 1];
335 format_pretty_path(ops->base_dir, estim->item, pretty_path,
336 sizeof(pretty_path));
337 draw_msgf(title, ctrl_msg, pdata->width,
338 "In %s\nestimating...\nItems: %" PRINTF_ULL "\n"
339 "Overall: %s\nCurrent: %s",
340 ops->target_dir, (unsigned long long)estim->total_items, total_size_str,
341 pretty_path);
342 pdata->width = getmaxx(error_win);
343 return;
344 }
345
346 (void)friendly_size_notation(estim->current_byte,
347 sizeof(current_size_str), current_size_str);
348
349 item_name = get_last_path_component(estim->item);
350
351 target_name = get_last_path_component(estim->target);
352 if(stroscmp(target_name, item_name) == 0)
353 {
354 as_part = strdup("");
355 }
356 else
357 {
358 as_part = format_str("\nas %s", target_name);
359 }
360
361 item_num = MIN(estim->current_item + 1, estim->total_items);
362
363 update_io_rate(pdata, estim);
364
365 if(progress < 0)
366 {
367 /* Simplified message for unknown total size. */
368 draw_msgf(title, ctrl_msg, pdata->width,
369 "Location: %s\nItem: %d of %" PRINTF_ULL "\n"
370 "Overall: %s %s\n"
371 " \n" /* Space is on purpose to preserve empty line. */
372 "file %s\nfrom %s%s",
373 replace_home_part(ops->target_dir), item_num,
374 (unsigned long long)estim->total_items, total_size_str, pdata->rate_str,
375 item_name, src_path, as_part);
376 }
377 else
378 {
379 char *const file_progress = format_file_progress(estim, IO_PRECISION);
380 update_progress_bar(pdata, estim);
381
382 draw_msgf(title, ctrl_msg, pdata->width,
383 "Location: %s\nItem: %d of %" PRINTF_ULL "\n"
384 "Overall: %s/%s (%2d%%) %s\n"
385 "%s\n"
386 " \n" /* Space is on purpose to preserve empty line. */
387 "file %s\nfrom %s%s%s",
388 replace_home_part(ops->target_dir), item_num,
389 (unsigned long long)estim->total_items, current_size_str,
390 total_size_str, progress/IO_PRECISION, pdata->rate_str,
391 pdata->progress_bar, item_name, src_path, as_part, file_progress);
392
393 free(file_progress);
394 }
395
396 pdata->width = getmaxx(error_win);
397
398 free(as_part);
399 }
400
401 /* Takes care of progress for foreground operations displayed on status line. */
402 static void
io_progress_fg_sb(const io_progress_t * state,int progress)403 io_progress_fg_sb(const io_progress_t *state, int progress)
404 {
405 const ioeta_estim_t *const estim = state->estim;
406 progress_data_t *const pdata = estim->param;
407 ops_t *const ops = pdata->ops;
408
409 char current_size_str[64];
410 char total_size_str[64];
411 char pretty_path[PATH_MAX + 1];
412 char *suffix;
413
414 (void)friendly_size_notation(estim->total_bytes, sizeof(total_size_str),
415 total_size_str);
416
417 format_pretty_path(ops->base_dir, estim->item, pretty_path,
418 sizeof(pretty_path));
419
420 switch(state->stage)
421 {
422 case IO_PS_ESTIMATING:
423 suffix = format_str("estimating... %" PRINTF_ULL "; %s %s",
424 (unsigned long long)estim->total_items, total_size_str, pretty_path);
425 break;
426 case IO_PS_IN_PROGRESS:
427 (void)friendly_size_notation(estim->current_byte,
428 sizeof(current_size_str), current_size_str);
429
430 if(progress < 0)
431 {
432 /* Simplified message for unknown total size. */
433 suffix = format_str("%" PRINTF_ULL " of %" PRINTF_ULL "; %s %s",
434 (unsigned long long)estim->current_item + 1U,
435 (unsigned long long)estim->total_items, total_size_str,
436 pretty_path);
437 }
438 else
439 {
440 suffix = format_str("%" PRINTF_ULL " of %" PRINTF_ULL "; "
441 "%s/%s (%2d%%) %s",
442 (unsigned long long)estim->current_item + 1,
443 (unsigned long long)estim->total_items, current_size_str,
444 total_size_str, progress/IO_PRECISION, pretty_path);
445 }
446 break;
447
448 default:
449 assert(0 && "Unhandled progress stage");
450 suffix = strdup("");
451 break;
452 }
453
454 ui_sb_quick_msgf("(hit %c for details) %s: %s", IO_DETAILS_KEY,
455 ops_describe(ops), suffix);
456 free(suffix);
457 }
458
459 /* Takes care of progress for background operations. */
460 static void
io_progress_bg(const io_progress_t * state,int progress)461 io_progress_bg(const io_progress_t *state, int progress)
462 {
463 const ioeta_estim_t *const estim = state->estim;
464 progress_data_t *const pdata = estim->param;
465 bg_op_t *const bg_op = pdata->bg_op;
466
467 bg_op->progress = progress/IO_PRECISION;
468 bg_op_changed(bg_op);
469 }
470
471 /* Formats file progress part of the progress message. Returns pointer to newly
472 * allocated memory. */
473 static char *
format_file_progress(const ioeta_estim_t * estim,int precision)474 format_file_progress(const ioeta_estim_t *estim, int precision)
475 {
476 char current_size[64];
477 char total_size[64];
478
479 const int file_progress = (estim->total_file_bytes == 0U) ? 0 :
480 (estim->current_file_byte*100*precision)/estim->total_file_bytes;
481
482 if(estim->total_items == 1)
483 {
484 return strdup("");
485 }
486
487 (void)friendly_size_notation(estim->current_file_byte, sizeof(current_size),
488 current_size);
489 (void)friendly_size_notation(estim->total_file_bytes, sizeof(total_size),
490 total_size);
491
492 return format_str("\nprogress %s/%s (%2d%%)", current_size, total_size,
493 file_progress/precision);
494 }
495
496 /* Pretty prints path shortening it by skipping base directory path if
497 * possible, otherwise fallbacks to the full path. */
498 static void
format_pretty_path(const char base_dir[],const char path[],char pretty[],size_t pretty_size)499 format_pretty_path(const char base_dir[], const char path[], char pretty[],
500 size_t pretty_size)
501 {
502 if(!path_starts_with(path, base_dir))
503 {
504 copy_str(pretty, pretty_size, path);
505 return;
506 }
507
508 copy_str(pretty, pretty_size, skip_char(path + strlen(base_dir), '/'));
509 }
510
511 int
fops_is_name_list_ok(int count,int nlines,char * list[],char * files[])512 fops_is_name_list_ok(int count, int nlines, char *list[], char *files[])
513 {
514 int i;
515
516 if(nlines < count)
517 {
518 ui_sb_errf("Not enough file names (%d/%d)", nlines, count);
519 curr_stats.save_msg = 1;
520 return 0;
521 }
522
523 if(nlines > count)
524 {
525 ui_sb_errf("Too many file names (%d/%d)", nlines, count);
526 curr_stats.save_msg = 1;
527 return 0;
528 }
529
530 for(i = 0; i < count; ++i)
531 {
532 chomp(list[i]);
533
534 if(files != NULL)
535 {
536 char *file_s = find_slashr(files[i]);
537 char *list_s = find_slashr(list[i]);
538 if(list_s != NULL || file_s != NULL)
539 {
540 if(list_s == NULL || file_s == NULL ||
541 list_s - list[i] != file_s - files[i] ||
542 strnoscmp(files[i], list[i], list_s - list[i]) != 0)
543 {
544 if(file_s == NULL)
545 ui_sb_errf("Name \"%s\" contains slash", list[i]);
546 else
547 ui_sb_errf("Won't move \"%s\" file", files[i]);
548 curr_stats.save_msg = 1;
549 return 0;
550 }
551 }
552 }
553
554 if(list[i][0] != '\0' && is_in_string_array(list, i, list[i]))
555 {
556 ui_sb_errf("Name \"%s\" duplicates", list[i]);
557 curr_stats.save_msg = 1;
558 return 0;
559 }
560 }
561
562 return 1;
563 }
564
565 int
fops_is_rename_list_ok(char * files[],char is_dup[],int len,char * list[])566 fops_is_rename_list_ok(char *files[], char is_dup[], int len, char *list[])
567 {
568 int i;
569 const char *const work_dir = flist_get_dir(curr_view);
570 for(i = 0; i < len; ++i)
571 {
572 int j;
573
574 const int check_result =
575 fops_check_file_rename(work_dir, files[i], list[i], ST_NONE);
576 if(check_result < 0)
577 {
578 continue;
579 }
580
581 for(j = 0; j < len; ++j)
582 {
583 if(strcmp(list[i], files[j]) == 0 && !is_dup[j])
584 {
585 is_dup[j] = 1;
586 break;
587 }
588 }
589 if(j >= len && check_result == 0)
590 {
591 /* Invoke fops_check_file_rename() again, but this time to produce error
592 * message. */
593 (void)fops_check_file_rename(work_dir, files[i], list[i], ST_STATUS_BAR);
594 break;
595 }
596 }
597 return i >= len;
598 }
599
600 int
fops_check_file_rename(const char dir[],const char old[],const char new[],SignalType signal_type)601 fops_check_file_rename(const char dir[], const char old[], const char new[],
602 SignalType signal_type)
603 {
604 if(!is_file_name_changed(old, new))
605 {
606 return -1;
607 }
608
609 if(path_exists_at(dir, new, NODEREF) && stroscmp(old, new) != 0 &&
610 !is_case_change(old, new))
611 {
612 switch(signal_type)
613 {
614 case ST_STATUS_BAR:
615 ui_sb_errf("File \"%s\" already exists", new);
616 curr_stats.save_msg = 1;
617 break;
618 case ST_DIALOG:
619 show_error_msg("File exists",
620 "That file already exists. Will not overwrite.");
621 break;
622
623 default:
624 assert(signal_type == ST_NONE && "Unhandled signaling type");
625 break;
626 }
627 return 0;
628 }
629
630 return 1;
631 }
632
633 /* Checks whether file name change was performed. Returns non-zero if change is
634 * detected, otherwise zero is returned. */
635 static int
is_file_name_changed(const char old[],const char new[])636 is_file_name_changed(const char old[], const char new[])
637 {
638 /* Empty new name means reuse of the old name (rename cancellation). Names
639 * are always compared in a case sensitive way, so that changes in case of
640 * letters triggers rename operation even for systems where paths are case
641 * insensitive. */
642 return (new[0] != '\0' && strcmp(old, new) != 0);
643 }
644
645 char **
fops_grab_marked_files(view_t * view,size_t * nmarked)646 fops_grab_marked_files(view_t *view, size_t *nmarked)
647 {
648 char **marked = NULL;
649 dir_entry_t *entry = NULL;
650 *nmarked = 0;
651 while(iter_marked_entries(view, &entry))
652 {
653 *nmarked = add_to_string_array(&marked, *nmarked, entry->name);
654 }
655 return marked;
656 }
657
658 int
fops_is_dir_entry(const char full_path[],const struct dirent * dentry)659 fops_is_dir_entry(const char full_path[], const struct dirent *dentry)
660 {
661 #ifndef _WIN32
662 struct stat s;
663 const int type = get_dirent_type(dentry, full_path);
664 if(type != DT_UNKNOWN)
665 {
666 return type == DT_DIR;
667 }
668 if(os_lstat(full_path, &s) == 0 && s.st_ino != 0)
669 {
670 return (s.st_mode&S_IFMT) == S_IFDIR;
671 }
672 return 0;
673 #else
674 return is_dir(full_path);
675 #endif
676 }
677
678 int
fops_enqueue_marked_files(ops_t * ops,view_t * view,const char dst_hint[],int to_trash)679 fops_enqueue_marked_files(ops_t *ops, view_t *view, const char dst_hint[],
680 int to_trash)
681 {
682 int nmarked_files = 0;
683 dir_entry_t *entry = NULL;
684
685 ui_cancellation_enable();
686
687 while(iter_marked_entries(view, &entry) && !ui_cancellation_requested())
688 {
689 char full_path[PATH_MAX + 1];
690
691 get_full_path_of(entry, sizeof(full_path), full_path);
692
693 if(to_trash)
694 {
695 char *const trash_dir = trash_pick_dir(entry->origin);
696 ops_enqueue(ops, full_path, trash_dir);
697 free(trash_dir);
698 }
699 else
700 {
701 ops_enqueue(ops, full_path, dst_hint);
702 }
703
704 ++nmarked_files;
705 }
706
707 ui_cancellation_disable();
708
709 return nmarked_files;
710 }
711
712 ops_t *
fops_get_ops(OPS main_op,const char descr[],const char base_dir[],const char target_dir[])713 fops_get_ops(OPS main_op, const char descr[], const char base_dir[],
714 const char target_dir[])
715 {
716 ops_t *const ops = ops_alloc(main_op, 0, descr, base_dir, target_dir);
717 if(ops->use_system_calls)
718 {
719 progress_data_t *const pdata = alloc_progress_data(ops->bg, ops);
720 const io_cancellation_t cancellation = { .hook = &ui_cancellation_hook };
721 ops->estim = ioeta_alloc(pdata, cancellation);
722 }
723 ui_cancellation_push_off();
724 return ops;
725 }
726
727 /* Implementation of cancellation hook for I/O unit. */
728 static int
ui_cancellation_hook(void * arg)729 ui_cancellation_hook(void *arg)
730 {
731 return ui_cancellation_requested();
732 }
733
734 void
fops_progress_msg(const char text[],int ready,int total)735 fops_progress_msg(const char text[], int ready, int total)
736 {
737 if(!cfg.use_system_calls)
738 {
739 char msg[strlen(text) + 32];
740
741 sprintf(msg, "%s %d/%d", text, ready + 1, total);
742 show_progress(msg, 1);
743 curr_stats.save_msg = 2;
744 }
745 }
746
747 const char *
fops_get_dst_name(const char src_path[],int from_trash)748 fops_get_dst_name(const char src_path[], int from_trash)
749 {
750 if(from_trash)
751 {
752 return trash_get_real_name_of(src_path);
753 }
754 return get_last_path_component(src_path);
755 }
756
757 int
fops_can_read_marked_files(view_t * view)758 fops_can_read_marked_files(view_t *view)
759 {
760 dir_entry_t *entry;
761
762 if(is_unc_path(view->curr_dir))
763 {
764 return 1;
765 }
766
767 entry = NULL;
768 while(iter_marked_entries(view, &entry))
769 {
770 char full_path[PATH_MAX + 1];
771 get_full_path_of(entry, sizeof(full_path), full_path);
772
773 /* We can copy links even when they are broken, so it's OK to don't check
774 * them (otherwise access() fails for broken links). */
775 if(entry->type == FT_LINK || os_access(full_path, R_OK) == 0)
776 {
777 continue;
778 }
779
780 show_error_msgf("Access denied",
781 "You don't have read permissions on \"%s\"", full_path);
782 flist_sel_stash(view);
783 redraw_view(view);
784 return 0;
785 }
786 return 1;
787 }
788
789 int
fops_check_dir_path(const view_t * view,const char path[],char buf[],size_t buf_len)790 fops_check_dir_path(const view_t *view, const char path[], char buf[],
791 size_t buf_len)
792 {
793 if(path[0] == '/' || path[0] == '~')
794 {
795 char *const expanded_path = expand_tilde(path);
796 copy_str(buf, buf_len, expanded_path);
797 free(expanded_path);
798 }
799 else
800 {
801 snprintf(buf, buf_len, "%s/%s", fops_get_dst_dir(view, -1), path);
802 }
803
804 if(is_dir(buf))
805 {
806 return 1;
807 }
808
809 copy_str(buf, buf_len, fops_get_dst_dir(view, -1));
810 return 0;
811 }
812
813 char **
fops_edit_list(size_t count,char * orig[],int * nlines,int load_always)814 fops_edit_list(size_t count, char *orig[], int *nlines, int load_always)
815 {
816 char rename_file[PATH_MAX + 1];
817 char **list = NULL;
818 mode_t saved_umask;
819
820 *nlines = 0;
821
822 generate_tmp_file_name("vifm.rename", rename_file, sizeof(rename_file));
823
824 /* Allow temporary file to be only readable and writable by current user. */
825 saved_umask = umask(~0600);
826 if(write_file_of_lines(rename_file, orig, count) != 0)
827 {
828 (void)umask(saved_umask);
829 show_error_msgf("Error Getting List Of Renames",
830 "Can't create temporary file \"%s\": %s", rename_file, strerror(errno));
831 return NULL;
832 }
833 (void)umask(saved_umask);
834
835 if(vim_view_file(rename_file, -1, -1, 0) != 0)
836 {
837 show_error_msgf("Error Editing File", "Editing of file \"%s\" failed.",
838 rename_file);
839 }
840 else
841 {
842 list = read_file_of_lines(rename_file, nlines);
843 if(list == NULL)
844 {
845 show_error_msgf("Error Getting List Of Renames",
846 "Can't open temporary file \"%s\": %s", rename_file, strerror(errno));
847 }
848
849 if(!load_always)
850 {
851 size_t i = count - 1U;
852 if((size_t)*nlines == count)
853 {
854 for(i = 0U; i < count; ++i)
855 {
856 if(strcmp(list[i], orig[i]) != 0)
857 {
858 break;
859 }
860 }
861 }
862
863 if(i == count)
864 {
865 free_string_array(list, *nlines);
866 list = NULL;
867 *nlines = 0;
868 }
869 }
870 }
871
872 unlink(rename_file);
873 return list;
874 }
875
876 void
fops_bg_ops_init(ops_t * ops,bg_op_t * bg_op)877 fops_bg_ops_init(ops_t *ops, bg_op_t *bg_op)
878 {
879 ops->bg_op = bg_op;
880 if(ops->estim != NULL)
881 {
882 progress_data_t *const pdata = ops->estim->param;
883 pdata->bg_op = bg_op;
884 }
885 }
886
887 ops_t *
fops_get_bg_ops(OPS main_op,const char descr[],const char dir[])888 fops_get_bg_ops(OPS main_op, const char descr[], const char dir[])
889 {
890 ops_t *const ops = ops_alloc(main_op, 1, descr, dir, dir);
891 if(ops->use_system_calls)
892 {
893 progress_data_t *const pdata = alloc_progress_data(ops->bg, NULL);
894 const io_cancellation_t no_cancellation = {};
895 ops->estim = ioeta_alloc(pdata, no_cancellation);
896 }
897 return ops;
898 }
899
900 /* Allocates progress data with specified parameters and initializes all the
901 * rest of structure fields with default values. */
902 static progress_data_t *
alloc_progress_data(int bg,void * info)903 alloc_progress_data(int bg, void *info)
904 {
905 progress_data_t *const pdata = malloc(sizeof(*pdata));
906
907 pdata->bg = bg;
908 pdata->ops = info;
909
910 pdata->last_progress = -1;
911 pdata->last_stage = (IoPs)-1;
912
913 pdata->progress_bar = strdup("");
914 pdata->progress_bar_value = 0;
915 pdata->progress_bar_max = 0;
916
917 /* Time of starting the operation to have meaningful first rate. */
918 pdata->last_calc_time = time_in_ms();
919 pdata->last_seen_byte = 0;
920 pdata->rate = 0;
921 pdata->rate_str = strdup("? B/s");
922
923 pdata->dialog = 0;
924 pdata->width = 0;
925
926 return pdata;
927 }
928
929 /* Retrieves current time in milliseconds. */
930 static long long
time_in_ms(void)931 time_in_ms(void)
932 {
933 struct timespec current_time;
934 if(clock_gettime(CLOCK_MONOTONIC, ¤t_time) != 0)
935 {
936 return 0;
937 }
938
939 return current_time.tv_sec*1000 + current_time.tv_nsec/1000000;
940 }
941
942 void
fops_free_ops(ops_t * ops)943 fops_free_ops(ops_t *ops)
944 {
945 if(ops == NULL)
946 {
947 return;
948 }
949
950 if(!ops->bg)
951 {
952 ui_cancellation_pop();
953 ui_drain_input();
954 }
955
956 if(ops->use_system_calls)
957 {
958 if(!ops->bg && ops->errors != NULL)
959 {
960 char *const title = format_str("Encountered errors on %s",
961 ops_describe(ops));
962 show_error_msg(title, ops->errors);
963 free(title);
964 }
965
966 progress_data_t *pdata = ops->estim->param;
967 free(pdata->progress_bar);
968 free(pdata->rate_str);
969 free(pdata);
970 }
971 ops_free(ops);
972 }
973
974 int
fops_mv_file(const char src[],const char src_dir[],const char dst[],const char dst_dir[],OPS op,int cancellable,ops_t * ops)975 fops_mv_file(const char src[], const char src_dir[], const char dst[],
976 const char dst_dir[], OPS op, int cancellable, ops_t *ops)
977 {
978 char full_src[PATH_MAX + 1], full_dst[PATH_MAX + 1];
979
980 to_canonic_path(src, src_dir, full_src, sizeof(full_src));
981 to_canonic_path(dst, dst_dir, full_dst, sizeof(full_dst));
982
983 return fops_mv_file_f(full_src, full_dst, op, 0, cancellable, ops);
984 }
985
986 int
fops_mv_file_f(const char src[],const char dst[],OPS op,int bg,int cancellable,ops_t * ops)987 fops_mv_file_f(const char src[], const char dst[], OPS op, int bg,
988 int cancellable, ops_t *ops)
989 {
990 int result;
991
992 /* Compare case sensitive strings even on Windows to let user rename file
993 * changing only case of some characters. */
994 if(strcmp(src, dst) == 0)
995 {
996 return 0;
997 }
998
999 result = perform_operation(op, ops, cancellable ? NULL : (void *)1, src, dst);
1000 if(result == 0 && !bg)
1001 {
1002 un_group_add_op(op, NULL, NULL, src, dst);
1003 }
1004 return result;
1005 }
1006
1007 void
fops_free_bg_args(bg_args_t * args)1008 fops_free_bg_args(bg_args_t *args)
1009 {
1010 free_string_array(args->list, args->nlines);
1011 free_string_array(args->sel_list, args->sel_list_len);
1012 free(args->is_in_trash);
1013 fops_free_ops(args->ops);
1014 free(args);
1015 }
1016
1017 void
fops_prepare_for_bg_task(view_t * view,bg_args_t * args)1018 fops_prepare_for_bg_task(view_t *view, bg_args_t *args)
1019 {
1020 dir_entry_t *entry;
1021
1022 entry = NULL;
1023 while(iter_marked_entries(view, &entry))
1024 {
1025 char full_path[PATH_MAX + 1];
1026
1027 get_full_path_of(entry, sizeof(full_path), full_path);
1028 args->sel_list_len = add_to_string_array(&args->sel_list,
1029 args->sel_list_len, full_path);
1030 }
1031
1032 ui_view_reset_selection_and_reload(view);
1033 }
1034
1035 void
fops_append_marked_files(view_t * view,char buf[],char ** fnames)1036 fops_append_marked_files(view_t *view, char buf[], char **fnames)
1037 {
1038 const int custom_fnames = (fnames != NULL);
1039 size_t len = strlen(buf);
1040 dir_entry_t *entry = NULL;
1041 while(iter_marked_entries(view, &entry) && len < COMMAND_GROUP_INFO_LEN)
1042 {
1043 fops_append_fname(buf, len, entry->name);
1044 len = strlen(buf);
1045
1046 if(custom_fnames)
1047 {
1048 const char *const custom_fname = *fnames++;
1049
1050 strncat(buf, " to ", COMMAND_GROUP_INFO_LEN - len - 1);
1051 len = strlen(buf);
1052 strncat(buf, custom_fname, COMMAND_GROUP_INFO_LEN - len - 1);
1053 len = strlen(buf);
1054 }
1055 }
1056 }
1057
1058 void
fops_append_fname(char buf[],size_t len,const char fname[])1059 fops_append_fname(char buf[], size_t len, const char fname[])
1060 {
1061 if(buf[len - 2] != ':')
1062 {
1063 strncat(buf, ", ", COMMAND_GROUP_INFO_LEN - len - 1);
1064 len = strlen(buf);
1065 }
1066 strncat(buf, fname, COMMAND_GROUP_INFO_LEN - len - 1);
1067 }
1068
1069 const char *
fops_get_cancellation_suffix(void)1070 fops_get_cancellation_suffix(void)
1071 {
1072 return ui_cancellation_requested() ? " (cancelled)" : "";
1073 }
1074
1075 int
fops_view_can_be_changed(const view_t * view)1076 fops_view_can_be_changed(const view_t *view)
1077 {
1078 /* TODO: maybe add check whether directory of specific entry is writable for
1079 * custom views. */
1080 return flist_custom_active(view)
1081 || fops_is_dir_writable(DR_CURRENT, view->curr_dir);
1082 }
1083
1084 int
fops_view_can_be_extended(const view_t * view,int at)1085 fops_view_can_be_extended(const view_t *view, int at)
1086 {
1087 if(flist_custom_active(view) && !cv_tree(view->custom.type))
1088 {
1089 show_error_msg("Operation error",
1090 "Custom view can't handle this operation.");
1091 return 0;
1092 }
1093
1094 return fops_is_dir_writable(DR_DESTINATION, fops_get_dst_dir(view, at));
1095 }
1096
1097 const char *
fops_get_dst_dir(const view_t * view,int at)1098 fops_get_dst_dir(const view_t *view, int at)
1099 {
1100 if(flist_custom_active(view) && cv_tree(view->custom.type))
1101 {
1102 if(at < 0)
1103 {
1104 at = view->list_pos;
1105 }
1106 else if(at >= view->list_rows)
1107 {
1108 at = view->list_rows - 1;
1109 }
1110 return view->dir_entry[at].origin;
1111 }
1112 return flist_get_dir(view);
1113 }
1114
1115 int
fops_is_dir_writable(DirRole dir_role,const char path[])1116 fops_is_dir_writable(DirRole dir_role, const char path[])
1117 {
1118 if(is_dir_writable(path))
1119 {
1120 return 1;
1121 }
1122
1123 if(dir_role == DR_DESTINATION)
1124 {
1125 show_error_msg("Operation error", "Destination directory is not writable");
1126 }
1127 else
1128 {
1129 show_error_msg("Operation error", "Current directory is not writable");
1130 }
1131 return 0;
1132 }
1133
1134 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1135 /* vim: set cinoptions+=t0 filetype=c : */
1136