1 /* FDUPES Copyright (c) 2018 Adrian Lopez
3 Permission is hereby granted, free of charge, to any person
4 obtaining a copy of this software and associated documentation files
5 (the "Software"), to deal in the Software without restriction,
6 including without limitation the rights to use, copy, modify, merge,
7 publish, distribute, sublicense, and/or sell copies of the Software,
8 and to permit persons to whom the Software is furnished to do so,
9 subject to the following conditions:
11 The above copyright notice and this permission notice shall be
12 included in all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
26 #ifdef HAVE_NCURSESW_CURSES_H
27 #include <ncursesw/curses.h>
31 #include "ncurses-interface.h"
32 #include "ncurses-getcommand.h"
33 #include "ncurses-commands.h"
34 #include "ncurses-prompt.h"
35 #include "ncurses-status.h"
36 #include "ncurses-print.h"
37 #include "mbstowcs_escape_invalid.h"
38 #include "positive_wcwidth.h"
39 #include "commandidentifier.h"
40 #include "filegroup.h"
46 char *fmtmtime(char *filename);
50 linestyle_groupheader = 0,
51 linestyle_groupheaderspacing,
53 linestyle_groupfooterspacing
56 enum linestyle getlinestyle(struct filegroup *group, int line)
58 if (line <= group->startline)
59 return linestyle_groupheader;
60 else if (line == group->startline + 1)
61 return linestyle_groupheaderspacing;
62 else if (line >= group->endline)
63 return linestyle_groupfooterspacing;
65 return linestyle_filename;
68 #define FILENAME_INDENT_EXTRA 5
69 #define FILE_INDEX_MIN_WIDTH 3
71 int filerowcount(file_t *file, const int columns, int group_file_count)
77 size_t filename_bytes;
85 memset(&mbstate, 0, sizeof(mbstate));
87 needed = mbstowcs_escape_invalid(0, file->d_name, 0);
89 wcfilename = (wchar_t*)malloc(sizeof(wchar_t) * needed);
93 mbstowcs_escape_invalid(wcfilename, file->d_name, needed);
95 index_width = get_num_digits(group_file_count);
96 if (index_width < FILE_INDEX_MIN_WIDTH)
97 index_width = FILE_INDEX_MIN_WIDTH;
99 timestamp_width = ISFLAG(flags, F_SHOWTIME) ? 19 : 0;
101 lines = (index_width + timestamp_width + FILENAME_INDENT_EXTRA) / columns + 1;
103 line_remaining = columns - (index_width + timestamp_width + FILENAME_INDENT_EXTRA) % columns;
105 while (wcfilename[x] != L'\0')
107 if (positive_wcwidth(wcfilename[x]) <= line_remaining)
109 line_remaining -= positive_wcwidth(wcfilename[x]);
113 line_remaining = columns - positive_wcwidth(wcfilename[x]);
125 int getgroupindex(struct filegroup *groups, int group_count, int group_hint, int line)
127 int group = group_hint;
129 while (group > 0 && line < groups[group].startline)
132 while (group < group_count && line > groups[group].endline)
138 int getgroupfileindex(int *row, struct filegroup *group, int line, int columns)
144 l = group->startline + 2;
146 while (f < group->filecount)
148 rowcount = filerowcount(group->files[f].file, columns, group->filecount);
150 if (line <= l + rowcount - 1)
163 int getgroupfileline(struct filegroup *group, int fileindex, int columns)
169 l = group->startline + 2;
171 while (f < fileindex && f < group->filecount)
173 rowcount = filerowcount(group->files[f].file, columns, group->filecount);
181 void set_file_action(struct groupfile *file, int new_action, size_t *deletion_tally)
183 switch (file->action)
186 if (new_action != -1)
191 if (new_action == -1)
196 file->action = new_action;
199 void scroll_to_group(int *topline, int group, int tail, struct filegroup *groups, WINDOW *filewin)
201 if (*topline < groups[group].startline)
203 if (groups[group].endline >= *topline + getmaxy(filewin))
205 if (groups[group].endline - groups[group].startline < getmaxy(filewin))
206 *topline = groups[group].endline - getmaxy(filewin) + 1;
208 *topline = groups[group].startline;
213 if (groups[group].endline - groups[group].startline < getmaxy(filewin) || !tail)
214 *topline = groups[group].startline;
216 *topline = groups[group].endline - getmaxy(filewin);
220 void move_to_next_group(int *topline, int *cursorgroup, int *cursorfile, struct filegroup *groups, WINDOW *filewin)
226 scroll_to_group(topline, *cursorgroup, 0, groups, filewin);
229 int move_to_next_selected_group(int *topline, int *cursorgroup, int *cursorfile, struct filegroup *groups, int totalgroups, WINDOW *filewin)
233 for (g = *cursorgroup + 1; g < totalgroups; ++g)
235 if (groups[g].selected)
240 scroll_to_group(topline, *cursorgroup, 0, groups, filewin);
249 void move_to_next_file(int *topline, int *cursorgroup, int *cursorfile, struct filegroup *groups, WINDOW *filewin)
253 if (getgroupfileline(&groups[*cursorgroup], *cursorfile, COLS) >= *topline + getmaxy(filewin))
255 if (groups[*cursorgroup].endline - getgroupfileline(&groups[*cursorgroup], *cursorfile, COLS) < getmaxy(filewin))
256 *topline = groups[*cursorgroup].endline - getmaxy(filewin) + 1;
258 *topline = getgroupfileline(&groups[*cursorgroup], *cursorfile, COLS);
262 void move_to_previous_group(int *topline, int *cursorgroup, int *cursorfile, struct filegroup *groups, WINDOW *filewin)
266 *cursorfile = groups[*cursorgroup].filecount - 1;
268 scroll_to_group(topline, *cursorgroup, 1, groups, filewin);
271 int move_to_previous_selected_group(int *topline, int *cursorgroup, int *cursorfile, struct filegroup *groups, int totalgroups, WINDOW *filewin)
275 for (g = *cursorgroup; g > 0; --g)
277 if (groups[g - 1].selected)
279 *cursorgroup = g - 1;
282 scroll_to_group(topline, *cursorgroup, 0, groups, filewin);
291 void move_to_previous_file(int *topline, int *cursorgroup, int *cursorfile, struct filegroup *groups, WINDOW *filewin)
295 if (getgroupfileline(&groups[*cursorgroup], *cursorfile, COLS) < *topline)
297 if (getgroupfileline(&groups[*cursorgroup], *cursorfile, COLS) - groups[*cursorgroup].startline < getmaxy(filewin))
298 *topline -= getgroupfileline(&groups[*cursorgroup], *cursorfile, COLS) - groups[*cursorgroup].startline + 1;
300 *topline -= getmaxy(filewin);
304 #define FILE_LIST_OK 1
305 #define FILE_LIST_ERROR_INDEX_OUT_OF_RANGE -1
306 #define FILE_LIST_ERROR_LIST_CONTAINS_INVALID_INDEX -2
307 #define FILE_LIST_ERROR_UNKNOWN_COMMAND -3
308 #define FILE_LIST_ERROR_OUT_OF_MEMORY -4
310 int validate_file_list(struct filegroup *currentgroup, wchar_t *commandbuffer_in)
312 wchar_t *commandbuffer;
315 wchar_t *wcstolcheck;
319 int out_of_bounds_error = 0;
321 commandbuffer = malloc(sizeof(wchar_t) * (wcslen(commandbuffer_in)+1));
322 if (commandbuffer == 0)
323 return FILE_LIST_ERROR_OUT_OF_MEMORY;
325 wcscpy(commandbuffer, commandbuffer_in);
327 token = wcstok(commandbuffer, L",", &wcsptr);
329 while (token != NULL)
333 number = wcstol(token, &wcstolcheck, 10);
334 if (wcstolcheck == token || *wcstolcheck != '\0')
337 if (number > currentgroup->filecount || number < 1)
338 out_of_bounds_error = 1;
340 token = wcstok(NULL, L",", &wcsptr);
345 if (parts == 1 && parse_error)
346 return FILE_LIST_ERROR_UNKNOWN_COMMAND;
347 else if (parse_error)
348 return FILE_LIST_ERROR_LIST_CONTAINS_INVALID_INDEX;
349 else if (out_of_bounds_error)
350 return FILE_LIST_ERROR_INDEX_OUT_OF_RANGE;
355 void deletefiles_ncurses(file_t *files, char *logfile)
362 struct filegroup *groups;
363 struct filegroup *reallocgroups;
364 size_t groupfilecount;
370 int groupfirstline = 0;
372 int allocatedgroups = 0;
374 size_t groupindex = 0;
375 enum linestyle linestyle;
380 size_t globaldeletiontally = 0;
390 wchar_t *commandbuffer;
391 size_t commandbuffersize;
392 wchar_t *commandarguments;
393 struct command_identifier_node *commandidentifier;
394 struct command_identifier_node *confirmationkeywordidentifier;
398 wchar_t *wcstolcheck;
400 struct status_text *status;
401 struct prompt_info *prompt;
405 int toplineoffset = 0;
406 int resumecommandinput = 0;
409 struct log_info *loginfo;
415 filewin = newwin(LINES - 2, COLS, 0, 0);
416 statuswin = newwin(1, COLS, LINES - 1, 0);
417 promptwin = newwin(1, COLS, LINES - 2, 0);
419 scrollok(filewin, FALSE);
420 scrollok(statuswin, FALSE);
421 scrollok(promptwin, FALSE);
423 wattron(statuswin, A_REVERSE);
425 keypad(promptwin, 1);
427 commandbuffersize = 80;
428 commandbuffer = malloc(commandbuffersize * sizeof(wchar_t));
429 if (commandbuffer == 0)
432 errormsg("out of memory\n");
436 allocatedgroups = 1024;
437 groups = malloc(sizeof(struct filegroup) * allocatedgroups);
443 errormsg("out of memory\n");
447 commandidentifier = build_command_identifier_tree(command_list);
448 if (commandidentifier == 0)
454 errormsg("out of memory\n");
458 confirmationkeywordidentifier = build_command_identifier_tree(confirmation_keyword_list);
459 if (confirmationkeywordidentifier == 0)
463 free_command_identifier_tree(commandidentifier);
466 errormsg("out of memory\n");
470 register_sigint_handler();
475 if (!curfile->hasdupes)
477 curfile = curfile->next;
481 if (totalgroups + 1 > allocatedgroups)
483 allocatedgroups *= 2;
485 reallocgroups = realloc(groups, sizeof(struct filegroup) * allocatedgroups);
486 if (reallocgroups == 0)
488 for (g = 0; g < totalgroups; ++g)
489 free(groups[g].files);
493 free_command_identifier_tree(commandidentifier);
494 free_command_identifier_tree(confirmationkeywordidentifier);
497 errormsg("out of memory\n");
501 groups = reallocgroups;
504 groups[totalgroups].startline = groupfirstline;
505 groups[totalgroups].endline = groupfirstline + 2;
506 groups[totalgroups].selected = 0;
515 dupefile = dupefile->duplicates;
521 groups[totalgroups].endline += filerowcount(dupefile, COLS, groupfilecount);
523 dupefile = dupefile->duplicates;
526 groups[totalgroups].files = malloc(sizeof(struct groupfile) * groupfilecount);
527 if (groups[totalgroups].files == 0)
529 for (g = 0; g < totalgroups; ++g)
530 free(groups[g].files);
534 free_command_identifier_tree(commandidentifier);
535 free_command_identifier_tree(confirmationkeywordidentifier);
538 errormsg("out of memory\n");
547 groups[totalgroups].files[groupfilecount].file = dupefile;
548 groups[totalgroups].files[groupfilecount].action = 0;
549 groups[totalgroups].files[groupfilecount].selected = 0;
552 dupefile = dupefile->duplicates;
555 groups[totalgroups].filecount = groupfilecount;
557 groupfirstline = groups[totalgroups].endline + 1;
561 curfile = curfile->next;
564 dupesfound = totalgroups > 0;
566 status = status_text_alloc(0, COLS);
569 for (g = 0; g < totalgroups; ++g)
570 free(groups[g].files);
574 free_command_identifier_tree(commandidentifier);
575 free_command_identifier_tree(confirmationkeywordidentifier);
578 errormsg("out of memory\n");
582 format_status_left(status, L"Ready");
584 prompt = prompt_info_alloc(80);
587 free_status_text(status);
589 for (g = 0; g < totalgroups; ++g)
590 free(groups[g].files);
594 free_command_identifier_tree(commandidentifier);
595 free_command_identifier_tree(confirmationkeywordidentifier);
598 errormsg("out of memory\n");
605 wmove(filewin, 0, 0);
609 totallines = groups[totalgroups-1].endline;
613 for (x = topline; x < topline + getmaxy(filewin); ++x)
621 groupindex = getgroupindex(groups, totalgroups, groupindex, x);
623 index_width = get_num_digits(groups[groupindex].filecount);
625 if (index_width < FILE_INDEX_MIN_WIDTH)
626 index_width = FILE_INDEX_MIN_WIDTH;
628 timestamp_width = ISFLAG(flags, F_SHOWTIME) ? 19 : 0;
630 linestyle = getlinestyle(groups + groupindex, x);
632 if (linestyle == linestyle_groupheader)
634 wattron(filewin, A_BOLD);
635 if (groups[groupindex].selected)
636 wattron(filewin, A_REVERSE);
637 wprintw(filewin, "Set %d of %d:\n", groupindex + 1, totalgroups);
638 if (groups[groupindex].selected)
639 wattroff(filewin, A_REVERSE);
640 wattroff(filewin, A_BOLD);
642 else if (linestyle == linestyle_groupheaderspacing)
644 wprintw(filewin, "\n");
646 else if (linestyle == linestyle_filename)
648 f = getgroupfileindex(&row, groups + groupindex, x, COLS);
650 if (cursorgroup != groupindex)
654 print_spaces(filewin, index_width);
656 wprintw(filewin, " [%c] ", groups[groupindex].files[f].action > 0 ? '+' : groups[groupindex].files[f].action < 0 ? '-' : ' ');
658 if (ISFLAG(flags, F_SHOWTIME))
659 wprintw(filewin, "[%s] ", fmtmtime(groups[groupindex].files[f].file->d_name));
662 cy = getcury(filewin);
664 if (groups[groupindex].files[f].selected)
665 wattron(filewin, A_REVERSE);
666 putline(filewin, groups[groupindex].files[f].file->d_name, row, COLS, index_width + timestamp_width + FILENAME_INDENT_EXTRA);
667 if (groups[groupindex].files[f].selected)
668 wattroff(filewin, A_REVERSE);
671 wmove(filewin, cy+1, 0);
677 print_right_justified_int(filewin, f+1, index_width);
678 wprintw(filewin, " ");
680 if (cursorgroup == groupindex && cursorfile == f)
681 wattron(filewin, A_REVERSE);
682 wprintw(filewin, "[%c]", groups[groupindex].files[f].action > 0 ? '+' : groups[groupindex].files[f].action < 0 ? '-' : ' ');
683 if (cursorgroup == groupindex && cursorfile == f)
684 wattroff(filewin, A_REVERSE);
685 wprintw(filewin, " ");
687 if (ISFLAG(flags, F_SHOWTIME))
688 wprintw(filewin, "[%s] ", fmtmtime(groups[groupindex].files[f].file->d_name));
691 cy = getcury(filewin);
693 if (groups[groupindex].files[f].selected)
694 wattron(filewin, A_REVERSE);
695 putline(filewin, groups[groupindex].files[f].file->d_name, row, COLS, index_width + timestamp_width + FILENAME_INDENT_EXTRA);
696 if (groups[groupindex].files[f].selected)
697 wattroff(filewin, A_REVERSE);
700 wmove(filewin, cy+1, 0);
703 else if (linestyle == linestyle_groupfooterspacing)
705 wprintw(filewin, "\n");
710 format_status_right(status, L"Set %d of %d", cursorgroup+1, totalgroups);
712 format_status_right(status, L"Finished");
714 print_status(statuswin, status);
717 format_prompt(prompt, L"( Preserve files [1 - %d, all, help] )", groups[cursorgroup].filecount);
719 format_prompt(prompt, L"( No duplicates remaining; type 'exit' to exit program )");
721 format_prompt(prompt, L"( No duplicates found; type 'exit' to exit program )");
723 print_prompt(promptwin, prompt, L"");
725 /* refresh windows (using wrefresh instead of wnoutrefresh to avoid bug in gnome-terminal) */
730 /* wait for user input */
731 if (!resumecommandinput)
735 keyresult = wget_wch(promptwin, &wch);
739 getyx(promptwin, cursor_y, cursor_x);
741 format_status_left(status, L"Type 'exit' to exit fdupes.");
742 print_status(statuswin, status);
744 wmove(promptwin, cursor_y, cursor_x);
750 } while (keyresult == ERR);
752 if (keyresult == OK && iswprint(wch))
754 commandbuffer[0] = wch;
755 commandbuffer[1] = '\0';
759 commandbuffer[0] = '\0';
763 if (resumecommandinput || (keyresult == OK && iswprint(wch) && ((wch != '\t' && wch != '\n' && wch != '?'))))
765 resumecommandinput = 0;
767 switch (get_command_text(&commandbuffer, &commandbuffersize, promptwin, prompt, 1, 1))
770 get_command_arguments(&commandarguments, commandbuffer);
772 switch (identify_command(commandidentifier, commandbuffer, 0))
774 case COMMAND_SELECT_CONTAINING:
775 cmd_select_containing(groups, totalgroups, commandarguments, status);
778 case COMMAND_SELECT_BEGINNING:
779 cmd_select_beginning(groups, totalgroups, commandarguments, status);
782 case COMMAND_SELECT_ENDING:
783 cmd_select_ending(groups, totalgroups, commandarguments, status);
786 case COMMAND_SELECT_MATCHING:
787 cmd_select_matching(groups, totalgroups, commandarguments, status);
790 case COMMAND_SELECT_REGEX:
791 cmd_select_regex(groups, totalgroups, commandarguments, status);
794 case COMMAND_CLEAR_SELECTIONS_CONTAINING:
795 cmd_clear_selections_containing(groups, totalgroups, commandarguments, status);
798 case COMMAND_CLEAR_SELECTIONS_BEGINNING:
799 cmd_clear_selections_beginning(groups, totalgroups, commandarguments, status);
802 case COMMAND_CLEAR_SELECTIONS_ENDING:
803 cmd_clear_selections_ending(groups, totalgroups, commandarguments, status);
806 case COMMAND_CLEAR_SELECTIONS_MATCHING:
807 cmd_clear_selections_matching(groups, totalgroups, commandarguments, status);
810 case COMMAND_CLEAR_SELECTIONS_REGEX:
811 cmd_clear_selections_regex(groups, totalgroups, commandarguments, status);
814 case COMMAND_CLEAR_ALL_SELECTIONS:
815 cmd_clear_all_selections(groups, totalgroups, commandarguments, status);
818 case COMMAND_INVERT_GROUP_SELECTIONS:
819 cmd_invert_group_selections(groups, totalgroups, commandarguments, status);
822 case COMMAND_KEEP_SELECTED:
823 cmd_keep_selected(groups, totalgroups, commandarguments, &globaldeletiontally, status);
826 case COMMAND_DELETE_SELECTED:
827 cmd_delete_selected(groups, totalgroups, commandarguments, &globaldeletiontally, status);
830 case COMMAND_RESET_SELECTED:
831 cmd_reset_selected(groups, totalgroups, commandarguments, &globaldeletiontally, status);
834 case COMMAND_RESET_GROUP:
835 for (x = 0; x < groups[cursorgroup].filecount; ++x)
836 set_file_action(&groups[cursorgroup].files[x], 0, &globaldeletiontally);
838 format_status_left(status, L"Reset all files in current group.");
842 case COMMAND_PRESERVE_ALL:
843 /* mark all files for preservation */
844 for (x = 0; x < groups[cursorgroup].filecount; ++x)
845 set_file_action(&groups[cursorgroup].files[x], 1, &globaldeletiontally);
847 format_status_left(status, L"%d files marked for preservation", groups[cursorgroup].filecount);
849 if (cursorgroup < totalgroups - 1)
850 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
854 case COMMAND_GOTO_SET:
855 number = wcstol(commandarguments, &wcstolcheck, 10);
856 if (wcstolcheck != commandarguments && *wcstolcheck == '\0')
858 if (number >= 1 && number <= totalgroups)
860 scroll_to_group(&topline, number - 1, 0, groups, filewin);
862 cursorgroup = number - 1;
867 format_status_left(status, L"Group index out of range.");
872 format_status_left(status, L"Invalid group index.");
880 if (system(HELP_COMMAND_STRING) == -1)
881 format_status_left(status, L"Could not display help text.");
887 case COMMAND_EXIT: /* exit program */
888 if (totalgroups == 0)
895 if (globaldeletiontally != 0)
896 format_prompt(prompt, L"( There are files marked for deletion. Exit without deleting? )");
898 format_prompt(prompt, L"( There are duplicates remaining. Exit anyway? )");
900 print_prompt(promptwin, prompt, L"");
904 switch (get_command_text(&commandbuffer, &commandbuffersize, promptwin, prompt, 0, 0))
907 switch (identify_command(confirmationkeywordidentifier, commandbuffer, 0))
914 case COMMAND_UNDEFINED:
915 commandbuffer[0] = '\0';
920 case GET_COMMAND_CANCELED:
921 commandbuffer[0] = '\0';
924 case GET_COMMAND_RESIZE_REQUESTED:
926 wresize(filewin, LINES - 2, COLS);
928 wresize(statuswin, 1, COLS);
929 wresize(promptwin, 1, COLS);
930 mvwin(statuswin, LINES - 1, 0);
931 mvwin(promptwin, LINES - 2, 0);
933 status_text_alloc(status, COLS);
935 /* recalculate line boundaries */
938 for (g = 0; g < totalgroups; ++g)
940 groups[g].startline = groupfirstline;
941 groups[g].endline = groupfirstline + 2;
943 for (f = 0; f < groups[g].filecount; ++f)
944 groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
946 groupfirstline = groups[g].endline + 1;
949 commandbuffer[0] = '\0';
953 case GET_COMMAND_ERROR_OUT_OF_MEMORY:
954 for (g = 0; g < totalgroups; ++g)
955 free(groups[g].files);
959 free_command_identifier_tree(commandidentifier);
960 free_command_identifier_tree(confirmationkeywordidentifier);
963 errormsg("out of memory\n");
970 default: /* parse list of files to preserve and mark for preservation */
971 intresult = validate_file_list(groups + cursorgroup, commandbuffer);
972 if (intresult != FILE_LIST_OK)
974 if (intresult == FILE_LIST_ERROR_UNKNOWN_COMMAND)
976 format_status_left(status, L"Unrecognized command");
979 else if (intresult == FILE_LIST_ERROR_INDEX_OUT_OF_RANGE)
981 format_status_left(status, L"Index out of range (1 - %d).", groups[cursorgroup].filecount);
984 else if (intresult == FILE_LIST_ERROR_LIST_CONTAINS_INVALID_INDEX)
986 format_status_left(status, L"Invalid index");
989 else if (intresult == FILE_LIST_ERROR_OUT_OF_MEMORY)
993 free_command_identifier_tree(commandidentifier);
995 for (g = 0; g < totalgroups; ++g)
996 free(groups[g].files);
1001 errormsg("out of memory\n");
1006 format_status_left(status, L"Could not interpret command");
1011 token = wcstok(commandbuffer, L",", &wcsptr);
1013 while (token != NULL)
1015 number = wcstol(token, &wcstolcheck, 10);
1016 if (wcstolcheck != token && *wcstolcheck == '\0')
1018 if (number > 0 && number <= groups[cursorgroup].filecount)
1019 set_file_action(&groups[cursorgroup].files[number - 1], 1, &globaldeletiontally);
1022 token = wcstok(NULL, L",", &wcsptr);
1025 /* mark remaining files for deletion */
1029 for (x = 0; x < groups[cursorgroup].filecount; ++x)
1031 if (groups[cursorgroup].files[x].action == 1)
1033 if (groups[cursorgroup].files[x].action == -1)
1037 if (preservecount > 0)
1039 for (x = 0; x < groups[cursorgroup].filecount; ++x)
1041 if (groups[cursorgroup].files[x].action == 0)
1043 set_file_action(&groups[cursorgroup].files[x], -1, &globaldeletiontally);
1049 format_status_left(status, L"%d files marked for preservation, %d for deletion", preservecount, deletecount);
1051 if (cursorgroup < totalgroups - 1 && preservecount > 0)
1052 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1059 case GET_COMMAND_KEY_SF:
1062 resumecommandinput = 1;
1066 case GET_COMMAND_KEY_SR:
1070 resumecommandinput = 1;
1074 case GET_COMMAND_KEY_NPAGE:
1075 topline += getmaxy(filewin);
1077 resumecommandinput = 1;
1081 case GET_COMMAND_KEY_PPAGE:
1082 topline -= getmaxy(filewin);
1087 resumecommandinput = 1;
1091 case GET_COMMAND_CANCELED:
1094 case GET_COMMAND_RESIZE_REQUESTED:
1095 /* resize windows */
1096 wresize(filewin, LINES - 2, COLS);
1098 wresize(statuswin, 1, COLS);
1099 wresize(promptwin, 1, COLS);
1100 mvwin(statuswin, LINES - 1, 0);
1101 mvwin(promptwin, LINES - 2, 0);
1103 status_text_alloc(status, COLS);
1105 /* recalculate line boundaries */
1108 for (g = 0; g < totalgroups; ++g)
1110 groups[g].startline = groupfirstline;
1111 groups[g].endline = groupfirstline + 2;
1113 for (f = 0; f < groups[g].filecount; ++f)
1114 groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
1116 groupfirstline = groups[g].endline + 1;
1119 commandbuffer[0] = '\0';
1123 case GET_COMMAND_ERROR_OUT_OF_MEMORY:
1124 for (g = 0; g < totalgroups; ++g)
1125 free(groups[g].files);
1128 free(commandbuffer);
1129 free_command_identifier_tree(commandidentifier);
1130 free_command_identifier_tree(confirmationkeywordidentifier);
1133 errormsg("out of memory\n");
1139 commandbuffer[0] = '\0';
1141 else if (keyresult == KEY_CODE_YES)
1146 if (cursorfile < groups[cursorgroup].filecount - 1)
1147 move_to_next_file(&topline, &cursorgroup, &cursorfile, groups, filewin);
1148 else if (cursorgroup < totalgroups - 1)
1149 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1155 move_to_previous_file(&topline, &cursorgroup, &cursorfile, groups, filewin);
1156 else if (cursorgroup > 0)
1157 move_to_previous_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1171 topline += getmaxy(filewin);
1175 topline -= getmaxy(filewin);
1183 set_file_action(&groups[cursorgroup].files[cursorfile], 1, &globaldeletiontally);
1185 format_status_left(status, L"1 file marked for preservation.");
1187 if (cursorfile < groups[cursorgroup].filecount - 1)
1188 move_to_next_file(&topline, &cursorgroup, &cursorfile, groups, filewin);
1189 else if (cursorgroup < totalgroups - 1)
1190 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1197 set_file_action(&groups[cursorgroup].files[cursorfile], -1, &globaldeletiontally);
1199 format_status_left(status, L"1 file marked for deletion.");
1201 for (x = 0; x < groups[cursorgroup].filecount; ++x)
1202 if (groups[cursorgroup].files[x].action == -1)
1205 if (deletecount < groups[cursorgroup].filecount)
1207 if (cursorfile < groups[cursorgroup].filecount - 1)
1208 move_to_next_file(&topline, &cursorgroup, &cursorfile, groups, filewin);
1209 else if (cursorgroup < totalgroups - 1)
1210 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1216 if (cursorgroup > 0)
1221 scroll_to_group(&topline, cursorgroup, 0, groups, filewin);
1226 move_to_next_selected_group(&topline, &cursorgroup, &cursorfile, groups, totalgroups, filewin);
1230 move_to_previous_selected_group(&topline, &cursorgroup, &cursorfile, groups, totalgroups, filewin);
1238 loginfo = log_open(logfile, 0);
1242 for (g = 0; g < totalgroups; ++g)
1246 unresolvedcount = 0;
1248 for (f = 0; f < groups[g].filecount; ++f)
1250 switch (groups[g].files[f].action)
1265 log_begin_set(loginfo);
1267 /* delete files marked for deletion unless no files left undeleted */
1268 if (deletecount < groups[g].filecount)
1270 for (f = 0; f < groups[g].filecount; ++f)
1272 if (groups[g].files[f].action == -1)
1274 if (remove(groups[g].files[f].file->d_name) == 0)
1276 set_file_action(&groups[g].files[f], -2, &globaldeletiontally);
1278 deletedbytes += groups[g].files[f].file->size;
1282 log_file_deleted(loginfo, groups[g].files[f].file->d_name);
1289 for (f = 0; f < groups[g].filecount; ++f)
1291 if (groups[g].files[f].action >= 0)
1292 log_file_remaining(loginfo, groups[g].files[f].file->d_name);
1300 log_end_set(loginfo);
1302 /* if no files left unresolved, mark preserved files for delisting */
1303 if (unresolvedcount == 0)
1305 for (f = 0; f < groups[g].filecount; ++f)
1306 if (groups[g].files[f].action == 1)
1307 set_file_action(&groups[g].files[f], -2, &globaldeletiontally);
1311 /* if only one file left unresolved, mark it for delesting */
1312 else if (unresolvedcount == 1 && preservecount + deletecount == 0)
1314 for (f = 0; f < groups[g].filecount; ++f)
1315 if (groups[g].files[f].action == 0)
1316 set_file_action(&groups[g].files[f], -2, &globaldeletiontally);
1319 /* delist any files marked for delisting */
1321 for (f = 0; f < groups[g].filecount; ++f)
1322 if (groups[g].files[f].action != -2)
1323 groups[g].files[to++] = groups[g].files[f];
1325 groups[g].filecount = to;
1327 /* reposition cursor, if necessary */
1328 if (cursorgroup == g && cursorfile > 0 && cursorfile >= groups[g].filecount)
1329 cursorfile = groups[g].filecount - 1;
1335 if (deletedbytes < 1000.0)
1336 format_status_left(status, L"Deleted %ld files (occupying %.0f bytes).", totaldeleted, deletedbytes);
1337 else if (deletedbytes <= (1000.0 * 1000.0))
1338 format_status_left(status, L"Deleted %ld files (occupying %.1f KB).", totaldeleted, deletedbytes / 1000.0);
1339 else if (deletedbytes <= (1000.0 * 1000.0 * 1000.0))
1340 format_status_left(status, L"Deleted %ld files (occupying %.1f MB).", totaldeleted, deletedbytes / (1000.0 * 1000.0));
1342 format_status_left(status, L"Deleted %ld files (occupying %.1f GB).", totaldeleted, deletedbytes / (1000.0 * 1000.0 * 1000.0));
1344 /* delist empty groups */
1346 for (g = 0; g < totalgroups; ++g)
1348 if (groups[g].filecount > 0)
1350 groups[to] = groups[g];
1352 /* reposition cursor, if necessary */
1353 if (to == cursorgroup && to != g)
1360 free(groups[g].files);
1366 /* reposition cursor, if necessary */
1367 if (cursorgroup >= totalgroups)
1369 cursorgroup = totalgroups - 1;
1373 /* recalculate line boundaries */
1378 for (g = 0; g < totalgroups; ++g)
1380 if (adjusttopline && groups[g].endline >= topline)
1381 toplineoffset = groups[g].endline - topline;
1383 groups[g].startline = groupfirstline;
1384 groups[g].endline = groupfirstline + 2;
1386 for (f = 0; f < groups[g].filecount; ++f)
1387 groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
1389 if (adjusttopline && toplineoffset > 0)
1391 topline = groups[g].endline - toplineoffset;
1399 groupfirstline = groups[g].endline + 1;
1402 if (totalgroups > 0 && groups[totalgroups-1].endline <= topline)
1404 topline = groups[totalgroups-1].endline - getmaxy(filewin) + 1;
1413 /* resize windows */
1414 wresize(filewin, LINES - 2, COLS);
1416 wresize(statuswin, 1, COLS);
1417 wresize(promptwin, 1, COLS);
1418 mvwin(statuswin, LINES - 1, 0);
1419 mvwin(promptwin, LINES - 2, 0);
1421 status_text_alloc(status, COLS);
1423 /* recalculate line boundaries */
1426 for (g = 0; g < totalgroups; ++g)
1428 groups[g].startline = groupfirstline;
1429 groups[g].endline = groupfirstline + 2;
1431 for (f = 0; f < groups[g].filecount; ++f)
1432 groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
1434 groupfirstline = groups[g].endline + 1;
1440 else if (keyresult == OK)
1445 if (groups[cursorgroup].files[cursorfile].action == 0)
1448 set_file_action(&groups[cursorgroup].files[cursorfile], 0, &globaldeletiontally);
1450 if (cursorfile < groups[cursorgroup].filecount - 1)
1451 move_to_next_file(&topline, &cursorgroup, &cursorfile, groups, filewin);
1452 else if (cursorgroup < totalgroups - 1)
1453 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1461 for (x = 0; x < groups[cursorgroup].filecount; ++x)
1463 if (groups[cursorgroup].files[x].action == 1)
1467 if (preservecount == 0)
1470 for (x = 0; x < groups[cursorgroup].filecount; ++x)
1472 if (groups[cursorgroup].files[x].action == 0)
1473 set_file_action(&groups[cursorgroup].files[x], -1, &globaldeletiontally);
1475 if (groups[cursorgroup].files[x].action == -1)
1479 if (cursorgroup < totalgroups - 1 && deletecount < groups[cursorgroup].filecount)
1480 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1485 if (cursorgroup < totalgroups - 1)
1486 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1495 free(commandbuffer);
1497 free_prompt_info(prompt);
1499 free_status_text(status);
1501 free_command_identifier_tree(commandidentifier);
1502 free_command_identifier_tree(confirmationkeywordidentifier);
1504 for (g = 0; g < totalgroups; ++g)
1505 free(groups[g].files);