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. */
23 #include "ncurses-status.h"
24 #include "ncurses-commands.h"
26 #include "mbstowcs_escape_invalid.h"
30 void set_file_action(struct groupfile *file, int new_action, size_t *deletion_tally);
32 struct command_map command_list[] = {
33 {L"sel", COMMAND_SELECT_CONTAINING},
34 {L"selb", COMMAND_SELECT_BEGINNING},
35 {L"sele", COMMAND_SELECT_ENDING},
36 {L"selm", COMMAND_SELECT_MATCHING},
37 {L"selr", COMMAND_SELECT_REGEX},
38 {L"dsel", COMMAND_CLEAR_SELECTIONS_CONTAINING},
39 {L"dselb", COMMAND_CLEAR_SELECTIONS_BEGINNING},
40 {L"dsele", COMMAND_CLEAR_SELECTIONS_ENDING},
41 {L"dselm", COMMAND_CLEAR_SELECTIONS_MATCHING},
42 {L"dselr", COMMAND_CLEAR_SELECTIONS_REGEX},
43 {L"cs", COMMAND_CLEAR_ALL_SELECTIONS},
44 {L"igs", COMMAND_INVERT_GROUP_SELECTIONS},
45 {L"ks", COMMAND_KEEP_SELECTED},
46 {L"ds", COMMAND_DELETE_SELECTED},
47 {L"rs", COMMAND_RESET_SELECTED},
48 {L"rg", COMMAND_RESET_GROUP},
49 {L"all", COMMAND_PRESERVE_ALL},
50 {L"goto", COMMAND_GOTO_SET},
51 {L"exit", COMMAND_EXIT},
52 {L"quit", COMMAND_EXIT},
53 {L"help", COMMAND_HELP},
54 {0, COMMAND_UNDEFINED}
57 struct command_map confirmation_keyword_list[] = {
58 {L"yes", COMMAND_YES},
60 {0, COMMAND_UNDEFINED}
63 /* select files containing string */
64 int cmd_select_containing(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
68 int selectedgroupcount = 0;
69 int selectedfilecount = 0;
72 if (wcscmp(commandarguments, L"") != 0)
74 for (g = 0; g < groupcount; ++g)
78 for (f = 0; f < groups[g].filecount; ++f)
80 if (wcsinmbcs(groups[g].files[f].file->d_name, commandarguments))
82 groups[g].selected = 1;
83 groups[g].files[f].selected = 1;
95 format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
100 /* select files beginning with string */
101 int cmd_select_beginning(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
105 int selectedgroupcount = 0;
106 int selectedfilecount = 0;
109 if (wcscmp(commandarguments, L"") != 0)
111 for (g = 0; g < groupcount; ++g)
115 for (f = 0; f < groups[g].filecount; ++f)
117 if (wcsbeginmbcs(groups[g].files[f].file->d_name, commandarguments))
119 groups[g].selected = 1;
120 groups[g].files[f].selected = 1;
128 ++selectedgroupcount;
132 format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
137 /* select files ending with string */
138 int cmd_select_ending(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
142 int selectedgroupcount = 0;
143 int selectedfilecount = 0;
146 if (wcscmp(commandarguments, L"") != 0)
148 for (g = 0; g < groupcount; ++g)
152 for (f = 0; f < groups[g].filecount; ++f)
154 if (wcsendsmbcs(groups[g].files[f].file->d_name, commandarguments))
156 groups[g].selected = 1;
157 groups[g].files[f].selected = 1;
165 ++selectedgroupcount;
169 format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
174 /* select files matching string */
175 int cmd_select_matching(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
179 int selectedgroupcount = 0;
180 int selectedfilecount = 0;
183 if (wcscmp(commandarguments, L"") != 0)
185 for (g = 0; g < groupcount; ++g)
189 for (f = 0; f < groups[g].filecount; ++f)
191 if (wcsmbcscmp(commandarguments, groups[g].files[f].file->d_name) == 0)
193 groups[g].selected = 1;
194 groups[g].files[f].selected = 1;
202 ++selectedgroupcount;
206 format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
211 /* select files matching pattern */
212 int cmd_select_regex(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
215 wchar_t *wcsfilename;
218 PCRE2_SIZE erroroffset;
220 pcre2_match_data *md;
224 int selectedgroupcount = 0;
225 int selectedfilecount = 0;
228 code = pcre2_compile((PCRE2_SPTR)commandarguments, PCRE2_ZERO_TERMINATED, PCRE2_UTF | PCRE2_UCP, &errorcode, &erroroffset, 0);
233 pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
235 md = pcre2_match_data_create(1, 0);
239 for (g = 0; g < groupcount; ++g)
243 for (f = 0; f < groups[g].filecount; ++f)
245 needed = mbstowcs_escape_invalid(0, groups[g].files[f].file->d_name, 0);
247 wcsfilename = (wchar_t*) malloc(needed * sizeof(wchar_t));
248 if (wcsfilename == 0)
251 mbstowcs_escape_invalid(wcsfilename, groups[g].files[f].file->d_name, needed);
253 matches = pcre2_match(code, (PCRE2_SPTR)wcsfilename, PCRE2_ZERO_TERMINATED, 0, 0, md, 0);
259 groups[g].selected = 1;
260 groups[g].files[f].selected = 1;
268 ++selectedgroupcount;
271 format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
273 pcre2_code_free(code);
278 /* clear selections containing string */
279 int cmd_clear_selections_containing(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
283 int matchedgroupcount = 0;
284 int matchedfilecount = 0;
287 int selectionsremaining;
289 if (wcscmp(commandarguments, L"") != 0)
291 for (g = 0; g < groupcount; ++g)
295 selectionsremaining = 0;
297 for (f = 0; f < groups[g].filecount; ++f)
299 if (wcsinmbcs(groups[g].files[f].file->d_name, commandarguments))
301 if (groups[g].files[f].selected)
303 groups[g].files[f].selected = 0;
311 if (groups[g].files[f].selected)
312 selectionsremaining = 1;
315 if (filedeselected && !selectionsremaining)
316 groups[g].selected = 0;
323 format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
328 /* clear selections beginning with string */
329 int cmd_clear_selections_beginning(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
333 int matchedgroupcount = 0;
334 int matchedfilecount = 0;
337 int selectionsremaining;
339 if (wcscmp(commandarguments, L"") != 0)
341 for (g = 0; g < groupcount; ++g)
345 selectionsremaining = 0;
347 for (f = 0; f < groups[g].filecount; ++f)
349 if (wcsbeginmbcs(groups[g].files[f].file->d_name, commandarguments))
351 if (groups[g].files[f].selected)
353 groups[g].files[f].selected = 0;
361 if (groups[g].files[f].selected)
362 selectionsremaining = 1;
365 if (filedeselected && !selectionsremaining)
366 groups[g].selected = 0;
373 format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
378 /* clear selections ending with string */
379 int cmd_clear_selections_ending(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
383 int matchedgroupcount = 0;
384 int matchedfilecount = 0;
387 int selectionsremaining;
389 if (wcscmp(commandarguments, L"") != 0)
391 for (g = 0; g < groupcount; ++g)
395 selectionsremaining = 0;
397 for (f = 0; f < groups[g].filecount; ++f)
399 if (wcsendsmbcs(groups[g].files[f].file->d_name, commandarguments))
401 if (groups[g].files[f].selected)
403 groups[g].files[f].selected = 0;
411 if (groups[g].files[f].selected)
412 selectionsremaining = 1;
415 if (filedeselected && !selectionsremaining)
416 groups[g].selected = 0;
423 format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
428 /* clear selections matching string */
429 int cmd_clear_selections_matching(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
433 int matchedgroupcount = 0;
434 int matchedfilecount = 0;
437 int selectionsremaining;
439 if (wcscmp(commandarguments, L"") != 0)
441 for (g = 0; g < groupcount; ++g)
445 selectionsremaining = 0;
447 for (f = 0; f < groups[g].filecount; ++f)
449 if (wcsmbcscmp(commandarguments, groups[g].files[f].file->d_name) == 0)
451 if (groups[g].files[f].selected)
453 groups[g].files[f].selected = 0;
461 if (groups[g].files[f].selected)
462 selectionsremaining = 1;
465 if (filedeselected && !selectionsremaining)
466 groups[g].selected = 0;
473 format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
478 /* clear selection matching pattern */
479 int cmd_clear_selections_regex(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
482 wchar_t *wcsfilename;
485 PCRE2_SIZE erroroffset;
487 pcre2_match_data *md;
491 int matchedgroupcount = 0;
492 int matchedfilecount = 0;
495 int selectionsremaining;
497 code = pcre2_compile((PCRE2_SPTR)commandarguments, PCRE2_ZERO_TERMINATED, PCRE2_UTF | PCRE2_UCP, &errorcode, &erroroffset, 0);
502 pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
504 md = pcre2_match_data_create(1, 0);
508 for (g = 0; g < groupcount; ++g)
512 selectionsremaining = 0;
514 for (f = 0; f < groups[g].filecount; ++f)
516 needed = mbstowcs_escape_invalid(0, groups[g].files[f].file->d_name, 0);
518 wcsfilename = (wchar_t*) malloc(needed * sizeof(wchar_t));
519 if (wcsfilename == 0)
522 mbstowcs_escape_invalid(wcsfilename, groups[g].files[f].file->d_name, needed);
524 matches = pcre2_match(code, (PCRE2_SPTR)wcsfilename, PCRE2_ZERO_TERMINATED, 0, 0, md, 0);
530 if (groups[g].files[f].selected)
532 groups[g].files[f].selected = 0;
540 if (groups[g].files[f].selected)
541 selectionsremaining = 1;
544 if (filedeselected && !selectionsremaining)
545 groups[g].selected = 0;
551 format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
553 pcre2_code_free(code);
558 /* clear all selections and selected groups */
559 int cmd_clear_all_selections(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
564 for (g = 0; g < groupcount; ++g)
566 for (f = 0; f < groups[g].filecount; ++f)
567 groups[g].files[f].selected = 0;
569 groups[g].selected = 0;
572 format_status_left(status, L"Cleared all selections.");
577 /* invert selections within selected groups */
578 int cmd_invert_group_selections(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
582 int selectedcount = 0;
583 int deselectedcount = 0;
585 for (g = 0; g < groupcount; ++g)
587 if (groups[g].selected)
589 for (f = 0; f < groups[g].filecount; ++f)
591 groups[g].files[f].selected = !groups[g].files[f].selected;
593 if (groups[g].files[f].selected)
601 format_status_left(status, L"Selected %d files. Deselected %d files.", selectedcount, deselectedcount);
606 /* mark selected files for preservation */
607 int cmd_keep_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
611 int keepfilecount = 0;
613 for (g = 0; g < groupcount; ++g)
615 for (f = 0; f < groups[g].filecount; ++f)
617 if (groups[g].files[f].selected)
619 set_file_action(&groups[g].files[f], 1, deletiontally);
625 format_status_left(status, L"Marked %d files for preservation.", keepfilecount);
630 /* mark selected files for deletion */
631 int cmd_delete_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
635 int deletefilecount = 0;
637 for (g = 0; g < groupcount; ++g)
639 for (f = 0; f < groups[g].filecount; ++f)
641 if (groups[g].files[f].selected)
643 set_file_action(&groups[g].files[f], -1, deletiontally);
649 format_status_left(status, L"Marked %d files for deletion.", deletefilecount);
654 /* mark selected files as unresolved */
655 int cmd_reset_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
659 int resetfilecount = 0;
661 for (g = 0; g < groupcount; ++g)
663 for (f = 0; f < groups[g].filecount; ++f)
665 if (groups[g].files[f].selected)
667 set_file_action(&groups[g].files[f], 0, deletiontally);
673 format_status_left(status, L"Unmarked %d files.", resetfilecount);