mirror of
https://github.com/Dvorinka/SpotifyRecAlg.git
synced 2026-06-03 20:13:03 +00:00
280 lines
8.1 KiB
Python
280 lines
8.1 KiB
Python
from __future__ import annotations
|
|
|
|
import threading
|
|
from typing import Any
|
|
|
|
from swingmusic.config import UserConfig
|
|
from swingmusic.db.production import SetupStateTable, UserRootDirOwnershipTable
|
|
from swingmusic.db.userdata import UserTable
|
|
from swingmusic.lib.index import run_index_pipeline
|
|
from swingmusic.services.production_readiness import bootstrap_owner_user
|
|
|
|
_index_lock = threading.RLock()
|
|
_index_thread: threading.Thread | None = None
|
|
|
|
|
|
def _normalize_root_dirs(root_dirs: list[str] | None) -> list[str]:
|
|
if not root_dirs:
|
|
return []
|
|
cleaned = [item.strip() for item in root_dirs if item and item.strip()]
|
|
return list(dict.fromkeys(cleaned))
|
|
|
|
|
|
def _owner_user():
|
|
users = list(UserTable.get_all())
|
|
owners = [user for user in users if "owner" in user.roles]
|
|
if owners:
|
|
return owners[0]
|
|
|
|
admins = [user for user in users if "admin" in user.roles]
|
|
if admins:
|
|
return admins[0]
|
|
|
|
return users[0] if users else None
|
|
|
|
|
|
def _primary_music_dir() -> str | None:
|
|
root_dirs = UserConfig().rootDirs or []
|
|
if not root_dirs:
|
|
return None
|
|
return root_dirs[0]
|
|
|
|
|
|
def _reconcile_legacy_ready_state() -> Any:
|
|
row = SetupStateTable.ensure_singleton()
|
|
owner = _owner_user()
|
|
primary_dir = _primary_music_dir()
|
|
|
|
owner_created = owner is not None
|
|
directory_configured = bool(primary_dir)
|
|
legacy_ready = owner_created and directory_configured and row.index_state == "idle"
|
|
|
|
if legacy_ready and not row.setup_completed:
|
|
row = SetupStateTable.update_state(
|
|
{
|
|
"setup_completed": True,
|
|
"owner_userid": owner.id if owner else None,
|
|
"primary_music_dir": primary_dir,
|
|
"index_state": "completed",
|
|
"index_progress": 100.0,
|
|
"index_message": "Setup inferred from existing installation",
|
|
}
|
|
)
|
|
|
|
return row
|
|
|
|
|
|
def get_setup_status() -> dict[str, Any]:
|
|
row = _reconcile_legacy_ready_state()
|
|
users = list(UserTable.get_all())
|
|
owner = _owner_user()
|
|
primary_dir = row.primary_music_dir or _primary_music_dir()
|
|
|
|
owner_created = owner is not None
|
|
directory_configured = bool(primary_dir)
|
|
initial_index_completed = row.index_state == "completed"
|
|
setup_completed = bool(
|
|
row.setup_completed
|
|
and owner_created
|
|
and directory_configured
|
|
and initial_index_completed
|
|
)
|
|
required = not setup_completed
|
|
|
|
if not owner_created:
|
|
stage = "owner"
|
|
elif not directory_configured:
|
|
stage = "directory"
|
|
elif not initial_index_completed:
|
|
stage = "indexing"
|
|
else:
|
|
stage = "completed"
|
|
|
|
return {
|
|
"required": required,
|
|
"setup_completed": setup_completed,
|
|
"stage": stage,
|
|
"needs_owner": stage == "owner",
|
|
"needs_directory": stage == "directory",
|
|
"needs_index": stage == "indexing",
|
|
"owner_created": owner_created,
|
|
"owner_username": owner.username if owner else None,
|
|
"owner_userid": owner.id if owner else None,
|
|
"directory_configured": directory_configured,
|
|
"primary_music_dir": primary_dir,
|
|
"index_state": row.index_state,
|
|
"index_progress": float(row.index_progress or 0.0),
|
|
"index_message": row.index_message,
|
|
"initial_index_completed": initial_index_completed,
|
|
"has_users": len(users) > 0,
|
|
"user_count": len(users),
|
|
}
|
|
|
|
|
|
def _set_index_state(
|
|
state: str, progress: float, message: str, extra: dict[str, Any] | None = None
|
|
):
|
|
SetupStateTable.mark_index_progress(
|
|
state=state,
|
|
progress=progress,
|
|
message=message,
|
|
extra=extra,
|
|
)
|
|
|
|
|
|
def _run_initial_index():
|
|
try:
|
|
_set_index_state("running", 1.0, "Starting initial index")
|
|
|
|
def _progress(state: str, progress: float, message: str):
|
|
_set_index_state(state, progress, message)
|
|
|
|
run_index_pipeline(progress_callback=_progress)
|
|
|
|
status = get_setup_status()
|
|
SetupStateTable.update_state(
|
|
{
|
|
"setup_completed": bool(
|
|
status["owner_created"] and status["directory_configured"]
|
|
),
|
|
"index_state": "completed",
|
|
"index_progress": 100.0,
|
|
"index_message": "Initial index completed",
|
|
"owner_userid": status.get("owner_userid"),
|
|
"primary_music_dir": status.get("primary_music_dir"),
|
|
}
|
|
)
|
|
except Exception as error:
|
|
SetupStateTable.update_state(
|
|
{
|
|
"setup_completed": False,
|
|
"index_state": "failed",
|
|
"index_message": str(error),
|
|
}
|
|
)
|
|
finally:
|
|
global _index_thread
|
|
with _index_lock:
|
|
_index_thread = None
|
|
|
|
|
|
def trigger_initial_index(force: bool = False) -> bool:
|
|
global _index_thread
|
|
with _index_lock:
|
|
if _index_thread and _index_thread.is_alive():
|
|
return False
|
|
|
|
row = SetupStateTable.ensure_singleton()
|
|
if not force and row.index_state == "running":
|
|
return False
|
|
|
|
SetupStateTable.update_state(
|
|
{
|
|
"index_state": "queued",
|
|
"index_progress": 0.0,
|
|
"index_message": "Queued initial index",
|
|
}
|
|
)
|
|
|
|
_index_thread = threading.Thread(
|
|
target=_run_initial_index,
|
|
daemon=True,
|
|
name="swingmusic-setup-index",
|
|
)
|
|
_index_thread.start()
|
|
return True
|
|
|
|
|
|
def bootstrap_setup(
|
|
*,
|
|
username: str,
|
|
password: str,
|
|
root_dirs: list[str] | None = None,
|
|
):
|
|
existing_users = list(UserTable.get_all())
|
|
if existing_users:
|
|
raise ValueError(
|
|
"Setup bootstrap is only available before any user account exists"
|
|
)
|
|
|
|
normalized_root_dirs = _normalize_root_dirs(root_dirs)
|
|
if not normalized_root_dirs:
|
|
raise ValueError("At least one primary music directory is required")
|
|
|
|
owner = bootstrap_owner_user(
|
|
username=username,
|
|
password=password,
|
|
root_dirs=normalized_root_dirs,
|
|
)
|
|
|
|
primary_dir = (
|
|
normalized_root_dirs[0] if normalized_root_dirs else _primary_music_dir()
|
|
)
|
|
SetupStateTable.update_state(
|
|
{
|
|
"owner_userid": owner.id,
|
|
"primary_music_dir": primary_dir,
|
|
"setup_completed": False,
|
|
"index_state": "queued",
|
|
"index_progress": 0.0,
|
|
"index_message": "Bootstrap complete. Initial index queued.",
|
|
"extra": {
|
|
"onboarding_version": 1,
|
|
},
|
|
}
|
|
)
|
|
|
|
trigger_initial_index(force=True)
|
|
return owner
|
|
|
|
|
|
def configure_primary_directory(
|
|
*,
|
|
root_dirs: list[str],
|
|
) -> bool:
|
|
"""
|
|
Configure primary music directories when setup is incomplete and owner already exists.
|
|
"""
|
|
normalized_root_dirs = _normalize_root_dirs(root_dirs)
|
|
if not normalized_root_dirs:
|
|
raise ValueError("At least one primary music directory is required")
|
|
|
|
owner = _owner_user()
|
|
if not owner:
|
|
raise ValueError(
|
|
"Owner account must exist before configuring music directories"
|
|
)
|
|
|
|
config = UserConfig()
|
|
config.rootDirs = normalized_root_dirs
|
|
UserRootDirOwnershipTable.assign_paths(owner.id, normalized_root_dirs)
|
|
|
|
SetupStateTable.update_state(
|
|
{
|
|
"owner_userid": owner.id,
|
|
"primary_music_dir": normalized_root_dirs[0],
|
|
"setup_completed": False,
|
|
"index_state": "queued",
|
|
"index_progress": 0.0,
|
|
"index_message": "Primary directory configured. Initial index queued.",
|
|
"extra": {
|
|
"onboarding_version": 1,
|
|
"directory_configured_at": "setup_api",
|
|
},
|
|
}
|
|
)
|
|
|
|
return trigger_initial_index(force=True)
|
|
|
|
|
|
def is_setup_complete() -> bool:
|
|
status = get_setup_status()
|
|
return bool(status["setup_completed"])
|
|
|
|
|
|
def resume_pending_index_if_needed() -> bool:
|
|
status = get_setup_status()
|
|
if status["index_state"] in {"queued", "running"} and not status["setup_completed"]:
|
|
return trigger_initial_index(force=True)
|
|
return False
|