diff --git a/linedance-api/app/routers/sync.py b/linedance-api/app/routers/sync.py index fdecb495..e43620b1 100644 --- a/linedance-api/app/routers/sync.py +++ b/linedance-api/app/routers/sync.py @@ -224,7 +224,6 @@ def pull( """Hent server-data til lokal app.""" # Dans-niveauer - levels = [ {"id": l.id, "name": l.name, "sort_order": l.sort_order} for l in db.query(DanceLevel).order_by(DanceLevel.sort_order).all() ] @@ -278,7 +277,10 @@ def pull( # Egne playlister 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 = [] for ps in p.project_songs: 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"]), }) + # 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 { "levels": levels, "dances": dances, "community": community, "shared": shared, "my_playlists": my_playlists, + "song_tags": song_tags, } diff --git a/linedance-app/app_logger.py b/linedance-app/app_logger.py index a1249700..0938584c 100644 --- a/linedance-app/app_logger.py +++ b/linedance-app/app_logger.py @@ -1,6 +1,6 @@ """ -app_logger.py — Central logging til fil i stedet for konsol. -P¥ Windows uden konsol skrives alt til ~/.linedance/app.log +app_logger.py - Central logging til fil i stedet for konsol. +Paa Windows uden konsol skrives alt til ~/.linedance/app.log """ import logging @@ -13,10 +13,9 @@ LOG_PATH = Path.home() / ".linedance" / "app.log" def setup_logging(): LOG_PATH.parent.mkdir(parents=True, exist_ok=True) 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'): try: - sys.stdout.write("") # test om konsol virker + sys.stdout.write("") handlers.append(logging.StreamHandler(sys.stdout)) except Exception: pass diff --git a/linedance-app/local/sync_manager.py b/linedance-app/local/sync_manager.py index ad7cd50e..b8a3156d 100644 --- a/linedance-app/local/sync_manager.py +++ b/linedance-app/local/sync_manager.py @@ -54,14 +54,14 @@ class SyncManager: def _run(): try: payload = self._build_push_payload() + logger.info(f"Push: {len(payload['songs'])} sange, {len(payload['playlists'])} playlister") result = self._post("/sync/push", payload) - # Gem server-IDs lokalt self._save_playlist_ids(result.get("playlist_id_map", {})) - logger.info(f"Sync push: {result}") + logger.info(f"Push OK: {result}") if on_done: on_done(result) except Exception as e: - logger.error(f"Sync push fejl: {e}") + logger.error(f"Sync push fejl: {e}", exc_info=True) if on_error: on_error(str(e)) threading.Thread(target=_run, daemon=True).start() @@ -79,6 +79,51 @@ class SyncManager: ) except Exception: 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.close() @@ -87,12 +132,15 @@ class SyncManager: def _run(): try: 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) - logger.info(f"Sync pull: {len(result.get('dances', []))} danse") if on_done: on_done(result) except Exception as e: - logger.error(f"Sync pull fejl: {e}") + logger.error(f"Sync pull fejl: {e}", exc_info=True) if on_error: on_error(str(e)) threading.Thread(target=_run, daemon=True).start() @@ -101,14 +149,21 @@ class SyncManager: """Push og derefter pull i samme trÃ¥d.""" def _run(): try: + logger.info("push_and_pull: bygger 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) + logger.info(f"push_and_pull: push OK — {push_result}") 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) if on_done: on_done({"push": push_result, "pull": pull_result}) except Exception as e: - logger.error(f"Sync fejl: {e}") + logger.error(f"push_and_pull fejl: {e}", exc_info=True) if on_error: on_error(str(e)) threading.Thread(target=_run, daemon=True).start() diff --git a/linedance-app/main.py b/linedance-app/main.py index 808c78d4..d147d064 100644 --- a/linedance-app/main.py +++ b/linedance-app/main.py @@ -10,6 +10,9 @@ import os sys.path.insert(0, os.path.dirname(__file__)) +from app_logger import setup_logging +setup_logging() + from PyQt6.QtWidgets import QApplication from ui.main_window import MainWindow from ui.themes import apply_theme diff --git a/linedance-app/ui/main_window.py b/linedance-app/ui/main_window.py index f0e46828..7b78f946 100644 --- a/linedance-app/ui/main_window.py +++ b/linedance-app/ui/main_window.py @@ -797,10 +797,14 @@ class MainWindow(QMainWindow): self._set_status(f"âš  Sync fejl: {e}", 5000) 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: self._set_status("Log ind for at synkronisere", 3000) return if not hasattr(self, "_sync_manager") or not self._sync_manager: + log.info("Ingen sync_manager — kalder _init_sync") self._init_sync() return self._set_status("Synkroniserer...", 2000)