1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2025-05-08 17:59:50 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
5a4958cff9
Update translation .pot files 2021-08-17 21:18:47 -05:00
be10b462fc
Add portable mode
If settings.ini is present next to the executable, will run in portable mode.
This results in settings, data, and cache all being in same folder as dupeGuru.
2021-08-17 21:12:32 -05:00
d62b13bcdb
Removing travis
- All CI is now covered by Github Actions
- Remove .travis.yml
- Remove tox-travis in requirements-extra.txt
2021-08-17 18:16:20 -05:00
13 changed files with 113 additions and 107 deletions

View File

@ -1,27 +0,0 @@
sudo: false
language: python
install:
- pip3 install -r requirements.txt -r requirements-extra.txt
script: tox
matrix:
include:
- os: "linux"
dist: "xenial"
python: "3.6"
- os: "linux"
dist: "xenial"
python: "3.7"
- os: "linux"
dist: "focal"
python: "3.8"
- os: "linux"
dist: "focal"
python: "3.9"
- os: "windows"
language: shell
python: "3.9"
env: "PATH=/c/python39:/c/python39/Scripts:$PATH"
before_install:
- choco install python --version=3.9.6
- cp /c/python39/python.exe /c/python39/python3.exe
script: tox -e py39

View File

@ -126,13 +126,13 @@ class DupeGuru(Broadcaster):
PICTURE_CACHE_TYPE = "sqlite" # set to 'shelve' for a ShelveCache PICTURE_CACHE_TYPE = "sqlite" # set to 'shelve' for a ShelveCache
def __init__(self, view): def __init__(self, view, portable=False):
if view.get_default(DEBUG_MODE_PREFERENCE): if view.get_default(DEBUG_MODE_PREFERENCE):
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
logging.debug("Debug mode enabled") logging.debug("Debug mode enabled")
Broadcaster.__init__(self) Broadcaster.__init__(self)
self.view = view self.view = view
self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME) self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME, portable=portable)
if not op.exists(self.appdata): if not op.exists(self.appdata):
os.makedirs(self.appdata) os.makedirs(self.appdata)
self.app_mode = AppMode.Standard self.app_mode = AppMode.Standard

View File

@ -5,6 +5,8 @@
# http://www.gnu.org/licenses/gpl-3.0.html # http://www.gnu.org/licenses/gpl-3.0.html
import time import time
import sys
import os
from hscommon.util import format_time_decimal from hscommon.util import format_time_decimal
@ -58,3 +60,7 @@ def fix_surrogate_encoding(s, encoding="utf-8"):
return s.encode(encoding, "replace").decode(encoding) return s.encode(encoding, "replace").decode(encoding)
else: else:
return s return s
def executable_folder():
return os.path.dirname(os.path.abspath(sys.argv[0]))

View File

@ -30,7 +30,7 @@ def reveal_path(path):
_reveal_path(str(path)) _reveal_path(str(path))
def special_folder_path(special_folder, appname=None): def special_folder_path(special_folder, appname=None, portable=False):
"""Returns the path of ``special_folder``. """Returns the path of ``special_folder``.
``special_folder`` is a SpecialFolder.* const. The result is the special folder for the current ``special_folder`` is a SpecialFolder.* const. The result is the special folder for the current
@ -38,7 +38,7 @@ def special_folder_path(special_folder, appname=None):
You can override the application name with ``appname``. This argument is ingored under Qt. You can override the application name with ``appname``. This argument is ingored under Qt.
""" """
return _special_folder_path(special_folder, appname) return _special_folder_path(special_folder, appname, portable=portable)
try: try:
@ -54,7 +54,7 @@ try:
_open_path = proxy.openPath_ _open_path = proxy.openPath_
_reveal_path = proxy.revealPath_ _reveal_path = proxy.revealPath_
def _special_folder_path(special_folder, appname=None): def _special_folder_path(special_folder, appname=None, portable=False):
if special_folder == SpecialFolder.Cache: if special_folder == SpecialFolder.Cache:
base = proxy.getCachePath() base = proxy.getCachePath()
else: else:
@ -68,6 +68,9 @@ except ImportError:
try: try:
from PyQt5.QtCore import QUrl, QStandardPaths from PyQt5.QtCore import QUrl, QStandardPaths
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from qtlib.util import getAppData
from core.util import executable_folder
from hscommon.plat import ISWINDOWS
def _open_url(url): def _open_url(url):
QDesktopServices.openUrl(QUrl(url)) QDesktopServices.openUrl(QUrl(url))
@ -79,12 +82,15 @@ except ImportError:
def _reveal_path(path): def _reveal_path(path):
_open_path(op.dirname(str(path))) _open_path(op.dirname(str(path)))
def _special_folder_path(special_folder, appname=None): def _special_folder_path(special_folder, appname=None, portable=False):
if special_folder == SpecialFolder.Cache: if special_folder == SpecialFolder.Cache:
qtfolder = QStandardPaths.CacheLocation if ISWINDOWS and portable:
folder = op.join(executable_folder(), "cache")
else: else:
qtfolder = QStandardPaths.DataLocation folder = QStandardPaths.standardLocations(QStandardPaths.CacheLocation)[0]
return QStandardPaths.standardLocations(qtfolder)[0] else:
folder = getAppData(portable)
return folder
except ImportError: except ImportError:
# We're either running tests, and these functions don't matter much or we're in a really # We're either running tests, and these functions don't matter much or we're in a really
@ -97,5 +103,5 @@ except ImportError:
def _reveal_path(path): def _reveal_path(path):
pass pass
def _special_folder_path(special_folder, appname=None): def _special_folder_path(special_folder, appname=None, portable=False):
return "/tmp" return "/tmp"

View File

@ -36,95 +36,95 @@ msgstr ""
msgid "Sending to Trash" msgid "Sending to Trash"
msgstr "" msgstr ""
#: core\app.py:308 #: core\app.py:290
msgid "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." msgid "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
msgstr "" msgstr ""
#: core\app.py:318 #: core\app.py:300
msgid "No duplicates found." msgid "No duplicates found."
msgstr "" msgstr ""
#: core\app.py:333 #: core\app.py:315
msgid "All marked files were copied successfully." msgid "All marked files were copied successfully."
msgstr "" msgstr ""
#: core\app.py:334 #: core\app.py:316
msgid "All marked files were moved successfully." msgid "All marked files were moved successfully."
msgstr "" msgstr ""
#: core\app.py:335 #: core\app.py:317
msgid "All marked files were successfully sent to Trash." msgid "All marked files were successfully sent to Trash."
msgstr "" msgstr ""
#: core\app.py:343 #: core\app.py:323
msgid "Could not load file: {}" msgid "Could not load file: {}"
msgstr "" msgstr ""
#: core\app.py:399 #: core\app.py:379
msgid "'{}' already is in the list." msgid "'{}' already is in the list."
msgstr "" msgstr ""
#: core\app.py:401 #: core\app.py:381
msgid "'{}' does not exist." msgid "'{}' does not exist."
msgstr "" msgstr ""
#: core\app.py:410 #: core\app.py:389
msgid "All selected %d matches are going to be ignored in all subsequent scans. Continue?" msgid "All selected %d matches are going to be ignored in all subsequent scans. Continue?"
msgstr "" msgstr ""
#: core\app.py:486 #: core\app.py:463
msgid "Select a directory to copy marked files to" msgid "Select a directory to copy marked files to"
msgstr "" msgstr ""
#: core\app.py:487 #: core\app.py:465
msgid "Select a directory to move marked files to" msgid "Select a directory to move marked files to"
msgstr "" msgstr ""
#: core\app.py:527 #: core\app.py:504
msgid "Select a destination for your exported CSV" msgid "Select a destination for your exported CSV"
msgstr "" msgstr ""
#: core\app.py:534 core\app.py:803 core\app.py:813 #: core\app.py:510 core\app.py:764 core\app.py:774
msgid "Couldn't write to file: {}" msgid "Couldn't write to file: {}"
msgstr "" msgstr ""
#: core\app.py:559 #: core\app.py:533
msgid "You have no custom command set up. Set it up in your preferences." msgid "You have no custom command set up. Set it up in your preferences."
msgstr "" msgstr ""
#: core\app.py:727 core\app.py:740 #: core\app.py:691 core\app.py:703
msgid "You are about to remove %d files from results. Continue?" msgid "You are about to remove %d files from results. Continue?"
msgstr "" msgstr ""
#: core\app.py:776 #: core\app.py:739
msgid "{} duplicate groups were changed by the re-prioritization." msgid "{} duplicate groups were changed by the re-prioritization."
msgstr "" msgstr ""
#: core\app.py:823 #: core\app.py:783
msgid "The selected directories contain no scannable file." msgid "The selected directories contain no scannable file."
msgstr "" msgstr ""
#: core\app.py:837 #: core\app.py:796
msgid "Collecting files to scan" msgid "Collecting files to scan"
msgstr "" msgstr ""
#: core\app.py:893 #: core\app.py:843
msgid "%s (%d discarded)" msgid "%s (%d discarded)"
msgstr "" msgstr ""
#: core\engine.py:255 core\engine.py:299 #: core\engine.py:251 core\engine.py:294
msgid "0 matches found" msgid "0 matches found"
msgstr "" msgstr ""
#: core\engine.py:273 core\engine.py:307 #: core\engine.py:269 core\engine.py:306
msgid "%d matches found" msgid "%d matches found"
msgstr "" msgstr ""
#: core\gui\deletion_options.py:73 #: core\gui\deletion_options.py:71
msgid "You are sending {} file(s) to the Trash." msgid "You are sending {} file(s) to the Trash."
msgstr "" msgstr ""
#: core\gui\exclude_list_table.py:15 #: core\gui\exclude_list_table.py:14
msgid "Regular Expressions" msgid "Regular Expressions"
msgstr "" msgstr ""
@ -156,15 +156,15 @@ msgstr ""
msgid "Analyzed %d/%d pictures" msgid "Analyzed %d/%d pictures"
msgstr "" msgstr ""
#: core\pe\matchblock.py:181 #: core\pe\matchblock.py:177
msgid "Performed %d/%d chunk matches" msgid "Performed %d/%d chunk matches"
msgstr "" msgstr ""
#: core\pe\matchblock.py:191 #: core\pe\matchblock.py:185
msgid "Preparing for matching" msgid "Preparing for matching"
msgstr "" msgstr ""
#: core\pe\matchblock.py:244 #: core\pe\matchblock.py:234
msgid "Verified %d/%d matches" msgid "Verified %d/%d matches"
msgstr "" msgstr ""
@ -212,11 +212,11 @@ msgstr ""
msgid "Oldest" msgid "Oldest"
msgstr "" msgstr ""
#: core\results.py:144 #: core\results.py:134
msgid "%d / %d (%s / %s) duplicates marked." msgid "%d / %d (%s / %s) duplicates marked."
msgstr "" msgstr ""
#: core\results.py:151 #: core\results.py:141
msgid " filter: %s" msgid " filter: %s"
msgstr "" msgstr ""

View File

@ -905,3 +905,11 @@ msgstr ""
#: qt\preferences_dialog.py:286 #: qt\preferences_dialog.py:286
msgid "Display" msgid "Display"
msgstr "" msgstr ""
#: qt\se\preferences_dialog.py:70
msgid "Partially hash files bigger than"
msgstr ""
#: qt\se\preferences_dialog.py:80
msgid "MB"
msgstr ""

View File

@ -52,7 +52,7 @@ class DupeGuru(QObject):
# Enable tabs instead of separate floating windows for each dialog # Enable tabs instead of separate floating windows for each dialog
# Could be passed as an argument to this class if we wanted # Could be passed as an argument to this class if we wanted
self.use_tabs = True self.use_tabs = True
self.model = DupeGuruModel(view=self) self.model = DupeGuruModel(view=self, portable=self.prefs.portable)
self._setup() self._setup()
# --- Private # --- Private

View File

@ -29,6 +29,7 @@ class Preferences(PreferencesBase):
self.language = get("Language", self.language) self.language = get("Language", self.language)
if not self.language and trans.installed_lang: if not self.language and trans.installed_lang:
self.language = trans.installed_lang self.language = trans.installed_lang
self.portable = get("Portable", False)
self.tableFontSize = get("TableFontSize", self.tableFontSize) self.tableFontSize = get("TableFontSize", self.tableFontSize)
self.reference_bold_font = get("ReferenceBoldFont", self.reference_bold_font) self.reference_bold_font = get("ReferenceBoldFont", self.reference_bold_font)
@ -138,6 +139,7 @@ class Preferences(PreferencesBase):
set_("DestinationType", self.destination_type) set_("DestinationType", self.destination_type)
set_("CustomCommand", self.custom_command) set_("CustomCommand", self.custom_command)
set_("Language", self.language) set_("Language", self.language)
set_("Portable", self.portable)
set_("TableFontSize", self.tableFontSize) set_("TableFontSize", self.tableFontSize)
set_("ReferenceBoldFont", self.reference_bold_font) set_("ReferenceBoldFont", self.reference_bold_font)

View File

@ -4,27 +4,27 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: utf-8\n" "Content-Transfer-Encoding: utf-8\n"
#: qtlib\about_box.py:43 #: qtlib\about_box.py:37
msgid "About {}" msgid "About {}"
msgstr "" msgstr ""
#: qtlib\about_box.py:65 #: qtlib\about_box.py:57
msgid "Version {}" msgid "Version {}"
msgstr "" msgstr ""
#: qtlib\about_box.py:70 #: qtlib\about_box.py:61
msgid "Licensed under GPLv3" msgid "Licensed under GPLv3"
msgstr "" msgstr ""
#: qtlib\error_report_dialog.py:49 #: qtlib\error_report_dialog.py:47
msgid "Error Report" msgid "Error Report"
msgstr "" msgstr ""
#: qtlib\error_report_dialog.py:53 #: qtlib\error_report_dialog.py:51
msgid "Something went wrong. How about reporting the error?" msgid "Something went wrong. How about reporting the error?"
msgstr "" msgstr ""
#: qtlib\error_report_dialog.py:59 #: qtlib\error_report_dialog.py:57
msgid "" msgid ""
"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue.\n" "Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue.\n"
"\n" "\n"
@ -35,83 +35,83 @@ msgid ""
"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." "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."
msgstr "" msgstr ""
#: qtlib\error_report_dialog.py:75 #: qtlib\error_report_dialog.py:73
msgid "Close" msgid "Close"
msgstr "" msgstr ""
#: qtlib\error_report_dialog.py:79 #: qtlib\error_report_dialog.py:77
msgid "Go to Github" msgid "Go to Github"
msgstr "" msgstr ""
#: qtlib\preferences.py:23 #: qtlib\preferences.py:24
msgid "Czech" msgid "Czech"
msgstr "" msgstr ""
#: qtlib\preferences.py:24 #: qtlib\preferences.py:25
msgid "German" msgid "German"
msgstr "" msgstr ""
#: qtlib\preferences.py:25 #: qtlib\preferences.py:26
msgid "Greek" msgid "Greek"
msgstr "" msgstr ""
#: qtlib\preferences.py:26 #: qtlib\preferences.py:27
msgid "English" msgid "English"
msgstr "" msgstr ""
#: qtlib\preferences.py:27 #: qtlib\preferences.py:28
msgid "Spanish" msgid "Spanish"
msgstr "" msgstr ""
#: qtlib\preferences.py:28 #: qtlib\preferences.py:29
msgid "French" msgid "French"
msgstr "" msgstr ""
#: qtlib\preferences.py:29 #: qtlib\preferences.py:30
msgid "Armenian" msgid "Armenian"
msgstr "" msgstr ""
#: qtlib\preferences.py:30 #: qtlib\preferences.py:31
msgid "Italian" msgid "Italian"
msgstr "" msgstr ""
#: qtlib\preferences.py:31 #: qtlib\preferences.py:32
msgid "Japanese" msgid "Japanese"
msgstr "" msgstr ""
#: qtlib\preferences.py:32 #: qtlib\preferences.py:33
msgid "Korean" msgid "Korean"
msgstr "" msgstr ""
#: qtlib\preferences.py:33 #: qtlib\preferences.py:34
msgid "Dutch" msgid "Dutch"
msgstr "" msgstr ""
#: qtlib\preferences.py:34 #: qtlib\preferences.py:35
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
#: qtlib\preferences.py:35 #: qtlib\preferences.py:36
msgid "Brazilian" msgid "Brazilian"
msgstr "" msgstr ""
#: qtlib\preferences.py:36 #: qtlib\preferences.py:37
msgid "Russian" msgid "Russian"
msgstr "" msgstr ""
#: qtlib\preferences.py:37 #: qtlib\preferences.py:38
msgid "Turkish" msgid "Turkish"
msgstr "" msgstr ""
#: qtlib\preferences.py:38 #: qtlib\preferences.py:39
msgid "Ukrainian" msgid "Ukrainian"
msgstr "" msgstr ""
#: qtlib\preferences.py:39 #: qtlib\preferences.py:40
msgid "Vietnamese" msgid "Vietnamese"
msgstr "" msgstr ""
#: qtlib\preferences.py:40 #: qtlib\preferences.py:41
msgid "Chinese (Simplified)" msgid "Chinese (Simplified)"
msgstr "" msgstr ""

View File

@ -12,6 +12,7 @@ from PyQt5.QtWidgets import QDockWidget
from hscommon.trans import trget from hscommon.trans import trget
from hscommon.util import tryint from hscommon.util import tryint
from hscommon.plat import ISWINDOWS from hscommon.plat import ISWINDOWS
from core.util import executable_folder
from os import path as op from os import path as op
@ -68,18 +69,25 @@ def adjust_after_deserialization(v):
return v return v
def createQSettings(): def create_qsettings():
# Create a QSettings instance with the correct arguments. # Create a QSettings instance with the correct arguments.
config_location = op.join(executable_folder(), "settings.ini")
if op.isfile(config_location):
settings = QSettings(config_location, QSettings.IniFormat)
settings.setValue("Portable", True)
elif ISWINDOWS:
# On windows use an ini file in the AppDataLocation instead of registry if possible as it # On windows use an ini file in the AppDataLocation instead of registry if possible as it
# makes it easier for a user to clear it out when there are issues. # makes it easier for a user to clear it out when there are issues.
if ISWINDOWS: locations = QStandardPaths.standardLocations(QStandardPaths.AppDataLocation)
Locations = QStandardPaths.standardLocations(QStandardPaths.AppDataLocation) if locations:
if Locations: settings = QSettings(op.join(locations[0], "settings.ini"), QSettings.IniFormat)
return QSettings(op.join(Locations[0], "settings.ini"), QSettings.IniFormat)
else: else:
return QSettings() settings = QSettings()
settings.setValue("Portable", False)
else: else:
return QSettings() settings = QSettings()
settings.setValue("Portable", False)
return settings
# About QRect conversion: # About QRect conversion:
@ -93,7 +101,7 @@ class Preferences(QObject):
def __init__(self): def __init__(self):
QObject.__init__(self) QObject.__init__(self)
self.reset() self.reset()
self._settings = createQSettings() self._settings = create_qsettings()
def _load_values(self, settings, get): def _load_values(self, settings, get):
pass pass

View File

@ -12,6 +12,7 @@ import os.path as op
import os import os
import logging import logging
from core.util import executable_folder
from hscommon.util import first from hscommon.util import first
from PyQt5.QtCore import QStandardPaths from PyQt5.QtCore import QStandardPaths
@ -88,8 +89,11 @@ def setAccelKeys(menu):
action.setText(newtext) action.setText(newtext)
def getAppData(): def getAppData(portable=False):
return QStandardPaths.standardLocations(QStandardPaths.DataLocation)[0] if portable:
return op.join(executable_folder(), "data")
else:
return QStandardPaths.standardLocations(QStandardPaths.AppDataLocation)[0]
class SysWrapper(io.IOBase): class SysWrapper(io.IOBase):

View File

@ -1,5 +1,4 @@
pytest>=6,<7 pytest>=6,<7
flake8 flake8
tox-travis
black black
pyinstaller>=4.5,<5.0; sys_platform != 'linux' pyinstaller>=4.5,<5.0; sys_platform != 'linux'

4
run.py
View File

@ -16,7 +16,7 @@ from PyQt5.QtWidgets import QApplication
from hscommon.trans import install_gettext_trans_under_qt from hscommon.trans import install_gettext_trans_under_qt
from qtlib.error_report_dialog import install_excepthook from qtlib.error_report_dialog import install_excepthook
from qtlib.util import setupQtLogging from qtlib.util import setupQtLogging
from qtlib.preferences import createQSettings from qtlib.preferences import create_qsettings
from qt import dg_rc # noqa: F401 from qt import dg_rc # noqa: F401
from qt.platform import BASE_PATH from qt.platform import BASE_PATH
from core import __version__, __appname__ from core import __version__, __appname__
@ -53,7 +53,7 @@ def main():
QCoreApplication.setApplicationName(__appname__) QCoreApplication.setApplicationName(__appname__)
QCoreApplication.setApplicationVersion(__version__) QCoreApplication.setApplicationVersion(__version__)
setupQtLogging() setupQtLogging()
settings = createQSettings() settings = create_qsettings()
lang = settings.value("Language") lang = settings.value("Language")
locale_folder = op.join(BASE_PATH, "locale") locale_folder = op.join(BASE_PATH, "locale")
install_gettext_trans_under_qt(locale_folder, lang) install_gettext_trans_under_qt(locale_folder, lang)