mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-05-07 17:29:50 +00:00
Next batch of qt6 upgrades
This commit is contained in:
parent
4d56bd3515
commit
ec35c2df8f
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -40,7 +40,7 @@ jobs:
|
|||||||
name: Build Cpp
|
name: Build Cpp
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install python3-pyqt5
|
sudo apt-get install python3-pyqt6
|
||||||
make modules
|
make modules
|
||||||
- if: matrix.language == 'python'
|
- if: matrix.language == 'python'
|
||||||
name: Autobuild
|
name: Autobuild
|
||||||
|
@ -555,7 +555,9 @@ class DupeGuru(Broadcaster):
|
|||||||
# a workaround to make the damn thing work.
|
# a workaround to make the damn thing work.
|
||||||
exepath, args = match.groups()
|
exepath, args = match.groups()
|
||||||
path, exename = op.split(exepath)
|
path, exename = op.split(exepath)
|
||||||
p = subprocess.Popen(exename + args, shell=True, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
p = subprocess.Popen(
|
||||||
|
exename + args, shell=True, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
output = p.stdout.read()
|
output = p.stdout.read()
|
||||||
logging.info("Custom command %s %s: %s", exename, args, output)
|
logging.info("Custom command %s %s: %s", exename, args, output)
|
||||||
else:
|
else:
|
||||||
|
@ -44,8 +44,8 @@ def special_folder_path(special_folder: SpecialFolder, portable: bool = False) -
|
|||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5.QtCore import QUrl, QStandardPaths
|
from PyQt6.QtCore import QUrl, QStandardPaths
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt6.QtGui import QDesktopServices
|
||||||
from qt.util import get_appdata
|
from qt.util import get_appdata
|
||||||
from core.util import executable_folder
|
from core.util import executable_folder
|
||||||
from hscommon.plat import ISWINDOWS, ISOSX
|
from hscommon.plat import ISWINDOWS, ISOSX
|
||||||
@ -71,7 +71,7 @@ try:
|
|||||||
if ISWINDOWS and portable:
|
if ISWINDOWS and portable:
|
||||||
folder = op.join(executable_folder(), "cache")
|
folder = op.join(executable_folder(), "cache")
|
||||||
else:
|
else:
|
||||||
folder = QStandardPaths.standardLocations(QStandardPaths.CacheLocation)[0]
|
folder = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.CacheLocation)[0]
|
||||||
else:
|
else:
|
||||||
folder = get_appdata(portable)
|
folder = get_appdata(portable)
|
||||||
return folder
|
return folder
|
||||||
|
@ -82,7 +82,7 @@ def get_locale_name(lang: str) -> Union[str, None]:
|
|||||||
|
|
||||||
# --- Qt
|
# --- Qt
|
||||||
def install_qt_trans(lang: str = None) -> None:
|
def install_qt_trans(lang: str = None) -> None:
|
||||||
from PyQt5.QtCore import QCoreApplication, QTranslator, QLocale
|
from PyQt6.QtCore import QCoreApplication, QTranslator, QLocale
|
||||||
|
|
||||||
if not lang:
|
if not lang:
|
||||||
lang = str(QLocale.system().name())[:2]
|
lang = str(QLocale.system().name())[:2]
|
||||||
@ -139,7 +139,7 @@ def install_gettext_trans_under_qt(base_folder: os.PathLike, lang: str = None) -
|
|||||||
# So, we install the gettext locale, great, but we also should try to install qt_*.qm if
|
# So, we install the gettext locale, great, but we also should try to install qt_*.qm if
|
||||||
# available so that strings that are inside Qt itself over which I have no control are in the
|
# available so that strings that are inside Qt itself over which I have no control are in the
|
||||||
# right language.
|
# right language.
|
||||||
from PyQt5.QtCore import QCoreApplication, QTranslator, QLocale, QLibraryInfo
|
from PyQt6.QtCore import QCoreApplication, QTranslator, QLocale, QLibraryInfo
|
||||||
|
|
||||||
if not lang:
|
if not lang:
|
||||||
lang = str(QLocale.system().name())[:2]
|
lang = str(QLocale.system().name())[:2]
|
||||||
@ -155,7 +155,7 @@ def install_gettext_trans_under_qt(base_folder: os.PathLike, lang: str = None) -
|
|||||||
if ISLINUX:
|
if ISLINUX:
|
||||||
# Under linux, a full Qt installation is already available in the system, we didn't bundle
|
# Under linux, a full Qt installation is already available in the system, we didn't bundle
|
||||||
# up the qm files in our package, so we have to load translations from the system.
|
# up the qm files in our package, so we have to load translations from the system.
|
||||||
qmpath = op.join(QLibraryInfo.location(QLibraryInfo.TranslationsPath), qmname)
|
qmpath = op.join(QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath), qmname)
|
||||||
else:
|
else:
|
||||||
qmpath = op.join(base_folder, qmname)
|
qmpath = op.join(base_folder, qmname)
|
||||||
qtr = QTranslator(QCoreApplication.instance())
|
qtr = QTranslator(QCoreApplication.instance())
|
||||||
|
2
macos.md
2
macos.md
@ -27,7 +27,7 @@ also need to install that via brew or with pyenv.
|
|||||||
### With build.py
|
### With build.py
|
||||||
OSX comes with a version of python 3 by default in newer versions of OSX. To produce universal
|
OSX comes with a version of python 3 by default in newer versions of OSX. To produce universal
|
||||||
builds either the 3.8 version shipped in macos or 3.9.1 or newer needs to be used. If needing to
|
builds either the 3.8 version shipped in macos or 3.9.1 or newer needs to be used. If needing to
|
||||||
build pyqt5 from source then the first line below is needed, else it may be omitted. (Path shown is
|
build pyqt6 from source then the first line below is needed, else it may be omitted. (Path shown is
|
||||||
for an arm mac.)
|
for an arm mac.)
|
||||||
|
|
||||||
$ export PATH="/opt/homebrew/opt/qt/bin:$PATH"
|
$ export PATH="/opt/homebrew/opt/qt/bin:$PATH"
|
||||||
|
@ -10,7 +10,7 @@ Vcs-Git: https://github.com/arsenetar/dupeguru.git
|
|||||||
|
|
||||||
Package: {pkgname}
|
Package: {pkgname}
|
||||||
Architecture: {arch}
|
Architecture: {arch}
|
||||||
Depends: ${shlibs:Depends}, python3 (>=3.7), python3-pyqt5, python3-mutagen, python3-semantic-version
|
Depends: ${shlibs:Depends}, python3 (>=3.7), python3-pyqt6, python3-mutagen, python3-semantic-version
|
||||||
Provides: dupeguru-se, dupeguru-me, dupeguru-pe
|
Provides: dupeguru-se, dupeguru-me, dupeguru-pe
|
||||||
Replaces: dupeguru-se, dupeguru-me, dupeguru-pe
|
Replaces: dupeguru-se, dupeguru-me, dupeguru-pe
|
||||||
Conflicts: dupeguru-se, dupeguru-me, dupeguru-pe
|
Conflicts: dupeguru-se, dupeguru-me, dupeguru-pe
|
||||||
|
@ -6,36 +6,46 @@
|
|||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QCoreApplication, QTimer
|
from PyQt6.QtCore import Qt, QCoreApplication, QTimer
|
||||||
from PyQt5.QtGui import QPixmap, QFont
|
from PyQt6.QtGui import QPixmap, QFont, QShowEvent
|
||||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QHBoxLayout, QVBoxLayout, QLabel
|
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QHBoxLayout, QVBoxLayout, QLabel, QWidget
|
||||||
|
|
||||||
from core.util import check_for_update
|
from core.util import check_for_update
|
||||||
from qt.util import move_to_screen_center
|
from qt.util import move_to_screen_center
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from qt.app import DupeGuru
|
||||||
|
|
||||||
tr = trget("ui")
|
tr = trget("ui")
|
||||||
|
|
||||||
|
|
||||||
class AboutBox(QDialog):
|
class AboutBox(QDialog):
|
||||||
def __init__(self, parent, app, **kwargs):
|
def __init__(self, parent: QWidget, app: "DupeGuru", **kwargs) -> None:
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint
|
flags = (
|
||||||
|
Qt.WindowType.CustomizeWindowHint
|
||||||
|
| Qt.WindowType.WindowTitleHint
|
||||||
|
| Qt.WindowType.WindowSystemMenuHint
|
||||||
|
| Qt.WindowType.MSWindowsFixedSizeDialogHint
|
||||||
|
)
|
||||||
super().__init__(parent, flags, **kwargs)
|
super().__init__(parent, flags, **kwargs)
|
||||||
self.app = app
|
self.app = app
|
||||||
self._setupUi()
|
self._setupUi()
|
||||||
|
|
||||||
self.button_box.accepted.connect(self.accept)
|
def _setupUi(self) -> None:
|
||||||
self.button_box.rejected.connect(self.reject)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setWindowTitle(tr("About {}").format(QCoreApplication.instance().applicationName()))
|
self.setWindowTitle(tr("About {}").format(QCoreApplication.instance().applicationName()))
|
||||||
size_policy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
size_policy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||||
self.setSizePolicy(size_policy)
|
self.setSizePolicy(size_policy)
|
||||||
main_layout = QHBoxLayout(self)
|
main_layout = QHBoxLayout(self)
|
||||||
|
|
||||||
logo_label = QLabel()
|
logo_label = QLabel()
|
||||||
logo_label.setPixmap(QPixmap(":/%s_big" % self.app.LOGO_NAME))
|
logo_label.setPixmap(QPixmap(f"images:{self.app.LOGO_NAME}_128.png"))
|
||||||
main_layout.addWidget(logo_label)
|
main_layout.addWidget(logo_label)
|
||||||
|
|
||||||
detail_layout = QVBoxLayout()
|
detail_layout = QVBoxLayout()
|
||||||
|
|
||||||
name_label = QLabel()
|
name_label = QLabel()
|
||||||
font = QFont()
|
font = QFont()
|
||||||
font.setWeight(75)
|
font.setWeight(75)
|
||||||
@ -43,26 +53,35 @@ class AboutBox(QDialog):
|
|||||||
name_label.setFont(font)
|
name_label.setFont(font)
|
||||||
name_label.setText(QCoreApplication.instance().applicationName())
|
name_label.setText(QCoreApplication.instance().applicationName())
|
||||||
detail_layout.addWidget(name_label)
|
detail_layout.addWidget(name_label)
|
||||||
|
|
||||||
version_label = QLabel()
|
version_label = QLabel()
|
||||||
version_label.setText(tr("Version {}").format(QCoreApplication.instance().applicationVersion()))
|
version_label.setText(tr("Version {}").format(QCoreApplication.instance().applicationVersion()))
|
||||||
detail_layout.addWidget(version_label)
|
detail_layout.addWidget(version_label)
|
||||||
|
|
||||||
self.update_label = QLabel(tr("Checking for updates..."))
|
self.update_label = QLabel(tr("Checking for updates..."))
|
||||||
self.update_label.setTextInteractionFlags(Qt.TextBrowserInteraction)
|
self.update_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
|
||||||
self.update_label.setOpenExternalLinks(True)
|
self.update_label.setOpenExternalLinks(True)
|
||||||
detail_layout.addWidget(self.update_label)
|
detail_layout.addWidget(self.update_label)
|
||||||
|
|
||||||
license_label = QLabel()
|
license_label = QLabel()
|
||||||
license_label.setText(tr("Licensed under GPLv3"))
|
license_label.setText(tr("Licensed under GPLv3"))
|
||||||
detail_layout.addWidget(license_label)
|
detail_layout.addWidget(license_label)
|
||||||
|
|
||||||
spacer_label = QLabel()
|
spacer_label = QLabel()
|
||||||
spacer_label.setFont(font)
|
spacer_label.setFont(font)
|
||||||
detail_layout.addWidget(spacer_label)
|
detail_layout.addWidget(spacer_label)
|
||||||
self.button_box = QDialogButtonBox()
|
|
||||||
self.button_box.setOrientation(Qt.Horizontal)
|
button_box = QDialogButtonBox()
|
||||||
self.button_box.setStandardButtons(QDialogButtonBox.Ok)
|
button_box.setOrientation(Qt.Orientation.Horizontal)
|
||||||
detail_layout.addWidget(self.button_box)
|
button_box.setStandardButtons(QDialogButtonBox.StandardButton.Ok)
|
||||||
|
detail_layout.addWidget(button_box)
|
||||||
|
|
||||||
main_layout.addLayout(detail_layout)
|
main_layout.addLayout(detail_layout)
|
||||||
|
|
||||||
def _check_for_update(self):
|
button_box.accepted.connect(self.accept)
|
||||||
|
button_box.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
def _check_for_update(self) -> None:
|
||||||
update = check_for_update(QCoreApplication.instance().applicationVersion(), include_prerelease=False)
|
update = check_for_update(QCoreApplication.instance().applicationVersion(), include_prerelease=False)
|
||||||
if update is None:
|
if update is None:
|
||||||
self.update_label.setText(tr("No update available."))
|
self.update_label.setText(tr("No update available."))
|
||||||
@ -71,7 +90,7 @@ class AboutBox(QDialog):
|
|||||||
tr('New version {} available, download <a href="{}">here</a>.').format(update["version"], update["url"])
|
tr('New version {} available, download <a href="{}">here</a>.').format(update["version"], update["url"])
|
||||||
)
|
)
|
||||||
|
|
||||||
def showEvent(self, event):
|
def showEvent(self, event: QShowEvent) -> None:
|
||||||
self.update_label.setText(tr("Checking for updates..."))
|
self.update_label.setText(tr("Checking for updates..."))
|
||||||
# have to do this here as the frameGeometry is not correct until shown
|
# have to do this here as the frameGeometry is not correct until shown
|
||||||
move_to_screen_center(self)
|
move_to_screen_center(self)
|
||||||
|
@ -11,8 +11,8 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QCoreApplication, QSize
|
from PyQt6.QtCore import Qt, QCoreApplication, QSize
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QDialog,
|
QDialog,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
@ -30,7 +30,7 @@ tr = trget("ui")
|
|||||||
|
|
||||||
class ErrorReportDialog(QDialog):
|
class ErrorReportDialog(QDialog):
|
||||||
def __init__(self, parent, github_url, error, **kwargs):
|
def __init__(self, parent, github_url, error, **kwargs):
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint
|
||||||
super().__init__(parent, flags, **kwargs)
|
super().__init__(parent, flags, **kwargs)
|
||||||
self._setupUi()
|
self._setupUi()
|
||||||
name = QCoreApplication.applicationName()
|
name = QCoreApplication.applicationName()
|
||||||
@ -40,23 +40,23 @@ class ErrorReportDialog(QDialog):
|
|||||||
)
|
)
|
||||||
# Under windows, we end up with an error report without linesep if we don't mangle it
|
# Under windows, we end up with an error report without linesep if we don't mangle it
|
||||||
error_text = error_text.replace("\n", os.linesep)
|
error_text = error_text.replace("\n", os.linesep)
|
||||||
self.errorTextEdit.setPlainText(error_text)
|
self.error_text_edit.setPlainText(error_text)
|
||||||
self.github_url = github_url
|
self.github_url = github_url
|
||||||
|
|
||||||
self.sendButton.clicked.connect(self.goToGithub)
|
|
||||||
self.dontSendButton.clicked.connect(self.reject)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
def _setupUi(self):
|
||||||
self.setWindowTitle(tr("Error Report"))
|
self.setWindowTitle(tr("Error Report"))
|
||||||
self.resize(553, 349)
|
self.resize(553, 349)
|
||||||
self.verticalLayout = QVBoxLayout(self)
|
main_layout = QVBoxLayout(self)
|
||||||
self.label = QLabel(self)
|
|
||||||
self.label.setText(tr("Something went wrong. How about reporting the error?"))
|
title_label = QLabel(self)
|
||||||
self.label.setWordWrap(True)
|
title_label.setText(tr("Something went wrong. How about reporting the error?"))
|
||||||
self.verticalLayout.addWidget(self.label)
|
title_label.setWordWrap(True)
|
||||||
self.errorTextEdit = QPlainTextEdit(self)
|
main_layout.addWidget(title_label)
|
||||||
self.errorTextEdit.setReadOnly(True)
|
|
||||||
self.verticalLayout.addWidget(self.errorTextEdit)
|
self.error_text_edit = QPlainTextEdit(self)
|
||||||
|
self.error_text_edit.setReadOnly(True)
|
||||||
|
main_layout.addWidget(self.error_text_edit)
|
||||||
|
|
||||||
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.\n\nPlease make sure to run a search for any already "
|
||||||
@ -67,21 +67,28 @@ class ErrorReportDialog(QDialog):
|
|||||||
"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."
|
||||||
)
|
)
|
||||||
self.label2 = QLabel(msg)
|
instructions_label = QLabel(msg)
|
||||||
self.label2.setWordWrap(True)
|
instructions_label.setWordWrap(True)
|
||||||
self.verticalLayout.addWidget(self.label2)
|
main_layout.addWidget(instructions_label)
|
||||||
self.horizontalLayout = QHBoxLayout()
|
|
||||||
self.horizontalLayout.addItem(horizontal_spacer())
|
button_layout = QHBoxLayout()
|
||||||
self.dontSendButton = QPushButton(self)
|
button_layout.addItem(horizontal_spacer())
|
||||||
self.dontSendButton.setText(tr("Close"))
|
|
||||||
self.dontSendButton.setMinimumSize(QSize(110, 0))
|
close_button = QPushButton(self)
|
||||||
self.horizontalLayout.addWidget(self.dontSendButton)
|
close_button.setText(tr("Close"))
|
||||||
self.sendButton = QPushButton(self)
|
close_button.setMinimumSize(QSize(110, 0))
|
||||||
self.sendButton.setText(tr("Go to Github"))
|
button_layout.addWidget(close_button)
|
||||||
self.sendButton.setMinimumSize(QSize(110, 0))
|
|
||||||
self.sendButton.setDefault(True)
|
report_button = QPushButton(self)
|
||||||
self.horizontalLayout.addWidget(self.sendButton)
|
report_button.setText(tr("Go to Github"))
|
||||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
report_button.setMinimumSize(QSize(110, 0))
|
||||||
|
report_button.setDefault(True)
|
||||||
|
button_layout.addWidget(report_button)
|
||||||
|
|
||||||
|
main_layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
report_button.clicked.connect(self.goToGithub)
|
||||||
|
close_button.clicked.connect(self.reject)
|
||||||
|
|
||||||
def goToGithub(self):
|
def goToGithub(self):
|
||||||
open_url(self.github_url)
|
open_url(self.github_url)
|
||||||
@ -91,6 +98,6 @@ def install_excepthook(github_url):
|
|||||||
def my_excepthook(exctype, value, tb):
|
def my_excepthook(exctype, value, tb):
|
||||||
s = "".join(traceback.format_exception(exctype, value, tb))
|
s = "".join(traceback.format_exception(exctype, value, tb))
|
||||||
dialog = ErrorReportDialog(None, github_url, s)
|
dialog = ErrorReportDialog(None, github_url, s)
|
||||||
dialog.exec_()
|
dialog.exec()
|
||||||
|
|
||||||
sys.excepthook = my_excepthook
|
sys.excepthook = my_excepthook
|
||||||
|
13
run.py
13
run.py
@ -9,8 +9,8 @@ import sys
|
|||||||
import os.path as op
|
import os.path as op
|
||||||
import gc
|
import gc
|
||||||
|
|
||||||
from PyQt6.QtCore import QCoreApplication
|
from PyQt6.QtCore import QDir
|
||||||
from PyQt6.QtGui import QIcon, QPixmap
|
from PyQt6.QtGui import QIcon
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
|
||||||
from hscommon.trans import install_gettext_trans_under_qt
|
from hscommon.trans import install_gettext_trans_under_qt
|
||||||
@ -48,9 +48,10 @@ def setup_signals():
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
QCoreApplication.setOrganizationName("Hardcoded Software")
|
QApplication.setOrganizationName("Hardcoded Software")
|
||||||
QCoreApplication.setApplicationName(__appname__)
|
QApplication.setApplicationName(__appname__)
|
||||||
QCoreApplication.setApplicationVersion(__version__)
|
QApplication.setApplicationVersion(__version__)
|
||||||
|
QDir.addSearchPath("images", op.join(BASE_PATH, "images"))
|
||||||
setup_qt_logging()
|
setup_qt_logging()
|
||||||
settings = create_qsettings()
|
settings = create_qsettings()
|
||||||
lang = settings.value("Language")
|
lang = settings.value("Language")
|
||||||
@ -70,7 +71,7 @@ def main():
|
|||||||
# has been installed
|
# has been installed
|
||||||
from qt.app import DupeGuru
|
from qt.app import DupeGuru
|
||||||
|
|
||||||
app.setWindowIcon(QIcon(QPixmap(f":/{DupeGuru.LOGO_NAME}")))
|
app.setWindowIcon(QIcon(f"images:{DupeGuru.LOGO_NAME}_32.png"))
|
||||||
global dgapp
|
global dgapp
|
||||||
dgapp = DupeGuru()
|
dgapp = DupeGuru()
|
||||||
install_excepthook("https://github.com/arsenetar/dupeguru/issues")
|
install_excepthook("https://github.com/arsenetar/dupeguru/issues")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user