Rettelsaer
This commit is contained in:
@@ -95,6 +95,9 @@ class MainWindow(QMainWindow):
|
||||
self._connect_player_signals()
|
||||
self._library_loaded.connect(self._apply_library)
|
||||
self._db_ready.connect(self._on_db_ready)
|
||||
self._login_success_signal.connect(self._on_login_success)
|
||||
self._login_fail_signal.connect(self._on_login_fail)
|
||||
self._status_signal.connect(self._set_status)
|
||||
self._build_menu()
|
||||
self._build_ui()
|
||||
self._build_statusbar()
|
||||
@@ -130,15 +133,15 @@ class MainWindow(QMainWindow):
|
||||
# ── Filer ─────────────────────────────────────────────────────────────
|
||||
file_menu = menubar.addMenu("Filer")
|
||||
|
||||
self._act_go_online = QAction("Gå online...", self)
|
||||
self._act_go_online = QAction("● Gå online", self)
|
||||
self._act_go_online.setShortcut("Ctrl+L")
|
||||
self._act_go_online.triggered.connect(self._go_online)
|
||||
file_menu.addAction(self._act_go_online)
|
||||
|
||||
self._act_go_offline = QAction("Gå offline", self)
|
||||
self._act_go_offline.triggered.connect(self._go_offline)
|
||||
self._act_go_offline.setEnabled(False)
|
||||
file_menu.addAction(self._act_go_offline)
|
||||
self._act_sync = QAction("↕ Synkroniser nu", self)
|
||||
self._act_sync.setShortcut("Ctrl+Shift+S")
|
||||
self._act_sync.triggered.connect(self._manual_sync)
|
||||
file_menu.addAction(self._act_sync)
|
||||
|
||||
file_menu.addSeparator()
|
||||
|
||||
@@ -287,28 +290,26 @@ class MainWindow(QMainWindow):
|
||||
b.setCheckable(True)
|
||||
return b
|
||||
|
||||
self._btn_prev = btn("|◀◀", size=52)
|
||||
self._btn_play = btn("▶", "btn_play", size=72)
|
||||
self._btn_stop = btn("■", "btn_stop", size=52)
|
||||
self._btn_next = btn("▶▶|", size=52)
|
||||
self._btn_stop = btn("■", "btn_stop", size=72)
|
||||
self._btn_demo = btn(f"▶\n{self._demo_seconds} SEK", "btn_demo", size=64, checkable=True)
|
||||
|
||||
self._btn_prev.clicked.connect(self._prev_song)
|
||||
self._btn_play.clicked.connect(self._toggle_play)
|
||||
self._btn_stop.clicked.connect(self._stop)
|
||||
self._btn_next.clicked.connect(self._next_song)
|
||||
self._btn_demo.clicked.connect(self._toggle_demo)
|
||||
|
||||
layout.addWidget(self._btn_prev)
|
||||
layout.addWidget(self._btn_play)
|
||||
layout.addWidget(self._btn_stop)
|
||||
layout.addWidget(self._btn_next)
|
||||
|
||||
layout.addSpacing(24)
|
||||
|
||||
sep1 = QFrame()
|
||||
sep1.setFrameShape(QFrame.Shape.VLine)
|
||||
sep1.setFixedWidth(1)
|
||||
layout.addWidget(sep1)
|
||||
|
||||
layout.addSpacing(24)
|
||||
|
||||
layout.addWidget(self._btn_demo)
|
||||
layout.addStretch()
|
||||
|
||||
@@ -319,7 +320,9 @@ class MainWindow(QMainWindow):
|
||||
self._vol_slider = QSlider(Qt.Orientation.Horizontal)
|
||||
self._vol_slider.setRange(0, 100)
|
||||
self._vol_slider.setValue(self._settings.get("volume", 78))
|
||||
self._vol_slider.setFixedWidth(100)
|
||||
self._vol_slider.setFixedWidth(160)
|
||||
self._vol_slider.setFixedHeight(36)
|
||||
self._vol_slider.setObjectName("vol_slider")
|
||||
self._vol_slider.valueChanged.connect(self._on_volume)
|
||||
layout.addWidget(self._vol_slider)
|
||||
|
||||
@@ -336,7 +339,14 @@ class MainWindow(QMainWindow):
|
||||
self._playlist_panel.song_selected.connect(self._load_song_by_idx)
|
||||
self._playlist_panel.song_dropped.connect(self._on_song_dropped)
|
||||
self._playlist_panel.event_started.connect(self._on_event_started)
|
||||
self._playlist_panel.next_song_ready.connect(self._load_song)
|
||||
self._playlist_panel.next_song_ready.connect(self._on_next_song_ready)
|
||||
self._playlist_panel.playlist_changed.connect(self._on_playlist_changed)
|
||||
|
||||
# Debounce-timer til auto-sync — starter sync 5 sek efter sidst ændring
|
||||
self._sync_debounce = QTimer(self)
|
||||
self._sync_debounce.setSingleShot(True)
|
||||
self._sync_debounce.setInterval(5000)
|
||||
self._sync_debounce.timeout.connect(self._auto_sync)
|
||||
|
||||
self._library_panel = LibraryPanel()
|
||||
self._library_panel.song_selected.connect(self._on_library_song_selected)
|
||||
@@ -433,9 +443,12 @@ class MainWindow(QMainWindow):
|
||||
QTimer.singleShot(200, self._reload_library)
|
||||
|
||||
# Signal til at opdatere biblioteket fra baggrundstråd
|
||||
_library_loaded = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(list)
|
||||
_db_ready = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal()
|
||||
_file_changed_signal = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal()
|
||||
_library_loaded = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(list)
|
||||
_db_ready = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal()
|
||||
_file_changed_signal = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal()
|
||||
_login_success_signal = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(str)
|
||||
_login_fail_signal = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(str)
|
||||
_status_signal = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(str, int)
|
||||
|
||||
def _reload_library(self):
|
||||
"""Hent sange fra DB i baggrundstråd — thread-safe via signal."""
|
||||
@@ -508,20 +521,36 @@ class MainWindow(QMainWindow):
|
||||
try:
|
||||
restored = self._playlist_panel.restore_active_playlist()
|
||||
if restored:
|
||||
# Hent den sang der er klar (current_idx sat af restore)
|
||||
idx = self._playlist_panel._current_idx
|
||||
song = self._playlist_panel.get_song(idx)
|
||||
|
||||
if self._playlist_panel.restore_event_state():
|
||||
idx = self._playlist_panel._current_idx
|
||||
# Event var i gang — genoptag
|
||||
idx = self._playlist_panel._current_idx
|
||||
song = self._playlist_panel.get_song(idx)
|
||||
if song:
|
||||
self._current_idx = idx
|
||||
self._song_ended = False
|
||||
self._load_song(song)
|
||||
self._playlist_panel.set_current(idx)
|
||||
self._set_status(
|
||||
f"Event genoptaget ved: {song.get('title','')} — tryk ▶ for at fortsætte",
|
||||
f"Event genoptaget ved: {song.get('title','')} — tryk ▶",
|
||||
6000,
|
||||
)
|
||||
elif song:
|
||||
# Normal opstart — load første sang klar
|
||||
self._current_idx = idx
|
||||
self._song_ended = False
|
||||
self._load_song(song)
|
||||
self._playlist_panel.set_current(idx)
|
||||
self._set_status(
|
||||
f"Klar: {song.get('title','')} — tryk ▶ for at starte",
|
||||
4000,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Scan 30 sek efter opstart — fanger ændringer siden sidst
|
||||
QTimer.singleShot(30000, self.start_background_scan)
|
||||
|
||||
def start_background_scan(self):
|
||||
@@ -602,40 +631,104 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _auto_login(self):
|
||||
"""Forsøg automatisk login med gemte oplysninger."""
|
||||
username = self._settings.get("username", "")
|
||||
password = self._settings.get("password", "")
|
||||
username = self._settings.get("username", "")
|
||||
password = self._settings.get("password", "")
|
||||
server_url = self._settings.get("server_url", "http://localhost:8000").rstrip("/")
|
||||
if not username or not password:
|
||||
return
|
||||
try:
|
||||
import urllib.request, urllib.parse, json
|
||||
data = urllib.parse.urlencode({"username": username, "password": password}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{API_URL}/auth/login", data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=8) as resp:
|
||||
body = json.loads(resp.read())
|
||||
self._api_token = body.get("access_token")
|
||||
self._api_url = API_URL
|
||||
self._api_username = username
|
||||
self._set_online_state(True)
|
||||
self._set_status(f"Automatisk logget ind som {username}", 4000)
|
||||
# Synkroniser dans-niveauer og navne
|
||||
QTimer.singleShot(500, self._sync_dance_data)
|
||||
except Exception:
|
||||
self._set_status("Auto-login fejlede — kør Filer → Gå online manuelt", 5000)
|
||||
|
||||
def _run():
|
||||
try:
|
||||
import urllib.request, urllib.parse, json
|
||||
data = urllib.parse.urlencode({"username": username, "password": password}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{server_url}/auth/login", data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=8) as resp:
|
||||
body = json.loads(resp.read())
|
||||
self._api_token = body.get("access_token")
|
||||
self._api_url = server_url
|
||||
self._api_username = username
|
||||
# Kald GUI-opdatering via signal — thread-safe
|
||||
self._login_success_signal.emit(username)
|
||||
except Exception as e:
|
||||
self._login_fail_signal.emit(str(e))
|
||||
|
||||
import threading
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
|
||||
def _on_playlist_changed(self):
|
||||
"""Danseliste ændret — start debounce-timer til auto-sync."""
|
||||
if hasattr(self, "_sync_debounce"):
|
||||
self._sync_debounce.start()
|
||||
|
||||
def _auto_sync(self):
|
||||
"""Kør sync hvis vi er online — kaldes af debounce-timer."""
|
||||
if not self._api_token:
|
||||
return
|
||||
if not hasattr(self, "_sync_manager") or not self._sync_manager:
|
||||
return
|
||||
self._sync_manager.push(
|
||||
on_done=lambda r: self._status_signal.emit(
|
||||
f"↑ Synkroniseret — {r.get('songs_synced', 0)} sange", 3000
|
||||
),
|
||||
on_error=lambda e: self._status_signal.emit(
|
||||
f"⚠ Sync fejl: {e}", 8000
|
||||
),
|
||||
)
|
||||
|
||||
def _on_next_song_ready(self, song: dict):
|
||||
"""Næste sang er klar — load den i afspilleren og markér orange."""
|
||||
idx = self._playlist_panel._current_idx
|
||||
self._current_idx = idx
|
||||
self._song_ended = False
|
||||
self._playlist_panel._song_ended = False
|
||||
self._load_song(song)
|
||||
self._playlist_panel.set_current(idx)
|
||||
|
||||
def _on_login_success(self, username: str):
|
||||
"""Kaldes i GUI-tråden når login lykkes."""
|
||||
self._set_online_state(True)
|
||||
self._set_status(f"Logget ind som {username}", 4000)
|
||||
|
||||
def _on_login_fail(self, error: str):
|
||||
"""Kaldes i GUI-tråden når login fejler."""
|
||||
self._set_status(f"Login fejlede: {error}", 5000)
|
||||
|
||||
def _go_online(self):
|
||||
dialog = LoginDialog(self)
|
||||
if dialog.exec():
|
||||
url, username, token = dialog.get_credentials()
|
||||
self._api_url = url
|
||||
self._api_token = token
|
||||
self._api_username = username
|
||||
self._set_online_state(True)
|
||||
self._set_status(f"Online som {username}", 5000)
|
||||
QTimer.singleShot(500, self._sync_dance_data)
|
||||
"""Log ind/ud med gemte credentials."""
|
||||
if self._api_token:
|
||||
self._go_offline()
|
||||
return
|
||||
username = self._settings.get("username", "")
|
||||
password = self._settings.get("password", "")
|
||||
server_url = self._settings.get("server_url", "http://localhost:8000").rstrip("/")
|
||||
if not username or not password:
|
||||
self._set_status("Udfyld brugernavn og kodeord i Indstillinger → Online", 5000)
|
||||
return
|
||||
|
||||
def _run():
|
||||
try:
|
||||
import urllib.request, urllib.parse, json
|
||||
data = urllib.parse.urlencode({"username": username, "password": password}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{server_url}/auth/login", data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=8) as resp:
|
||||
body = json.loads(resp.read())
|
||||
self._api_token = body.get("access_token")
|
||||
self._api_url = server_url
|
||||
self._api_username = username
|
||||
self._login_success_signal.emit(username)
|
||||
except Exception as e:
|
||||
self._login_fail_signal.emit(str(e))
|
||||
|
||||
import threading
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
|
||||
def _sync_dance_data(self):
|
||||
"""Synkroniser dans-niveauer og navne fra API."""
|
||||
@@ -669,15 +762,56 @@ class MainWindow(QMainWindow):
|
||||
self._set_status("Offline — arbejder lokalt", 3000)
|
||||
|
||||
def _set_online_state(self, online: bool):
|
||||
self._act_go_online.setEnabled(not online)
|
||||
self._act_go_offline.setEnabled(online)
|
||||
if online:
|
||||
name = self._api_username or "?"
|
||||
self._conn_label.setText(f"● ONLINE ({name})")
|
||||
self._conn_label.setStyleSheet("color: #2ecc71;")
|
||||
self._act_go_online.setText("● Gå offline")
|
||||
self._init_sync()
|
||||
else:
|
||||
self._conn_label.setText("● OFFLINE")
|
||||
self._conn_label.setStyleSheet("color: #5a6070;")
|
||||
self._act_go_online.setText("● Gå online")
|
||||
self._sync_manager = None
|
||||
|
||||
def _init_sync(self):
|
||||
"""Opret SyncManager og kør initial push+pull."""
|
||||
try:
|
||||
from local.local_db import DB_PATH
|
||||
from local.sync_manager import SyncManager
|
||||
server_url = self._settings.get("server_url", "http://localhost:8000")
|
||||
self._sync_manager = SyncManager(
|
||||
db_path=str(DB_PATH),
|
||||
server_url=server_url,
|
||||
token=self._api_token,
|
||||
)
|
||||
self._sync_manager.push_and_pull(
|
||||
on_done=lambda r: self._status_signal.emit(
|
||||
f"✓ Synkroniseret — {r['push']['songs_synced']} sange", 5000
|
||||
),
|
||||
on_error=lambda e: self._status_signal.emit(
|
||||
f"⚠ Sync fejl: {e}", 5000
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
self._set_status(f"⚠ Sync fejl: {e}", 5000)
|
||||
|
||||
def _manual_sync(self):
|
||||
if not self._api_token:
|
||||
self._set_status("Log ind for at synkronisere", 3000)
|
||||
return
|
||||
if not hasattr(self, "_sync_manager") or not self._sync_manager:
|
||||
self._init_sync()
|
||||
return
|
||||
self._set_status("Synkroniserer...", 2000)
|
||||
self._sync_manager.push_and_pull(
|
||||
on_done=lambda r: self._status_signal.emit(
|
||||
f"✓ Synkroniseret — {r['push']['songs_synced']} sange", 4000
|
||||
),
|
||||
on_error=lambda e: self._status_signal.emit(
|
||||
f"⚠ Sync fejl: {e}", 5000
|
||||
),
|
||||
)
|
||||
|
||||
def _new_playlist(self):
|
||||
self._stop()
|
||||
@@ -851,6 +985,12 @@ class MainWindow(QMainWindow):
|
||||
song = self._playlist_panel.get_song(idx)
|
||||
if not song:
|
||||
return
|
||||
# Nulstil gammel markering
|
||||
old_idx = self._playlist_panel._current_idx
|
||||
if old_idx is not None and old_idx != idx:
|
||||
if 0 <= old_idx < len(self._playlist_panel._statuses):
|
||||
if self._playlist_panel._statuses[old_idx] == "playing":
|
||||
self._playlist_panel._statuses[old_idx] = "pending"
|
||||
self._current_idx = idx
|
||||
self._load_song(song)
|
||||
self._playlist_panel.set_current(idx)
|
||||
@@ -944,13 +1084,16 @@ class MainWindow(QMainWindow):
|
||||
self._btn_play.setText("▶")
|
||||
self._vu.reset()
|
||||
|
||||
# Synkroniser current_idx til playlist_panel
|
||||
self._playlist_panel._current_idx = self._current_idx
|
||||
|
||||
# Markér den afspillede sang
|
||||
self._playlist_panel.mark_played(self._current_idx)
|
||||
|
||||
# Synkroniser event-status til den gemte navngivne liste
|
||||
self._sync_event_status_to_playlist()
|
||||
|
||||
# Find første ikke-afspillede og ikke-skippede sang fra TOPPEN
|
||||
# Find næste uafspillede
|
||||
ni = self._playlist_panel.next_playable_idx()
|
||||
next_song = self._playlist_panel.get_song(ni) if ni is not None else None
|
||||
if next_song:
|
||||
@@ -959,7 +1102,6 @@ class MainWindow(QMainWindow):
|
||||
self._load_song(next_song)
|
||||
self._set_status(f"Klar: {next_song.get('title','')} — tryk ▶ for at starte")
|
||||
else:
|
||||
# Danseliste afsluttet — nulstil liste-markering og synkroniser
|
||||
self._current_idx = -1
|
||||
self._playlist_panel._current_idx = -1
|
||||
self._playlist_panel._song_ended = False
|
||||
|
||||
Reference in New Issue
Block a user