"Fossies" - the Fresh Open Source Software Archive

Member "fdupes-2.1.2/ncurses-commands.c" (12 Aug 2020, 22203 Bytes) of package /linux/privat/fdupes-2.1.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ncurses-commands.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2.0.0_vs_2.1.0.

    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"
   26 #include "mbstowcs_escape_invalid.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},
   39   {L"dsel", COMMAND_CLEAR_SELECTIONS_CONTAINING},
   40   {L"dselb", COMMAND_CLEAR_SELECTIONS_BEGINNING},
   41   {L"dsele", COMMAND_CLEAR_SELECTIONS_ENDING},
   42   {L"dselm", COMMAND_CLEAR_SELECTIONS_MATCHING},
   43   {L"dselr", COMMAND_CLEAR_SELECTIONS_REGEX},
   44   {L"csel", COMMAND_CLEAR_ALL_SELECTIONS},
   45   {L"isel", COMMAND_INVERT_GROUP_SELECTIONS},
   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},
   56   {0, COMMAND_UNDEFINED}
   57 };
   58 
   59 struct command_map confirmation_keyword_list[] = {
   60   {L"yes", COMMAND_YES},
   61   {L"no", COMMAND_NO},
   62   {0, COMMAND_UNDEFINED}
   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 }