Compare commits

..

No commits in common. "6a03e1e3998252939ab8a1056dc5a46bc5f97d45" and "6a2c1eb293553657d08dbb83bbc2938b828115c3" have entirely different histories.

9 changed files with 57 additions and 127 deletions

View File

@ -24,8 +24,8 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- OS: [e.g. Windows 10 / OSX 10.15 / Ubuntu 20.04 / Arch Linux] - OS: [e.g. Windows 10 / OSX 10.15 / Ubuntu 20.04]
- Version [e.g. 4.1.0] - Version [e.g. 4.0.4]
**Additional context** **Additional context**
Add any other context about the problem here. You may include the debug log although it is normally best to attach it as a file. Add any other context about the problem here. You may include the debug log although it is normally best to attach it as a file.

View File

@ -1,21 +1,19 @@
# dupeGuru # dupeGuru
[dupeGuru][dupeguru] is a cross-platform (Linux, OS X, Windows) GUI tool to find duplicate files in [dupeGuru][dupeguru] is a cross-platform (Linux, OS X, Windows) GUI tool to find duplicate files in
a system. It is written mostly in Python 3 and has the peculiarity of using a system. It's written mostly in Python 3 and has the peculiarity of using
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer [multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
is written in Objective-C and uses Cocoa. On Linux, it is written in Python and uses Qt5. is written in Objective-C and uses Cocoa. On Linux, it's written in Python and uses Qt5.
The Cocoa UI of dupeGuru is hosted in a separate repo: https://github.com/arsenetar/dupeguru-cocoa The Cocoa UI of dupeGuru is hosted in a separate repo: https://github.com/hsoft/dupeguru-cocoa
## Current status ## Current status
2020: various bug fixes and small UI improvements have been added. Packaging for MacOS is still a problem. Development has been slow this past year, however very close to getting all the different 4.0.4 releases posted. Most of the work this past year (2019) has been towards packaging the application and issues related to that.
Still looking for additional help especially with regards to: Still looking for additional help especially with regards to:
* OSX maintenance: reproducing bugs & cocoa version, building package with Cocoa UI. - OSX maintenance (reproducing bugs & cocoa version)
* Linux maintenance: reproducing bugs, maintaining PPA repository, Debian package. - Linux maintenance (reproducing bugs)
* Translations: updating missing strings.
* Documentation: keeping it up-to-date.
## Contents of this folder ## Contents of this folder
@ -33,44 +31,26 @@ This folder contains the source for dupeGuru. Its documentation is in `help`, bu
## How to build dupeGuru from source ## How to build dupeGuru from source
### Windows & macOS specific additional instructions ### Windows
For windows instructions see the [Windows Instructions](Windows.md). For windows instructions see the [Windows Instructions](Windows.md).
For macos instructions (qt version) see the [macOS Instructions](macos.md).
### Prerequisites ### Prerequisites
* [Python 3.6+][python]
* [Python 3.5+][python]
* PyQt5 * PyQt5
### Building with Make ### make
dupeGuru comes with a makefile that can be used to build and run:
$ make && make run dupeGuru is built with "make":
### Building without Make $ make
$ make run
$ cd <dupeGuru directory> ### Generate Debian/Ubuntu package
$ python3 -m venv --system-site-packages .\env
$ source .\env\bin\activate
$ pip install -r requirements.txt
$ python build.py
$ python run.py
### Generating Debian/Ubuntu package $ bash -c "python3 -m venv --system-site-packages env && source env/bin/activate && pip install -r requirements.txt && python3 build.py --clean && python3 package.py"
To generate packages the extra requirements in requirements-extra.txt must be installed, the
steps are as follows:
$ cd <dupeGuru directory> ### Running tests
$ python3 -m venv --system-site-packages .\env
$ source .\env\bin\activate
$ pip install -r requirements.txt -r requirements-extra.txt
$ python build.py --clean
$ python package.py
This can be made a one-liner (once in the directory) as:
$ bash -c "python3 -m venv --system-site-packages env && source env/bin/activate && pip install -r requirements.txt -r requirements-extra.txt && python build.py --clean && python package.py"
## Running tests
The complete test suite is run with [Tox 1.7+][tox]. If you have it installed system-wide, you The complete test suite is run with [Tox 1.7+][tox]. If you have it installed system-wide, you
don't even need to set up a virtualenv. Just `cd` into the root project folder and run `tox`. don't even need to set up a virtualenv. Just `cd` into the root project folder and run `tox`.

View File

@ -1,2 +1,2 @@
__version__ = "4.1.0" __version__ = "4.0.4"
__appname__ = "dupeGuru" __appname__ = "dupeGuru"

View File

@ -55,8 +55,7 @@ class ExcludeListDialogCore:
"""Sets property on row to highlight if its regex matches test_string supplied.""" """Sets property on row to highlight if its regex matches test_string supplied."""
matched = False matched = False
for row in self.exclude_list_table.rows: for row in self.exclude_list_table.rows:
compiled_regex = self.exclude_list.get_compiled(row.regex) if self.exclude_list.get_compiled(row.regex).match(test_string):
if compiled_regex and compiled_regex.match(test_string):
matched = True matched = True
row.highlight = True row.highlight = True
else: else:

View File

@ -1,29 +1,3 @@
=== 4.1.0 (2020-12-29)
* Use tabs instead of separate windows (#688)
* Show the shortcut for "mark selected" in results dialog (#656, #641)
* Add image comparison features to details dialog (#683)
* Add the ability to use regex based exclusion filters (#705)
* Change reference row background color, and allow user to adjust the color (#701)
* Save / Load directories as XML (#706)
* Workaround for EXIF IFD type mismatch in parsing function (#630, #698)
* Progress dialog stuck at "Verified X/X matches" (#693, #694)
* Fix word wrap in ignore list dialog (#687)
* Fix issue with result window action on creation (#685)
* Colorize details table differences, allow moving rows (#682)
* Fix loading Result of 'Scan Type: Folders' shows only '---' in every table cell (#677, #676)
* Fix issue with details and results dialog row trimming (#655, #654)
* Add option to enable/disable bold font (#646, #314)
* Use relative icon path for themes to override more easily (#746)
* Fix issues with Python 3.8 compatibility (#665)
* Fix flake8 issues (#672)
* Update to use newer pytest and expand flake8 checking, cleanup various Deprecation Warnings
* Add warnings to packaging script when files are not built (#691)
* Use relative icon path for themes to override more easily (#746)
* Update Packaging for Ubuntu (#593)
* Minor Build Updates (#627, #575, #628, #614)
* Update CI builds and add windows CI (#572, #669)
=== 4.0.4 (2019-05-13) === 4.0.4 (2019-05-13)
* Update qt/platform.py to support other Unix style OSes (#444) * Update qt/platform.py to support other Unix style OSes (#444)

View File

@ -65,25 +65,20 @@ class DupeGuru(QObject):
self.recentResults.mustOpenItem.connect(self.model.load_from) self.recentResults.mustOpenItem.connect(self.model.load_from)
self.resultWindow = None self.resultWindow = None
if self.use_tabs: if self.use_tabs:
self.main_window = ( self.main_window = TabBarWindow(self) if not self.prefs.tabs_default_pos else TabWindow(self)
TabBarWindow(self)
if not self.prefs.tabs_default_pos
else TabWindow(self)
)
parent_window = self.main_window parent_window = self.main_window
self.directories_dialog = self.main_window.createPage( self.directories_dialog = self.main_window.createPage("DirectoriesDialog", app=self)
"DirectoriesDialog", app=self
)
self.main_window.addTab( self.main_window.addTab(
self.directories_dialog, "Directories", switch=False self.directories_dialog, "Directories", switch=False)
)
self.actionDirectoriesWindow.setEnabled(False) self.actionDirectoriesWindow.setEnabled(False)
else: # floating windows only else: # floating windows only
self.main_window = None self.main_window = None
self.directories_dialog = DirectoriesDialog(self) self.directories_dialog = DirectoriesDialog(self)
parent_window = self.directories_dialog parent_window = self.directories_dialog
self.progress_window = ProgressWindow(parent_window, self.model.progress_window) self.progress_window = ProgressWindow(
parent_window, self.model.progress_window
)
self.problemDialog = ProblemDialog( self.problemDialog = ProblemDialog(
parent=parent_window, model=self.model.problem_dialog parent=parent_window, model=self.model.problem_dialog
) )
@ -91,25 +86,22 @@ class DupeGuru(QObject):
self.ignoreListDialog = self.main_window.createPage( self.ignoreListDialog = self.main_window.createPage(
"IgnoreListDialog", "IgnoreListDialog",
parent=self.main_window, parent=self.main_window,
model=self.model.ignore_list_dialog, model=self.model.ignore_list_dialog)
)
self.excludeListDialog = self.main_window.createPage( self.excludeListDialog = self.main_window.createPage(
"ExcludeListDialog", "ExcludeListDialog",
app=self, app=self,
parent=self.main_window, parent=self.main_window,
model=self.model.exclude_list_dialog, model=self.model.exclude_list_dialog)
)
else: else:
self.ignoreListDialog = IgnoreListDialog( self.ignoreListDialog = IgnoreListDialog(
parent=parent_window, model=self.model.ignore_list_dialog parent=parent_window, model=self.model.ignore_list_dialog)
)
self.excludeDialog = ExcludeListDialog( self.excludeDialog = ExcludeListDialog(
app=self, parent=parent_window, model=self.model.exclude_list_dialog app=self, parent=parent_window, model=self.model.exclude_list_dialog)
)
self.deletionOptions = DeletionOptions( self.deletionOptions = DeletionOptions(
parent=parent_window, model=self.model.deletion_options parent=parent_window,
model=self.model.deletion_options
) )
self.about_box = AboutBox(parent_window, self) self.about_box = AboutBox(parent_window, self)
@ -137,13 +129,7 @@ class DupeGuru(QObject):
self.preferencesTriggered, self.preferencesTriggered,
), ),
("actionIgnoreList", "", "", tr("Ignore List"), self.ignoreListTriggered), ("actionIgnoreList", "", "", tr("Ignore List"), self.ignoreListTriggered),
( ("actionDirectoriesWindow", "", "", tr("Directories"), self.showDirectoriesWindow),
"actionDirectoriesWindow",
"",
"",
tr("Directories"),
self.showDirectoriesWindow,
),
( (
"actionClearPictureCache", "actionClearPictureCache",
"Ctrl+Shift+P", "Ctrl+Shift+P",
@ -151,13 +137,7 @@ class DupeGuru(QObject):
tr("Clear Picture Cache"), tr("Clear Picture Cache"),
self.clearPictureCacheTriggered, self.clearPictureCacheTriggered,
), ),
( ("actionExcludeList", "", "", tr("Exclusion Filters"), self.excludeListTriggered),
"actionExcludeList",
"",
"",
tr("Exclusion Filters"),
self.excludeListTriggered,
),
("actionShowHelp", "F1", "", tr("dupeGuru Help"), self.showHelpTriggered), ("actionShowHelp", "F1", "", tr("dupeGuru Help"), self.showHelpTriggered),
("actionAbout", "", "", tr("About dupeGuru"), self.showAboutBoxTriggered), ("actionAbout", "", "", tr("About dupeGuru"), self.showAboutBoxTriggered),
( (
@ -252,7 +232,8 @@ class DupeGuru(QObject):
if self.resultWindow is not None: if self.resultWindow is not None:
if self.use_tabs: if self.use_tabs:
if self.main_window.indexOfWidget(self.resultWindow) < 0: if self.main_window.indexOfWidget(self.resultWindow) < 0:
self.main_window.addTab(self.resultWindow, "Results", switch=True) self.main_window.addTab(
self.resultWindow, "Results", switch=True)
return return
self.main_window.showTab(self.resultWindow) self.main_window.showTab(self.resultWindow)
else: else:
@ -284,11 +265,9 @@ class DupeGuru(QObject):
"scanning have accented letters, you'll probably get a crash. It is advised that " "scanning have accented letters, you'll probably get a crash. It is advised that "
"you set your system locale properly." "you set your system locale properly."
) )
QMessageBox.warning( QMessageBox.warning(self.main_window if self.main_window
self.main_window if self.main_window else self.directories_dialog, else self.directories_dialog,
"Wrong Locale", "Wrong Locale", msg)
msg,
)
def clearPictureCacheTriggered(self): def clearPictureCacheTriggered(self):
title = tr("Clear Picture Cache") title = tr("Clear Picture Cache")
@ -314,9 +293,7 @@ class DupeGuru(QObject):
"""Add tab for dialog, name the tab with desc_string, then show it.""" """Add tab for dialog, name the tab with desc_string, then show it."""
index = self.main_window.indexOfWidget(dialog) index = self.main_window.indexOfWidget(dialog)
# Create the tab if it doesn't exist already # Create the tab if it doesn't exist already
if ( if index < 0: # or (not dialog.isVisible() and not self.main_window.isTabVisible(index)):
index < 0
): # or (not dialog.isVisible() and not self.main_window.isTabVisible(index)):
index = self.main_window.addTab(dialog, desc_string, switch=True) index = self.main_window.addTab(dialog, desc_string, switch=True)
# Show the tab for that widget # Show the tab for that widget
self.main_window.setCurrentIndex(index) self.main_window.setCurrentIndex(index)
@ -327,7 +304,8 @@ class DupeGuru(QObject):
def preferencesTriggered(self): def preferencesTriggered(self):
preferences_dialog = self._get_preferences_dialog_class()( preferences_dialog = self._get_preferences_dialog_class()(
self.main_window if self.main_window else self.directories_dialog, self self.main_window if self.main_window else self.directories_dialog,
self
) )
preferences_dialog.load() preferences_dialog.load()
result = preferences_dialog.exec() result = preferences_dialog.exec()
@ -355,7 +333,7 @@ class DupeGuru(QObject):
if op.exists(help_path): if op.exists(help_path):
url = QUrl.fromLocalFile(help_path) url = QUrl.fromLocalFile(help_path)
else: else:
url = QUrl("https://dupeguru.voltaicideas.net/help/en/") url = QUrl("https://www.hardcoded.net/dupeguru/help/en/")
QDesktopServices.openUrl(url) QDesktopServices.openUrl(url)
def handleSIGTERM(self): def handleSIGTERM(self):
@ -376,7 +354,8 @@ class DupeGuru(QObject):
return self.confirm("", prompt) return self.confirm("", prompt)
def create_results_window(self): def create_results_window(self):
"""Creates resultWindow and details_dialog depending on the selected ``app_mode``.""" """Creates resultWindow and details_dialog depending on the selected ``app_mode``.
"""
if self.details_dialog is not None: if self.details_dialog is not None:
# The object is not deleted entirely, avoid saving its geometry in the future # The object is not deleted entirely, avoid saving its geometry in the future
# self.willSavePrefs.disconnect(self.details_dialog.appWillSavePrefs) # self.willSavePrefs.disconnect(self.details_dialog.appWillSavePrefs)
@ -388,13 +367,10 @@ class DupeGuru(QObject):
if self.resultWindow is not None: if self.resultWindow is not None:
self.resultWindow.close() self.resultWindow.close()
# This is better for tabs, as it takes care of duplicate items in menu bar # This is better for tabs, as it takes care of duplicate items in menu bar
self.resultWindow.deleteLater() if self.use_tabs else self.resultWindow.setParent( self.resultWindow.deleteLater() if self.use_tabs else self.resultWindow.setParent(None)
None
)
if self.use_tabs: if self.use_tabs:
self.resultWindow = self.main_window.createPage( self.resultWindow = self.main_window.createPage(
"ResultWindow", parent=self.main_window, app=self "ResultWindow", parent=self.main_window, app=self)
)
else: # We don't use a tab widget, regular floating QMainWindow else: # We don't use a tab widget, regular floating QMainWindow
self.resultWindow = ResultWindow(self.directories_dialog, self) self.resultWindow = ResultWindow(self.directories_dialog, self)
self.directories_dialog._updateActionsState() self.directories_dialog._updateActionsState()

View File

@ -118,7 +118,9 @@ class ExcludeListDialog(QDialog):
return return
# if at least one row matched, we know whether table is highlighted or not # if at least one row matched, we know whether table is highlighted or not
self._row_matched = self.model.test_string(input_text) self._row_matched = self.model.test_string(input_text)
self.table.refresh() # FIXME There is a bug on Windows (7) where the table rows don't get
# repainted until the table receives a mouse click event.
self.tableView.update()
input_regex = self.inputLine.text() input_regex = self.inputLine.text()
if not input_regex: if not input_regex:
@ -146,7 +148,7 @@ class ExcludeListDialog(QDialog):
if self._row_matched: if self._row_matched:
self._row_matched = False self._row_matched = False
self.model.reset_rows_highlight() self.model.reset_rows_highlight()
self.table.refresh() self.tableView.update()
def display_help_message(self): def display_help_message(self):
self.app.show_message(tr("""\ self.app.show_message(tr("""\

View File

@ -58,10 +58,9 @@ class ErrorReportDialog(QDialog):
self.verticalLayout.addWidget(self.errorTextEdit) self.verticalLayout.addWidget(self.errorTextEdit)
msg = tr( msg = tr(
"Error reports should be reported as Github issues. You can copy the error traceback " "Error reports should be reported as Github issues. You can copy the error traceback "
"above and paste it in a new issue.\n\nPlease make sure to run a search for any already " "above and paste it in a new issue (bonus point if you run a search to make sure the "
"existing issues beforehand. Also make sure to test the very latest version available from the repository, " "issue doesn't already exist). What usually really helps is if you add a description "
"since the bug you are experiencing might have already been patched.\n\n" "of how you got the error. Thanks!"
"What usually really helps is if you add a description of how you got the error. Thanks!"
"\n\n" "\n\n"
"Although the application should continue to run after this error, it may be in an " "Although the application should continue to run after this error, it may be in an "
"unstable state, so it is recommended that you restart the application." "unstable state, so it is recommended that you restart the application."

View File

@ -48,9 +48,9 @@ SetCompressor /SOLID lzma
!define APPLICENSE "LICENSE" ; License is not in build directory !define APPLICENSE "LICENSE" ; License is not in build directory
!define APPICON "images\dgse_logo.ico" ; nor is the icon !define APPICON "images\dgse_logo.ico" ; nor is the icon
!define DISTDIR "dist" !define DISTDIR "dist"
!define HELPURL "https://github.com/arsenetar/dupeguru/issues" !define HELPURL "http://www.hardcoded.net/support/"
!define UPDATEURL "https://dupeguru.voltaicideas.net/" !define UPDATEURL "http://www.hardcoded.net/dupeguru/"
!define ABOUTURL "https://dupeguru.voltaicideas.net/" !define ABOUTURL "http://www.hardcoded.net/dupeguru/"
; Static Defines ; Static Defines
!define UNINSTALLREGBASE "Software\Microsoft\Windows\CurrentVersion\Uninstall" !define UNINSTALLREGBASE "Software\Microsoft\Windows\CurrentVersion\Uninstall"