Bedre sync
This commit is contained in:
@@ -88,6 +88,7 @@ CREATE TABLE IF NOT EXISTS dance_levels (
|
||||
);
|
||||
|
||||
-- Danse
|
||||
-- Dans + niveau + koreograf er unik kombination
|
||||
CREATE TABLE IF NOT EXISTS dances (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
@@ -98,7 +99,7 @@ CREATE TABLE IF NOT EXISTS dances (
|
||||
notes TEXT NOT NULL DEFAULT '',
|
||||
use_count INTEGER NOT NULL DEFAULT 1,
|
||||
source TEXT NOT NULL DEFAULT 'local',
|
||||
UNIQUE(name, level_id)
|
||||
UNIQUE(name, level_id, choreographer)
|
||||
);
|
||||
|
||||
-- Sang-dans tags
|
||||
@@ -582,4 +583,111 @@ def upsert_dance_levels(levels: list[dict]):
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
sort_order=excluded.sort_order,
|
||||
description=excluded.description
|
||||
""", lvl)
|
||||
""", lvl)
|
||||
|
||||
# ── Dans-søgning (til DancePickerDialog) ─────────────────────────────────────
|
||||
|
||||
def get_dance_suggestions(prefix: str = "", limit: int = 20) -> list:
|
||||
"""Hent dans-forslag med niveau og koreograf til autoudfyld."""
|
||||
with get_db() as conn:
|
||||
pattern = f"{prefix}%"
|
||||
return conn.execute("""
|
||||
SELECT d.id, d.name, d.level_id, dl.name as level_name,
|
||||
d.choreographer, d.use_count
|
||||
FROM dances d
|
||||
LEFT JOIN dance_levels dl ON dl.id = d.level_id
|
||||
WHERE d.name LIKE ? COLLATE NOCASE
|
||||
ORDER BY d.use_count DESC, d.name
|
||||
LIMIT ?
|
||||
""", (pattern, limit)).fetchall()
|
||||
|
||||
|
||||
def get_choreographer_suggestions(prefix: str = "", limit: int = 15) -> list[str]:
|
||||
"""Hent koreograf-navne til autoudfyld."""
|
||||
with get_db() as conn:
|
||||
pattern = f"{prefix}%"
|
||||
rows = conn.execute("""
|
||||
SELECT DISTINCT choreographer FROM dances
|
||||
WHERE choreographer != '' AND choreographer LIKE ? COLLATE NOCASE
|
||||
ORDER BY choreographer
|
||||
LIMIT ?
|
||||
""", (pattern, limit)).fetchall()
|
||||
return [r["choreographer"] for r in rows]
|
||||
|
||||
# ── Dans-søgning (til DancePickerDialog og DanceInfoDialog) ──────────────────
|
||||
|
||||
def get_dance_suggestions(prefix: str = "", limit: int = 20) -> list:
|
||||
"""Hent dans-forslag med niveau og koreograf til autoudfyld."""
|
||||
with get_db() as conn:
|
||||
pattern = f"{prefix}%"
|
||||
return conn.execute("""
|
||||
SELECT d.id, d.name, d.level_id, dl.name as level_name,
|
||||
d.choreographer, d.use_count
|
||||
FROM dances d
|
||||
LEFT JOIN dance_levels dl ON dl.id = d.level_id
|
||||
WHERE d.name LIKE ? COLLATE NOCASE
|
||||
ORDER BY d.use_count DESC, d.name
|
||||
LIMIT ?
|
||||
""", (pattern, limit)).fetchall()
|
||||
|
||||
|
||||
def get_choreographer_suggestions(prefix: str = "", limit: int = 15) -> list[str]:
|
||||
"""Hent koreograf-navne til autoudfyld."""
|
||||
with get_db() as conn:
|
||||
pattern = f"{prefix}%"
|
||||
rows = conn.execute("""
|
||||
SELECT DISTINCT choreographer FROM dances
|
||||
WHERE choreographer != '' AND choreographer LIKE ? COLLATE NOCASE
|
||||
ORDER BY choreographer
|
||||
LIMIT ?
|
||||
""", (pattern, limit)).fetchall()
|
||||
return [r["choreographer"] for r in rows]
|
||||
|
||||
|
||||
def get_dances_for_song(song_id: str) -> list:
|
||||
"""Hent alle danse tagget på en sang med niveau og koreograf."""
|
||||
with get_db() as conn:
|
||||
return conn.execute("""
|
||||
SELECT d.id, d.name, dl.name as level_name, d.choreographer,
|
||||
d.video_url, d.stepsheet_url, d.notes, sd.dance_order
|
||||
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
|
||||
""", (song_id,)).fetchall()
|
||||
|
||||
|
||||
def get_alt_dances_for_song(song_id: str) -> list:
|
||||
"""Hent alle alternativ-danse tagget på en sang."""
|
||||
with get_db() as conn:
|
||||
return conn.execute("""
|
||||
SELECT d.id, d.name, dl.name as level_name, d.choreographer,
|
||||
d.video_url, d.stepsheet_url, sad.note
|
||||
FROM song_alt_dances sad
|
||||
JOIN dances d ON d.id = sad.dance_id
|
||||
LEFT JOIN dance_levels dl ON dl.id = d.level_id
|
||||
WHERE sad.song_id = ?
|
||||
ORDER BY d.name
|
||||
""", (song_id,)).fetchall()
|
||||
|
||||
def get_or_create_dance(name: str, level_id: int | None, conn,
|
||||
choreographer: str = "") -> int:
|
||||
"""
|
||||
Find eller opret dans. Returnerer dance_id.
|
||||
Dans + niveau + koreograf er unik kombination.
|
||||
"""
|
||||
choreo = choreographer or ""
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM dances WHERE name=? "
|
||||
"AND (level_id=? OR (level_id IS NULL AND ? IS NULL)) "
|
||||
"AND choreographer=?",
|
||||
(name, level_id, level_id, choreo)
|
||||
).fetchone()
|
||||
if existing:
|
||||
return existing["id"]
|
||||
cur = conn.execute(
|
||||
"INSERT INTO dances (name, level_id, choreographer) VALUES (?,?,?)",
|
||||
(name, level_id, choreo)
|
||||
)
|
||||
return cur.lastrowid
|
||||
@@ -158,30 +158,28 @@ def scan_library(library_id: int, library_path: str, db_path: str,
|
||||
# Opret eller opdater fil-post
|
||||
_upsert_file_conn(conn, song_id, path_str, file_format, mtime, extra_tags)
|
||||
|
||||
# Dans-tags fra fil
|
||||
# Dans-tags fra fil — synkroniser altid fra filen
|
||||
file_dances = tags.get("dances", [])
|
||||
if file_dances:
|
||||
existing_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM song_dances WHERE song_id=?", (song_id,)
|
||||
).fetchone()[0]
|
||||
if existing_count == 0:
|
||||
import uuid
|
||||
for order, dance_name in enumerate(file_dances, start=1):
|
||||
dance_row = conn.execute(
|
||||
"SELECT id FROM dances WHERE name=? COLLATE NOCASE LIMIT 1",
|
||||
(dance_name,)
|
||||
).fetchone()
|
||||
if not dance_row:
|
||||
cur = conn.execute(
|
||||
"INSERT INTO dances (name) VALUES (?)", (dance_name,)
|
||||
)
|
||||
dance_id = cur.lastrowid
|
||||
else:
|
||||
dance_id = dance_row["id"]
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO song_dances (id, song_id, dance_id, dance_order) VALUES (?,?,?,?)",
|
||||
(str(uuid.uuid4()), song_id, dance_id, order)
|
||||
import uuid
|
||||
# Slet eksisterende og genindsæt fra filen
|
||||
conn.execute("DELETE FROM song_dances WHERE song_id=?", (song_id,))
|
||||
for order, dance_name in enumerate(file_dances, start=1):
|
||||
dance_row = conn.execute(
|
||||
"SELECT id FROM dances WHERE name=? COLLATE NOCASE LIMIT 1",
|
||||
(dance_name,)
|
||||
).fetchone()
|
||||
if not dance_row:
|
||||
cur = conn.execute(
|
||||
"INSERT INTO dances (name) VALUES (?)", (dance_name,)
|
||||
)
|
||||
dance_id = cur.lastrowid
|
||||
else:
|
||||
dance_id = dance_row["id"]
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO song_dances (id, song_id, dance_id, dance_order) VALUES (?,?,?,?)",
|
||||
(str(uuid.uuid4()), song_id, dance_id, order)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -323,22 +323,30 @@ class SyncManager:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
|
||||
try:
|
||||
# Opdater dans-info
|
||||
# Synkroniser danse fra server — opret nye, opdater eksisterende
|
||||
for d in data.get("dances", []):
|
||||
if not d.get("name"):
|
||||
continue
|
||||
choreo = d.get("choreographer", "") or ""
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM dances WHERE name=? COLLATE NOCASE", (d["name"],)
|
||||
"SELECT id FROM dances WHERE name=? COLLATE NOCASE "
|
||||
"AND choreographer=? LIMIT 1",
|
||||
(d["name"], choreo)
|
||||
).fetchone()
|
||||
if existing and (d.get("choreographer") or d.get("video_url")):
|
||||
if existing:
|
||||
conn.execute("""
|
||||
UPDATE dances SET
|
||||
choreographer = CASE WHEN choreographer='' THEN ? ELSE choreographer END,
|
||||
video_url = CASE WHEN video_url='' THEN ? ELSE video_url END,
|
||||
stepsheet_url = CASE WHEN stepsheet_url='' THEN ? ELSE stepsheet_url END
|
||||
WHERE id=?
|
||||
""", (d.get("choreographer",""), d.get("video_url",""),
|
||||
d.get("stepsheet_url",""), existing["id"]))
|
||||
""", (d.get("video_url",""), d.get("stepsheet_url",""), existing["id"]))
|
||||
else:
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO dances (name, choreographer, video_url, stepsheet_url, notes) "
|
||||
"VALUES (?,?,?,?,?)",
|
||||
(d["name"], choreo,
|
||||
d.get("video_url",""), d.get("stepsheet_url",""), d.get("notes",""))
|
||||
)
|
||||
|
||||
# Hent soft-slettede server-IDs så vi springer dem over
|
||||
deleted_server_ids = {
|
||||
@@ -468,6 +476,49 @@ class SyncManager:
|
||||
song_data.get("dance_override","") or ""))
|
||||
position += 1
|
||||
|
||||
# Importer sang-dans tags fra server
|
||||
for st in data.get("song_tags", []):
|
||||
server_song_id = st.get("song_id", "")
|
||||
dance_name = st.get("dance_name", "")
|
||||
dance_order = st.get("dance_order", 1)
|
||||
choreo = st.get("choreographer", "") or ""
|
||||
if not server_song_id or not dance_name:
|
||||
continue
|
||||
|
||||
# Find lokal sang
|
||||
local_song = conn.execute(
|
||||
"SELECT id FROM songs WHERE id=?", (server_song_id,)
|
||||
).fetchone()
|
||||
if not local_song:
|
||||
continue
|
||||
|
||||
# Find dans
|
||||
dance_row = conn.execute(
|
||||
"SELECT id FROM dances WHERE name=? COLLATE NOCASE "
|
||||
"AND choreographer=? LIMIT 1",
|
||||
(dance_name, choreo)
|
||||
).fetchone()
|
||||
if not dance_row:
|
||||
cur = conn.execute(
|
||||
"INSERT OR IGNORE INTO dances (name, choreographer) VALUES (?,?)",
|
||||
(dance_name, choreo)
|
||||
)
|
||||
dance_id = cur.lastrowid
|
||||
else:
|
||||
dance_id = dance_row["id"]
|
||||
|
||||
# Tilføj sang-dans tag hvis ikke allerede der
|
||||
existing_sd = conn.execute(
|
||||
"SELECT id FROM song_dances WHERE song_id=? AND dance_id=?",
|
||||
(server_song_id, dance_id)
|
||||
).fetchone()
|
||||
if not existing_sd:
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO song_dances (id, song_id, dance_id, dance_order) "
|
||||
"VALUES (?,?,?,?)",
|
||||
(str(uuid.uuid4()), server_song_id, dance_id, dance_order)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
|
||||
except Exception:
|
||||
|
||||
@@ -410,6 +410,11 @@ def read_dances_from_file(path: str | Path) -> list[str]:
|
||||
return tags.get("dances", [])
|
||||
|
||||
|
||||
def write_dance_to_file(path: str | Path, dances: list[str]) -> bool:
|
||||
"""Alias for write_dances — skriv danse-liste til fil."""
|
||||
return write_dances(path, dances)
|
||||
|
||||
|
||||
# ── BPM-analyse ───────────────────────────────────────────────────────────────
|
||||
|
||||
def analyze_bpm(path: str | Path) -> float | None:
|
||||
@@ -513,4 +518,4 @@ def _write_mbid_m4a(path: Path, mbid: str) -> bool:
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"MBID M4A skrivefejl {path}: {e}")
|
||||
return False
|
||||
return False
|
||||
Reference in New Issue
Block a user