v0.1.0: version in the status bar + one-click self-update via git pull
__version__ in lintunes/__init__.py (setup.py regex-reads it) shows bottom-left via the new VersionButton; the tooltip carries the git short hash so machines on the same version but different commits are distinguishable. The new Updater fetches upstream 10s after launch and every 4h on daemon threads (lastfm.py pattern); commits behind put a * on the button, and clicking it confirms, runs `git pull --ff-only`, quits cleanly (library flush + player shutdown), and re-execs `python -m lintunes.main` on the new code. Positional file args are stripped from the re-exec so they don't re-import as duplicates. Pull failures surface the git error: line in the status bar and leave the running app untouched; outside a git checkout the button is a plain label. Also fixes a Round-5 regression found while verifying: the totals label was a permanent status-bar widget with stretch=1, which squeezed the transient-message area to zero width — every showMessage (scrobbles, tag-write errors, import status) has been invisible since. Both readouts are now non-permanent widgets, so a transient message temporarily replaces them and they return.
This commit is contained in:
@@ -21,6 +21,8 @@ from lintunes.gui.transport import TransportBar
|
||||
from lintunes.gui.info_dialog import InfoDialog
|
||||
from lintunes.gui.preferences_dialog import PreferencesDialog
|
||||
from lintunes.gui.track_table import format_total_time
|
||||
from lintunes.gui.version_button import VersionButton
|
||||
from lintunes.updater import Updater
|
||||
|
||||
|
||||
def album_tracks(library, artist: str, album: str) -> list:
|
||||
@@ -81,11 +83,23 @@ class MainWindow(QMainWindow):
|
||||
self._content.addWidget(self._playlist_view)
|
||||
self._content.setCurrentWidget(self._library_view)
|
||||
|
||||
# Bottom status bar: a permanent, centered totals readout (transient
|
||||
# import/scrobble/error messages still use the left showMessage area).
|
||||
# Bottom status bar, left to right: version/self-update button, then a
|
||||
# centered totals readout. Both are non-permanent widgets, so a
|
||||
# transient showMessage (import/scrobble/error/update text) replaces
|
||||
# them for its duration and they come back — a permanent widget with
|
||||
# stretch would squeeze the message area to zero width and swallow
|
||||
# every message.
|
||||
self.restart_requested = False # run_gui re-execs when True on quit
|
||||
self._updater = Updater(self)
|
||||
self._version_button = VersionButton(self._updater)
|
||||
self.statusBar().addWidget(self._version_button)
|
||||
self._totals_label = QLabel()
|
||||
self._totals_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.statusBar().addPermanentWidget(self._totals_label, 1)
|
||||
self.statusBar().addWidget(self._totals_label, 1)
|
||||
self._version_button.status_message.connect(
|
||||
lambda msg: self.statusBar().showMessage(msg, 8000))
|
||||
self._updater.update_applied.connect(self._restart_for_update)
|
||||
self._updater.start_checking()
|
||||
|
||||
# Wiring
|
||||
self._transport.play_clicked.connect(self.play_pause)
|
||||
@@ -127,6 +141,12 @@ class MainWindow(QMainWindow):
|
||||
self.apply_ui_metrics()
|
||||
self._update_totals()
|
||||
|
||||
def _restart_for_update(self):
|
||||
# The pull already succeeded; quit through the normal path (flushes
|
||||
# the library, tears down the player) and let main() re-exec us.
|
||||
self.restart_requested = True
|
||||
QApplication.instance().quit()
|
||||
|
||||
# ---- status bar totals ----
|
||||
|
||||
def _update_totals(self):
|
||||
@@ -145,6 +165,7 @@ class MainWindow(QMainWindow):
|
||||
theme.apply_theme(QApplication.instance(), self._prefs)
|
||||
self.apply_ui_metrics()
|
||||
self._transport.refresh_theme()
|
||||
self._version_button.refresh_theme()
|
||||
|
||||
def apply_ui_metrics(self):
|
||||
metrics = theme.scale_metrics(self._prefs)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""The little version readout in the status bar's bottom-left corner.
|
||||
|
||||
Normally an inert label ("v0.1.0"; tooltip shows the git commit so identical
|
||||
versions on different machines are still distinguishable). When the Updater
|
||||
finds upstream commits it grows a "*" and becomes a button: click → confirm →
|
||||
git pull → the main window restarts the app on `update_applied`.
|
||||
"""
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QPalette
|
||||
from PyQt6.QtWidgets import QMessageBox, QPushButton
|
||||
|
||||
from lintunes import __version__
|
||||
|
||||
|
||||
class VersionButton(QPushButton):
|
||||
status_message = pyqtSignal(str)
|
||||
|
||||
def __init__(self, updater, parent=None):
|
||||
super().__init__(parent)
|
||||
self._updater = updater
|
||||
self._update_ready = False
|
||||
self.setFlat(True)
|
||||
self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||
self.refresh_theme()
|
||||
self.setText(f"v{__version__}")
|
||||
commit = updater.short_hash()
|
||||
self._base_tooltip = (f"LinTunes v{__version__} ({commit})" if commit
|
||||
else f"LinTunes v{__version__}")
|
||||
self.setToolTip(self._base_tooltip)
|
||||
|
||||
updater.update_available.connect(self._on_update_available)
|
||||
updater.update_failed.connect(self._on_update_failed)
|
||||
self.clicked.connect(self._on_clicked)
|
||||
|
||||
def refresh_theme(self):
|
||||
# Subtle but readable: the theme's text color at ~55% opacity
|
||||
# (palette(mid) is too close to the light backgrounds to read).
|
||||
text = self.palette().color(QPalette.ColorRole.WindowText)
|
||||
self.setStyleSheet(
|
||||
"QPushButton { border: none; padding: 0 6px; "
|
||||
f"color: rgba({text.red()}, {text.green()}, {text.blue()}, 140); }}")
|
||||
|
||||
def _on_update_available(self, commits: int):
|
||||
self._update_ready = True
|
||||
self.setText(f"v{__version__}*")
|
||||
noun = "commit" if commits == 1 else "commits"
|
||||
self.setToolTip(f"Update available ({commits} {noun}) — "
|
||||
"click to update and restart")
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
|
||||
def _on_clicked(self):
|
||||
if not self._update_ready or not self.isEnabled():
|
||||
return
|
||||
if not self._confirm():
|
||||
return
|
||||
self.setEnabled(False)
|
||||
self.setText("updating…")
|
||||
self._updater.update_async()
|
||||
|
||||
def _confirm(self) -> bool:
|
||||
answer = QMessageBox.question(
|
||||
self.window(), "Update LinTunes",
|
||||
"Pull the latest version and restart LinTunes?")
|
||||
return answer == QMessageBox.StandardButton.Yes
|
||||
|
||||
def _on_update_failed(self, message: str):
|
||||
self.setEnabled(True)
|
||||
self.setText(f"v{__version__}*")
|
||||
self.status_message.emit(f"Update failed: {message}")
|
||||
Reference in New Issue
Block a user