feat: Added updater

Added updater window and option to check for new version and update via pip.
This commit is contained in:
Mr Finchum 2025-01-31 14:58:14 +00:00
parent 89c3fb3e68
commit fba31cf3e6
8 changed files with 473 additions and 5 deletions

View file

@ -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+)",

View file

@ -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)

View file

@ -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

View file

@ -142,7 +142,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Export Format</string>
<string>Format </string>
</property>
</widget>
</item>
@ -841,6 +841,13 @@
</property>
<addaction name="actionAbout"/>
</widget>
<widget class="QMenu" name="menuSettings">
<property name="title">
<string>Settings</string>
</property>
<addaction name="actionUpdate"/>
</widget>
<addaction name="menuSettings"/>
<addaction name="menuHelp"/>
</widget>
<action name="actionInfo">
@ -858,6 +865,11 @@
<string>About</string>
</property>
</action>
<action name="actionUpdate">
<property name="text">
<string>Update</string>
</property>
</action>
</widget>
<resources/>
<connections>

View file

@ -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

View file

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Updater_Window</class>
<widget class="QMainWindow" name="Updater_Window">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>372</width>
<height>224</height>
</rect>
</property>
<property name="windowTitle">
<string>OptimaLab35 - Updater</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_last_check">
<property name="text">
<string>Last update check ago:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_last_check_2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QWidget" name="widget_2" native="true">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Local Version</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_optimalab35_latestversion">
<property name="text">
<string>unknown</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>optima35</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_optima35_latestversion">
<property name="text">
<string>unknown</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Latest version</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>OptimaLab35</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Package</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_optimalab35_localversion">
<property name="text">
<string>0.0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_optima35_localversion">
<property name="text">
<string>0.0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLabel" name="label_dev">
<property name="text">
<string>Detected dev env. updater disabled</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="check_for_update_Button">
<property name="text">
<string>Check for update</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="update_and_restart_Button">
<property name="text">
<string>Update and restart</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -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

View file

@ -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+)",