174 lines
6.2 KiB
Python
174 lines
6.2 KiB
Python
"""
|
|
linked_playlist.py — Håndter linkede server-playlister.
|
|
Pull ved åbning, push ved gem.
|
|
"""
|
|
import json
|
|
import sqlite3
|
|
import urllib.request
|
|
import urllib.error
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class LinkedPlaylistManager:
|
|
def __init__(self, db_path: str, server_url: str, token: str):
|
|
self._db_path = db_path
|
|
self._server_url = server_url.rstrip("/")
|
|
self._token = token
|
|
|
|
def _headers(self):
|
|
return {
|
|
"Content-Type": "application/json",
|
|
"Authorization": f"Bearer {self._token}",
|
|
}
|
|
|
|
def pull(self, playlist_id: int) -> list[dict]:
|
|
"""
|
|
Hent seneste version fra serveren og opdater lokal liste.
|
|
Returnerer sang-liste klar til playlist_panel.
|
|
"""
|
|
conn = sqlite3.connect(self._db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
|
|
pl = conn.execute(
|
|
"SELECT api_project_id, server_permission FROM playlists WHERE id=?",
|
|
(playlist_id,)
|
|
).fetchone()
|
|
if not pl or not pl["api_project_id"]:
|
|
conn.close()
|
|
return []
|
|
|
|
# Hent fra server
|
|
req = urllib.request.Request(
|
|
f"{self._server_url}/sharing/playlists/{pl['api_project_id']}",
|
|
headers=self._headers()
|
|
)
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
data = json.loads(resp.read())
|
|
|
|
# Slet eksisterende sange og erstat med server-version
|
|
conn.execute(
|
|
"DELETE FROM playlist_songs WHERE playlist_id=?", (playlist_id,)
|
|
)
|
|
|
|
songs = []
|
|
for song_data in sorted(data.get("songs", []), key=lambda x: x["position"]):
|
|
# Match lokalt på titel+artist
|
|
local = conn.execute(
|
|
"SELECT id, local_path, bpm, duration_sec, file_format, file_missing "
|
|
"FROM songs WHERE title=? AND artist=? AND file_missing=0 LIMIT 1",
|
|
(song_data["title"], song_data["artist"])
|
|
).fetchone()
|
|
|
|
if local:
|
|
conn.execute("""
|
|
INSERT OR IGNORE INTO playlist_songs
|
|
(playlist_id, song_id, position, status, is_workshop, dance_override)
|
|
VALUES (?,?,?,?,?,?)
|
|
""", (
|
|
playlist_id, local["id"],
|
|
song_data["position"], song_data["status"],
|
|
1 if song_data.get("is_workshop") else 0,
|
|
song_data.get("dance_override", ""),
|
|
))
|
|
|
|
# Hent danse
|
|
dances = conn.execute("""
|
|
SELECT d.name FROM song_dances sd
|
|
JOIN dances d ON d.id = sd.dance_id
|
|
WHERE sd.song_id=? ORDER BY sd.dance_order
|
|
""", (local["id"],)).fetchall()
|
|
|
|
songs.append({
|
|
"id": local["id"],
|
|
"title": song_data["title"],
|
|
"artist": song_data["artist"],
|
|
"album": song_data.get("album", ""),
|
|
"bpm": local["bpm"] or 0,
|
|
"duration_sec": local["duration_sec"] or 0,
|
|
"local_path": local["local_path"],
|
|
"file_format": local["file_format"] or "",
|
|
"file_missing": False,
|
|
"dances": [d["name"] for d in dances],
|
|
"active_dance": song_data.get("dance_override", ""),
|
|
"is_workshop": bool(song_data.get("is_workshop")),
|
|
"status": song_data.get("status", "pending"),
|
|
})
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
return songs
|
|
|
|
def push(self, playlist_id: int):
|
|
"""Push lokal version til serveren."""
|
|
conn = sqlite3.connect(self._db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
|
|
pl = conn.execute(
|
|
"SELECT api_project_id, server_permission, name FROM playlists WHERE id=?",
|
|
(playlist_id,)
|
|
).fetchone()
|
|
if not pl or not pl["api_project_id"]:
|
|
conn.close()
|
|
raise Exception("Playlisten er ikke linket til serveren")
|
|
|
|
if pl["server_permission"] not in ("edit",):
|
|
conn.close()
|
|
raise Exception(f"Du har ikke rettighed til at redigere denne liste (du har: {pl['server_permission']})")
|
|
|
|
# Byg payload til sync/push
|
|
songs_raw = conn.execute("""
|
|
SELECT s.id, s.title, s.artist, s.album, s.bpm, s.duration_sec,
|
|
s.file_format, ps.position, ps.status, ps.is_workshop, ps.dance_override
|
|
FROM playlist_songs ps
|
|
JOIN songs s ON s.id = ps.song_id
|
|
WHERE ps.playlist_id=? ORDER BY ps.position
|
|
""", (playlist_id,)).fetchall()
|
|
conn.close()
|
|
|
|
from local.sync_manager import SyncManager
|
|
mgr = SyncManager(self._db_path, self._server_url, self._token)
|
|
|
|
# Byg mini-payload med kun denne playliste
|
|
song_ids = [row["id"] for row in songs_raw]
|
|
songs_payload = []
|
|
for row in songs_raw:
|
|
songs_payload.append({
|
|
"local_id": str(row["id"]),
|
|
"title": row["title"] or "",
|
|
"artist": row["artist"] or "",
|
|
"album": row["album"] or "",
|
|
"bpm": row["bpm"] or 0,
|
|
"duration_sec": row["duration_sec"] or 0,
|
|
"file_format": row["file_format"] or "",
|
|
})
|
|
|
|
pl_payload = [{
|
|
"local_id": str(playlist_id),
|
|
"name": pl["name"],
|
|
"description": "",
|
|
"tags": "",
|
|
"visibility": "shared",
|
|
"songs": [
|
|
{
|
|
"song_local_id": str(row["id"]),
|
|
"position": int(row["position"]),
|
|
"status": row["status"] or "pending",
|
|
"is_workshop": bool(row["is_workshop"]),
|
|
"dance_override": row["dance_override"] or "",
|
|
}
|
|
for row in songs_raw
|
|
]
|
|
}]
|
|
|
|
result = mgr._post("/sync/push", {
|
|
"songs": songs_payload,
|
|
"dances": [],
|
|
"song_dances": [],
|
|
"song_alts": [],
|
|
"playlists": pl_payload,
|
|
})
|
|
return result
|