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.
This commit is contained in:
Andrew Senetar 2021-08-17 21:04:09 -05:00
parent d62b13bcdb
commit be10b462fc
Signed by: arsenetar
GPG Key ID: C63300DCE48AB2F1
8 changed files with 51 additions and 25 deletions

View File

@ -126,13 +126,13 @@ class DupeGuru(Broadcaster):
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):
logging.getLogger().setLevel(logging.DEBUG)
logging.debug("Debug mode enabled")
Broadcaster.__init__(self)
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):
os.makedirs(self.appdata)
self.app_mode = AppMode.Standard

View File

@ -5,6 +5,8 @@
# http://www.gnu.org/licenses/gpl-3.0.html
import time
import sys
import os
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)
else:
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))
def special_folder_path(special_folder, appname=None):
def special_folder_path(special_folder, appname=None, portable=False):
"""Returns the path of ``special_folder``.
``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.
"""
return _special_folder_path(special_folder, appname)
return _special_folder_path(special_folder, appname, portable=portable)
try:
@ -54,7 +54,7 @@ try:
_open_path = proxy.openPath_
_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:
base = proxy.getCachePath()
else:
@ -68,6 +68,9 @@ except ImportError:
try:
from PyQt5.QtCore import QUrl, QStandardPaths
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):
QDesktopServices.openUrl(QUrl(url))
@ -79,12 +82,15 @@ except ImportError:
def _reveal_path(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:
qtfolder = QStandardPaths.CacheLocation
if ISWINDOWS and portable:
folder = op.join(executable_folder(), "cache")
else:
folder = QStandardPaths.standardLocations(QStandardPaths.CacheLocation)[0]
else:
qtfolder = QStandardPaths.DataLocation
return QStandardPaths.standardLocations(qtfolder)[0]
folder = getAppData(portable)
return folder
except ImportError:
# 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):
pass
def _special_folder_path(special_folder, appname=None):
def _special_folder_path(special_folder, appname=None, portable=False):
return "/tmp"

View File

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

View File

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

View File

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

View File

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

4
run.py
View File

@ -16,7 +16,7 @@ from PyQt5.QtWidgets import QApplication
from hscommon.trans import install_gettext_trans_under_qt
from qtlib.error_report_dialog import install_excepthook
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.platform import BASE_PATH
from core import __version__, __appname__
@ -53,7 +53,7 @@ def main():
QCoreApplication.setApplicationName(__appname__)
QCoreApplication.setApplicationVersion(__version__)
setupQtLogging()
settings = createQSettings()
settings = create_qsettings()
lang = settings.value("Language")
locale_folder = op.join(BASE_PATH, "locale")
install_gettext_trans_under_qt(locale_folder, lang)