Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Basic updater #17

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion juniors_toolbox/gui/application.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import sys
from pathlib import Path
from typing import Dict, Iterable, List, Optional, Tuple
from PySide6.QtCore import QPoint, QSize, Slot
from PySide6.QtCore import QPoint, QSize, Slot, QThread
from PySide6.QtGui import QResizeEvent, Qt, QFontDatabase

from PySide6.QtWidgets import QApplication, QFileDialog, QLabel, QSizePolicy, QStyleFactory, QWidget
from juniors_toolbox import __version__
from juniors_toolbox.gui.dialogs.updatefound import UpdateFoundDialog
from juniors_toolbox.gui.settings import ToolboxSettings
from juniors_toolbox.gui.tabs import TabWidgetManager
from juniors_toolbox.gui.tabs.hierarchyviewer import NameRefHierarchyWidget
from juniors_toolbox.gui.tabs.projectviewer import ProjectViewerWidget
from juniors_toolbox.gui.tabs.propertyviewer import SelectedPropertiesWidget
from juniors_toolbox.gui.templates import ToolboxTemplates
from juniors_toolbox.gui.update import GitUpdateScraper
from juniors_toolbox.gui.widgets.dockinterface import A_DockingInterface
from juniors_toolbox.gui.windows.mainwindow import MainWindow
from juniors_toolbox.scene import SMSScene
from juniors_toolbox.utils import VariadicArgs, VariadicKwargs
from juniors_toolbox.utils.filesystem import get_program_folder, resource_path
from juniors_toolbox.gui import ToolboxManager

from github.GitRelease import GitRelease


class JuniorsToolbox(QApplication):
"""
Expand Down Expand Up @@ -78,6 +82,18 @@ def __init__(self):
lambda _: self.save_scene()
) # throw away checked flag

# Updates
self.updateThread = QThread()
self.updater = GitUpdateScraper("JoshuaMKW", "Juniors-Toolbox", self)
self.updater.moveToThread(self.updateThread)
self.updateThread.finished.connect(self.updateThread.deleteLater)
self.updateThread.start()

self.gui.actionCheckUpdate.triggered.connect(
self.updater.run
)
self.updater.updatesFound.connect(self.show_updates)

# Set up theme toggle

fontFolder = resource_path("gui/fonts/")
Expand Down Expand Up @@ -250,6 +266,11 @@ def closeDockerTab(self, tab: A_DockingInterface):
# self.gui.removeDockWidget(tab)
self.set_central_status(self.is_docker_empty())

@Slot(list)
def show_updates(self, updates: Iterable[GitRelease]):
dialog = UpdateFoundDialog(self.updater)
dialog.display_updates(updates)

def __init_tabs(self):
areas = [Qt.LeftDockWidgetArea, Qt.RightDockWidgetArea]
for i, tab in enumerate(TabWidgetManager.iter_tabs()):
Expand Down
1 change: 0 additions & 1 deletion juniors_toolbox/gui/dialogs/moveconflict.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from pathlib import Path
from tkinter import W
from typing import Optional, Tuple
from unittest import skip
from PySide6.QtCore import (QAbstractItemModel, QDataStream, QEvent, QIODevice,
Expand Down
91 changes: 91 additions & 0 deletions juniors_toolbox/gui/dialogs/updatefound.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from pathlib import Path
from typing import Iterable, Optional, Tuple

from github.GitRelease import GitRelease
from github.PaginatedList import PaginatedList

from PySide6.QtCore import (QAbstractItemModel, QDataStream, QEvent, QIODevice,
QLine, QMimeData, QModelIndex, QObject, QPoint,
QSize, Qt, QThread, QTimer, QUrl, Signal,
SignalInstance, Slot)
from PySide6.QtGui import (QAction, QColor, QCursor, QDrag, QDragEnterEvent,
QDragLeaveEvent, QDragMoveEvent, QDropEvent, QIcon,
QImage, QKeyEvent, QMouseEvent, QPaintDevice,
QPainter, QPaintEvent, QPalette, QPixmap,
QUndoCommand, QUndoStack)
from PySide6.QtWidgets import (QBoxLayout, QComboBox, QFormLayout, QFrame,
QGridLayout, QGroupBox, QHBoxLayout, QLabel,
QLayout, QLineEdit, QListView, QListWidget,
QListWidgetItem, QMenu, QMenuBar, QPushButton,
QScrollArea, QSizePolicy, QSpacerItem,
QSplitter, QStyle, QStyleOptionComboBox,
QStylePainter, QTableWidget, QTableWidgetItem,
QToolBar, QTreeWidget, QTreeWidgetItem, QDialog, QDialogButtonBox, QCheckBox, QTextBrowser,
QVBoxLayout, QWidget)
from enum import IntEnum

from juniors_toolbox import __version__
from juniors_toolbox.update import ReleaseManager


class UpdateFoundDialog(QDialog):
def __init__(self, manager: ReleaseManager, isMulti: bool = False, parent: Optional[QWidget] = None):
super().__init__(parent)
if isMulti:
self.setFixedSize(400, 190)
else:
self.setFixedSize(400, 160)

self.setWindowFlag(Qt.FramelessWindowHint, True)
self.setWindowFlag(Qt.CustomizeWindowHint, True)

self.mainLayout = QVBoxLayout()

self.titleText = QLabel()
titleFont = self.titleText.font()
titleFont.setBold(True)
titleFont.setPointSize(16)
self.titleText.setFont(titleFont)
self.mainLayout.addWidget(self.titleText)

self.descriptionText = QLabel()
descriptionFont = self.descriptionText.font()
descriptionFont.setPointSize(12)
self.descriptionText.setFont(descriptionFont)
self.mainLayout.addWidget(self.descriptionText)

self.releasesView = QTextBrowser()
self.releasesView.setAcceptRichText(True)
releasesFont = self.releasesView.font()
releasesFont.setPointSize(12)
self.releasesView.setFont(releasesFont)

self.buttonsLayout = QHBoxLayout()

self.rejectButton = QPushButton("Maybe Later")
self.acceptButton = QPushButton("Update")
self.rejectButton.clicked.connect(self.reject)
self.acceptButton.clicked.connect(self.accept)

self.setLayout(self.mainLayout)

self.manager = manager

def display_updates(self, updates: PaginatedList[GitRelease]) -> QDialog.DialogCode:
if updates.totalCount == 0:
return QDialog.Rejected

newestRelease: GitRelease = updates[0]

self.setWindowModality(Qt.ApplicationModal)
self.titleText.setText(
f"{__name__} {newestRelease.tag_name} available!"
)
self.releasesView.setMarkdown(
self.manager.compile_changelog_from(__version__)
)

retCode: QDialog.DialogCode = self.exec() # type: ignore
if retCode == QDialog.Accepted:
self.manager.view(newestRelease)
return retCode
58 changes: 58 additions & 0 deletions juniors_toolbox/gui/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from distutils.version import LooseVersion
import time
from typing import Iterable, Optional

from github.GitRelease import GitRelease
from PySide6.QtCore import Signal, Slot, QObject, QRunnable

from juniors_toolbox import __version__
from juniors_toolbox.update import ReleaseManager


class GitUpdateScraper(QObject, QRunnable, ReleaseManager):
updatesFound = Signal(list)

def __init__(self, owner: str, repository: str, parent: Optional[QObject] = None):
QObject.__init__(self, parent)
ReleaseManager.__init__(self, owner, repository)
self.setObjectName(f"{self.__class__.__name__}.{owner}.{repository}")

self.waitTime = 0.0
self._quitting = False

def set_wait_time(self, seconds: float):
self.waitTime = seconds

@Slot()
def run(self):
newReleases = self.check_updates()
if len(newReleases) > 0:
self.updatesFound(newReleases)

# while not self._quitting:
# newReleases = self.check_updates()
# if len(newReleases) > 0:
# self.updatesFound(newReleases)

# start = time.time()
# while time.time() - start < self.waitTime:
# if self._quitting:
# break
# time.sleep(1)

def check_updates(self) -> Iterable[GitRelease]:
successful = self.populate()
if not successful:
return []

newestRelease = self.get_newest_release()
if newestRelease is None:
return []

if LooseVersion(newestRelease.tag_name.lstrip("v")) <= LooseVersion(__version__.lstrip("v")):
return []

return self.get_releases()

def kill(self) -> None:
self._quitting = True
86 changes: 86 additions & 0 deletions juniors_toolbox/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import webbrowser
from distutils.version import LooseVersion
from typing import Iterable, List, Optional

from github import Github
from github.PaginatedList import PaginatedList
from github.GitRelease import GitRelease

from juniors_toolbox import __version__

class ReleaseManager():
def __init__(self, owner: str, repository: str):
self._owner = owner
self._repo = repository
self._releases: Optional[PaginatedList[GitRelease]] = None
self.populate()

@property
def owner(self) -> str:
return self._owner

@property
def repository(self) -> str:
return self._repo

@owner.setter
def owner(self, owner: str):
self._owner = owner

@repository.setter
def repository(self, repo: str):
self._repo = repo

@property
def releaseLatestURL(self) -> str:
return f"https://github.com/{self._owner}/{self._repo}/releases/latest"

@property
def releasesURL(self) -> str:
return f"https://github.com/{self._owner}/{self._repo}/releases"

def get_newest_release(self) -> Optional[GitRelease]:
if self._releases is None or self._releases.totalCount == 0:
return None
return self._releases[0]

def get_oldest_release(self) -> Optional[GitRelease]:
if self._releases is None or self._releases.totalCount == 0:
return None
return self._releases[-1]

def get_releases(self) -> Iterable[GitRelease]:
if self._releases is None or self._releases.totalCount == 0:
return []
return self._releases

def compile_changelog_from(self, version: str) -> str:
""" Returns a Markdown changelog from the info of future versions """
seperator = "\n\n---\n\n"

newReleases: List[GitRelease] = list()
lver = LooseVersion(version.lstrip("v"))
for release in self.get_releases():
if LooseVersion(release.tag_name.lstrip("v")) <= lver:
break
newReleases.append(release)

markdown = ""
for release in newReleases:
markdown += release.body.replace("Changelog",
f"Changelog ({release.tag_name})").strip() + seperator

return markdown.rstrip(seperator).strip()

def populate(self) -> bool:
g = Github()
repo = g.get_repo(f"{self.owner}/{self.repository}")
self._releases = repo.get_releases()
return True

@staticmethod
def view(release: GitRelease, browser: Optional[webbrowser.GenericBrowser] = None, asWindow: bool = False):
if browser is None:
webbrowser.open(release.html_url, int(asWindow))
else:
browser.open(release.html_url, int(asWindow))