mirror of
https://github.com/Dvorinka/SpotifyRecAlg.git
synced 2026-06-04 04:23:02 +00:00
first commit
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
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
|
||||
Reference in New Issue
Block a user