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 *fmttime(time_t t);
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;
379 size_t globaldeletiontally = 0;
387 wchar_t *commandbuffer;
388 size_t commandbuffersize;
389 wchar_t *commandarguments;
390 struct command_identifier_node *commandidentifier;
391 struct command_identifier_node *confirmationkeywordidentifier;
395 wchar_t *wcstolcheck;
397 struct status_text *status;
398 struct prompt_info *prompt;
401 int resumecommandinput = 0;
409 filewin = newwin(LINES - 2, COLS, 0, 0);
410 statuswin = newwin(1, COLS, LINES - 1, 0);
411 promptwin = newwin(1, COLS, LINES - 2, 0);
413 scrollok(filewin, FALSE);
414 scrollok(statuswin, FALSE);
415 scrollok(promptwin, FALSE);
417 wattron(statuswin, A_REVERSE);
419 keypad(promptwin, 1);
421 commandbuffersize = 80;
422 commandbuffer = malloc(commandbuffersize * sizeof(wchar_t));
423 if (commandbuffer == 0)
426 errormsg("out of memory\n");
430 allocatedgroups = 1024;
431 groups = malloc(sizeof(struct filegroup) * allocatedgroups);
437 errormsg("out of memory\n");
441 commandidentifier = build_command_identifier_tree(command_list);
442 if (commandidentifier == 0)
448 errormsg("out of memory\n");
452 confirmationkeywordidentifier = build_command_identifier_tree(confirmation_keyword_list);
453 if (confirmationkeywordidentifier == 0)
457 free_command_identifier_tree(commandidentifier);
460 errormsg("out of memory\n");
464 register_sigint_handler();
469 if (!curfile->hasdupes)
471 curfile = curfile->next;
475 if (totalgroups + 1 > allocatedgroups)
477 allocatedgroups *= 2;
479 reallocgroups = realloc(groups, sizeof(struct filegroup) * allocatedgroups);
480 if (reallocgroups == 0)
482 for (g = 0; g < totalgroups; ++g)
483 free(groups[g].files);
487 free_command_identifier_tree(commandidentifier);
488 free_command_identifier_tree(confirmationkeywordidentifier);
491 errormsg("out of memory\n");
495 groups = reallocgroups;
498 groups[totalgroups].startline = groupfirstline;
499 groups[totalgroups].endline = groupfirstline + 2;
500 groups[totalgroups].selected = 0;
509 dupefile = dupefile->duplicates;
515 groups[totalgroups].endline += filerowcount(dupefile, COLS, groupfilecount);
517 dupefile = dupefile->duplicates;
520 groups[totalgroups].files = malloc(sizeof(struct groupfile) * groupfilecount);
521 if (groups[totalgroups].files == 0)
523 for (g = 0; g < totalgroups; ++g)
524 free(groups[g].files);
528 free_command_identifier_tree(commandidentifier);
529 free_command_identifier_tree(confirmationkeywordidentifier);
532 errormsg("out of memory\n");
541 groups[totalgroups].files[groupfilecount].file = dupefile;
542 groups[totalgroups].files[groupfilecount].action = 0;
543 groups[totalgroups].files[groupfilecount].selected = 0;
546 dupefile = dupefile->duplicates;
549 groups[totalgroups].filecount = groupfilecount;
551 groupfirstline = groups[totalgroups].endline + 1;
555 curfile = curfile->next;
558 dupesfound = totalgroups > 0;
560 status = status_text_alloc(0, COLS);
563 for (g = 0; g < totalgroups; ++g)
564 free(groups[g].files);
568 free_command_identifier_tree(commandidentifier);
569 free_command_identifier_tree(confirmationkeywordidentifier);
572 errormsg("out of memory\n");
576 format_status_left(status, L"Ready");
578 prompt = prompt_info_alloc(80);
581 free_status_text(status);
583 for (g = 0; g < totalgroups; ++g)
584 free(groups[g].files);
588 free_command_identifier_tree(commandidentifier);
589 free_command_identifier_tree(confirmationkeywordidentifier);
592 errormsg("out of memory\n");
599 wmove(filewin, 0, 0);
603 totallines = groups[totalgroups-1].endline;
607 for (x = topline; x < topline + getmaxy(filewin); ++x)
615 groupindex = getgroupindex(groups, totalgroups, groupindex, x);
617 index_width = get_num_digits(groups[groupindex].filecount);
619 if (index_width < FILE_INDEX_MIN_WIDTH)
620 index_width = FILE_INDEX_MIN_WIDTH;
622 timestamp_width = ISFLAG(flags, F_SHOWTIME) ? 19 : 0;
624 linestyle = getlinestyle(groups + groupindex, x);
626 if (linestyle == linestyle_groupheader)
628 wattron(filewin, A_BOLD);
629 if (groups[groupindex].selected)
630 wattron(filewin, A_REVERSE);
631 wprintw(filewin, "Set %d of %d:\n", groupindex + 1, totalgroups);
632 if (groups[groupindex].selected)
633 wattroff(filewin, A_REVERSE);
634 wattroff(filewin, A_BOLD);
636 else if (linestyle == linestyle_groupheaderspacing)
638 wprintw(filewin, "\n");
640 else if (linestyle == linestyle_filename)
642 f = getgroupfileindex(&row, groups + groupindex, x, COLS);
644 if (cursorgroup != groupindex)
648 print_spaces(filewin, index_width);
650 wprintw(filewin, " [%c] ", groups[groupindex].files[f].action > 0 ? '+' : groups[groupindex].files[f].action < 0 ? '-' : ' ');
652 if (ISFLAG(flags, F_SHOWTIME))
653 wprintw(filewin, "[%s] ", fmttime(groups[groupindex].files[f].file->mtime));
656 cy = getcury(filewin);
658 if (groups[groupindex].files[f].selected)
659 wattron(filewin, A_REVERSE);
660 putline(filewin, groups[groupindex].files[f].file->d_name, row, COLS, index_width + timestamp_width + FILENAME_INDENT_EXTRA);
661 if (groups[groupindex].files[f].selected)
662 wattroff(filewin, A_REVERSE);
665 wmove(filewin, cy+1, 0);
671 print_right_justified_int(filewin, f+1, index_width);
672 wprintw(filewin, " ");
674 if (cursorgroup == groupindex && cursorfile == f)
675 wattron(filewin, A_REVERSE);
676 wprintw(filewin, "[%c]", groups[groupindex].files[f].action > 0 ? '+' : groups[groupindex].files[f].action < 0 ? '-' : ' ');
677 if (cursorgroup == groupindex && cursorfile == f)
678 wattroff(filewin, A_REVERSE);
679 wprintw(filewin, " ");
681 if (ISFLAG(flags, F_SHOWTIME))
682 wprintw(filewin, "[%s] ", fmttime(groups[groupindex].files[f].file->mtime));
685 cy = getcury(filewin);
687 if (groups[groupindex].files[f].selected)
688 wattron(filewin, A_REVERSE);
689 putline(filewin, groups[groupindex].files[f].file->d_name, row, COLS, index_width + timestamp_width + FILENAME_INDENT_EXTRA);
690 if (groups[groupindex].files[f].selected)
691 wattroff(filewin, A_REVERSE);
694 wmove(filewin, cy+1, 0);
697 else if (linestyle == linestyle_groupfooterspacing)
699 wprintw(filewin, "\n");
704 format_status_right(status, L"Set %d of %d", cursorgroup+1, totalgroups);
706 format_status_right(status, L"Finished");
708 print_status(statuswin, status);
711 format_prompt(prompt, L"( Preserve files [1 - %d, all, help] )", groups[cursorgroup].filecount);
713 format_prompt(prompt, L"( No duplicates remaining; type 'exit' to exit program )");
715 format_prompt(prompt, L"( No duplicates found; type 'exit' to exit program )");
717 print_prompt(promptwin, prompt, L"");
719 /* refresh windows (using wrefresh instead of wnoutrefresh to avoid bug in gnome-terminal) */
724 /* wait for user input */
725 if (!resumecommandinput)
729 keyresult = wget_wch(promptwin, &wch);
733 getyx(promptwin, cursor_y, cursor_x);
735 format_status_left(status, L"Type 'exit' to exit fdupes.");
736 print_status(statuswin, status);
738 wmove(promptwin, cursor_y, cursor_x);
744 } while (keyresult == ERR);
746 if (keyresult == OK && iswprint(wch))
748 commandbuffer[0] = wch;
749 commandbuffer[1] = '\0';
753 commandbuffer[0] = '\0';
757 if (resumecommandinput || (keyresult == OK && iswprint(wch) && ((wch != '\t' && wch != '\n' && wch != '?'))))
759 resumecommandinput = 0;
761 switch (get_command_text(&commandbuffer, &commandbuffersize, promptwin, prompt, 1, 1))
764 format_status_left(status, L"Ready");
766 get_command_arguments(&commandarguments, commandbuffer);
768 switch (identify_command(commandidentifier, commandbuffer, 0))
770 case COMMAND_SELECT_CONTAINING:
771 cmd_select_containing(groups, totalgroups, commandarguments, status);
774 case COMMAND_SELECT_BEGINNING:
775 cmd_select_beginning(groups, totalgroups, commandarguments, status);
778 case COMMAND_SELECT_ENDING:
779 cmd_select_ending(groups, totalgroups, commandarguments, status);
782 case COMMAND_SELECT_MATCHING:
783 cmd_select_matching(groups, totalgroups, commandarguments, status);
786 case COMMAND_SELECT_REGEX:
787 cmd_select_regex(groups, totalgroups, commandarguments, status);
790 case COMMAND_CLEAR_SELECTIONS_CONTAINING:
791 cmd_clear_selections_containing(groups, totalgroups, commandarguments, status);
794 case COMMAND_CLEAR_SELECTIONS_BEGINNING:
795 cmd_clear_selections_beginning(groups, totalgroups, commandarguments, status);
798 case COMMAND_CLEAR_SELECTIONS_ENDING:
799 cmd_clear_selections_ending(groups, totalgroups, commandarguments, status);
802 case COMMAND_CLEAR_SELECTIONS_MATCHING:
803 cmd_clear_selections_matching(groups, totalgroups, commandarguments, status);
806 case COMMAND_CLEAR_SELECTIONS_REGEX:
807 cmd_clear_selections_regex(groups, totalgroups, commandarguments, status);
810 case COMMAND_CLEAR_ALL_SELECTIONS:
811 cmd_clear_all_selections(groups, totalgroups, commandarguments, status);
814 case COMMAND_INVERT_GROUP_SELECTIONS:
815 cmd_invert_group_selections(groups, totalgroups, commandarguments, status);
818 case COMMAND_KEEP_SELECTED:
819 cmd_keep_selected(groups, totalgroups, commandarguments, &globaldeletiontally, status);
822 case COMMAND_DELETE_SELECTED:
823 cmd_delete_selected(groups, totalgroups, commandarguments, &globaldeletiontally, status);
826 case COMMAND_RESET_SELECTED:
827 cmd_reset_selected(groups, totalgroups, commandarguments, &globaldeletiontally, status);
830 case COMMAND_RESET_GROUP:
831 for (x = 0; x < groups[cursorgroup].filecount; ++x)
832 set_file_action(&groups[cursorgroup].files[x], 0, &globaldeletiontally);
834 format_status_left(status, L"Reset all files in current group.");
838 case COMMAND_PRESERVE_ALL:
839 /* mark all files for preservation */
840 for (x = 0; x < groups[cursorgroup].filecount; ++x)
841 set_file_action(&groups[cursorgroup].files[x], 1, &globaldeletiontally);
843 format_status_left(status, L"%d files marked for preservation", groups[cursorgroup].filecount);
845 if (cursorgroup < totalgroups - 1)
846 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
850 case COMMAND_GOTO_SET:
851 number = wcstol(commandarguments, &wcstolcheck, 10);
852 if (wcstolcheck != commandarguments && *wcstolcheck == '\0')
854 if (number >= 1 && number <= totalgroups)
856 scroll_to_group(&topline, number - 1, 0, groups, filewin);
858 cursorgroup = number - 1;
863 format_status_left(status, L"Group index out of range.");
868 format_status_left(status, L"Invalid group index.");
876 if (system(HELP_COMMAND_STRING) == -1)
877 format_status_left(status, L"Could not display help text.");
884 cmd_prune(groups, totalgroups, commandarguments, &globaldeletiontally, &totalgroups, &cursorgroup, &cursorfile, &topline, logfile, filewin, status);
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);
1234 cmd_prune(groups, totalgroups, commandarguments, &globaldeletiontally, &totalgroups, &cursorgroup, &cursorfile, &topline, logfile, filewin, status);
1238 /* resize windows */
1239 wresize(filewin, LINES - 2, COLS);
1241 wresize(statuswin, 1, COLS);
1242 wresize(promptwin, 1, COLS);
1243 mvwin(statuswin, LINES - 1, 0);
1244 mvwin(promptwin, LINES - 2, 0);
1246 status_text_alloc(status, COLS);
1248 /* recalculate line boundaries */
1251 for (g = 0; g < totalgroups; ++g)
1253 groups[g].startline = groupfirstline;
1254 groups[g].endline = groupfirstline + 2;
1256 for (f = 0; f < groups[g].filecount; ++f)
1257 groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
1259 groupfirstline = groups[g].endline + 1;
1265 else if (keyresult == OK)
1270 if (groups[cursorgroup].files[cursorfile].action == 0)
1273 set_file_action(&groups[cursorgroup].files[cursorfile], 0, &globaldeletiontally);
1275 if (cursorfile < groups[cursorgroup].filecount - 1)
1276 move_to_next_file(&topline, &cursorgroup, &cursorfile, groups, filewin);
1277 else if (cursorgroup < totalgroups - 1)
1278 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1286 for (x = 0; x < groups[cursorgroup].filecount; ++x)
1288 if (groups[cursorgroup].files[x].action == 1)
1292 if (preservecount == 0)
1295 for (x = 0; x < groups[cursorgroup].filecount; ++x)
1297 if (groups[cursorgroup].files[x].action == 0)
1298 set_file_action(&groups[cursorgroup].files[x], -1, &globaldeletiontally);
1300 if (groups[cursorgroup].files[x].action == -1)
1304 if (cursorgroup < totalgroups - 1 && deletecount < groups[cursorgroup].filecount)
1305 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1310 if (cursorgroup < totalgroups - 1)
1311 move_to_next_group(&topline, &cursorgroup, &cursorfile, groups, filewin);
1320 free(commandbuffer);
1322 free_prompt_info(prompt);
1324 free_status_text(status);
1326 free_command_identifier_tree(commandidentifier);
1327 free_command_identifier_tree(confirmationkeywordidentifier);
1329 for (g = 0; g < totalgroups; ++g)
1330 free(groups[g].files);