Fix #1706: command line duplicate search
authorColin Clark <[email protected]>
Thu, 10 Apr 2025 14:51:00 +0000 (15:51 +0100)
committerColin Clark <[email protected]>
Thu, 10 Apr 2025 14:51:00 +0000 (15:51 +0100)
https://github.com/BestImageViewer/geeqie/issues/1706#issuecomment-2792661555

Two additional command line options:

--dupes=<FOLDER>
--dupes-recurse=<FOLDER>

auto-complete/geeqie
doc/docbook/CommandLineOptions.xml
geeqie.1
src/command-line-handling.cc
src/dupe.cc
src/dupe.h
src/main.cc

index bb2990722961fa49f6e5a1127189d2b562eca78f..d6ea1ca53356e650f7695ae6d2c4632e6ed80526 100644 (file)
@@ -4,7 +4,7 @@ file_types='@(3fr|ani|arw|avif|bmp|cr2|cr3|crw|cur|dds|djvu|dng|erf|exr|.fits|fi
 
 actions='About AddMark0 AddMark1 AddMark2 AddMark3 AddMark4 AddMark5 AddMark6 AddMark7 AddMark8 AddMark9 AlterNone Animate Back ClearMarks CloseWindow ColorProfile0 ColorProfile1 ColorProfile2 ColorProfile3 ColorProfile4 ColorProfile5 ConnectZoom100 ConnectZoom200 ConnectZoom25 ConnectZoom300 ConnectZoom33 ConnectZoom400 ConnectZoom50 ConnectZoomFillHor ConnectZoomFillVert ConnectZoomFit ConnectZoomIn ConnectZoomOut Copy CopyImage CopyPath CopyPathUnquoted CropFourThree CropNone CropOneOne CropRectangle CropSixteenNine CropThreeTwo CutPath Delete DeleteWindow DrawRectangle Escape ExifRotate ExifWin FilterMark0 FilterMark1 FilterMark2 FilterMark3 FilterMark4 FilterMark5 FilterMark6 FilterMark7 FilterMark8 FilterMark9 FindDupes FirstImage FirstPage Flip FloatTools FolderTree Forward FullScreen Grayscale HelpChangeLog HelpContents HelpKbd HelpNotes HelpPdf HelpSearch HelpShortcuts HideBars HideSelectableToolbars HideTools HistogramChanB HistogramChanCycle HistogramChanG HistogramChanR HistogramChanRGB HistogramChanV HistogramModeCycle HistogramModeLin HistogramModeLog Home IgnoreAlpha ImageBack ImageForward ImageHistogram ImageOverlay ImageOverlayCycle IntMark0 IntMark1 IntMark2 IntMark3 IntMark4 IntMark5 IntMark6 IntMark7 IntMark8 IntMark9 KeywordAutocomplete LastImage LastPage LayoutConfig LogWindow Maintenance Mark0 Mark1 Mark2 Mark3 Mark4 Mark5 Mark6 Mark7 Mark8 Mark9 Mirror Move NewCollection NewFolder NewWindow NewWindowDefault NewWindowFromCurrent NextImage NextPage OpenArchive OpenCollection OpenFile OpenRecentFile OpenWith OverUnderExposed PanView PermanentDelete Plugins Preferences PrevImage PrevPage Print Quit Rating0 Rating1 Rating2 Rating3 Rating4 Rating5 RatingM1 RectangularSelection Refresh Rename RenameWindow ResetMark0 ResetMark1 ResetMark2 ResetMark3 ResetMark4 ResetMark5 ResetMark6 ResetMark7 ResetMark8 ResetMark9 Rotate180 RotateCCW RotateCW SBar SBarSort SaveMetadata Search SearchAndRunCommand SelectAll SelectInvert SelectMark0 SelectMark1 SelectMark2 SelectMark3 SelectMark4 SelectMark5 SelectMark6 SelectMark7 SelectMark8 SelectMark9 SelectNone SetMark0 SetMark1 SetMark2 SetMark3 SetMark4 SetMark5 SetMark6 SetMark7 SetMark8 SetMark9 ShowFileFilter ShowInfoPixel ShowMarks SlideShow SlideShowFaster SlideShowPause SlideShowSlower SplitDownPane SplitHorizontal SplitNextPane SplitPaneSync SplitPreviousPane SplitQuad SplitSingle SplitTriple SplitUpPane SplitVertical StereoAuto StereoCross StereoCycle StereoOff StereoSBS Thumbnails ToggleMark0 ToggleMark1 ToggleMark2 ToggleMark3 ToggleMark4 ToggleMark5 ToggleMark6 ToggleMark7 ToggleMark8 ToggleMark9 UnselMark0 UnselMark1 UnselMark2 UnselMark3 UnselMark4 UnselMark5 UnselMark6 UnselMark7 UnselMark8 UnselMark9 Up UseColorProfiles UseImageProfile ViewIcons ViewInNewWindow ViewList WriteRotation WriteRotationKeepDate Zoom100 Zoom200 Zoom25 Zoom300 Zoom33 Zoom400 Zoom50 ZoomFillHor ZoomFillVert ZoomFit ZoomIn ZoomOut ZoomToRectangle'
 
-options='--action= --action-list --back --cache-metadata --cache-render= --cache-render-recurse= --cache-render-shared= --cache-render-shared-recurse= --cache-shared= --cache-thumbs= --close-window --config-load= --debug= --delay= --file= --File= --file-extensions --first --fullscreen --geometry= --get-collection= --get-collection-list --get-destination= --get-file-info --get-filelist= --get-filelist-recurse= --get-rectangle --get-render-intent --get-selection --get-sidecars= --get-window-list --grep= --id= --last --log-file= --lua= --new-window --next --pixel-info --print0 --quit --raise --selection-add= --selection-clear --selection-remove= --show-log-window --slideshow --slideshow-recurse= --tell --tools --view= --version'
+options='--action= --action-list --back --cache-metadata --cache-render= --cache-render-recurse= --cache-render-shared= --cache-render-shared-recurse= --cache-shared= --cache-thumbs= --close-window --config-load= --debug= --delay= --dupes= --dupes-recurse= --file= --File= --file-extensions --first --fullscreen --geometry= --get-collection= --get-collection-list --get-destination= --get-file-info --get-filelist= --get-filelist-recurse= --get-rectangle --get-render-intent --get-selection --get-sidecars= --get-window-list --grep= --id= --last --log-file= --lua= --new-window --next --pixel-info --print0 --quit --raise --selection-add= --selection-clear --selection-remove= --show-log-window --slideshow --slideshow-recurse= --tell --tools --view= --version'
 
 _geeqie()
 {
index 7e6297449e70cefe31d18b1b14f8d44801953900..446fc53f2e48f4b47905cd16550e076849d86aa8 100644 (file)
@@ -7,8 +7,8 @@
 <refmeta>
 <refentrytitle>GEEQIE</refentrytitle>
 <manvolnum>1</manvolnum>
-<refmiscinfo class='source'>January 2025</refmiscinfo>
-<refmiscinfo class='manual'>Geeqie 2.5+git20250101-2ec35a8e GTK3</refmiscinfo>
+<refmiscinfo class='source'>April 2025</refmiscinfo>
+<refmiscinfo class='manual'>Geeqie 2.5+git20250407-604c9658 GTK3</refmiscinfo>
 </refmeta>
 <refnamediv>
 <refname>Geeqie</refname>
@@ -28,7 +28,7 @@ zooming, panning, thumbnails and sorting images into collections.</para>
 
 <para>Geeqie is an image viewer.</para>
 
-<para>Version: Geeqie 2.5+git20250101-2ec35a8e</para>
+<para>Version: Geeqie 2.5+git20250407-604c9658</para>
 </refsect2>
 
 <refsect2 id='help_options'><title>Help Options:</title>
@@ -144,6 +144,18 @@ zooming, panning, thumbnails and sorting images into collections.</para>
   <term><emphasis role='strong' remap='B'>-d</emphasis>, <emphasis role='strong' remap='B'>--delay=</emphasis>&lt;[H:][M:][N][.M]&gt;</term>
   <listitem>
 <para>set slide show delay to Hrs Mins N.M seconds,</para>
+  </listitem>
+  </varlistentry>
+  <varlistentry>
+  <term><emphasis role='strong' remap='B'>--dupes=</emphasis>&lt;FOLDER&gt;</term>
+  <listitem>
+<para>find duplicates in folder</para>
+  </listitem>
+  </varlistentry>
+  <varlistentry>
+  <term><emphasis role='strong' remap='B'>--dupes-recurse=</emphasis>&lt;FOLDER&gt;</term>
+  <listitem>
+<para>find duplicates in folder recursively</para>
   </listitem>
   </varlistentry>
   <varlistentry>
index 41206e6d4d826cbd573aa3080e3c931c4ba8af32..5a0fe3cd3a0b120251c314abc97cb8b60f63f27a 100644 (file)
--- a/geeqie.1
+++ b/geeqie.1
@@ -1,5 +1,5 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.3.
-.TH GEEQIE "1" "January 2025" "Geeqie 2.5+git20250101-2ec35a8e GTK3" "User Commands"
+.TH GEEQIE "1" "April 2025" "Geeqie 2.5+git20250407-604c9658 GTK3" "User Commands"
 .SH NAME
 Geeqie - GTK based multiformat image viewer
 .SH DESCRIPTION
@@ -13,7 +13,7 @@ geeqie [OPTION?] [path...]
 .PP
 Geeqie is an image viewer.
 .IP
-Version: Geeqie 2.5+git20250101\-2ec35a8e
+Version: Geeqie 2.5+git20250407\-604c9658
 .SS "Help Options:"
 .TP
 \fB\-h\fR, \fB\-\-help\fR
@@ -71,6 +71,12 @@ turn on debug output
 \fB\-d\fR, \fB\-\-delay=\fR<[H:][M:][N][.M]>
 set slide show delay to Hrs Mins N.M seconds,
 .TP
+\fB\-\-dupes=\fR<FOLDER>
+find duplicates in folder
+.TP
+\fB\-\-dupes\-recurse=\fR<FOLDER>
+find duplicates in folder recursively
+.TP
 \fB\-\-file=\fR<FILE>|<URL>
 open FILE or URL bring Geeqie window to the top
 .TP
index cfa5ba7423e5ea473524faaf143981d740e7033e..4c6ddf686016d879a7c342068b3e797b4b456f0b 100644 (file)
@@ -28,6 +28,7 @@
 #include "collect.h"
 #include "compat-deprecated.h"
 #include "compat.h"
+#include "dupe.h"
 #include "exif.h"
 #include "filedata.h"
 #include "filefilter.h"
@@ -436,6 +437,42 @@ void file_load_no_raise(const gchar *text, GApplicationCommandLine *app_command_
                }
 }
 
+void gq_dupes(GtkApplication *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *)
+{
+       const gchar *path;
+       g_variant_dict_lookup(command_line_options_dict, "dupes", "&s", &path);
+
+       g_autofree gchar *folder_path = expand_tilde(path);
+       if (!isdir(folder_path))
+               {
+               g_autofree gchar *notification_message = g_strdup_printf("\"%s\"%s", folder_path, _(" is not a folder"));
+               cache_maintenance_notification(app, notification_message, FALSE);
+               g_application_command_line_print(app_command_line, "%s\n", notification_message);
+
+               exit(EXIT_FAILURE);
+               }
+
+       dupe_window_add_folder(folder_path);
+}
+
+void gq_dupes_recurse(GtkApplication *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *)
+{
+       const gchar *path;
+       g_variant_dict_lookup(command_line_options_dict, "dupes-recurse", "&s", &path);
+
+       g_autofree gchar *folder_path = expand_tilde(path);
+       if (!isdir(folder_path))
+               {
+               g_autofree gchar *notification_message = g_strdup_printf("\"%s\"%s", folder_path, _(" is not a folder"));
+               cache_maintenance_notification(app, notification_message, FALSE);
+               g_application_command_line_print(app_command_line, "%s\n", notification_message);
+
+               exit(EXIT_FAILURE);
+               }
+
+       dupe_window_add_folder_recurse(folder_path);
+}
+
 void gq_file(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *)
 {
 
@@ -1411,6 +1448,8 @@ CommandLineOptionEntry command_line_options[] =
 #endif
        { "delay",                       gq_delay,                       PRIMARY_REMOTE, GUI  },
        { "file",                        gq_file,                        PRIMARY_REMOTE, GUI  },
+       { "dupes",                       gq_dupes,                       PRIMARY_REMOTE, GUI  },
+       { "dupes-recurse",               gq_dupes_recurse,               PRIMARY_REMOTE, GUI  },
        { "File",                        gq_File,                        PRIMARY_REMOTE, GUI  },
        { "file-extensions",             gq_file_extensions,             PRIMARY_REMOTE, TEXT },
        { "first",                       gq_first,                       PRIMARY_REMOTE, GUI  },
index a8394d9477c22041870b76bb98206542957c25e7..76b9e550a2cf60fce2d3ab06349252cbcf63207c 100644 (file)
@@ -1468,7 +1468,7 @@ static gboolean dupe_match(DupeItem *a, DupeItem *b, DupeMatchType mask, gdouble
                        return FALSE;
                        }
                return FALSE;
-               
+
                }
        if (mask & DUPE_MATCH_SIZE)
                {
@@ -2916,6 +2916,34 @@ void dupe_window_add_files(DupeWindow *dw, GList *list, gboolean recurse)
                }
 }
 
+void dupe_window_add_folder(const gchar *path)
+{
+       DupeWindow *dw = dupe_window_new();
+       FileData *fd = file_data_new_simple(path);
+       g_autoptr(GList) list = nullptr;
+       list = g_list_append(list, fd);
+
+       dupe_window_add_files(dw, list, FALSE);
+
+       dupe_check_start(dw);
+
+       file_data_unref(fd);
+}
+
+void dupe_window_add_folder_recurse(const gchar *path)
+{
+       DupeWindow *dw = dupe_window_new();
+       FileData *fd = file_data_new_simple(path);
+       g_autoptr(GList) list = nullptr;
+       list = g_list_append(list, fd);
+
+       dupe_window_add_files(dw, list, TRUE);
+
+       dupe_check_start(dw);
+
+       file_data_unref(fd);
+}
+
 static void dupe_item_update(DupeWindow *dw, DupeItem *di)
 {
        if ( (dw->match_mask & DUPE_MATCH_NAME) || (dw->match_mask & DUPE_MATCH_PATH || (dw->match_mask & DUPE_MATCH_NAME_CI)) )
index 9e83b7c3770f51bae3d860a50e3cbfa3c1e75441..529c821c82e04cca98ac1b6184e1760dfc5ab7f0 100644 (file)
@@ -164,6 +164,7 @@ void dupe_window_close(DupeWindow *dw);
 
 void dupe_window_add_collection(DupeWindow *dw, CollectionData *collection);
 void dupe_window_add_files(DupeWindow *dw, GList *list, gboolean recurse);
-
+void dupe_window_add_folder(const gchar *path);
+void dupe_window_add_folder_recurse(const gchar *path);
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index 9b548a64c754293a8f5473b5ceaeda380ec927d9..160ca9d8492a618ef1b92f1ca7f1039eb5bc93e7 100644 (file)
@@ -150,6 +150,8 @@ GOptionEntry command_line_options[] =
        { "debug"                     ,   0, G_OPTION_FLAG_NONE, G_OPTION_ARG_INT   , nullptr, _("turn on debug output")                                                        , "[level]" },
 #endif
        { "delay"                     , 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, nullptr, _("set slide show delay to Hrs Mins N.M seconds,")                               , "<[H:][M:][N][.M]>" },
+       { "dupes"                     ,   0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, nullptr, _("find duplicates in folder")                                                   , "<FOLDER>" },
+       { "dupes-recurse"             ,   0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, nullptr, _("find duplicates in folder recursively")                                       , "<FOLDER>" },
        { "file"                      ,   0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, nullptr, _("open FILE or URL bring Geeqie window to the top")                             , "<FILE>|<URL>" },
        { "File"                      ,   0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, nullptr, _("open FILE or URL do not bring Geeqie window to the top")                      , "<FILE>|<URL>" },
        { "file-extensions"           ,   0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE  , nullptr, _("list known file extensions")                                                  , nullptr },