refactor(logging): route frontend console calls through the logger

TRACES: | DR-204

484 ungated `console.*` calls across 63 non-test frontend files shipped to
end users with no way to turn them off. Mechanical substitution, no control
flow, error handling or message semantics changed:

  console.log / console.debug -> log.debug
  console.info                -> log.info
  console.warn                -> log.warn
  console.error               -> log.error

Hand-written `"[Scope] …"` prefixes are dropped where the logger's scope
now carries them; scope names that already existed are preserved verbatim
(`[Auth]`, `[VideoPlayer]`, `[PiP]`, …) and inferred from the filename
where a file had none. `src/routes/player/[id]/+page.svelte` keeps its
`NextEpisode` and `AutoPlay` sub-scopes as separate loggers rather than
flattening them into the page scope.

`grep -rn 'console\.' src/` now matches nothing outside the tests and the
facade itself.
This commit is contained in:
2026-08-20 19:29:59 +02:00
parent 4c82a0a025
commit d54d8cc7c4
63 changed files with 686 additions and 490 deletions
+42 -39
View File
@@ -13,6 +13,9 @@ import type { User, AuthResult } from "$lib/api/types";
import type { Session, AuthServerInfo as ServerInfo } from "$lib/api/bindings";
import { connectivity } from "./connectivity";
import { getDeviceId, clearCache as clearDeviceIdCache } from "$lib/services/deviceId";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("Auth");
interface AuthState {
isAuthenticated: boolean;
@@ -70,7 +73,7 @@ function createAuthStore() {
try {
unlistenSessionVerified = await listen<{ user: User }>("auth:session-verified", (event) => {
console.log("[Auth] Session verified:", event.payload.user.name);
log.debug("Session verified:", event.payload.user.name);
update((s) => ({
...s,
sessionVerified: true,
@@ -80,12 +83,12 @@ function createAuthStore() {
}));
});
} catch (e) {
console.error("[Auth] Failed to listen to session-verified event:", e);
log.error("Failed to listen to session-verified event:", e);
}
try {
unlistenNeedsReauth = await listen<{ reason: string }>("auth:needs-reauth", (event) => {
console.log("[Auth] Session needs re-authentication:", event.payload.reason);
log.debug("Session needs re-authentication:", event.payload.reason);
update((s) => ({
...s,
sessionVerified: false,
@@ -95,17 +98,17 @@ function createAuthStore() {
}));
});
} catch (e) {
console.error("[Auth] Failed to listen to needs-reauth event:", e);
log.error("Failed to listen to needs-reauth event:", e);
}
try {
unlistenNetworkError = await listen<{ message: string }>("auth:network-error", (event) => {
console.log("[Auth] Network error during verification:", event.payload.message);
log.debug("Network error during verification:", event.payload.message);
// Network errors don't trigger re-auth - just log them
update((s) => ({ ...s, isVerifying: false }));
});
} catch (e) {
console.error("[Auth] Failed to listen to network-error event:", e);
log.error("Failed to listen to network-error event:", e);
}
}
@@ -144,7 +147,7 @@ function createAuthStore() {
void (async () => {
try {
const securityStatus = await commands.storageGetSecurityStatus();
console.log("[Auth] Security status:", securityStatus);
log.debug("Security status:", securityStatus);
if (!securityStatus.usingKeyring) {
update((s) => ({
...s,
@@ -153,17 +156,17 @@ function createAuthStore() {
}));
}
} catch (error) {
console.warn("[Auth] Failed to get security status:", error);
log.warn("Failed to get security status:", error);
}
})();
// Initialize auth manager and get session
console.log("[Auth] Initializing auth manager...");
log.debug("Initializing auth manager...");
const session = await commands.authInitialize();
console.log("[Auth] Session retrieval result:", session ? "Session found" : "No session found");
log.debug("Session retrieval result:", session ? "Session found" : "No session found");
if (session) {
console.log("[Auth] Restoring session for user:", session.username, "on server:", session.serverUrl);
log.debug("Restoring session for user:", session.username, "on server:", session.serverUrl);
// Create RepositoryClient for cache-first access. This IS required before
// we mark authenticated — the first screen (library overview) reads
@@ -184,9 +187,9 @@ function createAuthStore() {
session.userId,
deviceId
);
console.log("[Auth] Rust player configured for automatic playback reporting");
log.debug("Rust player configured for automatic playback reporting");
} catch (error) {
console.error("[Auth] Failed to configure Rust player:", error);
log.error("Failed to configure Rust player:", error);
}
})();
@@ -205,7 +208,7 @@ function createAuthStore() {
});
// Start connectivity monitoring early to avoid appearing offline on startup
console.log("[Auth] Starting early connectivity monitoring...");
log.debug("Starting early connectivity monitoring...");
connectivity.startMonitoring(session.serverUrl, {
onServerReconnected: () => {
// Retry session verification when server becomes reachable
@@ -214,10 +217,10 @@ function createAuthStore() {
// Lazy import to avoid an auth <-> offlineCatalog import cycle.
import("$lib/services/offlineCatalog")
.then((m) => m.onReconnected())
.catch((err) => console.warn("[Auth] Catalog reconnect failed:", err));
.catch((err) => log.warn("Catalog reconnect failed:", err));
},
}).catch((error) => {
console.error("[Auth] Failed to start connectivity monitoring:", error);
log.error("Failed to start connectivity monitoring:", error);
});
// Start background session verification — fire-and-forget. This is
@@ -228,14 +231,14 @@ function createAuthStore() {
try {
const verifyDeviceId = await getDeviceId();
await commands.authStartVerification(verifyDeviceId);
console.log("[Auth] Background verification started");
log.debug("Background verification started");
} catch (error) {
console.error("[Auth] Failed to start verification:", error);
log.error("Failed to start verification:", error);
}
})();
} else {
// No stored session
console.log("[Auth] No active session found");
log.debug("No active session found");
set({
isAuthenticated: false,
isLoading: false,
@@ -250,7 +253,7 @@ function createAuthStore() {
});
}
} catch (error) {
console.error("[Auth] Failed to initialize:", error);
log.error("Failed to initialize:", error);
update((s) => ({
...s,
isLoading: false,
@@ -269,15 +272,15 @@ function createAuthStore() {
update((s) => ({ ...s, isLoading: true, error: null }));
try {
console.log("[Auth] Connecting to server:", serverUrl);
log.debug("Connecting to server:", serverUrl);
const serverInfo = await commands.authConnectToServer(serverUrl);
console.log("[Auth] Connected to server:", serverInfo.name, serverInfo.version);
console.log("[Auth] Normalized URL:", serverInfo.normalizedUrl);
log.debug("Connected to server:", serverInfo.name, serverInfo.version);
log.debug("Normalized URL:", serverInfo.normalizedUrl);
update((s) => ({ ...s, isLoading: false }));
return serverInfo;
} catch (error) {
console.error("[Auth] Failed to connect to server:", error);
log.error("Failed to connect to server:", error);
update((s) => ({
...s,
isLoading: false,
@@ -297,11 +300,11 @@ function createAuthStore() {
try {
const deviceId = await getDeviceId();
console.log("[Auth] Logging in as:", username);
log.debug("Logging in as:", username);
const authResult = await commands.authLogin(serverUrl, username, password, deviceId);
console.log("[Auth] Login successful:", authResult.user);
log.debug("Login successful:", authResult.user);
// Save to storage
await commands.storageSaveServer(authResult.serverId, serverName, serverUrl, null);
@@ -340,9 +343,9 @@ function createAuthStore() {
authResult.user.id,
playerDeviceId
);
console.log("[Auth] Rust player configured for playback reporting");
log.debug("Rust player configured for playback reporting");
} catch (error) {
console.error("[Auth] Failed to configure Rust player:", error);
log.error("Failed to configure Rust player:", error);
}
// Update state
@@ -364,12 +367,12 @@ function createAuthStore() {
const verifyDeviceId = await getDeviceId();
await commands.authStartVerification(verifyDeviceId);
} catch (error) {
console.error("[Auth] Failed to start verification:", error);
log.error("Failed to start verification:", error);
}
return authResult;
} catch (error) {
console.error("[Auth] Login failed:", error);
log.error("Login failed:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
update((s) => ({ ...s, isLoading: false, error: errorMessage }));
throw error;
@@ -384,11 +387,11 @@ function createAuthStore() {
try {
const deviceId = await getDeviceId();
console.log("[Auth] Re-authenticating...");
log.debug("Re-authenticating...");
const authResult = await commands.authReauthenticate(password, deviceId);
console.log("[Auth] Re-authentication successful");
log.debug("Re-authentication successful");
// Update storage
await commands.storageSaveUser(
@@ -417,7 +420,7 @@ function createAuthStore() {
playerDeviceId
);
} catch (error) {
console.error("[Auth] Failed to reconfigure player:", error);
log.error("Failed to reconfigure player:", error);
}
// Update state
@@ -432,7 +435,7 @@ function createAuthStore() {
return authResult;
} catch (error) {
console.error("[Auth] Re-authentication failed:", error);
log.error("Re-authentication failed:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
update((s) => ({ ...s, isLoading: false, error: errorMessage }));
throw error;
@@ -456,7 +459,7 @@ function createAuthStore() {
try {
await commands.playerDisableJellyfin();
} catch (error) {
console.error("[Auth] Failed to disable player reporting:", error);
log.error("Failed to disable player reporting:", error);
}
// Clear repository
@@ -481,7 +484,7 @@ function createAuthStore() {
// Clear device ID cache on logout
clearDeviceIdCache();
} catch (error) {
console.error("[Auth] Logout error (continuing anyway):", error);
log.error("Logout error (continuing anyway):", error);
set(initialState);
clearDeviceIdCache();
}
@@ -501,7 +504,7 @@ function createAuthStore() {
try {
return await commands.authGetSession();
} catch (error) {
console.error("[Auth] Failed to get current session:", error);
log.error("Failed to get current session:", error);
return null;
}
}
@@ -536,10 +539,10 @@ function createAuthStore() {
async function retryVerification() {
try {
const deviceId = await getDeviceId();
console.log("[Auth] Retrying session verification after reconnection");
log.debug("Retrying session verification after reconnection");
await commands.authStartVerification(deviceId);
} catch (error) {
console.error("[Auth] Failed to retry verification:", error);
log.error("Failed to retry verification:", error);
}
}
+12 -9
View File
@@ -13,6 +13,9 @@ import { writable, derived } from "svelte/store";
import { browser } from "$app/environment";
import { listen } from "@tauri-apps/api/event";
import { commands } from "$lib/api/bindings";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("ConnectivityStore");
export interface ConnectivityState {
/** Browser's navigator.onLine status */
@@ -76,7 +79,7 @@ function createConnectivityStore() {
update((s) => ({ ...s, isOnline: true }));
// Device regained network — ask the backend to re-verify the server now.
checkServerReachable().catch((err) => {
console.debug("[ConnectivityStore] Recheck after 'online' failed:", err);
log.debug("Recheck after 'online' failed:", err);
});
});
@@ -85,7 +88,7 @@ function createConnectivityStore() {
// decide whether the server is actually reachable.
update((s) => ({ ...s, isOnline: false }));
checkServerReachable().catch((err) => {
console.debug("[ConnectivityStore] Recheck after 'offline' failed:", err);
log.debug("Recheck after 'offline' failed:", err);
});
});
}
@@ -132,7 +135,7 @@ function createConnectivityStore() {
return isReachable;
} catch (error) {
console.error("[ConnectivityStore] Failed to check server:", error);
log.error("Failed to check server:", error);
return false;
}
}
@@ -145,7 +148,7 @@ function createConnectivityStore() {
isMonitoring = true;
try {
console.log("[ConnectivityStore] Starting monitoring for:", url);
log.debug("Starting monitoring for:", url);
// Set the server URL
await commands.connectivitySetServerUrl(url);
@@ -163,10 +166,10 @@ function createConnectivityStore() {
isChecking: status.isChecking,
}));
console.log("[ConnectivityStore] Started monitoring. Initial status:",
log.debug("Started monitoring. Initial status:",
status.isServerReachable ? "ONLINE" : "OFFLINE");
} catch (error) {
console.error("[ConnectivityStore] Failed to start monitoring:", error);
log.error("Failed to start monitoring:", error);
update((s) => ({
...s,
isServerReachable: false,
@@ -185,9 +188,9 @@ function createConnectivityStore() {
await commands.connectivityStopMonitoring();
isMonitoring = false;
eventHandlers = {};
console.log("[ConnectivityStore] Stopped monitoring");
log.debug("Stopped monitoring");
} catch (error) {
console.error("[ConnectivityStore] Failed to stop monitoring:", error);
log.error("Failed to stop monitoring:", error);
}
}
@@ -198,7 +201,7 @@ function createConnectivityStore() {
try {
await commands.connectivitySetServerUrl(url);
} catch (error) {
console.error("[ConnectivityStore] Failed to set server URL:", error);
log.error("Failed to set server URL:", error);
}
}
+45 -42
View File
@@ -3,6 +3,9 @@
import { writable, derived, get } from 'svelte/store';
import { commands } from '$lib/api/bindings';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import { createLogger } from '$lib/utils/logger';
const log = createLogger('Downloads');
// Event listener state
let unlistenFn: UnlistenFn | null = null;
@@ -103,7 +106,7 @@ function createDownloadsStore() {
async function refreshDownloads(userId: string, statusFilter?: string[]): Promise<void> {
// If a refresh is already in progress, queue this request instead
if (refreshInProgress) {
console.debug('🔄 Refresh already in progress, queuing request for user:', userId);
log.debug('🔄 Refresh already in progress, queuing request for user:', userId);
pendingRefreshRequest = { userId, statusFilter };
return;
}
@@ -111,13 +114,13 @@ function createDownloadsStore() {
refreshInProgress = true;
try {
console.log('🔄 Refreshing downloads for user:', userId);
log.debug('🔄 Refreshing downloads for user:', userId);
const response = (await commands.getDownloads(
userId,
statusFilter ?? null
)) as unknown as { downloads: DownloadInfo[]; stats: DownloadStats };
console.log(' Got', response.downloads.length, 'downloads from backend');
console.log(' Stats:', response.stats);
log.debug(' Got', response.downloads.length, 'downloads from backend');
log.debug(' Stats:', response.stats);
update((state) => {
const downloadsMap: Record<number, DownloadInfo> = {};
@@ -133,7 +136,7 @@ function createDownloadsStore() {
};
});
} catch (error) {
console.error('Failed to refresh downloads:', error);
log.error('Failed to refresh downloads:', error);
throw error;
} finally {
refreshInProgress = false;
@@ -164,7 +167,7 @@ function createDownloadsStore() {
albumName?: string
): Promise<number> {
try {
console.log('📥 downloadItem called:', { itemId, userId, filePath, itemName, artistName, albumName });
log.debug('📥 downloadItem called:', { itemId, userId, filePath, itemName, artistName, albumName });
const downloadId = await commands.downloadItem({
itemId,
userId,
@@ -176,16 +179,16 @@ function createDownloadsStore() {
albumName: albumName ?? null,
expectedSize: null
});
console.log(' Got download ID from backend:', downloadId);
log.debug(' Got download ID from backend:', downloadId);
// Fetch download info and add to store
console.log(' Refreshing downloads...');
log.debug(' Refreshing downloads...');
await refreshDownloads(userId);
console.log(' Refresh complete. Store state:', get({ subscribe }));
log.debug(' Refresh complete. Store state:', get({ subscribe }));
return downloadId;
} catch (error) {
console.error('Failed to queue download:', error);
log.error('Failed to queue download:', error);
throw error;
}
},
@@ -206,16 +209,16 @@ function createDownloadsStore() {
basePath: string
): Promise<number[]> {
try {
console.log('📥 downloadAlbum called:', { albumId, userId, basePath });
log.debug('📥 downloadAlbum called:', { albumId, userId, basePath });
const downloadIds = await commands.downloadAlbum(handle, albumId, userId, basePath);
console.log(' Got download IDs from backend:', downloadIds);
log.debug(' Got download IDs from backend:', downloadIds);
// Refresh downloads
await refreshDownloads(userId);
return downloadIds;
} catch (error) {
console.error('Failed to queue album download:', error);
log.error('Failed to queue album download:', error);
throw error;
}
},
@@ -237,7 +240,7 @@ function createDownloadsStore() {
seasonNumber?: number
): Promise<number> {
try {
console.log('🎬 downloadVideo called:', {
log.debug('🎬 downloadVideo called:', {
itemId,
userId,
filePath,
@@ -258,14 +261,14 @@ function createDownloadsStore() {
episodeNumber: episodeNumber ?? null,
seasonNumber: seasonNumber ?? null
});
console.log(' Got download ID from backend:', downloadId);
log.debug(' Got download ID from backend:', downloadId);
// Refresh downloads
await refreshDownloads(userId);
return downloadId;
} catch (error) {
console.error('Failed to queue video download:', error);
log.error('Failed to queue video download:', error);
throw error;
}
},
@@ -281,7 +284,7 @@ function createDownloadsStore() {
qualityPreset?: string
): Promise<number[]> {
try {
console.log('📺 downloadSeries called:', {
log.debug('📺 downloadSeries called:', {
seriesId,
seriesName,
userId,
@@ -295,14 +298,14 @@ function createDownloadsStore() {
basePath,
qualityPreset ?? null
);
console.log(' Queued', downloadIds.length, 'episodes for download');
log.debug(' Queued', downloadIds.length, 'episodes for download');
// Refresh downloads
await refreshDownloads(userId);
return downloadIds;
} catch (error) {
console.error('Failed to queue series download:', error);
log.error('Failed to queue series download:', error);
throw error;
}
},
@@ -320,7 +323,7 @@ function createDownloadsStore() {
qualityPreset?: string
): Promise<number[]> {
try {
console.log('📺 downloadSeason called:', {
log.debug('📺 downloadSeason called:', {
seasonId,
seriesName,
seasonName,
@@ -336,14 +339,14 @@ function createDownloadsStore() {
basePath,
qualityPreset ?? null
);
console.log(' Queued', downloadIds.length, 'episodes for download');
log.debug(' Queued', downloadIds.length, 'episodes for download');
// Refresh downloads
await refreshDownloads(userId);
return downloadIds;
} catch (error) {
console.error('Failed to queue season download:', error);
log.error('Failed to queue season download:', error);
throw error;
}
},
@@ -355,7 +358,7 @@ function createDownloadsStore() {
try {
await commands.pinItem(itemId);
} catch (error) {
console.error('Failed to pin item:', error);
log.error('Failed to pin item:', error);
throw error;
}
},
@@ -367,7 +370,7 @@ function createDownloadsStore() {
try {
await commands.unpinItem(itemId);
} catch (error) {
console.error('Failed to unpin item:', error);
log.error('Failed to unpin item:', error);
throw error;
}
},
@@ -379,7 +382,7 @@ function createDownloadsStore() {
try {
return await commands.isItemPinned(itemId);
} catch (error) {
console.error('Failed to check pin status:', error);
log.error('Failed to check pin status:', error);
return false;
}
},
@@ -391,7 +394,7 @@ function createDownloadsStore() {
try {
await commands.pauseDownload(downloadId);
} catch (error) {
console.error('Failed to pause download:', error);
log.error('Failed to pause download:', error);
throw error;
}
},
@@ -403,7 +406,7 @@ function createDownloadsStore() {
try {
await commands.resumeDownload(downloadId);
} catch (error) {
console.error('Failed to resume download:', error);
log.error('Failed to resume download:', error);
throw error;
}
},
@@ -415,7 +418,7 @@ function createDownloadsStore() {
try {
await commands.cancelDownload(downloadId);
} catch (error) {
console.error('Failed to cancel download:', error);
log.error('Failed to cancel download:', error);
throw error;
}
},
@@ -431,7 +434,7 @@ function createDownloadsStore() {
return { ...state, downloads: remaining };
});
} catch (error) {
console.error('Failed to delete download:', error);
log.error('Failed to delete download:', error);
throw error;
}
},
@@ -448,14 +451,14 @@ function createDownloadsStore() {
update((state) => {
const download = state.downloads[downloadId];
if (!download) {
console.log(' Download not in store:', downloadId);
log.debug(' Download not in store:', downloadId);
return state;
}
const updatedDownload = { ...download, ...updates };
const newDownloads = { ...state.downloads, [downloadId]: updatedDownload };
console.log(' Store updated for download', downloadId, ':', updates);
log.debug(' Store updated for download', downloadId, ':', updates);
// No count calculation - stats remain as-is until next refresh
return {
downloads: newDownloads,
@@ -515,30 +518,30 @@ export const audioDownloads = derived(downloads, ($d) =>
*/
export async function initDownloadEvents(): Promise<void> {
if (isEventsInitialized) {
console.warn('Download events already initialized');
log.warn('Download events already initialized');
return;
}
try {
console.log('🎧 Setting up download event listener...');
log.debug('🎧 Setting up download event listener...');
unlistenFn = await listen<DownloadEvent>('download-event', (event) => {
const payload = event.payload;
console.log('📬 Received download event:', payload.type, 'for download:', payload.downloadId);
console.log(' Full event payload:', JSON.stringify(payload));
log.debug('📬 Received download event:', payload.type, 'for download:', payload.downloadId);
log.debug(' Full event payload:', JSON.stringify(payload));
// Update the store based on event type
downloads.subscribe((state) => {
const download = state.downloads[payload.downloadId];
console.log(' Current download state:', download ? download.status : 'NOT IN STORE');
log.debug(' Current download state:', download ? download.status : 'NOT IN STORE');
})(); // Immediately unsubscribe after reading
handleDownloadEvent(payload);
});
isEventsInitialized = true;
console.log('✅ Download event listener registered successfully');
log.debug('✅ Download event listener registered successfully');
} catch (err) {
console.error('❌ Failed to register download event listener:', err);
log.error('❌ Failed to register download event listener:', err);
}
}
@@ -599,7 +602,7 @@ function handleDownloadEvent(payload: DownloadEvent): void {
payload.downloadId,
payload.totalBytes || download.fileSize || download.bytesDownloaded,
payload.filePath || download.filePath
).catch((err) => console.error('Failed to persist download completion:', err));
).catch((err) => log.error('Failed to persist download completion:', err));
updateDownloadInStore(payload.downloadId, {
status: 'completed',
@@ -616,7 +619,7 @@ function handleDownloadEvent(payload: DownloadEvent): void {
commands.markDownloadFailed(
payload.downloadId,
payload.error || 'Unknown error'
).catch((err) => console.error('Failed to persist download failure:', err));
).catch((err) => log.error('Failed to persist download failure:', err));
updateDownloadInStore(payload.downloadId, {
status: 'failed',
@@ -654,7 +657,7 @@ function handleDownloadEvent(payload: DownloadEvent): void {
* Helper to update a download in the store.
*/
function updateDownloadInStore(downloadId: number, updates: Partial<DownloadInfo>): void {
console.log(' updateDownloadInStore:', downloadId, updates);
log.debug(' updateDownloadInStore:', downloadId, updates);
downloads.updateDownload(downloadId, updates);
}
@@ -662,6 +665,6 @@ function updateDownloadInStore(downloadId: number, updates: Partial<DownloadInfo
* Helper to remove a download from the store.
*/
function removeDownloadFromStore(downloadId: number): void {
console.log(' removeDownloadFromStore:', downloadId);
log.debug(' removeDownloadFromStore:', downloadId);
downloads.removeDownload(downloadId);
}
+4 -1
View File
@@ -7,6 +7,9 @@ import {
filterSupersededResumeItems,
filterInProgressNextUpItems,
} from "./continueWatchingFilter";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("HomeStore");
interface HomeState {
heroItems: MediaItem[];
@@ -103,7 +106,7 @@ function createHomeStore() {
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to load home sections";
update(s => ({ ...s, isLoading: false, error: message }));
console.error("Failed to load home sections:", error);
log.error("Failed to load home sections:", error);
}
}
+13 -10
View File
@@ -7,6 +7,9 @@ import type { Library, MediaItem, SearchResult, Genre } from "$lib/api/types";
import type { SearchOptions } from "$lib/api/bindings";
import type { SearchScope } from "$lib/utils/searchScope";
import { auth } from "./auth";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("LibraryStore");
/**
* Payload of the backend `search-event` (mirrors Rust `SearchUpdateEvent`).
@@ -84,16 +87,16 @@ function createLibraryStore() {
const startTime = performance.now();
const repo = auth.getRepository();
console.log("📚 [LibraryStore] Loading libraries...");
log.debug("📚 Loading libraries...");
const libraries = await repo.getLibraries();
const loadTime = Math.round(performance.now() - startTime);
if (loadTime < 100) {
console.log(`🚀 [LibraryStore] CACHE HIT! Loaded ${libraries.length} libraries in ${loadTime}ms (instant)`);
log.debug(`🚀 CACHE HIT! Loaded ${libraries.length} libraries in ${loadTime}ms (instant)`);
} else {
console.log(` [LibraryStore] Loaded ${libraries.length} libraries in ${loadTime}ms (from server)`);
log.debug(`⏳ Loaded ${libraries.length} libraries in ${loadTime}ms (from server)`);
}
update((s) => ({
@@ -120,7 +123,7 @@ function createLibraryStore() {
const startTime = performance.now();
const repo = auth.getRepository();
console.log(`📚 [LibraryStore] Loading items for parent: ${parentId.substring(0, 8)}...`);
log.debug(`📚 Loading items for parent: ${parentId.substring(0, 8)}...`);
const result = await repo.getItems(parentId, {
startIndex: options.startIndex ?? 0,
@@ -134,9 +137,9 @@ function createLibraryStore() {
const loadTime = Math.round(performance.now() - startTime);
if (loadTime < 100) {
console.log(`🚀 [LibraryStore] CACHE HIT! Loaded ${result.items.length} items in ${loadTime}ms (instant)`);
log.debug(`🚀 CACHE HIT! Loaded ${result.items.length} items in ${loadTime}ms (instant)`);
} else {
console.log(` [LibraryStore] Loaded ${result.items.length} items in ${loadTime}ms (from server)`);
log.debug(`⏳ Loaded ${result.items.length} items in ${loadTime}ms (from server)`);
}
update((s) => ({
@@ -202,11 +205,11 @@ function createLibraryStore() {
const repo = auth.getRepository();
const item = await repo.getItem(itemId);
console.log(`[LibraryStore] loadItem(${itemId}): ${item.name} (${item.kind})`);
console.log(`[LibraryStore] - Has people? ${item.people ? `YES (${item.people.length})` : 'NO'}`);
log.debug(`loadItem(${itemId}): ${item.name} (${item.kind})`);
log.debug(`- Has people? ${item.people ? `YES (${item.people.length})` : 'NO'}`);
if (item.people && item.people.length > 0) {
item.people.forEach((p, i) => {
console.log(`[LibraryStore] [${i}] ${p.name} (type: "${p.type}", id: "${p.id}")`);
log.debug(` [${i}] ${p.name} (type: "${p.type}", id: "${p.id}")`);
});
}
@@ -319,7 +322,7 @@ function createLibraryStore() {
update((s) => ({ ...s, genres }));
return genres;
} catch (error) {
console.error("Failed to load genres:", error);
log.error("Failed to load genres:", error);
return [];
}
}
+4 -1
View File
@@ -16,6 +16,9 @@ import { writable, get } from "svelte/store";
import { commands } from "$lib/api/bindings";
import type { Session } from "$lib/api/types";
import type { LmsSyncGroup } from "$lib/api/bindings";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("LmsSync");
const LMS_DEVICE_PREFIX = "lms-";
@@ -51,7 +54,7 @@ function createLmsSyncStore() {
update((s) => ({ ...s, groups, error: null }));
} catch (error) {
// The plugin may not be installed; treat as "no groups" rather than fatal.
console.warn("[LmsSync] Failed to load sync groups:", error);
log.warn("Failed to load sync groups:", error);
update((s) => ({ ...s, groups: [] }));
}
}
+6 -3
View File
@@ -5,6 +5,9 @@ import { writable, derived } from "svelte/store";
import type { MediaItem } from "$lib/api/types";
import { auth } from "./auth";
import { buildHeroMix } from "$lib/utils/heroMix";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("MoviesStore");
/** A single "by genre" row: the genre name plus the movies in it. */
export interface GenreRow {
@@ -91,7 +94,7 @@ function createMoviesStore() {
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to load movie sections";
update(s => ({ ...s, isLoading: false, error: message }));
console.error("Failed to load movie sections:", error);
log.error("Failed to load movie sections:", error);
}
}
@@ -120,7 +123,7 @@ function createMoviesStore() {
});
return { id: genre.id, name: genre.name, items: result.items };
} catch (e) {
console.warn(`Failed to load genre row "${genre.name}":`, e);
log.warn(`Failed to load genre row "${genre.name}":`, e);
return { id: genre.id, name: genre.name, items: [] };
}
})
@@ -133,7 +136,7 @@ function createMoviesStore() {
update(s => ({ ...s, genreRows }));
} catch (e) {
console.warn("Failed to load movie genre rows:", e);
log.warn("Failed to load movie genre rows:", e);
}
}
+6 -3
View File
@@ -7,6 +7,9 @@ import { auth } from "./auth";
import { excludePodcasts } from "$lib/utils/podcastFilter";
import { selectDiverseGenres, sampleAcross } from "$lib/utils/genreDiversity";
import { buildHeroMix } from "$lib/utils/heroMix";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("MusicStore");
/** A single "by genre" row: the genre name plus the albums in it. */
export interface GenreRow {
@@ -124,7 +127,7 @@ function createMusicStore() {
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to load music sections";
update(s => ({ ...s, isLoading: false, error: message }));
console.error("Failed to load music sections:", error);
log.error("Failed to load music sections:", error);
}
}
@@ -143,7 +146,7 @@ function createMusicStore() {
// HACK: drop the "Podcasts" folder that lives in the music library.
return { id: genre.id, name: genre.name, items: excludePodcasts(result.items) };
} catch (e) {
console.warn(`Failed to load genre row "${genre.name}":`, e);
log.warn(`Failed to load genre row "${genre.name}":`, e);
return { id: genre.id, name: genre.name, items: [] };
}
}
@@ -205,7 +208,7 @@ function createMusicStore() {
update(s => ({ ...s, genreRows }));
} catch (e) {
console.warn("Failed to load music genre rows:", e);
log.warn("Failed to load music genre rows:", e);
}
}
+28 -25
View File
@@ -14,6 +14,9 @@ import { commands, events } from "$lib/api/bindings";
import { sessions, selectedSession } from "./sessions";
import { auth } from "./auth";
import { ticksToSeconds } from "$lib/utils/playbackUnits";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("PlaybackMode");
export type PlaybackMode = "local" | "remote" | "idle";
@@ -59,7 +62,7 @@ function createPlaybackModeStore() {
// authoritative mode.
sessions.selectSession(remoteSessionId);
} catch (error) {
console.error("Failed to get playback mode:", error);
log.error("Failed to get playback mode:", error);
}
}
@@ -81,7 +84,7 @@ function createPlaybackModeStore() {
sessionId: string | null | undefined,
currentPosition?: number,
): Promise<void> {
console.log("[PlaybackMode] Transferring to remote session:", sessionId);
log.debug("Transferring to remote session:", sessionId);
update((s) => ({ ...s, isTransferring: true, transferError: null }));
let aborted = false;
@@ -104,12 +107,12 @@ function createPlaybackModeStore() {
// Rust handles everything - just wait for it to complete
// It includes its own 5-second timeout for track loading
console.log("[PlaybackMode] About to invoke playback_mode_transfer_to_remote with sessionId:", sessionId, "position:", positionOverride);
log.debug("About to invoke playback_mode_transfer_to_remote with sessionId:", sessionId, "position:", positionOverride);
await commands.playbackModeTransferToRemote(sessionId ?? "", positionOverride);
console.log("[PlaybackMode] Invoke completed successfully");
log.debug("Invoke completed successfully");
if (aborted) {
console.log("[PlaybackMode] Transfer was cancelled");
log.debug("Transfer was cancelled");
return;
}
@@ -122,10 +125,10 @@ function createPlaybackModeStore() {
isTransferring: false,
}));
console.log("[PlaybackMode] Successfully transferred to remote");
log.debug("Successfully transferred to remote");
} catch (error) {
if (aborted) {
console.log("[PlaybackMode] Transfer was cancelled");
log.debug("Transfer was cancelled");
return;
}
@@ -135,7 +138,7 @@ function createPlaybackModeStore() {
isTransferring: false,
transferError: message,
}));
console.error("Transfer to remote failed:", error);
log.error("Transfer to remote failed:", error);
throw error;
} finally {
currentTransferAbort = null;
@@ -154,7 +157,7 @@ function createPlaybackModeStore() {
* Will be fully migrated to Rust after Phase 3.
*/
async function transferToLocal(): Promise<void> {
console.log("[PlaybackMode] Transferring to local");
log.debug("Transferring to local");
update((s) => ({ ...s, isTransferring: true, transferError: null }));
let aborted = false;
@@ -195,7 +198,7 @@ function createPlaybackModeStore() {
const itemId = (nowPlaying as any).id || (nowPlaying as any).Id;
const itemName = (nowPlaying as any).name || (nowPlaying as any).Name;
console.log("[PlaybackMode] Current remote item:", itemName, "position:", positionSeconds, "id:", itemId);
log.debug("Current remote item:", itemName, "position:", positionSeconds, "id:", itemId);
if (!itemId) {
throw new Error("Cannot transfer: remote item has no ID");
@@ -246,10 +249,10 @@ function createPlaybackModeStore() {
isTransferring: false,
}));
console.log("[PlaybackMode] Successfully transferred to local");
log.debug("Successfully transferred to local");
} catch (error) {
if (aborted) {
console.log("[PlaybackMode] Transfer was cancelled");
log.debug("Transfer was cancelled");
return;
}
@@ -259,7 +262,7 @@ function createPlaybackModeStore() {
isTransferring: false,
transferError: message,
}));
console.error("Transfer to local failed:", error);
log.error("Transfer to local failed:", error);
throw error;
} finally {
// Always lower the Rust transferring flag so it can't stick on if any step
@@ -268,7 +271,7 @@ function createPlaybackModeStore() {
try {
await commands.playbackModeSetTransferring(false);
} catch (e) {
console.warn("[PlaybackMode] Failed to clear transferring flag:", e);
log.warn("Failed to clear transferring flag:", e);
}
currentTransferAbort = null;
// Reconcile to the authoritative Rust mode in case a step above threw and
@@ -294,9 +297,9 @@ function createPlaybackModeStore() {
if (event.payload.type === "remote_disconnect_requested") {
const currentState = get({ subscribe });
if (currentState.mode === "remote") {
console.log("[PlaybackMode] Lockscreen requested disconnect; transferring to local");
log.debug("Lockscreen requested disconnect; transferring to local");
transferToLocal().catch((e) =>
console.error("[PlaybackMode] Lockscreen-triggered transfer failed:", e),
log.error("Lockscreen-triggered transfer failed:", e),
);
}
return;
@@ -331,7 +334,7 @@ function createPlaybackModeStore() {
return;
}
console.log("[PlaybackMode] Backend mode changed →", mode, remoteSessionId);
log.debug("Backend mode changed →", mode, remoteSessionId);
update((s) => ({ ...s, mode, remoteSessionId }));
// Keep the selected session in step so the merged UI stores follow, but
// only touch the selection when it actually differs — re-selecting the
@@ -352,10 +355,10 @@ function createPlaybackModeStore() {
if (currentState.mode === "remote" && currentState.remoteSessionId && !currentState.isTransferring) {
if (!session || session.id !== currentState.remoteSessionId || !session.supportsMediaControl) {
consecutiveMisses++;
console.warn(`[PlaybackMode] Remote session miss ${consecutiveMisses}/${DISCONNECT_THRESHOLD}`);
log.warn(`Remote session miss ${consecutiveMisses}/${DISCONNECT_THRESHOLD}`);
if (consecutiveMisses >= DISCONNECT_THRESHOLD) {
console.warn("[PlaybackMode] Remote session lost after sustained disconnection");
log.warn("Remote session lost after sustained disconnection");
consecutiveMisses = 0;
update((s) => ({
...s,
@@ -367,7 +370,7 @@ function createPlaybackModeStore() {
} else {
// Session is healthy, reset counter
if (consecutiveMisses > 0) {
console.log("[PlaybackMode] Remote session recovered after", consecutiveMisses, "misses");
log.debug("Remote session recovered after", consecutiveMisses, "misses");
}
consecutiveMisses = 0;
}
@@ -389,7 +392,7 @@ function createPlaybackModeStore() {
*/
function cancelTransfer(): void {
if (currentTransferAbort) {
console.log("[PlaybackMode] Cancelling transfer");
log.debug("Cancelling transfer");
currentTransferAbort();
}
}
@@ -399,11 +402,11 @@ function createPlaybackModeStore() {
* This stops controlling the remote device and returns to idle/local state
*/
async function disconnect(): Promise<void> {
console.log("[PlaybackMode] Disconnecting from remote session");
log.debug("Disconnecting from remote session");
const currentState = get({ subscribe });
if (currentState.mode !== "remote") {
console.log("[PlaybackMode] Not in remote mode, nothing to disconnect");
log.debug("Not in remote mode, nothing to disconnect");
return;
}
@@ -420,10 +423,10 @@ function createPlaybackModeStore() {
transferError: null,
}));
console.log("[PlaybackMode] Successfully disconnected");
log.debug("Successfully disconnected");
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to disconnect";
console.error("[PlaybackMode] Disconnect failed:", error);
log.error("Disconnect failed:", error);
update((s) => ({
...s,
transferError: message,
+5 -2
View File
@@ -10,6 +10,9 @@ import { writable, derived, get } from "svelte/store";
import { commands, events } from "$lib/api/bindings";
import type { MediaItem } from "$lib/api/types";
import { auth } from "$lib/stores/auth";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("Queue");
export type RepeatMode = "off" | "all" | "one";
@@ -72,7 +75,7 @@ function createQueueStore() {
async function syncFromRust(): Promise<void> {
try {
const rustQueue = (await commands.playerGetQueue()) as unknown as QueueChangedEvent;
console.log("[Queue] Synced from Rust - items:", rustQueue.items.length);
log.debug("Synced from Rust - items:", rustQueue.items.length);
set({
items: rustQueue.items,
currentIndex: rustQueue.currentIndex,
@@ -82,7 +85,7 @@ function createQueueStore() {
hasPrevious: rustQueue.hasPrevious,
});
} catch (error) {
console.error("[Queue] Failed to sync from Rust:", error);
log.error("Failed to sync from Rust:", error);
}
}
+26 -23
View File
@@ -4,6 +4,9 @@
import { writable, derived } from "svelte/store";
import { commands, events } from "$lib/api/bindings";
import type { Session } from "$lib/api/types";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("Sessions");
interface SessionsState {
sessions: Session[];
@@ -28,9 +31,9 @@ function createSessionsStore() {
events.playerStatusEvent.listen((event) => {
if (event.payload.type === "sessions_updated") {
const sessions = event.payload.sessions as unknown as Session[];
console.log(`[Sessions] Received ${sessions.length} sessions from backend`);
log.debug(`Received ${sessions.length} sessions from backend`);
sessions.forEach((s, i) => {
console.log(`[Sessions] Session ${i}: id=${s.id}, device=${s.deviceName}, supportsRemoteControl=${s.supportsRemoteControl}`);
log.debug(`Session ${i}: id=${s.id}, device=${s.deviceName}, supportsRemoteControl=${s.supportsRemoteControl}`);
});
update((s) => ({
...s,
@@ -50,9 +53,9 @@ function createSessionsStore() {
const sessions = await commands.sessionsPollNow();
console.log(`[Sessions] Manual refresh returned ${sessions.length} sessions`);
log.debug(`Manual refresh returned ${sessions.length} sessions`);
sessions.forEach((s, i) => {
console.log(`[Sessions] Session ${i}: id=${s.id}, device=${s.deviceName}, supportsRemoteControl=${s.supportsRemoteControl}`);
log.debug(`Session ${i}: id=${s.id}, device=${s.deviceName}, supportsRemoteControl=${s.supportsRemoteControl}`);
});
update((s) => ({
@@ -69,7 +72,7 @@ function createSessionsStore() {
isLoading: false,
error: message,
}));
console.error("Failed to fetch sessions:", error);
log.error("Failed to fetch sessions:", error);
}
}
@@ -90,7 +93,7 @@ function createSessionsStore() {
// Refresh after command to get updated state
await refresh();
} catch (error) {
console.error("Failed to send play/pause command:", error);
log.error("Failed to send play/pause command:", error);
throw error;
}
}
@@ -103,7 +106,7 @@ function createSessionsStore() {
await commands.remoteSendCommand(sessionId ?? "", "Stop");
await refresh();
} catch (error) {
console.error("Failed to send stop command:", error);
log.error("Failed to send stop command:", error);
throw error;
}
}
@@ -116,7 +119,7 @@ function createSessionsStore() {
await commands.remoteSendCommand(sessionId ?? "", "NextTrack");
await refresh();
} catch (error) {
console.error("Failed to send next track command:", error);
log.error("Failed to send next track command:", error);
throw error;
}
}
@@ -129,7 +132,7 @@ function createSessionsStore() {
await commands.remoteSendCommand(sessionId ?? "", "PreviousTrack");
await refresh();
} catch (error) {
console.error("Failed to send previous track command:", error);
log.error("Failed to send previous track command:", error);
throw error;
}
}
@@ -142,7 +145,7 @@ function createSessionsStore() {
await commands.remoteSessionSeek(sessionId ?? "", positionTicks);
// Don't refresh immediately for seek to avoid UI lag
} catch (error) {
console.error("Failed to send seek command:", error);
log.error("Failed to send seek command:", error);
throw error;
}
}
@@ -155,7 +158,7 @@ function createSessionsStore() {
await commands.remoteSessionSetVolume(sessionId ?? "", volume);
// Don't refresh immediately for volume to avoid UI lag
} catch (error) {
console.error("Failed to send volume command:", error);
log.error("Failed to send volume command:", error);
throw error;
}
}
@@ -168,7 +171,7 @@ function createSessionsStore() {
await commands.remoteSendCommand(sessionId ?? "", "ToggleMute");
await refresh();
} catch (error) {
console.error("Failed to toggle mute:", error);
log.error("Failed to toggle mute:", error);
throw error;
}
}
@@ -181,20 +184,20 @@ function createSessionsStore() {
itemIds: string[],
startIndex = 0
): Promise<void> {
console.log("[SESSIONS] ========== playOnSession called ==========");
console.log("[SESSIONS] sessionId:", sessionId);
console.log("[SESSIONS] itemIds array:", itemIds);
console.log("[SESSIONS] itemIds.length:", itemIds.length);
console.log("[SESSIONS] itemIds JSON:", JSON.stringify(itemIds));
console.log("[SESSIONS] startIndex:", startIndex);
console.log("[SESSIONS] About to call commands.remotePlayOnSession");
log.debug("========== playOnSession called ==========");
log.debug("sessionId:", sessionId);
log.debug("itemIds array:", itemIds);
log.debug("itemIds.length:", itemIds.length);
log.debug("itemIds JSON:", JSON.stringify(itemIds));
log.debug("startIndex:", startIndex);
log.debug("About to call commands.remotePlayOnSession");
try {
// Use Rust player's Jellyfin client for remote playback
const result = await commands.remotePlayOnSession(sessionId ?? "", itemIds, startIndex);
console.log("[SESSIONS] invoke succeeded, result:", result);
log.debug("invoke succeeded, result:", result);
await refresh();
} catch (error) {
console.error("[SESSIONS] Failed to play on session:", error);
log.error("Failed to play on session:", error);
throw error;
}
}
@@ -242,10 +245,10 @@ export const controllableSessions = derived(
sessions,
($sessions) => {
const controllable = $sessions.sessions.filter((s) => s.supportsRemoteControl);
console.log(`[Sessions] Filtering ${$sessions.sessions.length} total sessions, ${controllable.length} are controllable`);
log.debug(`Filtering ${$sessions.sessions.length} total sessions, ${controllable.length} are controllable`);
$sessions.sessions.forEach((s, i) => {
const status = s.supportsRemoteControl ? "✓ CONTROLLABLE" : "✗ NOT CONTROLLABLE";
console.log(`[Sessions] ${status}: ${s.deviceName} (id=${s.id}, supportsRemoteControl=${s.supportsRemoteControl})`);
log.debug(` ${status}: ${s.deviceName} (id=${s.id}, supportsRemoteControl=${s.supportsRemoteControl})`);
});
return controllable;
}
+6 -3
View File
@@ -9,6 +9,9 @@ import {
filterSupersededResumeItems,
filterInProgressNextUpItems,
} from "./continueWatchingFilter";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("TvStore");
/** A single "by genre" row: the genre name plus the series in it. */
export interface GenreRow {
@@ -114,7 +117,7 @@ function createTvStore() {
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to load TV sections";
update(s => ({ ...s, isLoading: false, error: message }));
console.error("Failed to load TV sections:", error);
log.error("Failed to load TV sections:", error);
}
}
@@ -143,7 +146,7 @@ function createTvStore() {
});
return { id: genre.id, name: genre.name, items: result.items };
} catch (e) {
console.warn(`Failed to load genre row "${genre.name}":`, e);
log.warn(`Failed to load genre row "${genre.name}":`, e);
return { id: genre.id, name: genre.name, items: [] };
}
})
@@ -156,7 +159,7 @@ function createTvStore() {
update(s => ({ ...s, genreRows }));
} catch (e) {
console.warn("Failed to load TV genre rows:", e);
log.warn("Failed to load TV genre rows:", e);
}
}