diff --git a/pyproject.toml b/pyproject.toml index b9e2577..a9e3c18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,12 @@ authors = [{ name = "Mr Finchum" }] description = "User interface for optima35." readme = "pip_README.md" requires-python = ">=3.8" -dependencies = ["optima35>=1.0.0, <2.0.0", "pyside6", "PyYAML"] +dependencies = [ + "optima35>=1.0.0, <2.0.0", + "PyPyUpdater>=0.3.0, <1.0.0", + "pyside6", + "PyYAML", +] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", diff --git a/src/OptimaLab35/gui.py b/src/OptimaLab35/gui.py index 1194845..e2d9384 100644 --- a/src/OptimaLab35/gui.py +++ b/src/OptimaLab35/gui.py @@ -2,10 +2,12 @@ import sys import os from datetime import datetime +from PyPiUpdater import PyPiUpdater from optima35.core import OptimaManager from OptimaLab35.utils.utility import Utilities from OptimaLab35.ui.main_window import Ui_MainWindow from OptimaLab35.ui.preview_window import Ui_Preview_Window +from OptimaLab35.ui.updater_window import Ui_Updater_Window from OptimaLab35.ui.exif_handler_window import ExifEditor from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class from OptimaLab35 import __version__ @@ -48,7 +50,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) self.sd = SimpleDialog() - self.preview_window = PreviewWindow() + # Change UI elements self.change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000) self.setWindowTitle(f"{self.name} v{self.version}") @@ -76,6 +78,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow): self.ui.actionAbout.triggered.connect(self.info_window) self.ui.preview_Button.clicked.connect(self.open_preview_window) + self.ui.actionUpdate.triggered.connect(self.open_updater_window) regex = QRegularExpression(r"^\d{1,2}\.\d{1,6}$") validator = QRegularExpressionValidator(regex) @@ -86,9 +89,14 @@ class OptimaLab35(QMainWindow, Ui_MainWindow): # UI related function, changing parts, open, etc. def open_preview_window(self): + self.preview_window = PreviewWindow() self.preview_window.values_selected.connect(self.update_values) self.preview_window.showMaximized() + def open_updater_window(self): + self.updater_window = UpdaterWindow(self.version, self.o.version) + self.updater_window.show() + def update_values(self, value1, value2, checkbox_state): # Update main window's widgets with the received values # ChatGPT @@ -454,6 +462,132 @@ class OptimaLab35(QMainWindow, Ui_MainWindow): elif do == "write": self.u.write_yaml(self.exif_file, self.available_exif_data) +class UpdaterWindow(QMainWindow, Ui_Updater_Window): + # Mixture of code by me, code/functions refactored by ChatGPT and code directly from ChatGPT + def __init__(self, optimalab35_localversion, optima35_localversion): + super(UpdaterWindow, self).__init__() + self.ui = Ui_Updater_Window() + self.ui.setupUi(self) + from PyPiUpdater import PyPiUpdater + # Update log file location + self.update_log_file = os.path.expanduser("~/.config/OptimaLab35/update.log") + # Store local versions + self.optimalab35_localversion = optimalab35_localversion + self.optima35_localversion = optima35_localversion + + # Create PyPiUpdater instances + self.ppu_ol35 = PyPiUpdater("OptimaLab35", self.optimalab35_localversion, self.update_log_file) + self.ppu_o35 = PyPiUpdater("optima35", self.optima35_localversion, self.update_log_file) + + self.last_update = self.ppu_o35.last_update_date_string() + + # Track which packages need an update + self.updates_available = {"OptimaLab35": False, "optima35": False} + + self.define_gui_interaction() + + def define_gui_interaction(self): + """Setup UI interactions.""" + if self.optimalab35_localversion == "0.0.1": + self.dev_mode() + return + else: + self.ui.label_dev.setVisible(False) + self.ui.label_optimalab35_localversion.setText(self.optimalab35_localversion) + self.ui.label_optima35_localversion.setText(self.optima35_localversion) + self.ui.update_and_restart_Button.setEnabled(False) + + # Connect buttons to functions + self.ui.check_for_update_Button.clicked.connect(self.check_for_updates) + self.ui.update_and_restart_Button.clicked.connect(self.update_and_restart) + self.ui.label_last_check_2.setText(self.last_update) + + def dev_mode(self): + self.ui.update_and_restart_Button.setEnabled(False) + self.ui.check_for_update_Button.setEnabled(False) + self.ui.label_dev.setStyleSheet("QLabel { background-color : red; color : black; }") + + def check_for_updates(self): + """Check for updates and update the UI.""" + self.ui.check_for_update_Button.setEnabled(False) + self.ui.label_optimalab35_latestversion.setText("Checking...") + self.ui.label_optima35_latestversion.setText("Checking...") + + # Check OptimaLab35 update + is_newer_ol35, latest_version_ol35 = self.ppu_ol35.check_for_update(True) + if is_newer_ol35 is None: + self.ui.label_optimalab35_latestversion.setText("Error") + else: + self.ui.label_optimalab35_latestversion.setText(latest_version_ol35) + self.updates_available["OptimaLab35"] = is_newer_ol35 + + # Check optima35 update + is_newer_o35, latest_version_o35 = self.ppu_o35.check_for_update(True) + if is_newer_o35 is None: + self.ui.label_optima35_latestversion.setText("Error") + else: + self.ui.label_optima35_latestversion.setText(latest_version_o35) + self.updates_available["optima35"] = is_newer_o35 + + # Enable update button if any update is available + if any(self.updates_available.values()): + self.ui.update_and_restart_Button.setEnabled(True) + + self.ppu_o35.record_update_check() + self.ui.label_last_check_2.setText(self.ppu_o35.last_update_date_string()) + self.ui.check_for_update_Button.setEnabled(True) + + def update_and_restart(self): + """Update selected packages and restart the application.""" + packages_to_update = [pkg for pkg, update in self.updates_available.items() if update] + + if not packages_to_update: + QMessageBox.information(self, "Update", "No updates available.") + return + + # Confirm update + msg = QMessageBox() + msg.setWindowTitle("Update Available") + msg.setText(f"Updating: {', '.join(packages_to_update)}\nRestart after update?") + msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + result = msg.exec() + + if result == QMessageBox.Yes: + update_results = [] # Store results + + for package in packages_to_update: + if package == "OptimaLab35": + success, message = self.ppu_ol35.update_package() + elif package == "optima35": + success, message = self.ppu_o35.update_package() + + update_results.append(f"{package}: {'✔ Success' if success else '❌ Failed'}\n{message}") + + # Show summary of updates + # Show update completion message + msg = QMessageBox() + msg.setWindowTitle("Update Complete") + msg.setText("\n\n".join(update_results)) + msg.setStandardButtons(QMessageBox.Ok) + msg.exec() + + # Restart the application after user clicks "OK" + self.ppu_ol35.restart_program() + + def restart_program(self): + """Restart the Python program after an update.""" + print("Restarting the application...") + + # Close all running Qt windows before restarting + app = QApplication.instance() + if app: + app.quit() + + python = sys.executable + os.execl(python, python, *sys.argv) + + + class PreviewWindow(QMainWindow, Ui_Preview_Window): values_selected = Signal(int, int, bool) diff --git a/src/OptimaLab35/ui/main_window.py b/src/OptimaLab35/ui/main_window.py index 5af0bc9..bca1e18 100644 --- a/src/OptimaLab35/ui/main_window.py +++ b/src/OptimaLab35/ui/main_window.py @@ -37,6 +37,8 @@ class Ui_MainWindow(object): self.actionPreview.setObjectName(u"actionPreview") self.actionAbout = QAction(MainWindow) self.actionAbout.setObjectName(u"actionAbout") + self.actionUpdate = QAction(MainWindow) + self.actionUpdate.setObjectName(u"actionUpdate") self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") self.gridLayout = QGridLayout(self.centralwidget) @@ -555,10 +557,14 @@ class Ui_MainWindow(object): self.menuBar.setGeometry(QRect(0, 0, 440, 27)) self.menuHelp = QMenu(self.menuBar) self.menuHelp.setObjectName(u"menuHelp") + self.menuSettings = QMenu(self.menuBar) + self.menuSettings.setObjectName(u"menuSettings") MainWindow.setMenuBar(self.menuBar) + self.menuBar.addAction(self.menuSettings.menuAction()) self.menuBar.addAction(self.menuHelp.menuAction()) self.menuHelp.addAction(self.actionAbout) + self.menuSettings.addAction(self.actionUpdate) self.retranslateUi(MainWindow) self.rename_checkbox.toggled.connect(self.filename.setEnabled) @@ -596,6 +602,7 @@ class Ui_MainWindow(object): self.actionInfo.setText(QCoreApplication.translate("MainWindow", u"About", None)) self.actionPreview.setText(QCoreApplication.translate("MainWindow", u"Preview image", None)) self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About", None)) + self.actionUpdate.setText(QCoreApplication.translate("MainWindow", u"Update", None)) self.input_folder_button.setText(QCoreApplication.translate("MainWindow", u"Input", None)) self.output_path.setText("") self.output_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Output folder", None)) @@ -604,7 +611,7 @@ class Ui_MainWindow(object): self.output_folder_button.setText(QCoreApplication.translate("MainWindow", u"Output", None)) self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Essential group", None)) self.quality_label_2.setText(QCoreApplication.translate("MainWindow", u"Quality", None)) - self.label_11.setText(QCoreApplication.translate("MainWindow", u"Export Format", None)) + self.label_11.setText(QCoreApplication.translate("MainWindow", u"Format ", None)) self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"optimize", None)) self.quality_label_1.setText(QCoreApplication.translate("MainWindow", u"Quality", None)) @@ -658,5 +665,6 @@ class Ui_MainWindow(object): self.add_date_checkBox.setText(QCoreApplication.translate("MainWindow", u"add date", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"EXIF", None)) self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) + self.menuSettings.setTitle(QCoreApplication.translate("MainWindow", u"Settings", None)) # retranslateUi diff --git a/src/OptimaLab35/ui/main_window.ui b/src/OptimaLab35/ui/main_window.ui index 3779add..62acda5 100644 --- a/src/OptimaLab35/ui/main_window.ui +++ b/src/OptimaLab35/ui/main_window.ui @@ -142,7 +142,7 @@ - Export Format + Format @@ -841,6 +841,13 @@ + + + Settings + + + + @@ -858,6 +865,11 @@ About + + + Update + + diff --git a/src/OptimaLab35/ui/updater_window.py b/src/OptimaLab35/ui/updater_window.py new file mode 100644 index 0000000..13a2eb4 --- /dev/null +++ b/src/OptimaLab35/ui/updater_window.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'updater_window.ui' +## +## Created by: Qt User Interface Compiler version 6.8.1 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout, QLabel, + QMainWindow, QPushButton, QSizePolicy, QWidget) + +class Ui_Updater_Window(object): + def setupUi(self, Updater_Window): + if not Updater_Window.objectName(): + Updater_Window.setObjectName(u"Updater_Window") + Updater_Window.setEnabled(True) + Updater_Window.resize(372, 224) + self.centralwidget = QWidget(Updater_Window) + self.centralwidget.setObjectName(u"centralwidget") + self.gridLayout_2 = QGridLayout(self.centralwidget) + self.gridLayout_2.setObjectName(u"gridLayout_2") + self.label_last_check = QLabel(self.centralwidget) + self.label_last_check.setObjectName(u"label_last_check") + + self.gridLayout_2.addWidget(self.label_last_check, 0, 0, 1, 1) + + self.label_last_check_2 = QLabel(self.centralwidget) + self.label_last_check_2.setObjectName(u"label_last_check_2") + self.label_last_check_2.setEnabled(True) + + self.gridLayout_2.addWidget(self.label_last_check_2, 0, 1, 1, 1) + + self.widget_2 = QWidget(self.centralwidget) + self.widget_2.setObjectName(u"widget_2") + self.gridLayout = QGridLayout(self.widget_2) + self.gridLayout.setObjectName(u"gridLayout") + self.label_6 = QLabel(self.widget_2) + self.label_6.setObjectName(u"label_6") + self.label_6.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_6, 0, 1, 1, 1) + + self.label_optimalab35_latestversion = QLabel(self.widget_2) + self.label_optimalab35_latestversion.setObjectName(u"label_optimalab35_latestversion") + self.label_optimalab35_latestversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_optimalab35_latestversion, 1, 2, 1, 1) + + self.label_2 = QLabel(self.widget_2) + self.label_2.setObjectName(u"label_2") + + self.gridLayout.addWidget(self.label_2, 3, 0, 1, 1) + + self.label_optima35_latestversion = QLabel(self.widget_2) + self.label_optima35_latestversion.setObjectName(u"label_optima35_latestversion") + self.label_optima35_latestversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_optima35_latestversion, 3, 2, 1, 1) + + self.label_3 = QLabel(self.widget_2) + self.label_3.setObjectName(u"label_3") + self.label_3.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_3, 0, 2, 1, 1) + + self.label = QLabel(self.widget_2) + self.label.setObjectName(u"label") + + self.gridLayout.addWidget(self.label, 1, 0, 1, 1) + + self.label_9 = QLabel(self.widget_2) + self.label_9.setObjectName(u"label_9") + + self.gridLayout.addWidget(self.label_9, 0, 0, 1, 1) + + self.label_optimalab35_localversion = QLabel(self.widget_2) + self.label_optimalab35_localversion.setObjectName(u"label_optimalab35_localversion") + self.label_optimalab35_localversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_optimalab35_localversion, 1, 1, 1, 1) + + self.label_optima35_localversion = QLabel(self.widget_2) + self.label_optima35_localversion.setObjectName(u"label_optima35_localversion") + self.label_optima35_localversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_optima35_localversion, 3, 1, 1, 1) + + self.label_dev = QLabel(self.widget_2) + self.label_dev.setObjectName(u"label_dev") + self.label_dev.setAlignment(Qt.AlignCenter) + + self.gridLayout.addWidget(self.label_dev, 2, 0, 1, 3) + + + self.gridLayout_2.addWidget(self.widget_2, 1, 0, 1, 2) + + self.widget = QWidget(self.centralwidget) + self.widget.setObjectName(u"widget") + self.horizontalLayout = QHBoxLayout(self.widget) + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.check_for_update_Button = QPushButton(self.widget) + self.check_for_update_Button.setObjectName(u"check_for_update_Button") + + self.horizontalLayout.addWidget(self.check_for_update_Button) + + self.update_and_restart_Button = QPushButton(self.widget) + self.update_and_restart_Button.setObjectName(u"update_and_restart_Button") + + self.horizontalLayout.addWidget(self.update_and_restart_Button) + + + self.gridLayout_2.addWidget(self.widget, 2, 0, 1, 2) + + Updater_Window.setCentralWidget(self.centralwidget) + + self.retranslateUi(Updater_Window) + + QMetaObject.connectSlotsByName(Updater_Window) + # setupUi + + def retranslateUi(self, Updater_Window): + Updater_Window.setWindowTitle(QCoreApplication.translate("Updater_Window", u"OptimaLab35 - Updater", None)) + self.label_last_check.setText(QCoreApplication.translate("Updater_Window", u"Last update check ago:", None)) + self.label_last_check_2.setText(QCoreApplication.translate("Updater_Window", u"TextLabel", None)) + self.label_6.setText(QCoreApplication.translate("Updater_Window", u"Local Version", None)) + self.label_optimalab35_latestversion.setText(QCoreApplication.translate("Updater_Window", u"unknown", None)) + self.label_2.setText(QCoreApplication.translate("Updater_Window", u"optima35", None)) + self.label_optima35_latestversion.setText(QCoreApplication.translate("Updater_Window", u"unknown", None)) + self.label_3.setText(QCoreApplication.translate("Updater_Window", u"Latest version", None)) + self.label.setText(QCoreApplication.translate("Updater_Window", u"OptimaLab35", None)) + self.label_9.setText(QCoreApplication.translate("Updater_Window", u"Package", None)) + self.label_optimalab35_localversion.setText(QCoreApplication.translate("Updater_Window", u"0.0.0", None)) + self.label_optima35_localversion.setText(QCoreApplication.translate("Updater_Window", u"0.0.0", None)) + self.label_dev.setText(QCoreApplication.translate("Updater_Window", u"Detected dev env. updater disabled", None)) + self.check_for_update_Button.setText(QCoreApplication.translate("Updater_Window", u"Check for update", None)) + self.update_and_restart_Button.setText(QCoreApplication.translate("Updater_Window", u"Update and restart", None)) + # retranslateUi + diff --git a/src/OptimaLab35/ui/updater_window.ui b/src/OptimaLab35/ui/updater_window.ui new file mode 100644 index 0000000..0e74e74 --- /dev/null +++ b/src/OptimaLab35/ui/updater_window.ui @@ -0,0 +1,160 @@ + + + Updater_Window + + + true + + + + 0 + 0 + 372 + 224 + + + + OptimaLab35 - Updater + + + + + + + Last update check ago: + + + + + + + true + + + TextLabel + + + + + + + + + + Local Version + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + unknown + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + optima35 + + + + + + + unknown + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Latest version + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + OptimaLab35 + + + + + + + Package + + + + + + + 0.0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Detected dev env. updater disabled + + + Qt::AlignCenter + + + + + + + + + + + + + Check for update + + + + + + + Update and restart + + + + + + + + + + + + diff --git a/src/OptimaLab35/update_ui.sh b/src/OptimaLab35/update_ui.sh index d533d7a..95cda5f 100755 --- a/src/OptimaLab35/update_ui.sh +++ b/src/OptimaLab35/update_ui.sh @@ -4,3 +4,5 @@ echo "Update main window." pyside6-uic OptimaLab35/ui/main_window.ui -o OptimaLab35/ui/main_window.py echo "Update preview window." pyside6-uic OptimaLab35/ui/preview_window.ui -o OptimaLab35/ui/preview_window.py +echo "Update updater window." +pyside6-uic OptimaLab35/ui/updater_window.ui -o OptimaLab35/ui/updater_window.py diff --git a/src/pyproject.toml b/src/pyproject.toml index 0b86791..4e3097a 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] authors = [{ name = "Mr Finchum" }] description = "User interface for optima35." requires-python = ">=3.8" -dependencies = ["optima35>=1.0.0, <2.0.0", "PyYAML"] +dependencies = ["optima35>=1.0.0, <2.0.0", "PyYAML", "PyPyUpdater"] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",