From 190c1a73710b5b9822772170d4fb2183921eb52a Mon Sep 17 00:00:00 2001 From: tapframe Date: Sun, 4 May 2025 01:03:38 +0530 Subject: [PATCH] Refactor FeaturedContent and RatingsSection components to enhance logging and error handling This update removes unnecessary logging statements from the FeaturedContent and RatingsSection components, streamlining the code for better readability. Additionally, it improves error handling by ensuring fallback logos are set appropriately when fetching fails. The useMetadataAssets hook is also optimized for logo and banner fetching, incorporating clearer conditions for asset retrieval based on user preferences. Overall, these changes enhance the maintainability and performance of the components. --- src/components/home/FeaturedContent.tsx | 27 +- src/components/metadata/RatingsSection.tsx | 15 - src/hooks/useMetadataAssets.ts | 314 +++++++++++++++------ src/services/tmdbService.ts | 69 ----- 4 files changed, 230 insertions(+), 195 deletions(-) diff --git a/src/components/home/FeaturedContent.tsx b/src/components/home/FeaturedContent.tsx index 5da195d1..b155ae5d 100644 --- a/src/components/home/FeaturedContent.tsx +++ b/src/components/home/FeaturedContent.tsx @@ -91,22 +91,18 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat try { const isValid = await isValidMetahubLogo(url); if (!isValid) { - console.warn(`[FeaturedContent] Metahub logo validation failed: ${url}`); return false; } } catch (validationError) { // If validation fails, still try to load the image - console.warn(`[FeaturedContent] Logo validation error, will try to load anyway: ${url}`, validationError); } } // Always attempt to prefetch the image regardless of format validation await ExpoImage.prefetch(url); imageCache[url] = true; - console.log(`[FeaturedContent] Successfully preloaded image: ${url}`); return true; } catch (error) { - console.error('[FeaturedContent] Error preloading image:', error); return false; } }; @@ -146,8 +142,6 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat } } - logger.log(`[FeaturedContent] Fetching logo with preference: ${logoPreference}, language: ${preferredLanguage}, ID: ${contentId}`); - // Extract IMDB ID if available let imdbId = null; if (featuredContent.id.startsWith('tt')) { @@ -172,13 +166,12 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat try { const response = await fetch(metahubUrl, { method: 'HEAD' }); if (response.ok) { - logger.log(`[FeaturedContent] Using Metahub logo: ${metahubUrl}`); setLogoUrl(metahubUrl); logoFetchInProgress.current = false; return; // Exit if Metahub logo was found } } catch (error) { - logger.warn(`[FeaturedContent] Failed to fetch Metahub logo:`, error); + // Removed logger.warn } // Fall back to TMDB if Metahub fails and we have a TMDB ID @@ -189,14 +182,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage); if (logoUrl) { - logger.log(`[FeaturedContent] Using fallback TMDB logo (${preferredLanguage}): ${logoUrl}`); setLogoUrl(logoUrl); } else if (currentLogo) { // If TMDB fails too, use existing logo if any setLogoUrl(currentLogo); } } catch (error) { - logger.error('[FeaturedContent] Error fetching TMDB logo:', error); + // Removed logger.error if (currentLogo) setLogoUrl(currentLogo); } } else if (currentLogo) { @@ -212,13 +204,12 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat const logoUrl = await tmdbService.getContentLogo(tmdbType, tmdbId, preferredLanguage); if (logoUrl) { - logger.log(`[FeaturedContent] Using TMDB logo (${preferredLanguage}): ${logoUrl}`); setLogoUrl(logoUrl); logoFetchInProgress.current = false; return; // Exit if TMDB logo was found } } catch (error) { - logger.error('[FeaturedContent] Error fetching TMDB logo:', error); + // Removed logger.error } } else if (imdbId) { // If we have IMDB ID but no TMDB ID, try to find TMDB ID @@ -231,14 +222,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat const logoUrl = await tmdbService.getContentLogo(tmdbType, foundTmdbId.toString(), preferredLanguage); if (logoUrl) { - logger.log(`[FeaturedContent] Using TMDB logo (${preferredLanguage}) via IMDB lookup: ${logoUrl}`); setLogoUrl(logoUrl); logoFetchInProgress.current = false; return; // Exit if TMDB logo was found } } } catch (error) { - logger.error('[FeaturedContent] Error finding TMDB ID from IMDB:', error); + // Removed logger.error } } @@ -249,14 +239,13 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat try { const response = await fetch(metahubUrl, { method: 'HEAD' }); if (response.ok) { - logger.log(`[FeaturedContent] Using fallback Metahub logo: ${metahubUrl}`); setLogoUrl(metahubUrl); } else if (currentLogo) { // If Metahub fails too, use existing logo if any setLogoUrl(currentLogo); } } catch (error) { - logger.warn(`[FeaturedContent] Failed to fetch fallback Metahub logo:`, error); + // Removed logger.warn if (currentLogo) setLogoUrl(currentLogo); } } else if (currentLogo) { @@ -265,10 +254,10 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary }: Feat } } } catch (error) { - logger.error('[FeaturedContent] Error fetching logo:', error); - if (featuredContent?.logo) setLogoUrl(featuredContent.logo); + // Removed logger.error + // Optionally set a fallback logo or handle the error state + setLogoUrl(featuredContent.logo ?? null); // Fallback to initial logo or null } finally { - // Clear fetch in progress flag logoFetchInProgress.current = false; } }; diff --git a/src/components/metadata/RatingsSection.tsx b/src/components/metadata/RatingsSection.tsx index 208cb8ba..a52076e1 100644 --- a/src/components/metadata/RatingsSection.tsx +++ b/src/components/metadata/RatingsSection.tsx @@ -2,9 +2,6 @@ import React, { useEffect, useState, useRef } from 'react'; import { View, Text, StyleSheet, ActivityIndicator, Image, Animated } from 'react-native'; import { colors } from '../../styles/colors'; import { useMDBListRatings } from '../../hooks/useMDBListRatings'; -import { logger } from '../../utils/logger'; -import { MaterialIcons } from '@expo/vector-icons'; -import { FontAwesome } from '@expo/vector-icons'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { isMDBListEnabled, RATING_PROVIDERS_STORAGE_KEY } from '../../screens/MDBListSettingsScreen'; @@ -67,9 +64,7 @@ export const RatingsSection: React.FC = ({ imdbId, type }) try { const enabled = await isMDBListEnabled(); setIsMDBEnabled(enabled); - logger.log('[RatingsSection] MDBList enabled:', enabled); } catch (error) { - logger.error('[RatingsSection] Failed to check if MDBList is enabled:', error); setIsMDBEnabled(true); // Default to enabled } }; @@ -88,26 +83,21 @@ export const RatingsSection: React.FC = ({ imdbId, type }) setEnabledProviders(defaultSettings); } } catch (error) { - logger.error('[RatingsSection] Failed to load provider settings:', error); } }; useEffect(() => { - logger.log(`[RatingsSection] Mounted for ${type}:`, imdbId); return () => { - logger.log(`[RatingsSection] Unmounted for ${type}:`, imdbId); }; }, [imdbId, type]); useEffect(() => { if (error) { - logger.error('[RatingsSection] Error state:', error); } }, [error]); useEffect(() => { if (ratings) { - logger.log('[RatingsSection] Received ratings:', ratings); } }, [ratings]); @@ -124,12 +114,10 @@ export const RatingsSection: React.FC = ({ imdbId, type }) // If MDBList is disabled, don't show anything if (!isMDBEnabled) { - logger.log('[RatingsSection] MDBList is disabled, not showing ratings'); return null; } if (loading) { - logger.log('[RatingsSection] Loading state'); return ( @@ -138,12 +126,9 @@ export const RatingsSection: React.FC = ({ imdbId, type }) } if (error || !ratings || Object.keys(ratings).length === 0) { - logger.log('[RatingsSection] No ratings to display'); return null; } - logger.log('[RatingsSection] Rendering ratings:', Object.keys(ratings).length); - // Define the order and icons/colors for the ratings const ratingConfig = { imdb: { diff --git a/src/hooks/useMetadataAssets.ts b/src/hooks/useMetadataAssets.ts index 8aaef104..0b44bf09 100644 --- a/src/hooks/useMetadataAssets.ts +++ b/src/hooks/useMetadataAssets.ts @@ -61,47 +61,83 @@ export const useMetadataAssets = ( // Fetch logo immediately for TMDB content - with guard against recursive updates useEffect(() => { + const logoPreference = settings.logoSourcePreference || 'metahub'; + const currentLogoUrl = metadata?.logo; + let shouldFetchLogo = false; + + // Determine if we need to fetch a new logo + if (!currentLogoUrl) { + logger.log(`[useMetadataAssets:Logo] Condition check: No current logo exists. Proceeding with fetch.`); + shouldFetchLogo = true; + } else { + const isCurrentLogoMetahub = isMetahubUrl(currentLogoUrl); + const isCurrentLogoTmdb = isTmdbUrl(currentLogoUrl); + + if (logoPreference === 'tmdb' && !isCurrentLogoTmdb) { + logger.log(`[useMetadataAssets:Logo] Condition check: Preference is TMDB, but current logo is not TMDB (${currentLogoUrl}). Proceeding with fetch.`); + shouldFetchLogo = true; + } else if (logoPreference === 'metahub' && !isCurrentLogoMetahub) { + logger.log(`[useMetadataAssets:Logo] Condition check: Preference is Metahub, but current logo is not Metahub (${currentLogoUrl}). Proceeding with fetch.`); + shouldFetchLogo = true; + } else { + logger.log(`[useMetadataAssets:Logo] Condition check: Skipping fetch. Preference (${logoPreference}) matches existing logo source. Current logo: ${currentLogoUrl}`); + } + } + // Guard against infinite loops by checking if we're already fetching - if (metadata && !metadata.logo && !logoFetchInProgress.current) { + if (shouldFetchLogo && !logoFetchInProgress.current) { + logger.log(`[useMetadataAssets:Logo] Starting logo fetch. Current metadata logo: ${currentLogoUrl}`); logoFetchInProgress.current = true; const fetchLogo = async () => { + // Clear existing logo before fetching new one to avoid briefly showing wrong logo + // Only do this if we decided to fetch because of a mismatch or non-existence + if (shouldFetchLogo) { + logger.log(`[useMetadataAssets:Logo] Clearing existing logo in metadata state before fetch.`); + setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: undefined })); + } + try { // Get logo source preference from settings - const logoPreference = settings.logoSourcePreference || 'metahub'; + // const logoPreference = settings.logoSourcePreference || 'metahub'; // Already defined above const preferredLanguage = settings.tmdbLanguagePreference || 'en'; - logger.log(`[useMetadataAssets] Fetching logo with strict preference: ${logoPreference}`); + logger.log(`[useMetadataAssets:Logo] Fetching logo. Preference: ${logoPreference}, Language: ${preferredLanguage}, IMDB ID: ${imdbId}`); if (logoPreference === 'metahub' && imdbId) { // Metahub path - direct fetch without HEAD request for speed + logger.log(`[useMetadataAssets:Logo] Preference is Metahub. Attempting Metahub fetch for ${imdbId}.`); const metahubUrl = `https://images.metahub.space/logo/medium/${imdbId}/img`; try { // Verify Metahub image exists to prevent showing broken images + logger.log(`[useMetadataAssets:Logo] Checking Metahub logo existence: ${metahubUrl}`); const response = await fetch(metahubUrl, { method: 'HEAD' }); if (response.ok) { // Update metadata with Metahub logo - setMetadata((prevMetadata: any) => ({ - ...prevMetadata!, - logo: metahubUrl - })); - logger.log(`[useMetadataAssets] Set Metahub logo: ${metahubUrl}`); + logger.log(`[useMetadataAssets:Logo] Metahub logo found. Updating metadata state.`); + setMetadata((prevMetadata: any) => { + logger.log(`[useMetadataAssets:Logo] setMetadata called with Metahub logo: ${metahubUrl}`); + return { ...prevMetadata!, logo: metahubUrl }; + }); } else { - logger.warn(`[useMetadataAssets] Metahub logo not found for ${imdbId}`); + logger.warn(`[useMetadataAssets:Logo] Metahub logo HEAD request failed with status ${response.status} for ${imdbId}`); } } catch (error) { - logger.error(`[useMetadataAssets] Error checking Metahub logo:`, error); + logger.error(`[useMetadataAssets:Logo] Error checking Metahub logo:`, error); } } else if (logoPreference === 'tmdb') { // TMDB path - optimized flow + logger.log(`[useMetadataAssets:Logo] Preference is TMDB. Attempting TMDB fetch.`); let tmdbId: string | null = null; let contentType = type === 'series' ? 'tv' : 'movie'; // Extract or find TMDB ID in one step if (id.startsWith('tmdb:')) { tmdbId = id.split(':')[1]; + logger.log(`[useMetadataAssets:Logo] Extracted TMDB ID from route ID: ${tmdbId}`); } else if (imdbId) { + logger.log(`[useMetadataAssets:Logo] Attempting to find TMDB ID from IMDB ID: ${imdbId}`); // Only look up TMDB ID if we don't already have it try { const tmdbService = TMDBService.getInstance(); @@ -109,33 +145,46 @@ export const useMetadataAssets = ( if (foundId) { tmdbId = String(foundId); setFoundTmdbId(tmdbId); // Save for banner fetching + logger.log(`[useMetadataAssets:Logo] Found TMDB ID: ${tmdbId}`); + } else { + logger.warn(`[useMetadataAssets:Logo] Could not find TMDB ID for IMDB ID: ${imdbId}`); } } catch (error) { - logger.error(`[useMetadataAssets] Error finding TMDB ID:`, error); + logger.error(`[useMetadataAssets:Logo] Error finding TMDB ID:`, error); } + } else { + logger.warn(`[useMetadataAssets:Logo] Cannot attempt TMDB fetch: No TMDB ID in route and no IMDB ID provided.`); } if (tmdbId) { try { // Direct fetch - avoid multiple service calls + logger.log(`[useMetadataAssets:Logo] Fetching TMDB logo for ${contentType} ID: ${tmdbId}, Language: ${preferredLanguage}`); const tmdbService = TMDBService.getInstance(); const logoUrl = await tmdbService.getContentLogo(contentType as 'tv' | 'movie', tmdbId, preferredLanguage); if (logoUrl) { - setMetadata((prevMetadata: any) => ({ - ...prevMetadata!, - logo: logoUrl - })); - logger.log(`[useMetadataAssets] Set TMDB logo: ${logoUrl}`); + logger.log(`[useMetadataAssets:Logo] TMDB logo found. Updating metadata state.`); + setMetadata((prevMetadata: any) => { + logger.log(`[useMetadataAssets:Logo] setMetadata called with TMDB logo: ${logoUrl}`); + return { ...prevMetadata!, logo: logoUrl }; + }); + } else { + logger.warn(`[useMetadataAssets:Logo] No TMDB logo found for ${contentType}/${tmdbId}.`); } } catch (error) { - logger.error(`[useMetadataAssets] Error fetching TMDB logo:`, error); + logger.error(`[useMetadataAssets:Logo] Error fetching TMDB logo:`, error); } + } else { + logger.warn(`[useMetadataAssets:Logo] Skipping TMDB logo fetch as no TMDB ID was determined.`); } + } else { + logger.log(`[useMetadataAssets:Logo] Preference not Metahub and no IMDB ID, or preference not TMDB. No logo fetched.`); } } catch (error) { - logger.error(`[useMetadataAssets] Error in fetchLogo:`, error); + logger.error(`[useMetadataAssets:Logo] Error in outer fetchLogo try block:`, error); } finally { + logger.log(`[useMetadataAssets:Logo] Finished logo fetch attempt.`); logoFetchInProgress.current = false; } }; @@ -143,23 +192,38 @@ export const useMetadataAssets = ( // Execute fetch without awaiting fetchLogo(); } - }, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference]); + // Add logging for when fetch is skipped due to already fetching + else if (shouldFetchLogo && logoFetchInProgress.current) { + logger.log(`[useMetadataAssets:Logo] Skipping logo fetch because logoFetchInProgress is true.`); + } + }, [id, type, metadata, setMetadata, imdbId, settings.logoSourcePreference, settings.tmdbLanguagePreference]); // Added tmdbLanguagePreference dependency // Fetch banner image based on logo source preference - optimized version useEffect(() => { // Skip if no metadata or already completed with the correct source - if (!metadata) return; + if (!metadata) { + logger.log(`[useMetadataAssets:Banner] Skipping banner fetch: No metadata.`); + return; + } // Check if we need to refresh the banner based on source const currentPreference = settings.logoSourcePreference || 'metahub'; + logger.log(`[useMetadataAssets:Banner] Checking banner fetch. Preference: ${currentPreference}, Current Banner Source: ${bannerSource}, Forced Refresh Done: ${forcedBannerRefreshDone.current}`); + if (bannerSource === currentPreference && forcedBannerRefreshDone.current) { + logger.log(`[useMetadataAssets:Banner] Skipping fetch: Banner already loaded with correct source (${currentPreference}).`); return; // Already have the correct source, no need to refresh } const fetchBanner = async () => { + logger.log(`[useMetadataAssets:Banner] Starting banner fetch.`); setLoadingBanner(true); setBannerImage(null); // Clear existing banner to prevent mixed sources + setBannerSource(null); // Clear source tracking + let finalBanner: string | null = null; + let bannerSourceType: 'tmdb' | 'metahub' | 'default' = 'default'; + try { // Extract all possible IDs at once const preferredLanguage = settings.tmdbLanguagePreference || 'en'; @@ -173,20 +237,36 @@ export const useMetadataAssets = ( tmdbId = foundTmdbId; } else if ((metadata as any).tmdbId) { tmdbId = (metadata as any).tmdbId; + } else if (imdbId) { + // Last attempt: Look up TMDB ID if we haven't yet + logger.log(`[useMetadataAssets:Banner] Attempting TMDB ID lookup from IMDB ID: ${imdbId} for banner fetch.`); + try { + const tmdbService = TMDBService.getInstance(); + const foundId = await tmdbService.findTMDBIdByIMDB(imdbId); + if (foundId) { + tmdbId = String(foundId); + logger.log(`[useMetadataAssets:Banner] Found TMDB ID: ${tmdbId}`); + } else { + logger.warn(`[useMetadataAssets:Banner] Could not find TMDB ID for IMDB ID: ${imdbId}`); + } + } catch (lookupError) { + logger.error(`[useMetadataAssets:Banner] Error looking up TMDB ID:`, lookupError); + } } + logger.log(`[useMetadataAssets:Banner] Determined TMDB ID for banner fetch: ${tmdbId}`); + // Default fallback to use if nothing else works - let finalBanner: string | null = null; - let bannerSourceType: 'tmdb' | 'metahub' | 'default' = 'default'; if (currentPreference === 'tmdb' && tmdbId) { // TMDB direct path - const endpoint = contentType === 'tv' ? 'tv' : 'movie'; + logger.log(`[useMetadataAssets:Banner] Preference is TMDB. Attempting TMDB banner fetch for ${contentType}/${tmdbId}.`); + const endpoint = contentType === 'tv' ? 'tv' : 'movie'; try { // Use TMDBService instead of direct fetch with hardcoded API key const tmdbService = TMDBService.getInstance(); - logger.log(`[useMetadataAssets] Fetching TMDB details for ${endpoint}/${tmdbId}`); + logger.log(`[useMetadataAssets:Banner] Fetching TMDB details for ${endpoint}/${tmdbId}`); try { // Get details with backdrop path using TMDBService @@ -196,111 +276,161 @@ export const useMetadataAssets = ( // Step 1: Get basic details if (endpoint === 'movie') { details = await tmdbService.getMovieDetails(tmdbId); + logger.log(`[useMetadataAssets:Banner] TMDB getMovieDetails result:`, details ? `Found backdrop: ${!!details.backdrop_path}, Found poster: ${!!details.poster_path}` : 'null'); - // Step 2: Get images separately if details succeeded - if (details) { - try { - // Use getMovieImages to get image data - this returns a logo URL but we need more - await tmdbService.getMovieImages(tmdbId, preferredLanguage); - - // We'll use the backdrop from the details - logger.log(`[useMetadataAssets] Got movie details for ${tmdbId}`); - } catch (imageError) { - logger.warn(`[useMetadataAssets] Could not get movie images: ${imageError}`); - } - } - } else { + // Step 2: Get images separately if details succeeded (This call might not be needed for banner) + // if (details) { + // try { + // await tmdbService.getMovieImages(tmdbId, preferredLanguage); + // logger.log(`[useMetadataAssets:Banner] Got movie images for ${tmdbId}`); + // } catch (imageError) { + // logger.warn(`[useMetadataAssets:Banner] Could not get movie images: ${imageError}`); + // } + //} + } else { // TV Show details = await tmdbService.getTVShowDetails(Number(tmdbId)); - - // Step 2: Get images separately if details succeeded - if (details) { - try { - // Use getTvShowImages to get image data - this returns a logo URL but we need more - await tmdbService.getTvShowImages(tmdbId, preferredLanguage); - - // We'll use the backdrop from the details - logger.log(`[useMetadataAssets] Got TV details for ${tmdbId}`); - } catch (imageError) { - logger.warn(`[useMetadataAssets] Could not get TV images: ${imageError}`); - } - } + logger.log(`[useMetadataAssets:Banner] TMDB getTVShowDetails result:`, details ? `Found backdrop: ${!!details.backdrop_path}, Found poster: ${!!details.poster_path}` : 'null'); + + // Step 2: Get images separately if details succeeded (This call might not be needed for banner) + // if (details) { + // try { + // await tmdbService.getTvShowImages(tmdbId, preferredLanguage); + // logger.log(`[useMetadataAssets:Banner] Got TV images for ${tmdbId}`); + // } catch (imageError) { + // logger.warn(`[useMetadataAssets:Banner] Could not get TV images: ${imageError}`); + // } + // } } // Check if we have a backdrop path from details if (details && details.backdrop_path) { finalBanner = tmdbService.getImageUrl(details.backdrop_path); bannerSourceType = 'tmdb'; - logger.log(`[useMetadataAssets] Using TMDB backdrop from details: ${finalBanner}`); + logger.log(`[useMetadataAssets:Banner] Using TMDB backdrop from details: ${finalBanner}`); } // If no backdrop, try poster as fallback else if (details && details.poster_path) { - logger.warn(`[useMetadataAssets] No backdrop available, using poster as fallback`); + logger.warn(`[useMetadataAssets:Banner] No TMDB backdrop available, using poster as fallback.`); finalBanner = tmdbService.getImageUrl(details.poster_path); bannerSourceType = 'tmdb'; } else { - logger.warn(`[useMetadataAssets] No backdrop or poster found for ${endpoint}/${tmdbId}`); + logger.warn(`[useMetadataAssets:Banner] No TMDB backdrop or poster found for ${endpoint}/${tmdbId}. TMDB path failed.`); + // Explicitly set finalBanner to null if TMDB fails + finalBanner = null; } } catch (innerErr) { - logger.error(`[useMetadataAssets] Error fetching TMDB details/images:`, innerErr); - } - } catch (err) { - logger.error(`[useMetadataAssets] TMDB service initialization error:`, err); + logger.error(`[useMetadataAssets:Banner] Error fetching TMDB details/images:`, innerErr); + finalBanner = null; // Ensure failure case nullifies banner } + } catch (err) { + logger.error(`[useMetadataAssets:Banner] TMDB service initialization error:`, err); + finalBanner = null; // Ensure failure case nullifies banner + } } else if (currentPreference === 'metahub' && imdbId) { // Metahub path - verify it exists to prevent broken images + logger.log(`[useMetadataAssets:Banner] Preference is Metahub. Attempting Metahub banner fetch for ${imdbId}.`); const metahubUrl = `https://images.metahub.space/background/medium/${imdbId}/img`; - try { + try { + logger.log(`[useMetadataAssets:Banner] Checking Metahub banner existence: ${metahubUrl}`); const response = await fetch(metahubUrl, { method: 'HEAD' }); if (response.ok) { finalBanner = metahubUrl; bannerSourceType = 'metahub'; - logger.log(`[useMetadataAssets] Using Metahub banner: ${finalBanner}`); + logger.log(`[useMetadataAssets:Banner] Metahub banner found: ${finalBanner}`); } else { - logger.warn(`[useMetadataAssets] Metahub banner not found, using default`); + logger.warn(`[useMetadataAssets:Banner] Metahub banner HEAD request failed with status ${response.status}, using default.`); + finalBanner = null; // Ensure fallback if Metahub fails } } catch (error) { - logger.error(`[useMetadataAssets] Error checking Metahub banner:`, error); + logger.error(`[useMetadataAssets:Banner] Error checking Metahub banner:`, error); + finalBanner = null; // Ensure fallback if Metahub errors } + } else { + // This case handles: + // 1. Preference is TMDB but no tmdbId could be found. + // 2. Preference is Metahub but no imdbId was provided. + logger.log(`[useMetadataAssets:Banner] Skipping direct fetch: Preference=${currentPreference}, tmdbId=${tmdbId}, imdbId=${imdbId}. Will rely on default/fallback.`); + finalBanner = null; // Explicitly nullify banner if preference conditions aren't met + } + + // Fallback logic if preferred source failed or wasn't attempted + if (!finalBanner) { + logger.log(`[useMetadataAssets:Banner] Preferred source (${currentPreference}) did not yield a banner. Checking fallbacks.`); + // Fallback 1: Try the *other* source if the preferred one failed + if (currentPreference === 'tmdb' && imdbId) { // If preferred was TMDB, try Metahub + logger.log(`[useMetadataAssets:Banner] Fallback: Trying Metahub for ${imdbId}.`); + const metahubUrl = `https://images.metahub.space/background/medium/${imdbId}/img`; + try { + const response = await fetch(metahubUrl, { method: 'HEAD' }); + if (response.ok) { + finalBanner = metahubUrl; + bannerSourceType = 'metahub'; + logger.log(`[useMetadataAssets:Banner] Fallback Metahub banner found: ${finalBanner}`); + } else { + logger.warn(`[useMetadataAssets:Banner] Fallback Metahub HEAD failed: ${response.status}`); + } + } catch (fallbackError) { + logger.error(`[useMetadataAssets:Banner] Fallback Metahub check error:`, fallbackError); + } + } else if (currentPreference === 'metahub' && tmdbId) { // If preferred was Metahub, try TMDB + logger.log(`[useMetadataAssets:Banner] Fallback: Trying TMDB for ${contentType}/${tmdbId}.`); + const endpoint = contentType === 'tv' ? 'tv' : 'movie'; + try { + const tmdbService = TMDBService.getInstance(); + let details = endpoint === 'movie' ? await tmdbService.getMovieDetails(tmdbId) : await tmdbService.getTVShowDetails(Number(tmdbId)); + if (details?.backdrop_path) { + finalBanner = tmdbService.getImageUrl(details.backdrop_path); + bannerSourceType = 'tmdb'; + logger.log(`[useMetadataAssets:Banner] Fallback TMDB banner found (backdrop): ${finalBanner}`); + } else if (details?.poster_path) { + finalBanner = tmdbService.getImageUrl(details.poster_path); + bannerSourceType = 'tmdb'; + logger.log(`[useMetadataAssets:Banner] Fallback TMDB banner found (poster): ${finalBanner}`); + } else { + logger.warn(`[useMetadataAssets:Banner] Fallback TMDB fetch found no backdrop or poster.`); + } + } catch (fallbackError) { + logger.error(`[useMetadataAssets:Banner] Fallback TMDB check error:`, fallbackError); + } + } + + // Fallback 2: Use metadata banner/poster if other source also failed + if (!finalBanner) { + logger.log(`[useMetadataAssets:Banner] Fallback source also failed or not applicable. Using metadata.banner or metadata.poster.`); + finalBanner = metadata?.banner || metadata?.poster || null; + bannerSourceType = 'default'; + if (finalBanner) { + logger.log(`[useMetadataAssets:Banner] Using default banner from metadata: ${finalBanner}`); + } else { + logger.warn(`[useMetadataAssets:Banner] No default banner found in metadata either.`); + } + } } - // If no source-specific banner was found, use default - if (!finalBanner) { - finalBanner = metadata.banner || metadata.poster; - bannerSourceType = 'default'; - logger.log(`[useMetadataAssets] Using default banner: ${finalBanner}`); - } - - // Set banner image once at the end - setBannerImage(finalBanner); - setBannerSource(bannerSourceType); - - } catch (error) { - logger.error(`[useMetadataAssets] Banner fetch error:`, error); - // Use default banner if error occurred - setBannerImage(metadata.banner || metadata.poster); + // Set the final state + logger.log(`[useMetadataAssets:Banner] Final decision: Setting banner to ${finalBanner} (Source: ${bannerSourceType})`); + setBannerImage(finalBanner); + setBannerSource(bannerSourceType); // Track the source of the final image + forcedBannerRefreshDone.current = true; // Mark this cycle as complete + + } catch (error) { + logger.error(`[useMetadataAssets:Banner] Error in outer fetchBanner try block:`, error); + // Ensure fallback to default even on outer error + const defaultBanner = metadata?.banner || metadata?.poster || null; + setBannerImage(defaultBanner); setBannerSource('default'); - } finally { - setLoadingBanner(false); - forcedBannerRefreshDone.current = true; + logger.log(`[useMetadataAssets:Banner] Setting default banner due to outer error: ${defaultBanner}`); + } finally { + logger.log(`[useMetadataAssets:Banner] Finished banner fetch attempt.`); + setLoadingBanner(false); } }; fetchBanner(); - }, [metadata, id, type, imdbId, settings.logoSourcePreference, foundTmdbId, bannerSource]); - - // Original reset forced refresh effect - useEffect(() => { - if (forcedBannerRefreshDone.current) { - logger.log(`[useMetadataAssets] Logo preference changed, resetting banner refresh flag`); - forcedBannerRefreshDone.current = false; - // Clear the banner image immediately to prevent showing the wrong source briefly - setBannerImage(null); - setBannerSource(null); - // This will trigger the banner fetch effect to run again - } - }, [settings.logoSourcePreference]); + + }, [metadata, id, type, imdbId, settings.logoSourcePreference, settings.tmdbLanguagePreference, setMetadata, foundTmdbId, bannerSource]); // Added bannerSource dependency to re-evaluate if it changes unexpectedly return { bannerImage, diff --git a/src/services/tmdbService.ts b/src/services/tmdbService.ts index 5fca12ed..405e4f6b 100644 --- a/src/services/tmdbService.ts +++ b/src/services/tmdbService.ts @@ -1,5 +1,4 @@ import axios from 'axios'; -import { logger } from '../utils/logger'; import AsyncStorage from '@react-native-async-storage/async-storage'; // TMDB API configuration @@ -100,15 +99,12 @@ export class TMDBService { if (this.useCustomKey && savedKey) { this.apiKey = savedKey; - logger.log('Using custom TMDb API key'); } else { this.apiKey = DEFAULT_API_KEY; - logger.log('Using default TMDb API key'); } this.apiKeyLoaded = true; } catch (error) { - logger.error('Failed to load TMDb API key from storage, using default:', error); this.apiKey = DEFAULT_API_KEY; this.apiKeyLoaded = true; } @@ -157,7 +153,6 @@ export class TMDBService { }); return response.data.results; } catch (error) { - logger.error('Failed to search TV show:', error); return []; } } @@ -175,7 +170,6 @@ export class TMDBService { }); return response.data; } catch (error) { - logger.error('Failed to get TV show details:', error); return null; } } @@ -198,7 +192,6 @@ export class TMDBService { ); return response.data; } catch (error) { - logger.error('Failed to get episode external IDs:', error); return null; } } @@ -234,7 +227,6 @@ export class TMDBService { TMDBService.ratingCache.set(cacheKey, rating); return rating; } catch (error) { - logger.error('Failed to get IMDb rating:', error); // Cache the failed result too to prevent repeated failed requests TMDBService.ratingCache.set(cacheKey, null); return null; @@ -289,7 +281,6 @@ export class TMDBService { return season; } catch (error) { - logger.error('Failed to get season details:', error); return null; } } @@ -314,7 +305,6 @@ export class TMDBService { ); return response.data; } catch (error) { - logger.error('Failed to get episode details:', error); return null; } } @@ -333,7 +323,6 @@ export class TMDBService { const tmdbId = await this.findTMDBIdByIMDB(imdbId); return tmdbId; } catch (error) { - logger.error('Failed to extract TMDB ID from Stremio ID:', error); return null; } } @@ -366,7 +355,6 @@ export class TMDBService { return null; } catch (error) { - logger.error('Failed to find TMDB ID by IMDB ID:', error); return null; } } @@ -376,13 +364,11 @@ export class TMDBService { */ getImageUrl(path: string | null, size: 'original' | 'w500' | 'w300' | 'w185' | 'profile' = 'original'): string | null { if (!path) { - logger.warn(`[TMDBService] Cannot construct image URL from null path`); return null; } const baseImageUrl = 'https://image.tmdb.org/t/p/'; const fullUrl = `${baseImageUrl}${size}${path}`; - logger.log(`[TMDBService] Constructed image URL: ${fullUrl}`); return fullUrl; } @@ -411,7 +397,6 @@ export class TMDBService { await Promise.all(seasonPromises); return allEpisodes; } catch (error) { - logger.error('Failed to get all episodes:', error); return {}; } } @@ -472,7 +457,6 @@ export class TMDBService { crew: response.data.crew || [] }; } catch (error) { - logger.error('Failed to fetch credits:', error); return { cast: [], crew: [] }; } } @@ -487,7 +471,6 @@ export class TMDBService { }); return response.data; } catch (error) { - logger.error('Failed to fetch person details:', error); return null; } } @@ -506,14 +489,12 @@ export class TMDBService { ); return response.data; } catch (error) { - logger.error('Failed to get show external IDs:', error); return null; } } async getRecommendations(type: 'movie' | 'tv', tmdbId: string): Promise { if (!this.apiKey) { - logger.error('TMDB API key not set'); return []; } try { @@ -523,7 +504,6 @@ export class TMDBService { }); return response.data.results || []; } catch (error) { - logger.error(`Error fetching TMDB ${type} recommendations for ID ${tmdbId}:`, error); return []; } } @@ -541,7 +521,6 @@ export class TMDBService { }); return response.data.results; } catch (error) { - logger.error('Failed to search multi:', error); return []; } } @@ -560,7 +539,6 @@ export class TMDBService { }); return response.data; } catch (error) { - logger.error('Failed to get movie details:', error); return null; } } @@ -570,8 +548,6 @@ export class TMDBService { */ async getMovieImages(movieId: number | string, preferredLanguage: string = 'en'): Promise { try { - logger.log(`[TMDBService] Fetching movie images for TMDB ID: ${movieId}, preferred language: ${preferredLanguage}`); - const response = await axios.get(`${BASE_URL}/movie/${movieId}/images`, { headers: await this.getHeaders(), params: await this.getParams({ @@ -580,7 +556,6 @@ export class TMDBService { }); const images = response.data; - logger.log(`[TMDBService] Retrieved ${images?.logos?.length || 0} logos for movie ID ${movieId}`); if (images && images.logos && images.logos.length > 0) { // First prioritize preferred language SVG logos if not English @@ -591,7 +566,6 @@ export class TMDBService { logo.iso_639_1 === preferredLanguage ); if (preferredSvgLogo) { - logger.log(`[TMDBService] Found ${preferredLanguage} SVG logo for movie ID ${movieId}: ${preferredSvgLogo.file_path}`); return this.getImageUrl(preferredSvgLogo.file_path); } @@ -602,7 +576,6 @@ export class TMDBService { logo.iso_639_1 === preferredLanguage ); if (preferredPngLogo) { - logger.log(`[TMDBService] Found ${preferredLanguage} PNG logo for movie ID ${movieId}: ${preferredPngLogo.file_path}`); return this.getImageUrl(preferredPngLogo.file_path); } @@ -611,7 +584,6 @@ export class TMDBService { logo.iso_639_1 === preferredLanguage ); if (preferredLogo) { - logger.log(`[TMDBService] Found ${preferredLanguage} logo for movie ID ${movieId}: ${preferredLogo.file_path}`); return this.getImageUrl(preferredLogo.file_path); } } @@ -623,7 +595,6 @@ export class TMDBService { logo.iso_639_1 === 'en' ); if (enSvgLogo) { - logger.log(`[TMDBService] Found English SVG logo for movie ID ${movieId}: ${enSvgLogo.file_path}`); return this.getImageUrl(enSvgLogo.file_path); } @@ -634,7 +605,6 @@ export class TMDBService { logo.iso_639_1 === 'en' ); if (enPngLogo) { - logger.log(`[TMDBService] Found English PNG logo for movie ID ${movieId}: ${enPngLogo.file_path}`); return this.getImageUrl(enPngLogo.file_path); } @@ -643,7 +613,6 @@ export class TMDBService { logo.iso_639_1 === 'en' ); if (enLogo) { - logger.log(`[TMDBService] Found English logo for movie ID ${movieId}: ${enLogo.file_path}`); return this.getImageUrl(enLogo.file_path); } @@ -652,7 +621,6 @@ export class TMDBService { logo.file_path && logo.file_path.endsWith('.svg') ); if (svgLogo) { - logger.log(`[TMDBService] Found SVG logo for movie ID ${movieId}: ${svgLogo.file_path}`); return this.getImageUrl(svgLogo.file_path); } @@ -661,20 +629,15 @@ export class TMDBService { logo.file_path && logo.file_path.endsWith('.png') ); if (pngLogo) { - logger.log(`[TMDBService] Found PNG logo for movie ID ${movieId}: ${pngLogo.file_path}`); return this.getImageUrl(pngLogo.file_path); } // Last resort: any logo - logger.log(`[TMDBService] Using first available logo for movie ID ${movieId}: ${images.logos[0].file_path}`); return this.getImageUrl(images.logos[0].file_path); } - logger.warn(`[TMDBService] No logos found for movie ID ${movieId}`); return null; // No logos found } catch (error) { - // Log error but don't throw, just return null if fetching images fails - logger.error(`[TMDBService] Failed to get movie images for ID ${movieId}:`, error); return null; } } @@ -684,8 +647,6 @@ export class TMDBService { */ async getTvShowImages(showId: number | string, preferredLanguage: string = 'en'): Promise { try { - logger.log(`[TMDBService] Fetching TV show images for TMDB ID: ${showId}, preferred language: ${preferredLanguage}`); - const response = await axios.get(`${BASE_URL}/tv/${showId}/images`, { headers: await this.getHeaders(), params: await this.getParams({ @@ -694,7 +655,6 @@ export class TMDBService { }); const images = response.data; - logger.log(`[TMDBService] Retrieved ${images?.logos?.length || 0} logos for TV show ID ${showId}`); if (images && images.logos && images.logos.length > 0) { // First prioritize preferred language SVG logos if not English @@ -705,7 +665,6 @@ export class TMDBService { logo.iso_639_1 === preferredLanguage ); if (preferredSvgLogo) { - logger.log(`[TMDBService] Found ${preferredLanguage} SVG logo for TV show ID ${showId}: ${preferredSvgLogo.file_path}`); return this.getImageUrl(preferredSvgLogo.file_path); } @@ -716,7 +675,6 @@ export class TMDBService { logo.iso_639_1 === preferredLanguage ); if (preferredPngLogo) { - logger.log(`[TMDBService] Found ${preferredLanguage} PNG logo for TV show ID ${showId}: ${preferredPngLogo.file_path}`); return this.getImageUrl(preferredPngLogo.file_path); } @@ -725,7 +683,6 @@ export class TMDBService { logo.iso_639_1 === preferredLanguage ); if (preferredLogo) { - logger.log(`[TMDBService] Found ${preferredLanguage} logo for TV show ID ${showId}: ${preferredLogo.file_path}`); return this.getImageUrl(preferredLogo.file_path); } } @@ -737,7 +694,6 @@ export class TMDBService { logo.iso_639_1 === 'en' ); if (enSvgLogo) { - logger.log(`[TMDBService] Found English SVG logo for TV show ID ${showId}: ${enSvgLogo.file_path}`); return this.getImageUrl(enSvgLogo.file_path); } @@ -748,7 +704,6 @@ export class TMDBService { logo.iso_639_1 === 'en' ); if (enPngLogo) { - logger.log(`[TMDBService] Found English PNG logo for TV show ID ${showId}: ${enPngLogo.file_path}`); return this.getImageUrl(enPngLogo.file_path); } @@ -757,7 +712,6 @@ export class TMDBService { logo.iso_639_1 === 'en' ); if (enLogo) { - logger.log(`[TMDBService] Found English logo for TV show ID ${showId}: ${enLogo.file_path}`); return this.getImageUrl(enLogo.file_path); } @@ -766,7 +720,6 @@ export class TMDBService { logo.file_path && logo.file_path.endsWith('.svg') ); if (svgLogo) { - logger.log(`[TMDBService] Found SVG logo for TV show ID ${showId}: ${svgLogo.file_path}`); return this.getImageUrl(svgLogo.file_path); } @@ -775,20 +728,15 @@ export class TMDBService { logo.file_path && logo.file_path.endsWith('.png') ); if (pngLogo) { - logger.log(`[TMDBService] Found PNG logo for TV show ID ${showId}: ${pngLogo.file_path}`); return this.getImageUrl(pngLogo.file_path); } // Last resort: any logo - logger.log(`[TMDBService] Using first available logo for TV show ID ${showId}: ${images.logos[0].file_path}`); return this.getImageUrl(images.logos[0].file_path); } - logger.warn(`[TMDBService] No logos found for TV show ID ${showId}`); return null; // No logos found } catch (error) { - // Log error but don't throw, just return null if fetching images fails - logger.error(`[TMDBService] Failed to get TV show images for ID ${showId}:`, error); return null; } } @@ -798,21 +746,16 @@ export class TMDBService { */ async getContentLogo(type: 'movie' | 'tv', id: number | string, preferredLanguage: string = 'en'): Promise { try { - logger.log(`[TMDBService] Getting content logo for ${type} with ID ${id}, preferred language: ${preferredLanguage}`); - const result = type === 'movie' ? await this.getMovieImages(id, preferredLanguage) : await this.getTvShowImages(id, preferredLanguage); if (result) { - logger.log(`[TMDBService] Successfully retrieved logo for ${type} ID ${id}: ${result}`); } else { - logger.warn(`[TMDBService] No logo found for ${type} ID ${id}`); } return result; } catch (error) { - logger.error(`[TMDBService] Failed to get content logo for ${type} ID ${id}:`, error); return null; } } @@ -847,7 +790,6 @@ export class TMDBService { } return null; } catch (error) { - logger.error('Error fetching certification:', error); return null; } } @@ -883,7 +825,6 @@ export class TMDBService { external_ids: externalIdsResponse.data }; } catch (error) { - logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error); return item; } }) @@ -891,7 +832,6 @@ export class TMDBService { return resultsWithExternalIds; } catch (error) { - logger.error(`Failed to get trending ${type} content:`, error); return []; } } @@ -928,7 +868,6 @@ export class TMDBService { external_ids: externalIdsResponse.data }; } catch (error) { - logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error); return item; } }) @@ -936,7 +875,6 @@ export class TMDBService { return resultsWithExternalIds; } catch (error) { - logger.error(`Failed to get popular ${type} content:`, error); return []; } } @@ -976,7 +914,6 @@ export class TMDBService { external_ids: externalIdsResponse.data }; } catch (error) { - logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error); return item; } }) @@ -984,7 +921,6 @@ export class TMDBService { return resultsWithExternalIds; } catch (error) { - logger.error(`Failed to get upcoming ${type} content:`, error); return []; } } @@ -1002,7 +938,6 @@ export class TMDBService { }); return response.data.genres || []; } catch (error) { - logger.error('Failed to fetch movie genres:', error); return []; } } @@ -1020,7 +955,6 @@ export class TMDBService { }); return response.data.genres || []; } catch (error) { - logger.error('Failed to fetch TV genres:', error); return []; } } @@ -1041,7 +975,6 @@ export class TMDBService { const genre = genreList.find(g => g.name.toLowerCase() === genreName.toLowerCase()); if (!genre) { - logger.error(`Genre ${genreName} not found`); return []; } @@ -1075,7 +1008,6 @@ export class TMDBService { external_ids: externalIdsResponse.data }; } catch (error) { - logger.error(`Failed to get external IDs for ${type} ${item.id}:`, error); return item; } }) @@ -1083,7 +1015,6 @@ export class TMDBService { return resultsWithExternalIds; } catch (error) { - logger.error(`Failed to discover ${type} by genre ${genreName}:`, error); return []; } }