slette lister
This commit is contained in:
@@ -71,11 +71,12 @@ class PlaylistData(BaseModel):
|
|||||||
songs: list[PlaylistSongData] = []
|
songs: list[PlaylistSongData] = []
|
||||||
|
|
||||||
class PushPayload(BaseModel):
|
class PushPayload(BaseModel):
|
||||||
songs: list[SongData] = []
|
songs: list[SongData] = []
|
||||||
dances: list[DanceData] = []
|
dances: list[DanceData] = []
|
||||||
song_dances: list[SongDanceData] = []
|
song_dances: list[SongDanceData] = []
|
||||||
song_alts: list[SongAltDanceData] = []
|
song_alts: list[SongAltDanceData] = []
|
||||||
playlists: list[PlaylistData] = []
|
playlists: list[PlaylistData] = []
|
||||||
|
deleted_playlists: list[str] = [] # navne på slettede playlister
|
||||||
|
|
||||||
|
|
||||||
# ── Push ──────────────────────────────────────────────────────────────────────
|
# ── Push ──────────────────────────────────────────────────────────────────────
|
||||||
@@ -206,8 +207,9 @@ def push(
|
|||||||
if existing:
|
if existing:
|
||||||
existing.description = pl.description
|
existing.description = pl.description
|
||||||
existing.visibility = pl.visibility
|
existing.visibility = pl.visibility
|
||||||
# Slet og geninsert sange
|
# Opdater kun sange hvis push faktisk har sange med
|
||||||
db.query(ProjectSong).filter_by(project_id=existing.id).delete()
|
if pl.songs:
|
||||||
|
db.query(ProjectSong).filter_by(project_id=existing.id).delete()
|
||||||
project = existing
|
project = existing
|
||||||
else:
|
else:
|
||||||
project = Project(
|
project = Project(
|
||||||
@@ -238,6 +240,13 @@ def push(
|
|||||||
)
|
)
|
||||||
db.add(proj_song)
|
db.add(proj_song)
|
||||||
|
|
||||||
|
# ── Slet playlister der er fjernet lokalt ─────────────────────────────────
|
||||||
|
for name in payload.deleted_playlists:
|
||||||
|
proj = db.query(Project).filter_by(owner_id=me.id, name=name).first()
|
||||||
|
if proj:
|
||||||
|
db.query(ProjectSong).filter_by(project_id=proj.id).delete()
|
||||||
|
db.delete(proj)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -301,6 +301,14 @@ MIGRATIONS: dict[int, list[str]] = {
|
|||||||
"INSERT INTO dance_levels (sort_order, name, description) VALUES (90, 'High Intermediate', 'Stærk intermediate')",
|
"INSERT INTO dance_levels (sort_order, name, description) VALUES (90, 'High Intermediate', 'Stærk intermediate')",
|
||||||
"INSERT INTO dance_levels (sort_order, name, description) VALUES (99, 'Advanced', 'Fuld beherskelse af trin og teknik')",
|
"INSERT INTO dance_levels (sort_order, name, description) VALUES (99, 'Advanced', 'Fuld beherskelse af trin og teknik')",
|
||||||
],
|
],
|
||||||
|
12: [
|
||||||
|
# Tabel til at huske slettede playlister — til sync med serveren
|
||||||
|
"""CREATE TABLE IF NOT EXISTS deleted_playlists (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
deleted_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
)""",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ class SyncManager:
|
|||||||
logger.info(f"Push OK: {len(payload['songs'])} sange")
|
logger.info(f"Push OK: {len(payload['songs'])} sange")
|
||||||
result = self._post("/sync/push", payload)
|
result = self._post("/sync/push", payload)
|
||||||
self._save_playlist_ids(result.get("playlist_id_map", {}))
|
self._save_playlist_ids(result.get("playlist_id_map", {}))
|
||||||
|
# Ryd deleted_playlists nu de er sendt til serveren
|
||||||
|
if payload.get("deleted_playlists"):
|
||||||
|
conn = sqlite3.connect(self._db_path)
|
||||||
|
conn.execute("DELETE FROM deleted_playlists")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
logger.info(f"Push OK: {result.get('songs_synced', '?')} sange synkroniseret")
|
logger.info(f"Push OK: {result.get('songs_synced', '?')} sange synkroniseret")
|
||||||
if on_done:
|
if on_done:
|
||||||
on_done(result)
|
on_done(result)
|
||||||
@@ -99,19 +105,24 @@ class SyncManager:
|
|||||||
threading.Thread(target=_run, daemon=True).start()
|
threading.Thread(target=_run, daemon=True).start()
|
||||||
|
|
||||||
def push_and_pull(self, on_done=None, on_error=None):
|
def push_and_pull(self, on_done=None, on_error=None):
|
||||||
"""Push og derefter pull i samme tråd."""
|
"""Pull FØR push — server er sandhed for playlister."""
|
||||||
def _run():
|
def _run():
|
||||||
try:
|
try:
|
||||||
|
# 1. Pull FØR push — hent server-data ned lokalt
|
||||||
|
pull_result = self._get("/sync/pull")
|
||||||
|
self._apply_pull(pull_result)
|
||||||
|
|
||||||
|
# 2. Push lokal data op (sange, danse, dans-tags)
|
||||||
|
# — playlister der kom fra serveren pushes IKKE
|
||||||
payload = self._build_push_payload()
|
payload = self._build_push_payload()
|
||||||
push_result = self._post("/sync/push", payload)
|
push_result = self._post("/sync/push", payload)
|
||||||
pull_result = self._get("/sync/pull")
|
|
||||||
pl_count = len(pull_result.get("my_playlists", []))
|
pl_count = len(pull_result.get("my_playlists", []))
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Sync OK — {len(payload['songs'])} sange, "
|
f"Sync OK — {len(payload['songs'])} sange, "
|
||||||
f"{len(payload['playlists'])} playlister, "
|
f"{len(payload['playlists'])} playlister, "
|
||||||
f"{pl_count} server-playlister"
|
f"{pl_count} server-playlister"
|
||||||
)
|
)
|
||||||
self._apply_pull(pull_result)
|
|
||||||
if on_done:
|
if on_done:
|
||||||
on_done({"push": push_result, "pull": pull_result})
|
on_done({"push": push_result, "pull": pull_result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -190,11 +201,11 @@ class SyncManager:
|
|||||||
"note": row["note"] or "",
|
"note": row["note"] or "",
|
||||||
})
|
})
|
||||||
|
|
||||||
# Playlister (kun navngivne — ikke __aktiv__)
|
# Playlister (kun lokalt oprettede — IKKE dem der kom fra serveren)
|
||||||
playlists = []
|
playlists = []
|
||||||
for pl in conn.execute(
|
for pl in conn.execute(
|
||||||
"SELECT id, name, description, tags FROM playlists "
|
"SELECT id, name, description, tags, api_project_id FROM playlists "
|
||||||
"WHERE name != '__aktiv__'"
|
"WHERE name != '__aktiv__' AND (api_project_id IS NULL OR api_project_id = '')"
|
||||||
).fetchall():
|
).fetchall():
|
||||||
pl_songs = []
|
pl_songs = []
|
||||||
for ps in conn.execute("""
|
for ps in conn.execute("""
|
||||||
@@ -222,13 +233,21 @@ class SyncManager:
|
|||||||
"songs": pl_songs,
|
"songs": pl_songs,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Slettede playlister — skal fjernes fra serveren
|
||||||
|
deleted = [
|
||||||
|
row["name"] for row in conn.execute(
|
||||||
|
"SELECT name FROM deleted_playlists"
|
||||||
|
).fetchall()
|
||||||
|
]
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
return {
|
return {
|
||||||
"songs": songs,
|
"songs": songs,
|
||||||
"dances": dances,
|
"dances": dances,
|
||||||
"song_dances": song_dances,
|
"song_dances": song_dances,
|
||||||
"song_alts": song_alts,
|
"song_alts": song_alts,
|
||||||
"playlists": playlists,
|
"playlists": playlists,
|
||||||
|
"deleted_playlists": deleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Anvend pull ───────────────────────────────────────────────────────────
|
# ── Anvend pull ───────────────────────────────────────────────────────────
|
||||||
@@ -255,42 +274,44 @@ class SyncManager:
|
|||||||
""", (d.get("choreographer",""), d.get("video_url",""),
|
""", (d.get("choreographer",""), d.get("video_url",""),
|
||||||
d.get("stepsheet_url",""), existing["id"]))
|
d.get("stepsheet_url",""), existing["id"]))
|
||||||
|
|
||||||
# Importer egne playlister fra server hvis de ikke findes lokalt
|
# Importer/opdater egne playlister fra server — server er sandhed
|
||||||
for pl in data.get("my_playlists", []):
|
for pl in data.get("my_playlists", []):
|
||||||
server_id = pl.get("server_id")
|
server_id = pl.get("server_id")
|
||||||
name = pl.get("name", "")
|
name = pl.get("name", "")
|
||||||
if not server_id or not name:
|
if not server_id or not name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Tjek om listen allerede eksisterer lokalt
|
|
||||||
existing = conn.execute(
|
existing = conn.execute(
|
||||||
"SELECT id FROM playlists WHERE api_project_id=?", (server_id,)
|
"SELECT id FROM playlists WHERE api_project_id=?", (server_id,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
continue # Allerede importeret — spring over
|
pl_id = existing["id"]
|
||||||
|
# Opdater navn hvis det er ændret på serveren
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE playlists SET name=? WHERE id=?", (name, pl_id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cur = conn.execute(
|
||||||
|
"INSERT INTO playlists (name, description, api_project_id, is_linked, server_permission) "
|
||||||
|
"VALUES (?,?,?,1,'edit')",
|
||||||
|
(name, pl.get("description",""), server_id)
|
||||||
|
)
|
||||||
|
pl_id = cur.lastrowid
|
||||||
|
|
||||||
# Opret liste
|
# Genindlæs sange fra serveren — server er sandhed
|
||||||
cur = conn.execute(
|
conn.execute("DELETE FROM playlist_songs WHERE playlist_id=?", (pl_id,))
|
||||||
"INSERT INTO playlists (name, description, api_project_id, is_linked, server_permission) "
|
|
||||||
"VALUES (?,?,?,1,'edit')",
|
|
||||||
(name, pl.get("description",""), server_id)
|
|
||||||
)
|
|
||||||
pl_id = cur.lastrowid
|
|
||||||
|
|
||||||
# Indsæt sange — opret dem lokalt hvis de ikke findes endnu
|
|
||||||
position = 1
|
position = 1
|
||||||
for song_data in pl.get("songs", []):
|
for song_data in pl.get("songs", []):
|
||||||
title = song_data.get("title", "")
|
title = song_data.get("title", "")
|
||||||
artist = song_data.get("artist", "")
|
artist = song_data.get("artist", "")
|
||||||
if not title:
|
if not title:
|
||||||
continue
|
continue
|
||||||
# Find sangen lokalt
|
|
||||||
local = conn.execute(
|
local = conn.execute(
|
||||||
"SELECT id FROM songs WHERE title=? AND artist=? LIMIT 1",
|
"SELECT id FROM songs WHERE title=? AND artist=? LIMIT 1",
|
||||||
(title, artist)
|
(title, artist)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if not local:
|
if not local:
|
||||||
# Opret som file_missing=1 — kobles til rigtig fil ved næste scan
|
|
||||||
import uuid
|
import uuid
|
||||||
new_id = str(uuid.uuid4())
|
new_id = str(uuid.uuid4())
|
||||||
conn.execute(
|
conn.execute(
|
||||||
|
|||||||
@@ -372,6 +372,12 @@ class MainWindow(QMainWindow):
|
|||||||
self._sync_debounce.setInterval(5000)
|
self._sync_debounce.setInterval(5000)
|
||||||
self._sync_debounce.timeout.connect(self._auto_sync)
|
self._sync_debounce.timeout.connect(self._auto_sync)
|
||||||
|
|
||||||
|
# Periodisk sync — kører hvert 10. minut
|
||||||
|
self._sync_periodic = QTimer(self)
|
||||||
|
self._sync_periodic.setInterval(10 * 60 * 1000)
|
||||||
|
self._sync_periodic.timeout.connect(self._manual_sync)
|
||||||
|
self._sync_periodic.start()
|
||||||
|
|
||||||
self._library_panel = LibraryPanel()
|
self._library_panel = LibraryPanel()
|
||||||
self._library_panel.set_preview_player(self._preview_player)
|
self._library_panel.set_preview_player(self._preview_player)
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,10 @@ class PlaylistManagerDialog(QDialog):
|
|||||||
try:
|
try:
|
||||||
from local.local_db import get_db
|
from local.local_db import get_db
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO deleted_playlists (name) "
|
||||||
|
"SELECT name FROM playlists WHERE id=?", (pl["id"],)
|
||||||
|
)
|
||||||
conn.execute("DELETE 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:
|
||||||
|
|||||||
Reference in New Issue
Block a user