rettelser

This commit is contained in:
2026-04-19 17:19:59 +02:00
parent c966d38f11
commit fb7622549c
7 changed files with 81 additions and 33 deletions

View File

@@ -252,9 +252,9 @@ def push(
return { return {
"status": "ok", "status": "ok",
"songs_synced": len(song_id_map), "songs_synced": len(song_id_map),
"playlists_synced": len(playlist_id_map), "playlists_synced": len(playlist_id_map)
"song_id_map": song_id_map, #"song_id_map": song_id_map,
"playlist_id_map": playlist_id_map, #"playlist_id_map": playlist_id_map,
} }

View File

@@ -304,11 +304,15 @@ MIGRATIONS: dict[int, list[str]] = {
12: [ 12: [
# Tabel til at huske slettede playlister — til sync med serveren # Tabel til at huske slettede playlister — til sync med serveren
"""CREATE TABLE IF NOT EXISTS deleted_playlists ( """CREATE TABLE IF NOT EXISTS deleted_playlists (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
deleted_at TEXT NOT NULL DEFAULT (datetime('now')) deleted_at TEXT NOT NULL DEFAULT (datetime('now'))
)""", )""",
], ],
13: [
# Tilføj api_project_id så serveren præcist ved hvilken playlist der skal slettes
"""ALTER TABLE deleted_playlists ADD COLUMN api_project_id TEXT""",
],
} }
@@ -546,6 +550,24 @@ def create_playlist(name: str, description: str = "", tags: str = "") -> int:
return cur.lastrowid return cur.lastrowid
def delete_playlist(playlist_id: int):
"""
Slet en playliste lokalt og registrér sletningen til næste sync.
Gemmer api_project_id så serveren præcist ved hvad der skal slettes.
"""
with get_db() as conn:
row = conn.execute(
"SELECT name, api_project_id FROM playlists WHERE id=?",
(playlist_id,)
).fetchone()
if row:
conn.execute(
"INSERT INTO deleted_playlists (name, api_project_id) VALUES (?,?)",
(row["name"], row["api_project_id"] or None)
)
conn.execute("DELETE FROM playlists WHERE id=?", (playlist_id,))
def create_linked_playlist(name: str, api_project_id: str, def create_linked_playlist(name: str, api_project_id: str,
permission: str = "view", permission: str = "view",
description: str = "", tags: str = "") -> int: description: str = "", tags: str = "") -> int:

View File

@@ -110,19 +110,29 @@ class SyncManager:
try: try:
# 1. Push lokal data op — inkl. sletninger # 1. Push lokal data op — inkl. sletninger
payload = self._build_push_payload() payload = self._build_push_payload()
deleted = payload.get("deleted_playlists", [])
logger.info(f"Sync push — {len(payload['songs'])} sange, "
f"{len(payload['playlists'])} playlister, "
f"sletter {len(deleted)}: {deleted}")
push_result = self._post("/sync/push", payload) push_result = self._post("/sync/push", payload)
self._save_playlist_ids(push_result.get("playlist_id_map", {})) self._save_playlist_ids(push_result.get("playlist_id_map", {}))
logger.info(f"Push svar: {push_result}")
# Ryd deleted_playlists nu de er sendt # 2. Pull — sletninger er nu gennemført på serveren.
if payload.get("deleted_playlists"): # VIGTIGT: ryd deleted_playlists EFTER pull, så _apply_pull
# stadig kan filtrere de slettede lister fra.
pull_result = self._get("/sync/pull")
pl_names = [p.get("name") for p in pull_result.get("my_playlists", [])]
logger.info(f"Pull modtog {len(pl_names)} playlister: {pl_names}")
self._apply_pull(pull_result)
# Ryd deleted_playlists nu pull er kørt og filtreringen er sket
if deleted:
conn = sqlite3.connect(self._db_path) conn = sqlite3.connect(self._db_path)
conn.execute("DELETE FROM deleted_playlists") conn.execute("DELETE FROM deleted_playlists")
conn.commit() conn.commit()
conn.close() conn.close()
logger.info(f"deleted_playlists ryddet efter pull")
# 2. Pull — nu er sletninger gennemført på serveren
pull_result = self._get("/sync/pull")
self._apply_pull(pull_result)
pl_count = len(pull_result.get("my_playlists", [])) pl_count = len(pull_result.get("my_playlists", []))
logger.info( logger.info(
@@ -240,11 +250,15 @@ class SyncManager:
"songs": pl_songs, "songs": pl_songs,
}) })
# Slettede playlister — skal fjernes fra serveren # Slettede playlister — skal fjernes fra serveren.
# Serveren forventer en liste af strings (api_project_id).
# Playlister uden api_project_id har aldrig nået serveren — ignorer dem.
deleted = [ deleted = [
row["name"] for row in conn.execute( row["api_project_id"]
"SELECT name FROM deleted_playlists" for row in conn.execute(
"SELECT api_project_id FROM deleted_playlists"
).fetchall() ).fetchall()
if row["api_project_id"]
] ]
conn.close() conn.close()
@@ -282,14 +296,18 @@ class SyncManager:
d.get("stepsheet_url",""), existing["id"])) d.get("stepsheet_url",""), existing["id"]))
# Importer/opdater egne playlister fra server — server er sandhed # Importer/opdater egne playlister fra server — server er sandhed
# Hent navne på lokalt slettede playlister så vi ikke genskaber dem # Hent både navne og server-IDs på lokalt slettede playlister
try: try:
deleted_names = set( deleted_rows = conn.execute(
row["name"] for row in conn.execute( "SELECT name, api_project_id FROM deleted_playlists"
"SELECT name FROM deleted_playlists" ).fetchall()
).fetchall() deleted_server_ids = {
) row["api_project_id"] for row in deleted_rows
if row["api_project_id"]
}
deleted_names = {row["name"] for row in deleted_rows}
except Exception: except Exception:
deleted_server_ids = set()
deleted_names = set() deleted_names = set()
for pl in data.get("my_playlists", []): for pl in data.get("my_playlists", []):
@@ -299,7 +317,10 @@ class SyncManager:
continue continue
# Spring over hvis listen er markeret som slettet lokalt # Spring over hvis listen er markeret som slettet lokalt
if name in deleted_names: # Tjek på server-ID (præcist) og navn (fallback)
if server_id in deleted_server_ids:
continue
if name in deleted_names and not server_id:
continue continue
existing = conn.execute( existing = conn.execute(
@@ -408,4 +429,4 @@ class SyncManager:
position += 1 position += 1
conn.commit() conn.commit()
conn.close() conn.close()

View File

@@ -365,6 +365,7 @@ class MainWindow(QMainWindow):
self._playlist_panel.event_started.connect(self._on_event_started) self._playlist_panel.event_started.connect(self._on_event_started)
self._playlist_panel.next_song_ready.connect(self._on_next_song_ready) self._playlist_panel.next_song_ready.connect(self._on_next_song_ready)
self._playlist_panel.playlist_changed.connect(self._on_playlist_changed) self._playlist_panel.playlist_changed.connect(self._on_playlist_changed)
self._playlist_panel.sync_requested.connect(self._manual_sync)
# Debounce-timer til auto-sync — starter sync 5 sek efter sidst ændring # Debounce-timer til auto-sync — starter sync 5 sek efter sidst ændring
self._sync_debounce = QTimer(self) self._sync_debounce = QTimer(self)

View File

@@ -20,6 +20,7 @@ class PlaylistBrowserDialog(QDialog):
"""Kombineret gem/hent dialog til danselister.""" """Kombineret gem/hent dialog til danselister."""
playlist_selected = pyqtSignal(int, str) # playlist_id, name playlist_selected = pyqtSignal(int, str) # playlist_id, name
sync_requested = pyqtSignal() # bed main_window om at køre sync
def __init__(self, mode: str = "load", current_songs: list = None, def __init__(self, mode: str = "load", current_songs: list = None,
current_name: str = "", parent=None): current_name: str = "", parent=None):
@@ -344,10 +345,11 @@ class PlaylistBrowserDialog(QDialog):
) )
if reply == QMessageBox.StandardButton.Yes: if reply == QMessageBox.StandardButton.Yes:
try: try:
from local.local_db import get_db from local.local_db import delete_playlist
with get_db() as conn: delete_playlist(pl["id"])
conn.execute("DELETE FROM playlists WHERE id=?", (pl["id"],))
self._load_data() self._load_data()
# Signal til main_window om at køre sync
self.sync_requested.emit()
except Exception as e: except Exception as e:
QMessageBox.warning(self, "Fejl", f"Kunne ikke slette: {e}") QMessageBox.warning(self, "Fejl", f"Kunne ikke slette: {e}")

View File

@@ -178,13 +178,8 @@ class PlaylistManagerDialog(QDialog):
) )
if reply == QMessageBox.StandardButton.Yes: if reply == QMessageBox.StandardButton.Yes:
try: try:
from local.local_db import get_db from local.local_db import delete_playlist
with get_db() as conn: delete_playlist(pl["id"])
conn.execute(
"INSERT INTO deleted_playlists (name) "
"SELECT name FROM playlists WHERE id=?", (pl["id"],)
)
conn.execute("DELETE FROM playlists WHERE id=?", (pl["id"],))
self._load_saved_playlists() self._load_saved_playlists()
except Exception as e: except Exception as e:
self._load_status.setText(f"Fejl: {e}") self._load_status.setText(f"Fejl: {e}")

View File

@@ -39,6 +39,7 @@ class PlaylistPanel(QWidget):
playlist_changed = pyqtSignal() playlist_changed = pyqtSignal()
event_started = pyqtSignal() event_started = pyqtSignal()
next_song_ready = pyqtSignal(dict) # udsendes når næste sang ændres — main_window indlæser den # udsendes af Start event — main_window indlæser første sang # udsendes ved enhver ændring → trigger autogem next_song_ready = pyqtSignal(dict) # udsendes når næste sang ændres — main_window indlæser den # udsendes af Start event — main_window indlæser første sang # udsendes ved enhver ændring → trigger autogem
sync_requested = pyqtSignal() # bed main_window om at køre sync (efter sletning)
STATUS_ICON = {"pending": " ", "playing": "", "played": "", "skipped": "", "next": ""} STATUS_ICON = {"pending": " ", "playing": "", "played": "", "skipped": "", "next": ""}
STATUS_COLOR = {"pending": "#5a6070", "playing": "#e8a020", "played": "#2ecc71", "skipped": "#e74c3c", "next": "#3b8fd4"} STATUS_COLOR = {"pending": "#5a6070", "playing": "#e8a020", "played": "#2ecc71", "skipped": "#e74c3c", "next": "#3b8fd4"}
@@ -526,6 +527,7 @@ class PlaylistPanel(QWidget):
self._btn_save_current.setToolTip(f"Gem ændringer til '{name}'") self._btn_save_current.setToolTip(f"Gem ændringer til '{name}'")
self._save_named_playlist_id(pl_id) self._save_named_playlist_id(pl_id)
dialog.playlist_selected.connect(on_saved) dialog.playlist_selected.connect(on_saved)
dialog.sync_requested.connect(self._request_sync)
dialog.exec() dialog.exec()
def _save_current(self): def _save_current(self):
@@ -572,10 +574,15 @@ class PlaylistPanel(QWidget):
except Exception as e: except Exception as e:
QMessageBox.warning(self, "Fejl", f"Kunne ikke gemme: {e}") QMessageBox.warning(self, "Fejl", f"Kunne ikke gemme: {e}")
def _request_sync(self):
"""Bobl sync-anmodning op til main_window."""
self.sync_requested.emit()
def _load_dialog(self): def _load_dialog(self):
from ui.playlist_browser import PlaylistBrowserDialog from ui.playlist_browser import PlaylistBrowserDialog
dialog = PlaylistBrowserDialog(mode="load", parent=self.window()) dialog = PlaylistBrowserDialog(mode="load", parent=self.window())
dialog.playlist_selected.connect(self._load_playlist_by_id) dialog.playlist_selected.connect(self._load_playlist_by_id)
dialog.sync_requested.connect(self._request_sync)
dialog.exec() dialog.exec()
def _load_playlist_by_id(self, pl_id: int, pl_name: str): def _load_playlist_by_id(self, pl_id: int, pl_name: str):