Næste version
This commit is contained in:
@@ -91,6 +91,7 @@ class MainWindow(QMainWindow):
|
||||
self._demo_fade_seconds = self._settings.get("demo_fade_seconds", 5)
|
||||
|
||||
self._connect_player_signals()
|
||||
self._library_loaded.connect(self._apply_library)
|
||||
self._build_menu()
|
||||
self._build_ui()
|
||||
self._build_statusbar()
|
||||
@@ -367,66 +368,60 @@ class MainWindow(QMainWindow):
|
||||
# ── Lokal DB + scanning ───────────────────────────────────────────────────
|
||||
|
||||
def _init_local_db(self):
|
||||
# Debounce-timer til reload (skal oprettes i GUI-tråden)
|
||||
self._reload_timer = QTimer(self)
|
||||
self._reload_timer.setSingleShot(True)
|
||||
self._reload_timer.setInterval(2000)
|
||||
self._reload_timer.timeout.connect(self._reload_library)
|
||||
|
||||
# Kør init_db i baggrundstråd — blokerer ikke GUI
|
||||
import threading
|
||||
threading.Thread(target=self._init_db_background, daemon=True).start()
|
||||
|
||||
def _init_db_background(self):
|
||||
"""Kører i baggrundstråd — initialiserer DB og loader bibliotek."""
|
||||
try:
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
from local.local_db import init_db
|
||||
from local.file_watcher import get_watcher
|
||||
|
||||
init_db()
|
||||
|
||||
# Brug et Qt signal til thread-safe reload fra watcher-tråden
|
||||
from PyQt6.QtCore import QMetaObject, Q_ARG
|
||||
def on_file_change(event_type, path, song_id):
|
||||
QTimer.singleShot(0, self._reload_library)
|
||||
|
||||
self._watcher = get_watcher(on_change=on_file_change)
|
||||
self._watcher.start()
|
||||
|
||||
# Indlæs hvad vi allerede kender fra SQLite
|
||||
self._reload_library()
|
||||
|
||||
# Gendan sidst aktive danseliste
|
||||
restored = self._playlist_panel.restore_active_playlist()
|
||||
|
||||
# Gendan event-fremgang hvis liste blev gendannet
|
||||
if restored:
|
||||
if self._playlist_panel.restore_event_state():
|
||||
# Indlæs den sang vi var nået til
|
||||
idx = self._playlist_panel._current_idx
|
||||
song = self._playlist_panel.get_song(idx)
|
||||
if song:
|
||||
self._current_idx = idx
|
||||
self._load_song(song)
|
||||
self._set_status(
|
||||
f"Event genoptaget ved: {song.get('title','')} — tryk ▶ for at fortsætte",
|
||||
6000,
|
||||
)
|
||||
|
||||
# Kør automatisk scanning ved opstart
|
||||
self._set_status("Starter scanning af biblioteker...")
|
||||
QTimer.singleShot(100, self.start_scan)
|
||||
|
||||
# Trigger library load via signal
|
||||
self._library_loaded.emit([]) # tomt signal = "DB klar, load nu"
|
||||
except Exception as e:
|
||||
self._set_status(f"DB fejl: {e}")
|
||||
pass
|
||||
|
||||
def start_scan(self):
|
||||
"""Start fuld scanning af alle biblioteker i baggrundstråd."""
|
||||
if self._scan_worker and self._scan_worker.isRunning():
|
||||
return # Scanning kører allerede
|
||||
def _start_watcher(self):
|
||||
"""Start fil-watcher i baggrundstråd — blokerer aldrig GUI."""
|
||||
import threading
|
||||
|
||||
def _start():
|
||||
try:
|
||||
from local.file_watcher import get_watcher
|
||||
|
||||
def on_file_change(event_type, path, song_id):
|
||||
# QMetaObject.invokeMethod er den korrekte måde at kalde
|
||||
# en slot fra en ikke-GUI-tråd
|
||||
from PyQt6.QtCore import QMetaObject, Qt
|
||||
QMetaObject.invokeMethod(
|
||||
self._reload_timer, "start",
|
||||
Qt.ConnectionType.QueuedConnection
|
||||
)
|
||||
|
||||
watcher = get_watcher(on_change=on_file_change)
|
||||
watcher.start()
|
||||
self._watcher = watcher # sæt først når den er klar
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=_start, daemon=True).start()
|
||||
|
||||
def start_scan(self):
|
||||
"""Start fuld scanning af alle biblioteker — watcher kører i egne baggrundstråde."""
|
||||
if not self._watcher:
|
||||
self._set_status("Ingen biblioteker at scanne — tilføj en mappe først")
|
||||
return
|
||||
|
||||
self._library_panel.set_scanning(True, "Forbereder scanning...")
|
||||
self._act_scan.setEnabled(False)
|
||||
|
||||
self._scan_worker = ScanWorker(self._watcher, parent=self)
|
||||
self._scan_worker.status_update.connect(self._on_scan_status)
|
||||
self._scan_worker.scan_done.connect(self._on_scan_done)
|
||||
self._scan_worker.start()
|
||||
self._set_status("Scanner biblioteker i baggrunden...")
|
||||
self._watcher._full_scan_all()
|
||||
# Genindlæs bibliotekslisten efter et øjeblik
|
||||
QTimer.singleShot(3000, self._reload_library)
|
||||
|
||||
def _on_scan_status(self, text: str):
|
||||
self._set_status(text)
|
||||
@@ -440,20 +435,41 @@ class MainWindow(QMainWindow):
|
||||
# Genindlæs biblioteket
|
||||
QTimer.singleShot(200, self._reload_library)
|
||||
|
||||
# Signal til at opdatere biblioteket fra baggrundstråd
|
||||
_library_loaded = __import__('PyQt6.QtCore', fromlist=['pyqtSignal']).pyqtSignal(list)
|
||||
|
||||
def _reload_library(self):
|
||||
"""Hent sange fra DB i baggrundstråd — thread-safe via signal."""
|
||||
import threading
|
||||
threading.Thread(target=self._fetch_library, daemon=True).start()
|
||||
|
||||
def _fetch_library(self):
|
||||
"""Kører i baggrundstråd — henter sange og sender til GUI via signal."""
|
||||
try:
|
||||
from local.local_db import search_songs, get_db
|
||||
songs_raw = search_songs("", limit=5000)
|
||||
import sqlite3
|
||||
from local.local_db import DB_PATH
|
||||
conn = sqlite3.connect(str(DB_PATH))
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute("""
|
||||
SELECT s.id, s.title, s.artist, s.album, s.bpm,
|
||||
s.duration_sec, s.local_path, s.file_format,
|
||||
s.file_missing,
|
||||
GROUP_CONCAT(d.name, '||') AS dance_names,
|
||||
GROUP_CONCAT(COALESCE(dl.name,''), '||') AS dance_levels
|
||||
FROM songs s
|
||||
LEFT JOIN song_dances sd ON sd.song_id = s.id
|
||||
LEFT JOIN dances d ON d.id = sd.dance_id
|
||||
LEFT JOIN dance_levels dl ON dl.id = d.level_id
|
||||
WHERE s.file_missing = 0
|
||||
GROUP BY s.id
|
||||
ORDER BY s.artist, s.title
|
||||
""").fetchall()
|
||||
conn.close()
|
||||
|
||||
songs = []
|
||||
for row in songs_raw:
|
||||
with get_db() as conn:
|
||||
dances_raw = conn.execute("""
|
||||
SELECT d.name, dl.name as level_name
|
||||
FROM song_dances sd
|
||||
JOIN dances d ON d.id = sd.dance_id
|
||||
LEFT JOIN dance_levels dl ON dl.id = d.level_id
|
||||
WHERE sd.song_id=? ORDER BY sd.dance_order
|
||||
""", (row["id"],)).fetchall()
|
||||
for row in rows:
|
||||
dances = row["dance_names"].split("||") if row["dance_names"] else []
|
||||
levels = row["dance_levels"].split("||") if row["dance_levels"] else []
|
||||
songs.append({
|
||||
"id": row["id"],
|
||||
"title": row["title"],
|
||||
@@ -464,25 +480,54 @@ class MainWindow(QMainWindow):
|
||||
"local_path": row["local_path"],
|
||||
"file_format": row["file_format"],
|
||||
"file_missing": bool(row["file_missing"]),
|
||||
"dances": [d["name"] for d in dances_raw],
|
||||
"dance_levels": [d["level_name"] or "" for d in dances_raw],
|
||||
"dances": dances,
|
||||
"dance_levels": levels,
|
||||
})
|
||||
self._library_panel.load_songs(songs)
|
||||
count = len(songs)
|
||||
self._set_status(f"Bibliotek: {count} sang{'e' if count != 1 else ''}", 3000)
|
||||
except Exception as e:
|
||||
self._library_loaded.emit(songs)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _apply_library(self, songs: list):
|
||||
if not songs:
|
||||
# Tomt signal = DB er klar, start library load og post-init
|
||||
self._reload_library()
|
||||
self._post_init()
|
||||
return
|
||||
self._library_panel.load_songs(songs)
|
||||
count = len(songs)
|
||||
self._set_status(
|
||||
f"Bibliotek: {count} sang{'e' if count != 1 else ''}", 3000
|
||||
)
|
||||
|
||||
def _post_init(self):
|
||||
"""Kør efter DB er initialiseret — gendan state og start watcher."""
|
||||
try:
|
||||
restored = self._playlist_panel.restore_active_playlist()
|
||||
if restored:
|
||||
if self._playlist_panel.restore_event_state():
|
||||
idx = self._playlist_panel._current_idx
|
||||
song = self._playlist_panel.get_song(idx)
|
||||
if song:
|
||||
self._current_idx = idx
|
||||
self._load_song(song)
|
||||
self._set_status(
|
||||
f"Event genoptaget ved: {song.get('title','')} — tryk ▶ for at fortsætte",
|
||||
6000,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
QTimer.singleShot(5000, self._start_watcher)
|
||||
QTimer.singleShot(60000, self.start_scan)
|
||||
|
||||
def add_library_path(self, path: str):
|
||||
try:
|
||||
if not self._watcher:
|
||||
self._set_status("Watcher ikke klar endnu — prøv igen om et øjeblik", 3000)
|
||||
return
|
||||
self._watcher.add_library(path)
|
||||
self._set_status(f"Tilføjet: {path} — scanner...")
|
||||
# Genindlæs bibliotekslisten og start scan
|
||||
QTimer.singleShot(500, self._reload_library)
|
||||
QTimer.singleShot(1000, self.start_scan)
|
||||
self._set_status(f"Tilføjet: {path} — scanner i baggrunden...")
|
||||
# Genindlæs bibliotekslisten efter kort pause
|
||||
QTimer.singleShot(800, self._reload_library)
|
||||
except Exception as e:
|
||||
self._set_status(f"Fejl ved tilføjelse: {e}")
|
||||
|
||||
@@ -820,6 +865,10 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _on_library_song_selected(self, song: dict):
|
||||
self._load_song(song)
|
||||
# VLC er asynkron — vent kort på at media er klar
|
||||
QTimer.singleShot(150, self._play_after_load)
|
||||
|
||||
def _play_after_load(self):
|
||||
self._player.play()
|
||||
self._btn_play.setText("⏸")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user