Bedre tag sync
This commit is contained in:
@@ -224,7 +224,6 @@ def pull(
|
|||||||
"""Hent server-data til lokal app."""
|
"""Hent server-data til lokal app."""
|
||||||
|
|
||||||
# Dans-niveauer
|
# Dans-niveauer
|
||||||
levels = [
|
|
||||||
{"id": l.id, "name": l.name, "sort_order": l.sort_order}
|
{"id": l.id, "name": l.name, "sort_order": l.sort_order}
|
||||||
for l in db.query(DanceLevel).order_by(DanceLevel.sort_order).all()
|
for l in db.query(DanceLevel).order_by(DanceLevel.sort_order).all()
|
||||||
]
|
]
|
||||||
@@ -278,7 +277,10 @@ def pull(
|
|||||||
|
|
||||||
# Egne playlister
|
# Egne playlister
|
||||||
my_playlists = []
|
my_playlists = []
|
||||||
for p in db.query(Project).filter_by(owner_id=me.id).all():
|
all_projects = db.query(Project).filter_by(owner_id=me.id).all()
|
||||||
|
import logging
|
||||||
|
logging.getLogger(__name__).info(f"Pull: fandt {len(all_projects)} projekter for {me.id}")
|
||||||
|
for p in all_projects:
|
||||||
songs_out = []
|
songs_out = []
|
||||||
for ps in p.project_songs:
|
for ps in p.project_songs:
|
||||||
song = db.query(Song).filter_by(id=ps.song_id).first()
|
song = db.query(Song).filter_by(id=ps.song_id).first()
|
||||||
@@ -299,10 +301,25 @@ def pull(
|
|||||||
"songs": sorted(songs_out, key=lambda x: x["position"]),
|
"songs": sorted(songs_out, key=lambda x: x["position"]),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Brugerens egne dans-tags (via community dances submitted_by me)
|
||||||
|
song_tags = []
|
||||||
|
for cd in db.query(CommunityDance).filter_by(submitted_by=me.id).all():
|
||||||
|
dance = db.query(Dance).filter_by(id=cd.dance_id).first()
|
||||||
|
if not dance:
|
||||||
|
continue
|
||||||
|
level = db.query(DanceLevel).filter_by(id=dance.level_id).first() if dance.level_id else None
|
||||||
|
song_tags.append({
|
||||||
|
"song_title": cd.song_title,
|
||||||
|
"song_artist": cd.song_artist,
|
||||||
|
"dance_name": dance.name,
|
||||||
|
"level_name": level.name if level else "",
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"levels": levels,
|
"levels": levels,
|
||||||
"dances": dances,
|
"dances": dances,
|
||||||
"community": community,
|
"community": community,
|
||||||
"shared": shared,
|
"shared": shared,
|
||||||
"my_playlists": my_playlists,
|
"my_playlists": my_playlists,
|
||||||
|
"song_tags": song_tags,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
app_logger.py — Central logging til fil i stedet for konsol.
|
app_logger.py - Central logging til fil i stedet for konsol.
|
||||||
P<EFBFBD> Windows uden konsol skrives alt til ~/.linedance/app.log
|
Paa Windows uden konsol skrives alt til ~/.linedance/app.log
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -13,10 +13,9 @@ LOG_PATH = Path.home() / ".linedance" / "app.log"
|
|||||||
def setup_logging():
|
def setup_logging():
|
||||||
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
handlers = [logging.FileHandler(LOG_PATH, encoding="utf-8")]
|
handlers = [logging.FileHandler(LOG_PATH, encoding="utf-8")]
|
||||||
# Kun tilføj konsol-handler hvis vi kører med konsol (development)
|
|
||||||
if sys.stdout and hasattr(sys.stdout, 'write'):
|
if sys.stdout and hasattr(sys.stdout, 'write'):
|
||||||
try:
|
try:
|
||||||
sys.stdout.write("") # test om konsol virker
|
sys.stdout.write("")
|
||||||
handlers.append(logging.StreamHandler(sys.stdout))
|
handlers.append(logging.StreamHandler(sys.stdout))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ class SyncManager:
|
|||||||
def _run():
|
def _run():
|
||||||
try:
|
try:
|
||||||
payload = self._build_push_payload()
|
payload = self._build_push_payload()
|
||||||
|
logger.info(f"Push: {len(payload['songs'])} sange, {len(payload['playlists'])} playlister")
|
||||||
result = self._post("/sync/push", payload)
|
result = self._post("/sync/push", payload)
|
||||||
# Gem server-IDs lokalt
|
|
||||||
self._save_playlist_ids(result.get("playlist_id_map", {}))
|
self._save_playlist_ids(result.get("playlist_id_map", {}))
|
||||||
logger.info(f"Sync push: {result}")
|
logger.info(f"Push OK: {result}")
|
||||||
if on_done:
|
if on_done:
|
||||||
on_done(result)
|
on_done(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Sync push fejl: {e}")
|
logger.error(f"Sync push fejl: {e}", exc_info=True)
|
||||||
if on_error:
|
if on_error:
|
||||||
on_error(str(e))
|
on_error(str(e))
|
||||||
threading.Thread(target=_run, daemon=True).start()
|
threading.Thread(target=_run, daemon=True).start()
|
||||||
@@ -79,6 +79,51 @@ class SyncManager:
|
|||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# Importer brugerens egne dans-tags
|
||||||
|
for tag in data.get("song_tags", []):
|
||||||
|
title = tag.get("song_title", "")
|
||||||
|
artist = tag.get("song_artist", "")
|
||||||
|
dance_name = tag.get("dance_name", "")
|
||||||
|
level_name = tag.get("level_name", "")
|
||||||
|
if not title or not dance_name:
|
||||||
|
continue
|
||||||
|
song = conn.execute(
|
||||||
|
"SELECT id FROM songs WHERE title=? AND artist=? LIMIT 1",
|
||||||
|
(title, artist)
|
||||||
|
).fetchone()
|
||||||
|
if not song:
|
||||||
|
continue
|
||||||
|
level_row = conn.execute(
|
||||||
|
"SELECT id FROM dance_levels WHERE name=? COLLATE NOCASE LIMIT 1",
|
||||||
|
(level_name,)
|
||||||
|
).fetchone() if level_name else None
|
||||||
|
level_id = level_row["id"] if level_row else None
|
||||||
|
dance_row = conn.execute(
|
||||||
|
"SELECT id FROM dances WHERE name=? AND level_id IS ? LIMIT 1",
|
||||||
|
(dance_name, level_id)
|
||||||
|
).fetchone()
|
||||||
|
if not dance_row:
|
||||||
|
cur = conn.execute(
|
||||||
|
"INSERT OR IGNORE INTO dances (name, level_id) VALUES (?,?)",
|
||||||
|
(dance_name, level_id)
|
||||||
|
)
|
||||||
|
dance_id = cur.lastrowid
|
||||||
|
else:
|
||||||
|
dance_id = dance_row["id"]
|
||||||
|
existing = conn.execute(
|
||||||
|
"SELECT id FROM song_dances WHERE song_id=? AND dance_id=?",
|
||||||
|
(song["id"], dance_id)
|
||||||
|
).fetchone()
|
||||||
|
if not existing:
|
||||||
|
max_order = conn.execute(
|
||||||
|
"SELECT COALESCE(MAX(dance_order),0) FROM song_dances WHERE song_id=?",
|
||||||
|
(song["id"],)
|
||||||
|
).fetchone()[0]
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO song_dances (song_id, dance_id, dance_order) VALUES (?,?,?)",
|
||||||
|
(song["id"], dance_id, max_order + 1)
|
||||||
|
)
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -87,12 +132,15 @@ class SyncManager:
|
|||||||
def _run():
|
def _run():
|
||||||
try:
|
try:
|
||||||
result = self._get("/sync/pull")
|
result = self._get("/sync/pull")
|
||||||
|
pl_count = len(result.get("my_playlists", []))
|
||||||
|
logger.info(f"Pull: {len(result.get('dances', []))} danse, {pl_count} playlister")
|
||||||
|
for pl in result.get("my_playlists", []):
|
||||||
|
logger.info(f" Playliste fra server: '{pl['name']}' ({len(pl.get('songs',[]))} sange)")
|
||||||
self._apply_pull(result)
|
self._apply_pull(result)
|
||||||
logger.info(f"Sync pull: {len(result.get('dances', []))} danse")
|
|
||||||
if on_done:
|
if on_done:
|
||||||
on_done(result)
|
on_done(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Sync pull fejl: {e}")
|
logger.error(f"Sync pull fejl: {e}", exc_info=True)
|
||||||
if on_error:
|
if on_error:
|
||||||
on_error(str(e))
|
on_error(str(e))
|
||||||
threading.Thread(target=_run, daemon=True).start()
|
threading.Thread(target=_run, daemon=True).start()
|
||||||
@@ -101,14 +149,21 @@ class SyncManager:
|
|||||||
"""Push og derefter pull i samme tråd."""
|
"""Push og derefter pull i samme tråd."""
|
||||||
def _run():
|
def _run():
|
||||||
try:
|
try:
|
||||||
|
logger.info("push_and_pull: bygger payload...")
|
||||||
payload = self._build_push_payload()
|
payload = self._build_push_payload()
|
||||||
|
logger.info(f"push_and_pull: sender {len(payload['songs'])} sange, {len(payload['playlists'])} playlister")
|
||||||
push_result = self._post("/sync/push", payload)
|
push_result = self._post("/sync/push", payload)
|
||||||
|
logger.info(f"push_and_pull: push OK — {push_result}")
|
||||||
pull_result = self._get("/sync/pull")
|
pull_result = self._get("/sync/pull")
|
||||||
|
pl_count = len(pull_result.get("my_playlists", []))
|
||||||
|
logger.info(f"push_and_pull: pull OK — {pl_count} playlister")
|
||||||
|
for pl in pull_result.get("my_playlists", []):
|
||||||
|
logger.info(f" Playliste: '{pl['name']}' ({len(pl.get('songs',[]))} sange)")
|
||||||
self._apply_pull(pull_result)
|
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:
|
||||||
logger.error(f"Sync fejl: {e}")
|
logger.error(f"push_and_pull fejl: {e}", exc_info=True)
|
||||||
if on_error:
|
if on_error:
|
||||||
on_error(str(e))
|
on_error(str(e))
|
||||||
threading.Thread(target=_run, daemon=True).start()
|
threading.Thread(target=_run, daemon=True).start()
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import os
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
|
||||||
|
from app_logger import setup_logging
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
from ui.main_window import MainWindow
|
from ui.main_window import MainWindow
|
||||||
from ui.themes import apply_theme
|
from ui.themes import apply_theme
|
||||||
|
|||||||
@@ -797,10 +797,14 @@ class MainWindow(QMainWindow):
|
|||||||
self._set_status(f"⚠ Sync fejl: {e}", 5000)
|
self._set_status(f"⚠ Sync fejl: {e}", 5000)
|
||||||
|
|
||||||
def _manual_sync(self):
|
def _manual_sync(self):
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.info(f"Manuel sync — token: {'ja' if self._api_token else 'NEJ'}, manager: {'ja' if hasattr(self, '_sync_manager') and self._sync_manager else 'NEJ'}")
|
||||||
if not self._api_token:
|
if not self._api_token:
|
||||||
self._set_status("Log ind for at synkronisere", 3000)
|
self._set_status("Log ind for at synkronisere", 3000)
|
||||||
return
|
return
|
||||||
if not hasattr(self, "_sync_manager") or not self._sync_manager:
|
if not hasattr(self, "_sync_manager") or not self._sync_manager:
|
||||||
|
log.info("Ingen sync_manager — kalder _init_sync")
|
||||||
self._init_sync()
|
self._init_sync()
|
||||||
return
|
return
|
||||||
self._set_status("Synkroniserer...", 2000)
|
self._set_status("Synkroniserer...", 2000)
|
||||||
|
|||||||
Reference in New Issue
Block a user