Synk virker
This commit is contained in:
@@ -419,13 +419,13 @@ def get_all_songs_with_files(limit: int = 5000) -> list:
|
|||||||
|
|
||||||
# ── Playlister ────────────────────────────────────────────────────────────────
|
# ── Playlister ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def create_playlist(name: str, description: str = "") -> str:
|
def create_playlist(name: str, description: str = "", tags: str = "") -> str:
|
||||||
import uuid as _uuid
|
import uuid as _uuid
|
||||||
pl_id = str(_uuid.uuid4())
|
pl_id = str(_uuid.uuid4())
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO playlists (id, name, description) VALUES (?,?,?)",
|
"INSERT INTO playlists (id, name, description, tags) VALUES (?,?,?,?)",
|
||||||
(pl_id, name, description)
|
(pl_id, name, description, tags)
|
||||||
)
|
)
|
||||||
return pl_id
|
return pl_id
|
||||||
|
|
||||||
@@ -434,7 +434,9 @@ def get_playlists(tag_filter: str | None = None) -> list:
|
|||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
if tag_filter:
|
if tag_filter:
|
||||||
return conn.execute("""
|
return conn.execute("""
|
||||||
SELECT p.*, COUNT(ps.id) as song_count
|
SELECT p.id, p.name, p.description, p.tags, p.api_project_id,
|
||||||
|
p.is_linked, p.server_permission, p.is_deleted, p.created_at,
|
||||||
|
COUNT(ps.position) as song_count
|
||||||
FROM playlists p
|
FROM playlists p
|
||||||
LEFT JOIN playlist_songs ps ON ps.playlist_id = p.id
|
LEFT JOIN playlist_songs ps ON ps.playlist_id = p.id
|
||||||
WHERE p.name != '__aktiv__' AND p.is_deleted = 0
|
WHERE p.name != '__aktiv__' AND p.is_deleted = 0
|
||||||
@@ -444,7 +446,9 @@ def get_playlists(tag_filter: str | None = None) -> list:
|
|||||||
f"%, {tag_filter}", tag_filter)).fetchall()
|
f"%, {tag_filter}", tag_filter)).fetchall()
|
||||||
else:
|
else:
|
||||||
return conn.execute("""
|
return conn.execute("""
|
||||||
SELECT p.*, COUNT(ps.id) as song_count
|
SELECT p.id, p.name, p.description, p.tags, p.api_project_id,
|
||||||
|
p.is_linked, p.server_permission, p.is_deleted, p.created_at,
|
||||||
|
COUNT(ps.position) as song_count
|
||||||
FROM playlists p
|
FROM playlists p
|
||||||
LEFT JOIN playlist_songs ps ON ps.playlist_id = p.id
|
LEFT JOIN playlist_songs ps ON ps.playlist_id = p.id
|
||||||
WHERE p.name != '__aktiv__' AND p.is_deleted = 0
|
WHERE p.name != '__aktiv__' AND p.is_deleted = 0
|
||||||
@@ -506,6 +510,10 @@ def add_song_to_playlist(playlist_id: str, song_id: str,
|
|||||||
|
|
||||||
# ── Dans-tags ─────────────────────────────────────────────────────────────────
|
# ── Dans-tags ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def get_all_playlist_tags() -> list[str]:
|
||||||
|
return get_playlist_tags()
|
||||||
|
|
||||||
|
|
||||||
def get_playlist_tags() -> list[str]:
|
def get_playlist_tags() -> list[str]:
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class SyncManager:
|
class SyncManager:
|
||||||
|
|
||||||
def __init__(self, api_url: str, db_path: str):
|
def __init__(self, api_url: str = "", db_path: str = "",
|
||||||
self._api_url = api_url.rstrip("/")
|
server_url: str = "", token: str | None = None):
|
||||||
|
# Støt både api_url og server_url som parameter-navn
|
||||||
|
self._api_url = (api_url or server_url).rstrip("/")
|
||||||
self._db_path = db_path
|
self._db_path = db_path
|
||||||
self._token: str | None = None
|
self._token: str | None = token
|
||||||
|
|
||||||
def set_token(self, token: str):
|
def set_token(self, token: str):
|
||||||
self._token = token
|
self._token = token
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from PyQt6.QtGui import QColor
|
|||||||
class PlaylistBrowserDialog(QDialog):
|
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(str, str) # playlist_id, name
|
||||||
sync_requested = pyqtSignal() # bed main_window om at køre sync
|
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,
|
||||||
@@ -315,7 +315,9 @@ class PlaylistBrowserDialog(QDialog):
|
|||||||
)
|
)
|
||||||
for i, song in enumerate(self._current_songs, start=1):
|
for i, song in enumerate(self._current_songs, start=1):
|
||||||
if song.get("id"):
|
if song.get("id"):
|
||||||
add_song_to_playlist(pl_id, song["id"], position=i)
|
add_song_to_playlist(pl_id, song["id"],
|
||||||
|
file_id=song.get("file_id"),
|
||||||
|
position=i)
|
||||||
self.playlist_selected.emit(pl_id, name)
|
self.playlist_selected.emit(pl_id, name)
|
||||||
self.accept()
|
self.accept()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -327,7 +329,9 @@ class PlaylistBrowserDialog(QDialog):
|
|||||||
pl_id = create_playlist(name, tags=tags)
|
pl_id = create_playlist(name, tags=tags)
|
||||||
for i, song in enumerate(self._current_songs, start=1):
|
for i, song in enumerate(self._current_songs, start=1):
|
||||||
if song.get("id"):
|
if song.get("id"):
|
||||||
add_song_to_playlist(pl_id, song["id"], position=i)
|
add_song_to_playlist(pl_id, song["id"],
|
||||||
|
file_id=song.get("file_id"),
|
||||||
|
position=i)
|
||||||
self.playlist_selected.emit(pl_id, name)
|
self.playlist_selected.emit(pl_id, name)
|
||||||
self.accept()
|
self.accept()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -515,4 +519,4 @@ class PlaylistBrowserDialog(QDialog):
|
|||||||
f"'{name}' er nu linket til server-listen.\n"
|
f"'{name}' er nu linket til server-listen.\n"
|
||||||
f"Du har rettighed til at {perm_text} listen.\n\n"
|
f"Du har rettighed til at {perm_text} listen.\n\n"
|
||||||
f"{matched} af {len(pl_data.get('songs', []))} sange fundet lokalt."
|
f"{matched} af {len(pl_data.get('songs', []))} sange fundet lokalt."
|
||||||
)
|
)
|
||||||
@@ -50,8 +50,8 @@ class PlaylistPanel(QWidget):
|
|||||||
self._statuses: list[str] = []
|
self._statuses: list[str] = []
|
||||||
self._current_idx = -1
|
self._current_idx = -1
|
||||||
self._song_ended = False
|
self._song_ended = False
|
||||||
self._active_playlist_id: int | None = None
|
self._active_playlist_id: str | None = None
|
||||||
self._named_playlist_id: int | None = None # den indlæste/gemte navngivne liste
|
self._named_playlist_id: str | None = None # den indlæste/gemte navngivne liste
|
||||||
self._build_ui()
|
self._build_ui()
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
# Autogem-timer — venter 800ms efter sidst ændring
|
# Autogem-timer — venter 800ms efter sidst ændring
|
||||||
@@ -317,7 +317,7 @@ class PlaylistPanel(QWidget):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_named_playlist_id(self) -> int | None:
|
def get_named_playlist_id(self) -> str | None:
|
||||||
return self._named_playlist_id
|
return self._named_playlist_id
|
||||||
|
|
||||||
def next_playable_idx(self) -> int | None:
|
def next_playable_idx(self) -> int | None:
|
||||||
@@ -360,11 +360,14 @@ class PlaylistPanel(QWidget):
|
|||||||
for i, song in enumerate(self._songs, start=1):
|
for i, song in enumerate(self._songs, start=1):
|
||||||
if song.get("id"):
|
if song.get("id"):
|
||||||
status = self._statuses[i-1] if i-1 < len(self._statuses) else "pending"
|
status = self._statuses[i-1] if i-1 < len(self._statuses) else "pending"
|
||||||
|
import uuid as _uuid
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO playlist_songs "
|
"INSERT INTO playlist_songs "
|
||||||
"(playlist_id, song_id, position, status, is_workshop, dance_override) "
|
"(id, playlist_id, song_id, file_id, position, status, is_workshop, dance_override) "
|
||||||
"VALUES (?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?)",
|
||||||
(self._named_playlist_id, song["id"], i, status,
|
(str(_uuid.uuid4()), self._named_playlist_id,
|
||||||
|
song["id"], song.get("file_id"),
|
||||||
|
i, status,
|
||||||
1 if song.get("is_workshop") else 0,
|
1 if song.get("is_workshop") else 0,
|
||||||
song.get("active_dance") or "")
|
song.get("active_dance") or "")
|
||||||
)
|
)
|
||||||
@@ -374,7 +377,7 @@ class PlaylistPanel(QWidget):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._lbl_autosave.setText("⚠ gemfejl")
|
self._lbl_autosave.setText("⚠ gemfejl")
|
||||||
|
|
||||||
def _save_named_playlist_id(self, pl_id: int | None):
|
def _save_named_playlist_id(self, pl_id: str | None):
|
||||||
"""Gem hvilken navngiven liste der er aktiv — til brug ved næste opstart."""
|
"""Gem hvilken navngiven liste der er aktiv — til brug ved næste opstart."""
|
||||||
from PyQt6.QtCore import QSettings
|
from PyQt6.QtCore import QSettings
|
||||||
s = QSettings("LineDance", "Player")
|
s = QSettings("LineDance", "Player")
|
||||||
@@ -388,7 +391,7 @@ class PlaylistPanel(QWidget):
|
|||||||
try:
|
try:
|
||||||
from PyQt6.QtCore import QSettings
|
from PyQt6.QtCore import QSettings
|
||||||
s = QSettings("LineDance", "Player")
|
s = QSettings("LineDance", "Player")
|
||||||
pl_id = s.value("session/named_playlist_id", None, type=int)
|
pl_id = s.value("session/named_playlist_id", None, type=str)
|
||||||
if not pl_id:
|
if not pl_id:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -410,11 +413,14 @@ class PlaylistPanel(QWidget):
|
|||||||
# file_missing betyder bare at filen ikke er på denne maskine
|
# file_missing betyder bare at filen ikke er på denne maskine
|
||||||
songs_raw = conn.execute("""
|
songs_raw = conn.execute("""
|
||||||
SELECT s.id, s.title, s.artist, s.album,
|
SELECT s.id, s.title, s.artist, s.album,
|
||||||
s.bpm, s.duration_sec, s.file_format,
|
s.bpm, s.duration_sec,
|
||||||
s.local_path, s.file_missing,
|
ps.file_id,
|
||||||
|
f.local_path, f.file_format,
|
||||||
|
COALESCE(f.file_missing, 1) as file_missing,
|
||||||
ps.position, ps.status, ps.is_workshop, ps.dance_override
|
ps.position, ps.status, ps.is_workshop, ps.dance_override
|
||||||
FROM playlist_songs ps
|
FROM playlist_songs ps
|
||||||
JOIN songs s ON s.id = ps.song_id
|
JOIN songs s ON s.id = ps.song_id
|
||||||
|
LEFT JOIN files f ON f.id = ps.file_id
|
||||||
WHERE ps.playlist_id=? ORDER BY ps.position
|
WHERE ps.playlist_id=? ORDER BY ps.position
|
||||||
""", (pl_id,)).fetchall()
|
""", (pl_id,)).fetchall()
|
||||||
|
|
||||||
@@ -433,13 +439,13 @@ class PlaylistPanel(QWidget):
|
|||||||
local_path = row["local_path"] or ""
|
local_path = row["local_path"] or ""
|
||||||
file_missing = bool(row["file_missing"])
|
file_missing = bool(row["file_missing"])
|
||||||
|
|
||||||
# Forsøg at finde filen lokalt hvis den mangler på denne maskine
|
# Forsøg at finde en anden fil lokalt hvis den specifikke mangler
|
||||||
if file_missing or not local_path:
|
if file_missing or not local_path:
|
||||||
match = conn.execute("""
|
match = conn.execute(
|
||||||
SELECT local_path FROM songs
|
"SELECT f.local_path FROM files f "
|
||||||
WHERE title=? AND artist=? AND file_missing=0
|
"WHERE f.song_id=? AND f.file_missing=0 LIMIT 1",
|
||||||
LIMIT 1
|
(row["id"],)
|
||||||
""", (row["title"], row["artist"])).fetchone()
|
).fetchone()
|
||||||
if match:
|
if match:
|
||||||
local_path = match["local_path"]
|
local_path = match["local_path"]
|
||||||
file_missing = False
|
file_missing = False
|
||||||
@@ -451,6 +457,7 @@ class PlaylistPanel(QWidget):
|
|||||||
"album": row["album"] or "",
|
"album": row["album"] or "",
|
||||||
"bpm": row["bpm"] or 0,
|
"bpm": row["bpm"] or 0,
|
||||||
"duration_sec": row["duration_sec"] or 0,
|
"duration_sec": row["duration_sec"] or 0,
|
||||||
|
"file_id": row["file_id"] if "file_id" in row.keys() else None,
|
||||||
"local_path": local_path,
|
"local_path": local_path,
|
||||||
"file_format": row["file_format"] or "",
|
"file_format": row["file_format"] or "",
|
||||||
"file_missing": file_missing,
|
"file_missing": file_missing,
|
||||||
@@ -551,11 +558,14 @@ class PlaylistPanel(QWidget):
|
|||||||
for i, song in enumerate(self._songs, start=1):
|
for i, song in enumerate(self._songs, start=1):
|
||||||
if song.get("id"):
|
if song.get("id"):
|
||||||
status = self._statuses[i-1] if i-1 < len(self._statuses) else "pending"
|
status = self._statuses[i-1] if i-1 < len(self._statuses) else "pending"
|
||||||
|
import uuid as _uuid
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO playlist_songs "
|
"INSERT INTO playlist_songs "
|
||||||
"(playlist_id, song_id, position, status, is_workshop, dance_override) "
|
"(id, playlist_id, song_id, file_id, position, status, is_workshop, dance_override) "
|
||||||
"VALUES (?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?)",
|
||||||
(self._named_playlist_id, song["id"], i, status,
|
(str(_uuid.uuid4()), self._named_playlist_id,
|
||||||
|
song["id"], song.get("file_id"),
|
||||||
|
i, status,
|
||||||
1 if song.get("is_workshop") else 0,
|
1 if song.get("is_workshop") else 0,
|
||||||
song.get("active_dance") or "")
|
song.get("active_dance") or "")
|
||||||
)
|
)
|
||||||
@@ -589,7 +599,7 @@ class PlaylistPanel(QWidget):
|
|||||||
dialog.sync_requested.connect(self._request_sync)
|
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: str, pl_name: str):
|
||||||
try:
|
try:
|
||||||
from local.local_db import get_db
|
from local.local_db import get_db
|
||||||
|
|
||||||
@@ -612,11 +622,14 @@ class PlaylistPanel(QWidget):
|
|||||||
# JOIN songs — sangen er altid i songs tabellen (oprettet ved pull med file_missing=1)
|
# JOIN songs — sangen er altid i songs tabellen (oprettet ved pull med file_missing=1)
|
||||||
songs_raw = conn.execute("""
|
songs_raw = conn.execute("""
|
||||||
SELECT s.id, s.title, s.artist, s.album,
|
SELECT s.id, s.title, s.artist, s.album,
|
||||||
s.bpm, s.duration_sec, s.file_format,
|
s.bpm, s.duration_sec,
|
||||||
s.local_path, s.file_missing,
|
ps.file_id,
|
||||||
|
f.local_path, f.file_format,
|
||||||
|
COALESCE(f.file_missing, 1) as file_missing,
|
||||||
ps.position, ps.status, ps.is_workshop, ps.dance_override
|
ps.position, ps.status, ps.is_workshop, ps.dance_override
|
||||||
FROM playlist_songs ps
|
FROM playlist_songs ps
|
||||||
JOIN songs s ON s.id = ps.song_id
|
JOIN songs s ON s.id = ps.song_id
|
||||||
|
LEFT JOIN files f ON f.id = ps.file_id
|
||||||
WHERE ps.playlist_id=? ORDER BY ps.position
|
WHERE ps.playlist_id=? ORDER BY ps.position
|
||||||
""", (pl_id,)).fetchall()
|
""", (pl_id,)).fetchall()
|
||||||
songs = []
|
songs = []
|
||||||
@@ -625,7 +638,7 @@ class PlaylistPanel(QWidget):
|
|||||||
for row in songs_raw:
|
for row in songs_raw:
|
||||||
dances = conn.execute("""
|
dances = conn.execute("""
|
||||||
SELECT d.name FROM song_dances sd
|
SELECT d.name FROM song_dances sd
|
||||||
JOIN dances d ON d.id = sad.dance_id
|
JOIN dances d ON d.id = sd.dance_id
|
||||||
WHERE sd.song_id=? ORDER BY sd.dance_order
|
WHERE sd.song_id=? ORDER BY sd.dance_order
|
||||||
""", (row["id"],)).fetchall()
|
""", (row["id"],)).fetchall()
|
||||||
dance_names = [d["name"] for d in dances]
|
dance_names = [d["name"] for d in dances]
|
||||||
@@ -635,13 +648,13 @@ class PlaylistPanel(QWidget):
|
|||||||
local_path = row["local_path"] or ""
|
local_path = row["local_path"] or ""
|
||||||
file_missing = bool(row["file_missing"])
|
file_missing = bool(row["file_missing"])
|
||||||
|
|
||||||
# Forsøg at finde filen lokalt hvis den mangler på denne maskine
|
# Forsøg at finde en anden fil lokalt hvis den specifikke mangler
|
||||||
if file_missing or not local_path:
|
if file_missing or not local_path:
|
||||||
match = conn.execute("""
|
match = conn.execute(
|
||||||
SELECT local_path FROM songs
|
"SELECT f.local_path FROM files f "
|
||||||
WHERE title=? AND artist=? AND file_missing=0
|
"WHERE f.song_id=? AND f.file_missing=0 LIMIT 1",
|
||||||
LIMIT 1
|
(row["id"],)
|
||||||
""", (row["title"], row["artist"])).fetchone()
|
).fetchone()
|
||||||
if match:
|
if match:
|
||||||
local_path = match["local_path"]
|
local_path = match["local_path"]
|
||||||
file_missing = False
|
file_missing = False
|
||||||
@@ -654,6 +667,7 @@ class PlaylistPanel(QWidget):
|
|||||||
"album": row["album"] or "",
|
"album": row["album"] or "",
|
||||||
"bpm": row["bpm"] or 0,
|
"bpm": row["bpm"] or 0,
|
||||||
"duration_sec": row["duration_sec"] or 0,
|
"duration_sec": row["duration_sec"] or 0,
|
||||||
|
"file_id": row["file_id"] if "file_id" in row.keys() else None,
|
||||||
"local_path": local_path,
|
"local_path": local_path,
|
||||||
"file_format": row["file_format"] or "",
|
"file_format": row["file_format"] or "",
|
||||||
"file_missing": file_missing,
|
"file_missing": file_missing,
|
||||||
@@ -798,53 +812,91 @@ class PlaylistPanel(QWidget):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _pull_linked_playlist(self, pl_id: int, server_id: str):
|
def _pull_linked_playlist(self, pl_id: str, server_id: str):
|
||||||
"""Hent seneste version af en linket liste fra serveren."""
|
"""Hent seneste version af en linket liste fra serveren — i baggrundstråd."""
|
||||||
try:
|
import threading
|
||||||
from ui.settings_dialog import load_settings
|
import uuid
|
||||||
from local.local_db import get_db, DB_PATH
|
|
||||||
s = load_settings()
|
|
||||||
server_url = s.get("server_url", "").rstrip("/")
|
|
||||||
# Hent token fra main_window
|
|
||||||
mw = self.window()
|
|
||||||
token = getattr(mw, "_api_token", None)
|
|
||||||
if not token or not server_url:
|
|
||||||
return
|
|
||||||
|
|
||||||
import urllib.request, json
|
def _do_pull():
|
||||||
req = urllib.request.Request(
|
try:
|
||||||
f"{server_url}/sharing/playlists/{server_id}",
|
from ui.settings_dialog import load_settings
|
||||||
headers={"Authorization": f"Bearer {token}"}
|
from local.local_db import DB_PATH
|
||||||
)
|
import sqlite3, urllib.request, json
|
||||||
with urllib.request.urlopen(req, timeout=8) as resp:
|
|
||||||
pl_data = json.loads(resp.read())
|
s = load_settings()
|
||||||
|
server_url = s.get("server_url", "").rstrip("/")
|
||||||
|
mw = self.window()
|
||||||
|
token = getattr(mw, "_api_token", None)
|
||||||
|
if not token or not server_url:
|
||||||
|
return
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{server_url}/sharing/playlists/{server_id}",
|
||||||
|
headers={"Authorization": f"Bearer {token}"}
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=8) as resp:
|
||||||
|
pl_data = json.loads(resp.read())
|
||||||
|
|
||||||
|
conn = sqlite3.connect(str(DB_PATH), timeout=10)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
conn.execute("PRAGMA journal_mode=WAL")
|
||||||
|
conn.execute("DELETE FROM playlist_songs WHERE playlist_id=?", (pl_id,))
|
||||||
|
|
||||||
|
position = 1
|
||||||
|
for song_data in pl_data.get("songs", []):
|
||||||
|
title = song_data.get("title", "")
|
||||||
|
artist = song_data.get("artist", "")
|
||||||
|
if not title:
|
||||||
|
continue
|
||||||
|
# Find sang via titel+artist
|
||||||
|
local = conn.execute(
|
||||||
|
"SELECT s.id FROM songs s "
|
||||||
|
"JOIN files f ON f.song_id = s.id AND f.file_missing=0 "
|
||||||
|
"WHERE s.title=? AND s.artist=? LIMIT 1",
|
||||||
|
(title, artist)
|
||||||
|
).fetchone()
|
||||||
|
if not local:
|
||||||
|
# Sang mangler lokalt — opret som missing
|
||||||
|
local = conn.execute(
|
||||||
|
"SELECT id FROM songs WHERE title=? AND artist=? LIMIT 1",
|
||||||
|
(title, artist)
|
||||||
|
).fetchone()
|
||||||
|
if not local:
|
||||||
|
new_id = str(uuid.uuid4())
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO songs (id, title, artist) VALUES (?,?,?)",
|
||||||
|
(new_id, title, artist)
|
||||||
|
)
|
||||||
|
song_id = new_id
|
||||||
|
else:
|
||||||
|
song_id = local["id"]
|
||||||
|
|
||||||
|
# Find fil
|
||||||
|
file_row = conn.execute(
|
||||||
|
"SELECT id FROM files WHERE song_id=? AND file_missing=0 LIMIT 1",
|
||||||
|
(song_id,)
|
||||||
|
).fetchone()
|
||||||
|
file_id = file_row["id"] if file_row else None
|
||||||
|
|
||||||
# Opdater lokal liste med server-data
|
|
||||||
import sqlite3
|
|
||||||
conn = sqlite3.connect(str(DB_PATH))
|
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
conn.execute("DELETE FROM playlist_songs WHERE playlist_id=?", (pl_id,))
|
|
||||||
for song_data in pl_data.get("songs", []):
|
|
||||||
local = conn.execute(
|
|
||||||
"SELECT id FROM songs WHERE title=? AND artist=? AND file_missing=0",
|
|
||||||
(song_data["title"], song_data["artist"])
|
|
||||||
).fetchone()
|
|
||||||
if local:
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO playlist_songs "
|
"INSERT INTO playlist_songs "
|
||||||
"(playlist_id, song_id, position, status, is_workshop, dance_override) "
|
"(id, playlist_id, song_id, file_id, position, status, is_workshop, dance_override) "
|
||||||
"VALUES (?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?)",
|
||||||
(pl_id, local["id"], song_data["position"],
|
(str(uuid.uuid4()), pl_id, song_id, file_id,
|
||||||
song_data.get("status", "pending"),
|
position, song_data.get("status", "pending"),
|
||||||
1 if song_data.get("is_workshop") else 0,
|
1 if song_data.get("is_workshop") else 0,
|
||||||
song_data.get("dance_override") or "")
|
song_data.get("dance_override") or "")
|
||||||
)
|
)
|
||||||
conn.commit()
|
position += 1
|
||||||
conn.close()
|
|
||||||
except Exception as e:
|
|
||||||
pass # Offline — brug lokalt cachet version
|
|
||||||
|
|
||||||
def _push_linked_playlist(self, pl_id: int, server_id: str):
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
pass # Offline — brug lokalt cachet version
|
||||||
|
|
||||||
|
threading.Thread(target=_do_pull, daemon=True).start()
|
||||||
|
|
||||||
|
def _push_linked_playlist(self, pl_id: str, server_id: str):
|
||||||
"""Push ændringer til server for en linket liste."""
|
"""Push ændringer til server for en linket liste."""
|
||||||
try:
|
try:
|
||||||
from ui.settings_dialog import load_settings
|
from ui.settings_dialog import load_settings
|
||||||
@@ -969,42 +1021,41 @@ class PlaylistPanel(QWidget):
|
|||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
for song in self._songs:
|
for song in self._songs:
|
||||||
path = song.get("local_path", "")
|
path = song.get("local_path", "")
|
||||||
# Grøn — eksisterer og tilgængeligt
|
# Grøn — filen eksisterer lokalt
|
||||||
if path and Path(path).exists():
|
if path and Path(path).exists():
|
||||||
song["availability"] = "green"
|
song["availability"] = "green"
|
||||||
song["file_missing"] = False
|
song["file_missing"] = False
|
||||||
# Opdater songs tabellen
|
# Opdater files tabellen
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE songs SET file_missing=0, local_path=? WHERE id=?",
|
"UPDATE files SET file_missing=0 WHERE local_path=?",
|
||||||
(path, song["id"])
|
(path,)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Forsøg auto-match via titel+artist
|
# Forsøg auto-match via titel+artist i files tabellen
|
||||||
title = song.get("title", "")
|
title = song.get("title", "")
|
||||||
artist = song.get("artist", "")
|
artist = song.get("artist", "")
|
||||||
match = conn.execute("""
|
match = conn.execute("""
|
||||||
SELECT id, local_path FROM songs
|
SELECT f.id as file_id, f.local_path, s.id as song_id
|
||||||
WHERE title=? AND artist=? AND file_missing=0
|
FROM files f
|
||||||
AND local_path IS NOT NULL AND local_path != ''
|
JOIN songs s ON s.id = f.song_id
|
||||||
|
WHERE s.title=? AND s.artist=? AND f.file_missing=0
|
||||||
|
AND f.local_path IS NOT NULL AND f.local_path != ''
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""", (title, artist)).fetchone()
|
""", (title, artist)).fetchone()
|
||||||
|
|
||||||
if match and Path(match["local_path"]).exists():
|
if match and Path(match["local_path"]).exists():
|
||||||
song["local_path"] = match["local_path"]
|
song["local_path"] = match["local_path"]
|
||||||
song["id"] = match["id"]
|
song["file_id"] = match["file_id"]
|
||||||
song["availability"] = "green"
|
song["availability"] = "green"
|
||||||
song["file_missing"] = False
|
song["file_missing"] = False
|
||||||
# Opdater playlist_songs til at pege på den fundne sang
|
# Opdater playlist_songs til at pege på den fundne fil
|
||||||
if self._named_playlist_id:
|
if self._named_playlist_id:
|
||||||
conn.execute("""
|
conn.execute(
|
||||||
UPDATE playlist_songs SET song_id=?
|
"UPDATE playlist_songs SET file_id=? "
|
||||||
WHERE playlist_id=? AND song_id=(
|
"WHERE playlist_id=? AND song_id=?",
|
||||||
SELECT id FROM songs
|
(match["file_id"], self._named_playlist_id, song["id"])
|
||||||
WHERE title=? AND artist=?
|
)
|
||||||
LIMIT 1
|
|
||||||
)
|
|
||||||
""", (match["id"], self._named_playlist_id, title, artist))
|
|
||||||
else:
|
else:
|
||||||
song["availability"] = "red"
|
song["availability"] = "red"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user