Imported Upstream version 2.1.0 upstream/2.1.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 25 Oct 2021 01:41:31 +0000 (10:41 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 25 Oct 2021 01:41:31 +0000 (10:41 +0900)
CHANGES
Makefile.am
README
configure.ac
fdupes-help.7
fdupes.c
ncurses-commands.c
ncurses-commands.h
ncurses-interface.c

diff --git a/CHANGES b/CHANGES
index 59ff4fa..13dc67d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,25 @@ who contributed the patch or idea appears first, followed by
 those who've otherwise worked on that item. For a list of 
 contributors names and identifiers please see the CONTRIBUTORS file.
 
+Changes from 2.0.0 to 2.1.0:
+
+ - Rename cs command ("clear all selections") from cs to csel.
+ - Rename igs command ("invert selections") from igs to isel.
+ - Add "prune" command as synonym for DELETE key.
+ - Clear selections after deleting files via prune/DELETE.
+ - Fix dependency issues when fdupes is configured to not use ncurses.
+
+Changes from 1.6.1 to 2.0.0:
+
+- Add ncurses mode for interactive file deletion (plain mode still available
+  via --plain or ./configure).
+- Add --minsize option.
+- Add --maxsize option.
+- Add --time option.
+- Add --order=ctime option.
+- Add --log option.
+- Use configure script for installation (Autotools/Automake).
+
 Changes from 1.6.0 to 1.6.1:
 
 - Fix 'invalid option' error for -I. [AL]
index ecb5087..18ffdbf 100644 (file)
@@ -17,8 +17,6 @@ fdupes_SOURCES = fdupes.c\
  flags.h\
  mbstowcs_escape_invalid.c\
  mbstowcs_escape_invalid.h\
- positive_wcwidth.c\
- positive_wcwidth.h\
  md5/md5.c\
  md5/md5.h
 dist_man1_MANS = fdupes.1
diff --git a/README b/README
index 9876dc4..d8d2786 100644 (file)
--- a/README
+++ b/README
@@ -17,8 +17,8 @@ Usage: fdupes [options] DIRECTORY...
  -H --hardlinks        normally, when two or more files point to the same
                        disk area they are treated as non-duplicates; this
                        option will change this behavior
- -G --minsize=SIZE     consider only files greater than or equal to SIZE
- -L --maxsize=SIZE     consider only files less than or equal to SIZE
+ -G --minsize=SIZE     consider only files greater than or equal to SIZE in bytes
+ -L --maxsize=SIZE     consider only files less than or equal to SIZE in bytes
  -n --noempty          exclude zero-length files from consideration
  -A --nohidden         exclude hidden files from consideration
  -f --omitfirst        omit the first file in each set of matches
index a250f83..1ee4308 100644 (file)
@@ -1,4 +1,4 @@
-AC_INIT([fdupes], [2.0.0])
+AC_INIT([fdupes], [2.1.0])
 
 AM_INIT_AUTOMAKE([foreign subdir-objects])
 
index 51a1349..42d9df0 100644 (file)
@@ -181,14 +181,14 @@ regular expression
 (see below).
 
 .B
-.IP "'cs'"
+.IP "'csel'"
 Clear all selections.
 
 .B
-.IP "'igs'"
+.IP "'isel'"
 Invert selections within selected sets. For example, if files 1 and 4 in a set of 5 are selected,
 .B
-igs
+isel
 will deselect files 1 and 4, and select files 2, 3, and 5. Immediately repeating the same command will deselect files 2, 3, and 5, and select files 1 and 4, restoring selections to their previous state.
 
 .SH "TAGGING SELECTED FILES"
@@ -207,10 +207,12 @@ Tag selected files for deletion.
 Remove all tags from selected files.
 
 .SH "DELETING DUPLICATES"
-Once they've been tagged for deletion, files can be deleted by pressing
+Once tagged for deletion, files can be deleted by pressing
 .B
-DELETE.
-Fdupes will delete any files that are tagged for deletion and delist any sets whose remaining files have been tagged for keeping. For safety, fdupes will refuse to act on sets for which all files have been tagged for deletion. To handle these cases, tag at least one file for keeping and run the delete command again.
+DELETE
+or using the
+.B 'prune'
+command. Fdupes will delete any files that are tagged for deletion and delist any sets whose remaining files have been tagged for keeping. For safety, fdupes will refuse to act on sets for which all files have been tagged for deletion. To handle these cases, tag at least one file for keeping and run the delete command again.
 
 .SH "OTHER COMMANDS"
 .B
index 29b2921..7bab2a8 100644 (file)
--- a/fdupes.c
+++ b/fdupes.c
 #include <errno.h>
 #include <libgen.h>
 #include <locale.h>
+#ifndef NO_NCURSES
 #ifdef HAVE_NCURSESW_CURSES_H
   #include <ncursesw/curses.h>
 #else
   #include <curses.h>
 #endif
+#include "ncurses-interface.h"
+#endif
 #include "fdupes.h"
 #include "errormsg.h"
-#include "ncurses-interface.h"
 #include "log.h"
 #include "sigint.h"
 #include "flags.h"
index d00d58e..1601c70 100644 (file)
@@ -24,6 +24,7 @@
 #include "ncurses-commands.h"
 #include "wcs.h"
 #include "mbstowcs_escape_invalid.h"
+#include "log.h"
 #include <wchar.h>
 #include <pcre2.h>
 
@@ -40,14 +41,15 @@ struct command_map command_list[] = {
   {L"dsele", COMMAND_CLEAR_SELECTIONS_ENDING},
   {L"dselm", COMMAND_CLEAR_SELECTIONS_MATCHING},
   {L"dselr", COMMAND_CLEAR_SELECTIONS_REGEX},
-  {L"cs", COMMAND_CLEAR_ALL_SELECTIONS},
-  {L"igs", COMMAND_INVERT_GROUP_SELECTIONS},
+  {L"csel", COMMAND_CLEAR_ALL_SELECTIONS},
+  {L"isel", COMMAND_INVERT_GROUP_SELECTIONS},
   {L"ks", COMMAND_KEEP_SELECTED},
   {L"ds", COMMAND_DELETE_SELECTED},
   {L"rs", COMMAND_RESET_SELECTED},
   {L"rg", COMMAND_RESET_GROUP},
   {L"all", COMMAND_PRESERVE_ALL},
   {L"goto", COMMAND_GOTO_SET},
+  {L"prune", COMMAND_PRUNE},
   {L"exit", COMMAND_EXIT},
   {L"quit", COMMAND_EXIT},
   {L"help", COMMAND_HELP},
@@ -569,7 +571,8 @@ int cmd_clear_all_selections(struct filegroup *groups, int groupcount, wchar_t *
     groups[g].selected = 0;
   }
 
-  format_status_left(status, L"Cleared all selections.");
+  if (status)
+    format_status_left(status, L"Cleared all selections.");
 
   return 1;
 }
@@ -674,3 +677,197 @@ int cmd_reset_selected(struct filegroup *groups, int groupcount, wchar_t *comman
 
   return 1;
 }
+
+int filerowcount(file_t *file, const int columns, int group_file_count);
+
+/* delete files tagged for deletion, delist sets with no untagged files */
+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)
+{
+  int deletecount;
+  int preservecount;
+  int unresolvedcount;
+  int totaldeleted = 0;
+  double deletedbytes = 0;
+  struct log_info *loginfo;
+  int g;
+  int f;
+  int to;
+  int adjusttopline;
+  int toplineoffset;
+  int groupfirstline;
+
+  if (logfile != 0)
+    loginfo = log_open(logfile, 0);
+  else
+    loginfo = 0;
+
+  for (g = 0; g < *totalgroups; ++g)
+  {
+    preservecount = 0;
+    deletecount = 0;
+    unresolvedcount = 0;
+
+    for (f = 0; f < groups[g].filecount; ++f)
+    {
+      switch (groups[g].files[f].action)
+      {
+        case -1:
+          ++deletecount;
+          break;
+        case 0:
+          ++unresolvedcount;
+          break;
+        case 1:
+          ++preservecount;
+          break;
+      }
+    }
+
+    if (loginfo)
+      log_begin_set(loginfo);
+
+    /* delete files marked for deletion unless no files left undeleted */
+    if (deletecount < groups[g].filecount)
+    {
+      for (f = 0; f < groups[g].filecount; ++f)
+      {
+        if (groups[g].files[f].action == -1)
+        {
+          if (remove(groups[g].files[f].file->d_name) == 0)
+          {
+            set_file_action(&groups[g].files[f], -2, deletiontally);
+
+            deletedbytes += groups[g].files[f].file->size;
+            ++totaldeleted;
+
+            if (loginfo)
+              log_file_deleted(loginfo, groups[g].files[f].file->d_name);
+          }
+        }
+      }
+
+      if (loginfo)
+      {
+        for (f = 0; f < groups[g].filecount; ++f)
+        {
+          if (groups[g].files[f].action >= 0)
+            log_file_remaining(loginfo, groups[g].files[f].file->d_name);
+        }
+      }
+
+      deletecount = 0;
+    }
+
+    if (loginfo)
+      log_end_set(loginfo);
+
+    /* if no files left unresolved, mark preserved files for delisting */
+    if (unresolvedcount == 0)
+    {
+      for (f = 0; f < groups[g].filecount; ++f)
+        if (groups[g].files[f].action == 1)
+          set_file_action(&groups[g].files[f], -2, deletiontally);
+
+      preservecount = 0;
+    }
+    /* if only one file left unresolved, mark it for delesting */
+    else if (unresolvedcount == 1 && preservecount + deletecount == 0)
+    {
+      for (f = 0; f < groups[g].filecount; ++f)
+        if (groups[g].files[f].action == 0)
+          set_file_action(&groups[g].files[f], -2, deletiontally);
+    }
+
+    /* delist any files marked for delisting */
+    to = 0;
+    for (f = 0; f < groups[g].filecount; ++f)
+      if (groups[g].files[f].action != -2)
+        groups[g].files[to++] = groups[g].files[f];
+
+    groups[g].filecount = to;
+
+    /* reposition cursor, if necessary */
+    if (*cursorgroup == g && *cursorfile > 0 && *cursorfile >= groups[g].filecount)
+      *cursorfile = groups[g].filecount - 1;
+  }
+
+  if (loginfo != 0)
+    log_close(loginfo);
+
+  if (deletedbytes < 1000.0)
+    format_status_left(status, L"Deleted %ld files (occupying %.0f bytes).", totaldeleted, deletedbytes);
+  else if (deletedbytes <= (1000.0 * 1000.0))
+    format_status_left(status, L"Deleted %ld files (occupying %.1f KB).", totaldeleted, deletedbytes / 1000.0);
+  else if (deletedbytes <= (1000.0 * 1000.0 * 1000.0))
+    format_status_left(status, L"Deleted %ld files (occupying %.1f MB).", totaldeleted, deletedbytes / (1000.0 * 1000.0));
+  else
+    format_status_left(status, L"Deleted %ld files (occupying %.1f GB).", totaldeleted, deletedbytes / (1000.0 * 1000.0 * 1000.0));
+
+  /* delist empty groups */
+  to = 0;
+  for (g = 0; g < *totalgroups; ++g)
+  {
+    if (groups[g].filecount > 0)
+    {
+      groups[to] = groups[g];
+
+      /* reposition cursor, if necessary */
+      if (to == *cursorgroup && to != g)
+        *cursorfile = 0;
+
+      ++to;
+    }
+    else
+    {
+      free(groups[g].files);
+    }
+  }
+
+  *totalgroups = to;
+
+  /* reposition cursor, if necessary */
+  if (*cursorgroup >= *totalgroups)
+  {
+    *cursorgroup = *totalgroups - 1;
+    *cursorfile = 0;
+  }
+
+  /* recalculate line boundaries */
+  adjusttopline = 1;
+  toplineoffset = 0;
+  groupfirstline = 0;
+
+  for (g = 0; g < *totalgroups; ++g)
+  {
+    if (adjusttopline && groups[g].endline >= *topline)
+      toplineoffset = groups[g].endline - *topline;
+
+    groups[g].startline = groupfirstline;
+    groups[g].endline = groupfirstline + 2;
+
+    for (f = 0; f < groups[g].filecount; ++f)
+      groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
+
+    if (adjusttopline && toplineoffset > 0)
+    {
+      *topline = groups[g].endline - toplineoffset;
+
+      if (*topline < 0)
+        *topline = 0;
+
+      adjusttopline = 0;
+    }
+
+    groupfirstline = groups[g].endline + 1;
+  }
+
+  if (*totalgroups > 0 && groups[*totalgroups-1].endline <= *topline)
+  {
+    *topline = groups[*totalgroups-1].endline - getmaxy(filewin) + 1;
+
+    if (*topline < 0)
+      *topline = 0;
+  }
+
+  cmd_clear_all_selections(groups, *totalgroups, commandarguments, 0);
+}
\ No newline at end of file
index e0fca68..9e5ebd2 100644 (file)
@@ -48,6 +48,7 @@
 #define COMMAND_SELECT_REGEX 20
 #define COMMAND_CLEAR_SELECTIONS_REGEX 21
 #define COMMAND_GOTO_SET 22
+#define COMMAND_PRUNE 23
 
 extern struct command_map command_list[];
 extern struct command_map confirmation_keyword_list[];
@@ -70,5 +71,6 @@ int cmd_invert_group_selections(struct filegroup *groups, int groupcount, wchar_
 int cmd_keep_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status);
 int cmd_delete_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status);
 int cmd_reset_selected(struct filegroup *groups, int groupcount, wchar_t *commandarguments, size_t *deletiontally, struct status_text *status);
+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);;
 
 #endif
\ No newline at end of file
index 8129ed2..6c80f7f 100644 (file)
@@ -376,9 +376,7 @@ void deletefiles_ncurses(file_t *files, char *logfile)
   int preservecount;
   int deletecount;
   int unresolvedcount;
-  int totaldeleted;
   size_t globaldeletiontally = 0;
-  double deletedbytes;
   int row;
   int x;
   int g;
@@ -386,7 +384,6 @@ void deletefiles_ncurses(file_t *files, char *logfile)
   int keyresult;
   int cy;
   int f;
-  int to;
   wchar_t *commandbuffer;
   size_t commandbuffersize;
   wchar_t *commandarguments;
@@ -401,12 +398,9 @@ void deletefiles_ncurses(file_t *files, char *logfile)
   struct prompt_info *prompt;
   int dupesfound;
   int intresult;
-  int adjusttopline;
-  int toplineoffset = 0;
   int resumecommandinput = 0;
   int index_width;
   int timestamp_width;
-  struct log_info *loginfo;
 
   noecho();
   cbreak();
@@ -884,6 +878,10 @@ void deletefiles_ncurses(file_t *files, char *logfile)
 
               break;
 
+            case COMMAND_PRUNE:
+              cmd_prune(groups, totalgroups, commandarguments, &globaldeletiontally, &totalgroups, &cursorgroup, &cursorfile, &topline, logfile, filewin, status);
+              break;
+
             case COMMAND_EXIT: /* exit program */
               if (totalgroups == 0)
               {
@@ -1231,182 +1229,7 @@ void deletefiles_ncurses(file_t *files, char *logfile)
         break;
 
       case KEY_DC:
-        totaldeleted = 0;
-        deletedbytes = 0;
-
-        if (logfile != 0)
-          loginfo = log_open(logfile, 0);
-        else
-          loginfo = 0;
-
-        for (g = 0; g < totalgroups; ++g)
-        {
-          preservecount = 0;
-          deletecount = 0;
-          unresolvedcount = 0;
-
-          for (f = 0; f < groups[g].filecount; ++f)
-          {
-            switch (groups[g].files[f].action)
-            {
-              case -1:
-                ++deletecount;
-                break;
-              case 0:
-                ++unresolvedcount;
-                break;
-              case 1:
-                ++preservecount;
-                break;
-            }
-          }
-
-          if (loginfo)
-            log_begin_set(loginfo);
-
-          /* delete files marked for deletion unless no files left undeleted */
-          if (deletecount < groups[g].filecount)
-          {
-            for (f = 0; f < groups[g].filecount; ++f)
-            {
-              if (groups[g].files[f].action == -1)
-              {
-                if (remove(groups[g].files[f].file->d_name) == 0)
-                {
-                  set_file_action(&groups[g].files[f], -2, &globaldeletiontally);
-
-                  deletedbytes += groups[g].files[f].file->size;
-                  ++totaldeleted;
-
-                  if (loginfo)
-                    log_file_deleted(loginfo, groups[g].files[f].file->d_name);
-                }
-              }
-            }
-
-            if (loginfo)
-            {
-              for (f = 0; f < groups[g].filecount; ++f)
-              {
-                if (groups[g].files[f].action >= 0)
-                  log_file_remaining(loginfo, groups[g].files[f].file->d_name);
-              }
-            }
-
-            deletecount = 0;
-          }
-
-          if (loginfo)
-            log_end_set(loginfo);
-
-          /* if no files left unresolved, mark preserved files for delisting */
-          if (unresolvedcount == 0)
-          {
-            for (f = 0; f < groups[g].filecount; ++f)
-              if (groups[g].files[f].action == 1)
-                set_file_action(&groups[g].files[f], -2, &globaldeletiontally);
-
-            preservecount = 0;
-          }
-          /* if only one file left unresolved, mark it for delesting */
-          else if (unresolvedcount == 1 && preservecount + deletecount == 0)
-          {
-            for (f = 0; f < groups[g].filecount; ++f)
-              if (groups[g].files[f].action == 0)
-                set_file_action(&groups[g].files[f], -2, &globaldeletiontally);
-          }
-
-          /* delist any files marked for delisting */
-          to = 0;
-          for (f = 0; f < groups[g].filecount; ++f)
-            if (groups[g].files[f].action != -2)
-              groups[g].files[to++] = groups[g].files[f];
-
-          groups[g].filecount = to;
-
-          /* reposition cursor, if necessary */
-          if (cursorgroup == g && cursorfile > 0 && cursorfile >= groups[g].filecount)
-            cursorfile = groups[g].filecount - 1;
-        }
-
-        if (loginfo != 0)
-          log_close(loginfo);
-
-        if (deletedbytes < 1000.0)
-          format_status_left(status, L"Deleted %ld files (occupying %.0f bytes).", totaldeleted, deletedbytes);
-        else if (deletedbytes <= (1000.0 * 1000.0))
-          format_status_left(status, L"Deleted %ld files (occupying %.1f KB).", totaldeleted, deletedbytes / 1000.0);
-        else if (deletedbytes <= (1000.0 * 1000.0 * 1000.0))
-          format_status_left(status, L"Deleted %ld files (occupying %.1f MB).", totaldeleted, deletedbytes / (1000.0 * 1000.0));
-        else
-          format_status_left(status, L"Deleted %ld files (occupying %.1f GB).", totaldeleted, deletedbytes / (1000.0 * 1000.0 * 1000.0));
-
-        /* delist empty groups */
-        to = 0;
-        for (g = 0; g < totalgroups; ++g)
-        {
-          if (groups[g].filecount > 0)
-          {
-            groups[to] = groups[g];
-
-            /* reposition cursor, if necessary */
-            if (to == cursorgroup && to != g)
-              cursorfile = 0;
-
-            ++to;
-          }
-          else
-          {
-            free(groups[g].files);
-          }
-        }
-
-        totalgroups = to;
-
-        /* reposition cursor, if necessary */
-        if (cursorgroup >= totalgroups)
-        {
-          cursorgroup = totalgroups - 1;
-          cursorfile = 0;
-        }
-
-        /* recalculate line boundaries */
-        adjusttopline = 1;
-        toplineoffset = 0;
-        groupfirstline = 0;
-
-        for (g = 0; g < totalgroups; ++g)
-        {
-          if (adjusttopline && groups[g].endline >= topline)
-            toplineoffset = groups[g].endline - topline;
-
-          groups[g].startline = groupfirstline;
-          groups[g].endline = groupfirstline + 2;
-
-          for (f = 0; f < groups[g].filecount; ++f)
-            groups[g].endline += filerowcount(groups[g].files[f].file, COLS, groups[g].filecount);
-
-          if (adjusttopline && toplineoffset > 0)
-          {
-            topline = groups[g].endline - toplineoffset;
-
-            if (topline < 0)
-              topline = 0;
-
-            adjusttopline = 0;
-          }
-
-          groupfirstline = groups[g].endline + 1;
-        }
-
-        if (totalgroups > 0 && groups[totalgroups-1].endline <= topline)
-        {
-          topline = groups[totalgroups-1].endline - getmaxy(filewin) + 1;
-
-          if (topline < 0)
-            topline = 0;
-        }
-
+        cmd_prune(groups, totalgroups, commandarguments, &globaldeletiontally, &totalgroups, &cursorgroup, &cursorfile, &topline, logfile, filewin, status);
         break;
 
       case KEY_RESIZE: