fdupes  2.1.2
About: FDUPES finds duplicate files in a given set of directories.
  Fossies Dox: fdupes-2.1.2.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

ncurses-commands.c
Go to the documentation of this file.
1 /* FDUPES Copyright (c) 2018 Adrian Lopez
2 
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:
10 
11  The above copyright notice and this permission notice shall be
12  included in all copies or substantial portions of the Software.
13 
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. */
21 
22 #include "config.h"
23 #include "ncurses-status.h"
24 #include "ncurses-commands.h"
25 #include "wcs.h"
27 #include "log.h"
28 #include <wchar.h>
29 #include <pcre2.h>
30 
31 void set_file_action(struct groupfile *file, int new_action, size_t *deletion_tally);
32 
33 struct command_map command_list[] = {
34  {L"sel", COMMAND_SELECT_CONTAINING},
35  {L"selb", COMMAND_SELECT_BEGINNING},
36  {L"sele", COMMAND_SELECT_ENDING},
37  {L"selm", COMMAND_SELECT_MATCHING},
38  {L"selr", COMMAND_SELECT_REGEX},
46  {L"ks", COMMAND_KEEP_SELECTED},
47  {L"ds", COMMAND_DELETE_SELECTED},
48  {L"rs", COMMAND_RESET_SELECTED},
49  {L"rg", COMMAND_RESET_GROUP},
50  {L"all", COMMAND_PRESERVE_ALL},
51  {L"goto", COMMAND_GOTO_SET},
52  {L"prune", COMMAND_PRUNE},
53  {L"exit", COMMAND_EXIT},
54  {L"quit", COMMAND_EXIT},
55  {L"help", COMMAND_HELP},
57 };
58 
60  {L"yes", COMMAND_YES},
61  {L"no", COMMAND_NO},
63 };
64 
65 /* select files containing string */
66 int cmd_select_containing(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
67 {
68  int g;
69  int f;
70  int selectedgroupcount = 0;
71  int selectedfilecount = 0;
72  int groupselected;
73 
74  if (wcscmp(commandarguments, L"") != 0)
75  {
76  for (g = 0; g < groupcount; ++g)
77  {
78  groupselected = 0;
79 
80  for (f = 0; f < groups[g].filecount; ++f)
81  {
82  if (wcsinmbcs(groups[g].files[f].file->d_name, commandarguments))
83  {
84  groups[g].selected = 1;
85  groups[g].files[f].selected = 1;
86 
87  groupselected = 1;
88  ++selectedfilecount;
89  }
90  }
91 
92  if (groupselected)
93  ++selectedgroupcount;
94  }
95  }
96 
97  format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
98 
99  return 1;
100 }
101 
102 /* select files beginning with string */
103 int cmd_select_beginning(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
104 {
105  int g;
106  int f;
107  int selectedgroupcount = 0;
108  int selectedfilecount = 0;
109  int groupselected;
110 
111  if (wcscmp(commandarguments, L"") != 0)
112  {
113  for (g = 0; g < groupcount; ++g)
114  {
115  groupselected = 0;
116 
117  for (f = 0; f < groups[g].filecount; ++f)
118  {
119  if (wcsbeginmbcs(groups[g].files[f].file->d_name, commandarguments))
120  {
121  groups[g].selected = 1;
122  groups[g].files[f].selected = 1;
123 
124  groupselected = 1;
125  ++selectedfilecount;
126  }
127  }
128 
129  if (groupselected)
130  ++selectedgroupcount;
131  }
132  }
133 
134  format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
135 
136  return 1;
137 }
138 
139 /* select files ending with string */
140 int cmd_select_ending(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
141 {
142  int g;
143  int f;
144  int selectedgroupcount = 0;
145  int selectedfilecount = 0;
146  int groupselected;
147 
148  if (wcscmp(commandarguments, L"") != 0)
149  {
150  for (g = 0; g < groupcount; ++g)
151  {
152  groupselected = 0;
153 
154  for (f = 0; f < groups[g].filecount; ++f)
155  {
156  if (wcsendsmbcs(groups[g].files[f].file->d_name, commandarguments))
157  {
158  groups[g].selected = 1;
159  groups[g].files[f].selected = 1;
160 
161  groupselected = 1;
162  ++selectedfilecount;
163  }
164  }
165 
166  if (groupselected)
167  ++selectedgroupcount;
168  }
169  }
170 
171  format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
172 
173  return 1;
174 }
175 
176 /* select files matching string */
177 int cmd_select_matching(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
178 {
179  int g;
180  int f;
181  int selectedgroupcount = 0;
182  int selectedfilecount = 0;
183  int groupselected;
184 
185  if (wcscmp(commandarguments, L"") != 0)
186  {
187  for (g = 0; g < groupcount; ++g)
188  {
189  groupselected = 0;
190 
191  for (f = 0; f < groups[g].filecount; ++f)
192  {
193  if (wcsmbcscmp(commandarguments, groups[g].files[f].file->d_name) == 0)
194  {
195  groups[g].selected = 1;
196  groups[g].files[f].selected = 1;
197 
198  groupselected = 1;
199  ++selectedfilecount;
200  }
201  }
202 
203  if (groupselected)
204  ++selectedgroupcount;
205  }
206  }
207 
208  format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
209 
210  return 1;
211 }
212 
213 /* select files matching pattern */
214 int cmd_select_regex(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
215 {
216  size_t size;
217  wchar_t *wcsfilename;
218  size_t needed;
219  int errorcode;
220  PCRE2_SIZE erroroffset;
221  pcre2_code *code;
222  pcre2_match_data *md;
223  int matches;
224  int g;
225  int f;
226  int selectedgroupcount = 0;
227  int selectedfilecount = 0;
228  int groupselected;
229 
230  code = pcre2_compile((PCRE2_SPTR)commandarguments, PCRE2_ZERO_TERMINATED, PCRE2_UTF | PCRE2_UCP, &errorcode, &erroroffset, 0);
231 
232  if (code == 0)
233  return -1;
234 
235  pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
236 
237  md = pcre2_match_data_create(1, 0);
238  if (md == 0)
239  return -1;
240 
241  for (g = 0; g < groupcount; ++g)
242  {
243  groupselected = 0;
244 
245  for (f = 0; f < groups[g].filecount; ++f)
246  {
247  needed = mbstowcs_escape_invalid(0, groups[g].files[f].file->d_name, 0);
248 
249  wcsfilename = (wchar_t*) malloc(needed * sizeof(wchar_t));
250  if (wcsfilename == 0)
251  continue;
252 
253  mbstowcs_escape_invalid(wcsfilename, groups[g].files[f].file->d_name, needed);
254 
255  matches = pcre2_match(code, (PCRE2_SPTR)wcsfilename, PCRE2_ZERO_TERMINATED, 0, 0, md, 0);
256 
257  free(wcsfilename);
258 
259  if (matches > 0)
260  {
261  groups[g].selected = 1;
262  groups[g].files[f].selected = 1;
263 
264  groupselected = 1;
265  ++selectedfilecount;
266  }
267  }
268 
269  if (groupselected)
270  ++selectedgroupcount;
271  }
272 
273  format_status_left(status, L"Matched %d files in %d groups.", selectedfilecount, selectedgroupcount);
274 
275  pcre2_code_free(code);
276 
277  return 1;
278 }
279 
280 /* clear selections containing string */
281 int cmd_clear_selections_containing(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
282 {
283  int g;
284  int f;
285  int matchedgroupcount = 0;
286  int matchedfilecount = 0;
287  int groupmatched;
288  int filedeselected;
289  int selectionsremaining;
290 
291  if (wcscmp(commandarguments, L"") != 0)
292  {
293  for (g = 0; g < groupcount; ++g)
294  {
295  groupmatched = 0;
296  filedeselected = 0;
297  selectionsremaining = 0;
298 
299  for (f = 0; f < groups[g].filecount; ++f)
300  {
301  if (wcsinmbcs(groups[g].files[f].file->d_name, commandarguments))
302  {
303  if (groups[g].files[f].selected)
304  {
305  groups[g].files[f].selected = 0;
306  filedeselected = 1;
307  }
308 
309  groupmatched = 1;
310  ++matchedfilecount;
311  }
312 
313  if (groups[g].files[f].selected)
314  selectionsremaining = 1;
315  }
316 
317  if (filedeselected && !selectionsremaining)
318  groups[g].selected = 0;
319 
320  if (groupmatched)
321  ++matchedgroupcount;
322  }
323  }
324 
325  format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
326 
327  return 1;
328 }
329 
330 /* clear selections beginning with string */
331 int cmd_clear_selections_beginning(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
332 {
333  int g;
334  int f;
335  int matchedgroupcount = 0;
336  int matchedfilecount = 0;
337  int groupmatched;
338  int filedeselected;
339  int selectionsremaining;
340 
341  if (wcscmp(commandarguments, L"") != 0)
342  {
343  for (g = 0; g < groupcount; ++g)
344  {
345  groupmatched = 0;
346  filedeselected = 0;
347  selectionsremaining = 0;
348 
349  for (f = 0; f < groups[g].filecount; ++f)
350  {
351  if (wcsbeginmbcs(groups[g].files[f].file->d_name, commandarguments))
352  {
353  if (groups[g].files[f].selected)
354  {
355  groups[g].files[f].selected = 0;
356  filedeselected = 1;
357  }
358 
359  groupmatched = 1;
360  ++matchedfilecount;
361  }
362 
363  if (groups[g].files[f].selected)
364  selectionsremaining = 1;
365  }
366 
367  if (filedeselected && !selectionsremaining)
368  groups[g].selected = 0;
369 
370  if (groupmatched)
371  ++matchedgroupcount;
372  }
373  }
374 
375  format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
376 
377  return 1;
378 }
379 
380 /* clear selections ending with string */
381 int cmd_clear_selections_ending(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
382 {
383  int g;
384  int f;
385  int matchedgroupcount = 0;
386  int matchedfilecount = 0;
387  int groupmatched;
388  int filedeselected;
389  int selectionsremaining;
390 
391  if (wcscmp(commandarguments, L"") != 0)
392  {
393  for (g = 0; g < groupcount; ++g)
394  {
395  groupmatched = 0;
396  filedeselected = 0;
397  selectionsremaining = 0;
398 
399  for (f = 0; f < groups[g].filecount; ++f)
400  {
401  if (wcsendsmbcs(groups[g].files[f].file->d_name, commandarguments))
402  {
403  if (groups[g].files[f].selected)
404  {
405  groups[g].files[f].selected = 0;
406  filedeselected = 1;
407  }
408 
409  groupmatched = 1;
410  ++matchedfilecount;
411  }
412 
413  if (groups[g].files[f].selected)
414  selectionsremaining = 1;
415  }
416 
417  if (filedeselected && !selectionsremaining)
418  groups[g].selected = 0;
419 
420  if (groupmatched)
421  ++matchedgroupcount;
422  }
423  }
424 
425  format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
426 
427  return 1;
428 }
429 
430 /* clear selections matching string */
431 int cmd_clear_selections_matching(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
432 {
433  int g;
434  int f;
435  int matchedgroupcount = 0;
436  int matchedfilecount = 0;
437  int groupmatched;
438  int filedeselected;
439  int selectionsremaining;
440 
441  if (wcscmp(commandarguments, L"") != 0)
442  {
443  for (g = 0; g < groupcount; ++g)
444  {
445  groupmatched = 0;
446  filedeselected = 0;
447  selectionsremaining = 0;
448 
449  for (f = 0; f < groups[g].filecount; ++f)
450  {
451  if (wcsmbcscmp(commandarguments, groups[g].files[f].file->d_name) == 0)
452  {
453  if (groups[g].files[f].selected)
454  {
455  groups[g].files[f].selected = 0;
456  filedeselected = 1;
457  }
458 
459  groupmatched = 1;
460  ++matchedfilecount;
461  }
462 
463  if (groups[g].files[f].selected)
464  selectionsremaining = 1;
465  }
466 
467  if (filedeselected && !selectionsremaining)
468  groups[g].selected = 0;
469 
470  if (groupmatched)
471  ++matchedgroupcount;
472  }
473  }
474 
475  format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
476 
477  return 1;
478 }
479 
480 /* clear selection matching pattern */
481 int cmd_clear_selections_regex(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
482 {
483  size_t size;
484  wchar_t *wcsfilename;
485  size_t needed;
486  int errorcode;
487  PCRE2_SIZE erroroffset;
488  pcre2_code *code;
489  pcre2_match_data *md;
490  int matches;
491  int g;
492  int f;
493  int matchedgroupcount = 0;
494  int matchedfilecount = 0;
495  int groupmatched;
496  int filedeselected;
497  int selectionsremaining;
498 
499  code = pcre2_compile((PCRE2_SPTR)commandarguments, PCRE2_ZERO_TERMINATED, PCRE2_UTF | PCRE2_UCP, &errorcode, &erroroffset, 0);
500 
501  if (code == 0)
502  return -1;
503 
504  pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
505 
506  md = pcre2_match_data_create(1, 0);
507  if (md == 0)
508  return -1;
509 
510  for (g = 0; g < groupcount; ++g)
511  {
512  groupmatched = 0;
513  filedeselected = 0;
514  selectionsremaining = 0;
515 
516  for (f = 0; f < groups[g].filecount; ++f)
517  {
518  needed = mbstowcs_escape_invalid(0, groups[g].files[f].file->d_name, 0);
519 
520  wcsfilename = (wchar_t*) malloc(needed * sizeof(wchar_t));
521  if (wcsfilename == 0)
522  continue;
523 
524  mbstowcs_escape_invalid(wcsfilename, groups[g].files[f].file->d_name, needed);
525 
526  matches = pcre2_match(code, (PCRE2_SPTR)wcsfilename, PCRE2_ZERO_TERMINATED, 0, 0, md, 0);
527 
528  free(wcsfilename);
529 
530  if (matches > 0)
531  {
532  if (groups[g].files[f].selected)
533  {
534  groups[g].files[f].selected = 0;
535  filedeselected = 1;
536  }
537 
538  groupmatched = 1;
539  ++matchedfilecount;
540  }
541 
542  if (groups[g].files[f].selected)
543  selectionsremaining = 1;
544  }
545 
546  if (filedeselected && !selectionsremaining)
547  groups[g].selected = 0;
548 
549  if (groupmatched)
550  ++matchedgroupcount;
551  }
552 
553  format_status_left(status, L"Matched %d files in %d groups.", matchedfilecount, matchedgroupcount);
554 
555  pcre2_code_free(code);
556 
557  return 1;
558 }
559 
560 /* clear all selections and selected groups */
561 int cmd_clear_all_selections(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
562 {
563  int g;
564  int f;
565 
566  for (g = 0; g < groupcount; ++g)
567  {
568  for (f = 0; f < groups[g].filecount; ++f)
569  groups[g].files[f].selected = 0;
570 
571  groups[g].selected = 0;
572  }
573 
574  if (status)
575  format_status_left(status, L"Cleared all selections.");
576 
577  return 1;
578 }
579 
580 /* invert selections within selected groups */
581 int cmd_invert_group_selections(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
582 {
583  int g;
584  int f;
585  int selectedcount = 0;
586  int deselectedcount = 0;
587 
588  for (g = 0; g < groupcount; ++g)
589  {
590  if (groups[g].selected)
591  {
592  for (f = 0; f < groups[g].filecount; ++f)
593  {
594  groups[g].files[f].selected = !groups[g].files[f].selected;
595 
596  if (groups[g].files[f].selected)
597  ++selectedcount;
598  else
599  ++deselectedcount;
600  }
601  }
602  }
603 
604  format_status_left(status, L"Selected %d files. Deselected %d files.", selectedcount, deselectedcount);
605 
606  return 1;
607 }
608 
609 /* mark selected files for preservation */
610 int cmd_keep_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
611 {
612  int g;
613  int f;
614  int keepfilecount = 0;
615 
616  for (g = 0; g < groupcount; ++g)
617  {
618  for (f = 0; f < groups[g].filecount; ++f)
619  {
620  if (groups[g].files[f].selected)
621  {
622  set_file_action(&groups[g].files[f], 1, deletiontally);
623  ++keepfilecount;
624  }
625  }
626  }
627 
628  format_status_left(status, L"Marked %d files for preservation.", keepfilecount);
629 
630  return 1;
631 }
632 
633 /* mark selected files for deletion */
634 int cmd_delete_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
635 {
636  int g;
637  int f;
638  int deletefilecount = 0;
639 
640  for (g = 0; g < groupcount; ++g)
641  {
642  for (f = 0; f < groups[g].filecount; ++f)
643  {
644  if (groups[g].files[f].selected)
645  {
646  set_file_action(&groups[g].files[f], -1, deletiontally);
647  ++deletefilecount;
648  }
649  }
650  }
651 
652  format_status_left(status, L"Marked %d files for deletion.", deletefilecount);
653 
654  return 1;
655 }
656 
657 /* mark selected files as unresolved */
658 int cmd_reset_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
659 {
660  int g;
661  int f;
662  int resetfilecount = 0;
663 
664  for (g = 0; g < groupcount; ++g)
665  {
666  for (f = 0; f < groups[g].filecount; ++f)
667  {
668  if (groups[g].files[f].selected)
669  {
670  set_file_action(&groups[g].files[f], 0, deletiontally);
671  ++resetfilecount;
672  }
673  }
674  }
675 
676  format_status_left(status, L"Unmarked %d files.", resetfilecount);
677 
678  return 1;
679 }
680 
681 int filerowcount(file_t *file, const int columns, int group_file_count);
682 
683 /* delete files tagged for deletion, delist sets with no untagged files */
684 int cmd_prune(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, int *totalgroups, int *cursorgroup, int *cursorfile, int *topline, char *logfile, WINDOW *filewin, struct status_text *status)
685 {
686  int deletecount;
687  int preservecount;
688  int unresolvedcount;
689  int totaldeleted = 0;
690  double deletedbytes = 0;
691  struct log_info *loginfo;
692  int g;
693  int f;
694  int to;
695  int adjusttopline;
696  int toplineoffset;
697  int groupfirstline;
698 
699  if (logfile != 0)
700  loginfo = log_open(logfile, 0);
701  else
702  loginfo = 0;
703 
704  for (g = 0; g < *totalgroups; ++g)
705  {
706  preservecount = 0;
707  deletecount = 0;
708  unresolvedcount = 0;
709 
710  for (f = 0; f < groups[g].filecount; ++f)
711  {
712  switch (groups[g].files[f].action)
713  {
714  case -1:
715  ++deletecount;
716  break;
717  case 0:
718  ++unresolvedcount;
719  break;
720  case 1:
721  ++preservecount;
722  break;
723  }
724  }
725 
726  if (loginfo)
727  log_begin_set(loginfo);
728 
729  /* delete files marked for deletion unless no files left undeleted */
730  if (deletecount < groups[g].filecount)
731  {
732  for (f = 0; f < groups[g].filecount; ++f)
733  {
734  if (groups[g].files[f].action == -1)
735  {
736  if (remove(groups[g].files[f].file->d_name) == 0)
737  {
738  set_file_action(&groups[g].files[f], -2, deletiontally);
739 
740  deletedbytes += groups[g].files[f].file->size;
741  ++totaldeleted;
742 
743  if (loginfo)
744  log_file_deleted(loginfo, groups[g].files[f].file->d_name);
745  }
746  }
747  }
748 
749  if (loginfo)
750  {
751  for (f = 0; f < groups[g].filecount; ++f)
752  {
753  if (groups[g].files[f].action >= 0)
754  log_file_remaining(loginfo, groups[g].files[f].file->d_name);
755  }
756  }
757 
758  deletecount = 0;
759  }
760 
761  if (loginfo)
762  log_end_set(loginfo);
763 
764  /* if no files left unresolved, mark preserved files for delisting */
765  if (unresolvedcount == 0)
766  {
767  for (f = 0; f < groups[g].filecount; ++f)
768  if (groups[g].files[f].action == 1)
769  set_file_action(&groups[g].files[f], -2, deletiontally);
770 
771  preservecount = 0;
772  }
773  /* if only one file left unresolved, mark it for delesting */
774  else if (unresolvedcount == 1 && preservecount + deletecount == 0)
775  {
776  for (f = 0; f < groups[g].filecount; ++f)
777  if (groups[g].files[f].action == 0)
778  set_file_action(&groups[g].files[f], -2, deletiontally);
779  }
780 
781  /* delist any files marked for delisting */
782  to = 0;
783  for (f = 0; f < groups[g].filecount; ++f)
784  if (groups[g].files[f].action != -2)
785  groups[g].files[to++] = groups[g].files[f];
786 
787  groups[g].filecount = to;
788 
789  /* reposition cursor, if necessary */
790  if (*cursorgroup == g && *cursorfile > 0 && *cursorfile >= groups[g].filecount)
791  *cursorfile = groups[g].filecount - 1;
792  }
793 
794  if (loginfo != 0)
795  log_close(loginfo);
796 
797  if (deletedbytes < 1000.0)
798  format_status_left(status, L"Deleted %ld files (occupying %.0f bytes).", totaldeleted, deletedbytes);
799  else if (deletedbytes <= (1000.0 * 1000.0))
800  format_status_left(status, L"Deleted %ld files (occupying %.1f KB).", totaldeleted, deletedbytes / 1000.0);
801  else if (deletedbytes <= (1000.0 * 1000.0 * 1000.0))
802  format_status_left(status, L"Deleted %ld files (occupying %.1f MB).", totaldeleted, deletedbytes / (1000.0 * 1000.0));
803  else
804  format_status_left(status, L"Deleted %ld files (occupying %.1f GB).", totaldeleted, deletedbytes / (1000.0 * 1000.0 * 1000.0));
805 
806  /* delist empty groups */
807  to = 0;
808  for (g = 0; g < *totalgroups; ++g)
809  {
810  if (groups[g].filecount > 0)
811  {
812  groups[to] = groups[g];
813 
814  /* reposition cursor, if necessary */
815  if (to == *cursorgroup && to != g)
816  *cursorfile = 0;
817 
818  ++to;
819  }
820  else
821  {
822  free(groups[g].files);
823  }
824  }
825 
826  *totalgroups = to;
827 
828  /* reposition cursor, if necessary */
829  if (*cursorgroup >= *totalgroups)
830  {
831  *cursorgroup = *totalgroups - 1;
832  *cursorfile = 0;
833  }
834 
835  /* recalculate line boundaries */
836  adjusttopline = 1;
837  toplineoffset = 0;
838  groupfirstline = 0;
839 
840  for (g = 0; g < *totalgroups; ++g)
841  {
842  if (adjusttopline && groups[g].endline >= *topline)
843  toplineoffset = groups[g].endline - *topline;
844 
845  groups[g].startline = groupfirstline;
846  groups[g].endline = groupfirstline + 2;
847 
848  for (f = 0; f < groups[g].filecount; ++f)
849  groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
850 
851  if (adjusttopline && toplineoffset > 0)
852  {
853  *topline = groups[g].endline - toplineoffset;
854 
855  if (*topline < 0)
856  *topline = 0;
857 
858  adjusttopline = 0;
859  }
860 
861  groupfirstline = groups[g].endline + 1;
862  }
863 
864  if (*totalgroups > 0 && groups[*totalgroups-1].endline <= *topline)
865  {
866  *topline = groups[*totalgroups-1].endline - getmaxy(filewin) + 1;
867 
868  if (*topline < 0)
869  *topline = 0;
870  }
871 
872  cmd_clear_all_selections(groups, *totalgroups, commandarguments, 0);
873 }
COMMAND_RESET_GROUP
#define COMMAND_RESET_GROUP
Definition: ncurses-commands.h:42
COMMAND_YES
#define COMMAND_YES
Definition: ncurses-commands.h:46
ncurses-status.h
cmd_delete_selected
int cmd_delete_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
Definition: ncurses-commands.c:634
COMMAND_KEEP_SELECTED
#define COMMAND_KEEP_SELECTED
Definition: ncurses-commands.h:39
filegroup::selected
int selected
Definition: filegroup.h:40
COMMAND_CLEAR_SELECTIONS_BEGINNING
#define COMMAND_CLEAR_SELECTIONS_BEGINNING
Definition: ncurses-commands.h:34
mbstowcs_escape_invalid
size_t mbstowcs_escape_invalid(wchar_t *dest, const char *src, size_t n)
Definition: mbstowcs_escape_invalid.c:56
_file
Definition: fdupes.h:28
filegroup::filecount
size_t filecount
Definition: filegroup.h:37
log_close
void log_close(struct log_info *info)
Definition: log.c:222
log_end_set
void log_end_set(struct log_info *info)
Definition: log.c:181
COMMAND_CLEAR_ALL_SELECTIONS
#define COMMAND_CLEAR_ALL_SELECTIONS
Definition: ncurses-commands.h:37
cmd_invert_group_selections
int cmd_invert_group_selections(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:581
COMMAND_INVERT_GROUP_SELECTIONS
#define COMMAND_INVERT_GROUP_SELECTIONS
Definition: ncurses-commands.h:38
COMMAND_DELETE_SELECTED
#define COMMAND_DELETE_SELECTED
Definition: ncurses-commands.h:40
cmd_select_regex
int cmd_select_regex(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:214
groupfile::file
file_t * file
Definition: filegroup.h:29
COMMAND_SELECT_MATCHING
#define COMMAND_SELECT_MATCHING
Definition: ncurses-commands.h:32
wcsinmbcs
int wcsinmbcs(char *haystack, wchar_t *needle)
Definition: wcs.c:46
log_info
Definition: log.h:36
COMMAND_SELECT_BEGINNING
#define COMMAND_SELECT_BEGINNING
Definition: ncurses-commands.h:30
cmd_clear_selections_ending
int cmd_clear_selections_ending(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:381
status_text
Definition: ncurses-status.h:33
groupfile
Definition: filegroup.h:28
_file::size
off_t size
Definition: fdupes.h:30
set_file_action
void set_file_action(struct groupfile *file, int new_action, size_t *deletion_tally)
Definition: ncurses-interface.c:181
cmd_clear_selections_regex
int cmd_clear_selections_regex(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:481
log_open
struct log_info * log_open(char *filename, int *error)
Definition: log.c:30
COMMAND_NO
#define COMMAND_NO
Definition: ncurses-commands.h:47
wcsbeginmbcs
int wcsbeginmbcs(char *haystack, wchar_t *needle)
Definition: wcs.c:71
COMMAND_SELECT_CONTAINING
#define COMMAND_SELECT_CONTAINING
Definition: ncurses-commands.h:29
wcsmbcscmp
int wcsmbcscmp(wchar_t *s1, char *s2)
Definition: wcs.c:24
COMMAND_CLEAR_SELECTIONS_REGEX
#define COMMAND_CLEAR_SELECTIONS_REGEX
Definition: ncurses-commands.h:49
cmd_clear_selections_beginning
int cmd_clear_selections_beginning(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:331
command_list
struct command_map command_list[]
Definition: ncurses-commands.c:33
COMMAND_UNDEFINED
#define COMMAND_UNDEFINED
Definition: commandidentifier.h:26
cmd_select_ending
int cmd_select_ending(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:140
cmd_clear_selections_matching
int cmd_clear_selections_matching(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:431
cmd_clear_all_selections
int cmd_clear_all_selections(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:561
log_begin_set
void log_begin_set(struct log_info *info)
Definition: log.c:110
COMMAND_RESET_SELECTED
#define COMMAND_RESET_SELECTED
Definition: ncurses-commands.h:41
COMMAND_PRESERVE_ALL
#define COMMAND_PRESERVE_ALL
Definition: ncurses-commands.h:43
mbstowcs_escape_invalid.h
wcs.h
filegroup::files
struct groupfile * files
Definition: filegroup.h:36
log.h
cmd_keep_selected
int cmd_keep_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
Definition: ncurses-commands.c:610
filegroup::endline
int endline
Definition: filegroup.h:39
log_file_remaining
int log_file_remaining(struct log_info *info, char *name)
Definition: log.c:135
confirmation_keyword_list
struct command_map confirmation_keyword_list[]
Definition: ncurses-commands.c:59
groupfile::selected
int selected
Definition: filegroup.h:31
cmd_select_beginning
int cmd_select_beginning(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:103
command_map
Definition: commandidentifier.h:29
COMMAND_GOTO_SET
#define COMMAND_GOTO_SET
Definition: ncurses-commands.h:50
COMMAND_EXIT
#define COMMAND_EXIT
Definition: ncurses-commands.h:44
cmd_prune
int cmd_prune(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, int *totalgroups, int *cursorgroup, int *cursorfile, int *topline, char *logfile, WINDOW *filewin, struct status_text *status)
Definition: ncurses-commands.c:684
COMMAND_PRUNE
#define COMMAND_PRUNE
Definition: ncurses-commands.h:51
filegroup
Definition: filegroup.h:35
wcsendsmbcs
int wcsendsmbcs(char *haystack, wchar_t *needle)
Definition: wcs.c:96
cmd_select_containing
int cmd_select_containing(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:66
COMMAND_HELP
#define COMMAND_HELP
Definition: ncurses-commands.h:45
log_info::file
FILE * file
Definition: log.h:37
COMMAND_CLEAR_SELECTIONS_CONTAINING
#define COMMAND_CLEAR_SELECTIONS_CONTAINING
Definition: ncurses-commands.h:33
filerowcount
int filerowcount(file_t *file, const int columns, int group_file_count)
Definition: ncurses-interface.c:71
COMMAND_CLEAR_SELECTIONS_ENDING
#define COMMAND_CLEAR_SELECTIONS_ENDING
Definition: ncurses-commands.h:35
ncurses-commands.h
COMMAND_SELECT_REGEX
#define COMMAND_SELECT_REGEX
Definition: ncurses-commands.h:48
COMMAND_SELECT_ENDING
#define COMMAND_SELECT_ENDING
Definition: ncurses-commands.h:31
log_file_deleted
int log_file_deleted(struct log_info *info, char *name)
Definition: log.c:117
cmd_reset_selected
int cmd_reset_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status)
Definition: ncurses-commands.c:658
COMMAND_CLEAR_SELECTIONS_MATCHING
#define COMMAND_CLEAR_SELECTIONS_MATCHING
Definition: ncurses-commands.h:36
cmd_select_matching
int cmd_select_matching(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:177
format_status_left
void format_status_left(struct status_text *status, wchar_t *format,...)
Definition: ncurses-status.c:91
cmd_clear_selections_containing
int cmd_clear_selections_containing(struct filegroup *groups, int groupcount, wchar_t *commandarguments, struct status_text *status)
Definition: ncurses-commands.c:281
filegroup::startline
int startline
Definition: filegroup.h:38