diff --git a/app/(auth)/(tabs)/(home)/settings/logs/page.tsx b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx index 03a8e1ca..12878717 100644 --- a/app/(auth)/(tabs)/(home)/settings/logs/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx @@ -1,3 +1,4 @@ +import { File, Paths } from "expo-file-system"; import { useNavigation } from "expo-router"; import * as Sharing from "expo-sharing"; import { useCallback, useEffect, useId, useMemo, useState } from "react"; @@ -48,18 +49,17 @@ export default function Page() { // Sharing it as txt while its formatted allows us to share it with many more applications const share = useCallback(async () => { - const uri = `${FileSystem.documentDirectory}logs.txt`; + const logsFile = new File(Paths.document, "logs.txt"); setLoading(true); - FileSystem.writeAsStringAsync(uri, JSON.stringify(filteredLogs)) - .then(() => { - setLoading(false); - Sharing.shareAsync(uri, { mimeType: "txt", UTI: "txt" }); - }) - .catch((e) => - writeErrorLog("Something went wrong attempting to export", e), - ) - .finally(() => setLoading(false)); + try { + logsFile.write(JSON.stringify(filteredLogs)); + await Sharing.shareAsync(logsFile.uri, { mimeType: "txt", UTI: "txt" }); + } catch (e: any) { + writeErrorLog("Something went wrong attempting to export", e); + } finally { + setLoading(false); + } }, [filteredLogs]); useEffect(() => { diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx index 7bef1d7a..a29e1280 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx @@ -87,14 +87,15 @@ export default function page() { {data?.details?.name} {t("jellyseerr.born")}{" "} - {new Date(data?.details?.birthday!).toLocaleDateString( - `${locale}-${region}`, - { - year: "numeric", - month: "long", - day: "numeric", - }, - )}{" "} + {data?.details?.birthday && + new Date(data.details.birthday).toLocaleDateString( + `${locale}-${region}`, + { + year: "numeric", + month: "long", + day: "numeric", + }, + )}{" "} | {data?.details?.placeOfBirth} diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx index 2aabbe46..6c9790b5 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx @@ -33,7 +33,6 @@ export default function page() { ( diff --git a/biome.json b/biome.json index 5c18980e..b94bda94 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.7/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.5/schema.json", "files": { "includes": [ "**/*", diff --git a/bun.lock b/bun.lock index dd515c17..b63e9016 100644 --- a/bun.lock +++ b/bun.lock @@ -86,7 +86,7 @@ }, "devDependencies": { "@babel/core": "^7.20.0", - "@biomejs/biome": "^2.2.4", + "@biomejs/biome": "^2.3.5", "@react-native-community/cli": "^20.0.0", "@react-native-tvos/config-tv": "^0.1.1", "@types/jest": "^29.5.12", @@ -300,23 +300,23 @@ "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@biomejs/biome": ["@biomejs/biome@2.3.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.4", "@biomejs/cli-darwin-x64": "2.3.4", "@biomejs/cli-linux-arm64": "2.3.4", "@biomejs/cli-linux-arm64-musl": "2.3.4", "@biomejs/cli-linux-x64": "2.3.4", "@biomejs/cli-linux-x64-musl": "2.3.4", "@biomejs/cli-win32-arm64": "2.3.4", "@biomejs/cli-win32-x64": "2.3.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TU08LXjBHdy0mEY9APtEtZdNQQijXUDSXR7IK1i45wgoPD5R0muK7s61QcFir6FpOj/RP1+YkPx5QJlycXUU3w=="], + "@biomejs/biome": ["@biomejs/biome@2.3.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.5", "@biomejs/cli-darwin-x64": "2.3.5", "@biomejs/cli-linux-arm64": "2.3.5", "@biomejs/cli-linux-arm64-musl": "2.3.5", "@biomejs/cli-linux-x64": "2.3.5", "@biomejs/cli-linux-x64-musl": "2.3.5", "@biomejs/cli-win32-arm64": "2.3.5", "@biomejs/cli-win32-x64": "2.3.5" }, "bin": { "biome": "bin/biome" } }, "sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w40GvlNzLaqmuWYiDU6Ys9FNhJiclngKqcGld3iJIiy2bpJ0Q+8n3haiaC81uTPY/NA0d8Q/I3Z9+ajc14102Q=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-3s7TLVtjJ7ni1xADXsS7x7GMUrLBZXg8SemXc3T0XLslzvqKj/dq1xGeBQ+pOWQzng9MaozfacIHdK2UlJ3jGA=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-y7efHyyM2gYmHy/AdWEip+VgTMe9973aP7XYKPzu/j8JxnPHuSUXftzmPhkVw0lfm4ECGbdBdGD6+rLmTgNZaA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-IruVGQRwMURivWazchiq7gKAqZSFs5so6gi0hJyxk7x6HR+iwZbO2IxNOqyLURBvL06qkIHs7Wffl6Bw30vCbQ=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gKfjWR/6/dfIxPJCw8REdEowiXCkIpl9jycpNVHux8aX2yhWPLjydOshkDL6Y/82PcQJHn95VCj7J+BRcE5o1Q=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-mzKFFv/w66e4/jCobFmD3kymCqG+FuWE7sVa4Yjqd9v7qt2UhXo67MSZKY9Ih18V2IwPzRKQPCw6KwdZs6AXSA=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-5TJ6JfVez+yyupJ/iGUici2wzKf0RrSAxJhghQXtAEsc67OIpdwSKAQboemILrwKfHDi5s6mu7mX+VTCTUydkw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-FGCijXecmC4IedQ0esdYNlMpx0Jxgf4zceCaMu6fkjWyjgn50ZQtMiqZZQ0Q/77yqPxvtkgZAvt5uGw0gAAjig=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ=="], "@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@1.0.2", "", { "dependencies": { "color": "^5.0.0" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-OrCw8s2NzFxO1TO5W2vyr7HNvh1Yjy00f72D/0BIPtImc0aj5CRrT9nFRE7YP0FWZb0AY5+0QU9jaoph1rBlSg=="], diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 61f3f2d5..a1bf4f6f 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -50,7 +50,6 @@ export const PlayButton: React.FC = ({ selectedOptions, isOffline, colors, - ...props }: Props) => { const { showActionSheetWithOptions } = useActionSheet(); const client = useRemoteMediaClient(); diff --git a/components/common/InfiniteHorizontalScroll.tsx b/components/common/InfiniteHorizontalScroll.tsx index 182a2817..78eac996 100644 --- a/components/common/InfiniteHorizontalScroll.tsx +++ b/components/common/InfiniteHorizontalScroll.tsx @@ -120,7 +120,6 @@ export function InfiniteHorizontalScroll({ renderItem={({ item, index }) => ( {renderItem(item, index)} )} - estimatedItemSize={height} horizontal onEndReached={() => { if (hasNextPage) { diff --git a/components/downloads/MovieCard.tsx b/components/downloads/MovieCard.tsx index b0a5d555..f02fe796 100644 --- a/components/downloads/MovieCard.tsx +++ b/components/downloads/MovieCard.tsx @@ -29,15 +29,15 @@ export const MovieCard: React.FC = ({ item }) => { const { showActionSheetWithOptions } = useActionSheet(); const base64Image = useMemo(() => { - return storage.getString(item?.Id!); - }, []); + return item?.Id ? storage.getString(item.Id) : undefined; + }, [item?.Id]); /** * Handles deleting the file with haptic feedback. */ const handleDeleteFile = useCallback(() => { if (item.Id) { - deleteFile(item.Id, item.Type); + deleteFile(item.Id); } }, [deleteFile, item.Id]); diff --git a/components/jellyseerr/ParallaxSlideShow.tsx b/components/jellyseerr/ParallaxSlideShow.tsx index 8e080812..b4b75592 100644 --- a/components/jellyseerr/ParallaxSlideShow.tsx +++ b/components/jellyseerr/ParallaxSlideShow.tsx @@ -143,7 +143,6 @@ const ParallaxSlideShow = ({ renderItem={({ item, index }) => renderItem(item, index)} keyExtractor={keyExtractor} numColumns={3} - estimatedItemSize={214} ItemSeparatorComponent={() => } /> diff --git a/components/jellyseerr/discover/Slide.tsx b/components/jellyseerr/discover/Slide.tsx index 7352fb6c..41b4667e 100644 --- a/components/jellyseerr/discover/Slide.tsx +++ b/components/jellyseerr/discover/Slide.tsx @@ -1,16 +1,15 @@ import { FlashList } from "@shopify/flash-list"; -import type { ContentStyle } from "@shopify/flash-list/src/FlashListProps"; import { t } from "i18next"; import type React from "react"; import type { PropsWithChildren } from "react"; -import { View, type ViewProps } from "react-native"; +import { View, type ViewProps, type ViewStyle } from "react-native"; import { Text } from "@/components/common/Text"; import { DiscoverSliderType } from "@/utils/jellyseerr/server/constants/discover"; import type DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider"; export interface SlideProps { slide: DiscoverSlider; - contentContainerStyle?: ContentStyle; + contentContainerStyle?: ViewStyle; } interface Props extends SlideProps { @@ -45,7 +44,6 @@ const Slide = ({ }} showsHorizontalScrollIndicator={false} keyExtractor={keyExtractor} - estimatedItemSize={250} data={data} onEndReachedThreshold={1} onEndReached={onEndReached} diff --git a/components/search/SearchItemWrapper.tsx b/components/search/SearchItemWrapper.tsx index 2451d3aa..3c266084 100644 --- a/components/search/SearchItemWrapper.tsx +++ b/components/search/SearchItemWrapper.tsx @@ -34,7 +34,6 @@ export const SearchItemWrapper = ({ }} showsHorizontalScrollIndicator={false} keyExtractor={(_, index) => index.toString()} - estimatedItemSize={250} data={items} onEndReachedThreshold={1} onEndReached={onEndReached} diff --git a/components/series/JellyseerrSeasons.tsx b/components/series/JellyseerrSeasons.tsx index e339e84b..3b130683 100644 --- a/components/series/JellyseerrSeasons.tsx +++ b/components/series/JellyseerrSeasons.tsx @@ -47,7 +47,6 @@ const JellyseerrSeasonEpisodes: React.FC<{ horizontal loading={isLoading} showsHorizontalScrollIndicator={false} - estimatedItemSize={50} data={seasonWithEpisodes?.episodes} keyExtractor={(item) => item.id.toString()} renderItem={(item, index) => ( @@ -284,7 +283,6 @@ const JellyseerrSeasons: React.FC<{ )} ItemSeparatorComponent={() => } - estimatedItemSize={250} renderItem={({ item: season }) => ( <> = ({ seriesId }) => { ( diff --git a/components/series/SeasonEpisodesCarousel.tsx b/components/series/SeasonEpisodesCarousel.tsx index 8e3051ee..cf763b8d 100644 --- a/components/series/SeasonEpisodesCarousel.tsx +++ b/components/series/SeasonEpisodesCarousel.tsx @@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query"; import { router } from "expo-router"; import { useAtom } from "jotai"; import { useEffect, useMemo, useRef } from "react"; -import { TouchableOpacity, type ViewProps } from "react-native"; +import { TouchableOpacity, type ViewStyle } from "react-native"; import { useDownload } from "@/providers/DownloadProvider"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import ContinueWatchingPoster from "../ContinueWatchingPoster"; @@ -14,17 +14,20 @@ import { } from "../common/HorizontalScroll"; import { ItemCardText } from "../ItemCardText"; -interface Props extends ViewProps { +interface Props { item?: BaseItemDto | null; loading?: boolean; isOffline?: boolean; + style?: ViewStyle; + containerStyle?: ViewStyle; } export const SeasonEpisodesCarousel: React.FC = ({ item, loading, isOffline, - ...props + style, + containerStyle, }) => { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); @@ -90,6 +93,8 @@ export const SeasonEpisodesCarousel: React.FC = ({ data={episodes} extraData={item} loading={loading || isPending} + style={style} + containerStyle={containerStyle} renderItem={(_item, _idx) => ( = ({ )} - {...props} /> ); }; diff --git a/components/video-player/controls/EpisodeList.tsx b/components/video-player/controls/EpisodeList.tsx index 0a90b429..a7d6c5bc 100644 --- a/components/video-player/controls/EpisodeList.tsx +++ b/components/video-player/controls/EpisodeList.tsx @@ -271,7 +271,6 @@ export const EpisodeList: React.FC = ({ item, close, goToItem }) => { )} keyExtractor={(e: BaseItemDto) => e.Id ?? ""} - estimatedItemSize={200} showsHorizontalScrollIndicator={false} /> )} diff --git a/components/video-player/controls/SliderScrubbter.tsx b/components/video-player/controls/SliderScrubbter.tsx index 7de3c7d5..f0a39a12 100644 --- a/components/video-player/controls/SliderScrubbter.tsx +++ b/components/video-player/controls/SliderScrubbter.tsx @@ -89,10 +89,10 @@ const SliderScrubber: React.FC = ({ = ({ ; + enqueueDownload(url: string, destinationPath?: string): Promise; cancelDownload(taskId: number): void; + cancelQueuedDownload(url: string): void; cancelAllDownloads(): void; getActiveDownloads(): Promise; addListener( diff --git a/package.json b/package.json index b627cfdc..2d3d21cd 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ }, "devDependencies": { "@babel/core": "^7.20.0", - "@biomejs/biome": "^2.2.4", + "@biomejs/biome": "^2.3.5", "@react-native-community/cli": "^20.0.0", "@react-native-tvos/config-tv": "^0.1.1", "@types/jest": "^29.5.12", diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx index 4610c30b..98080767 100644 --- a/providers/DownloadProvider.tsx +++ b/providers/DownloadProvider.tsx @@ -8,6 +8,7 @@ import { getAllDownloadedItems, getDownloadedItemById, getDownloadsDatabase, + updateDownloadedItem, } from "./Downloads/database"; import { getDownloadedItemSize } from "./Downloads/fileOperations"; import { useDownloadEventHandlers } from "./Downloads/hooks/useDownloadEventHandlers"; @@ -29,7 +30,7 @@ function useDownloadProvider() { const successHapticFeedback = useHaptic("success"); // Track task ID to process ID mapping - const taskMapRef = useRef>(new Map()); + const taskMapRef = useRef>(new Map()); // Reactive downloaded items that updates when refreshKey changes const downloadedItems = useMemo(() => { @@ -130,13 +131,13 @@ function useDownloadProvider() { cancelDownload, getDownloadedItemSize, getDownloadedItemById, + updateDownloadedItem, triggerRefresh, APP_CACHE_DOWNLOAD_DIRECTORY: APP_CACHE_DOWNLOAD_DIRECTORY.uri, appSizeUsage, // Deprecated/not implemented in simple version startDownload: async () => {}, cleanCacheDirectory: async () => {}, - updateDownloadedItem: () => {}, dumpDownloadDiagnostics: async () => "", }; } @@ -161,9 +162,9 @@ export function useDownload() { startDownload: async () => {}, getDownloadedItemSize: () => 0, getDownloadedItemById: () => undefined, + updateDownloadedItem: () => {}, APP_CACHE_DOWNLOAD_DIRECTORY: "", cleanCacheDirectory: async () => {}, - updateDownloadedItem: () => {}, appSizeUsage: async () => ({ total: 0, remaining: 0, appSize: 0 }), dumpDownloadDiagnostics: async () => "", }; diff --git a/providers/Downloads/additionalDownloads.ts b/providers/Downloads/additionalDownloads.ts index af6a5894..eca28e0d 100644 --- a/providers/Downloads/additionalDownloads.ts +++ b/providers/Downloads/additionalDownloads.ts @@ -185,10 +185,16 @@ export async function fetchSegments( }> { try { const segments = await fetchAndParseSegments(itemId, api); - return segments; + return { + introSegments: segments.introSegments, + creditSegments: segments.creditSegments, + }; } catch (error) { console.error(`[SEGMENTS] Failed to fetch segments:`, error); - return {}; + return { + introSegments: undefined, + creditSegments: undefined, + }; } } @@ -222,7 +228,12 @@ export async function downloadAdditionalAssets(params: { mediaSource.TranscodingUrl ? Promise.resolve(mediaSource) : downloadSubtitles(mediaSource, item, api.basePath || ""), - item.Id ? fetchSegments(item.Id, api) : Promise.resolve({}), + item.Id + ? fetchSegments(item.Id, api) + : Promise.resolve({ + introSegments: undefined, + creditSegments: undefined, + }), // Cover image downloads (run but don't wait for results) downloadCoverImage(item, api, saveImageFn).catch((err) => { console.error("[COVER] Error downloading cover:", err); diff --git a/providers/Downloads/database.ts b/providers/Downloads/database.ts index 27521edb..667a5b4d 100644 --- a/providers/Downloads/database.ts +++ b/providers/Downloads/database.ts @@ -181,6 +181,41 @@ export function removeDownloadedItem(id: string): DownloadedItem | undefined { return itemToDelete; } +/** + * Update a downloaded item in the database + */ +export function updateDownloadedItem( + _id: string, + updatedItem: DownloadedItem, +): void { + const db = getDownloadsDatabase(); + const baseItem = updatedItem.item; + + if (baseItem.Type === "Movie" && baseItem.Id) { + db.movies[baseItem.Id] = updatedItem; + } else if ( + baseItem.Type === "Episode" && + baseItem.SeriesId && + baseItem.ParentIndexNumber !== undefined && + baseItem.ParentIndexNumber !== null && + baseItem.IndexNumber !== undefined && + baseItem.IndexNumber !== null + ) { + const seriesId = baseItem.SeriesId; + const seasonNumber = baseItem.ParentIndexNumber; + const episodeNumber = baseItem.IndexNumber; + + if (db.series[seriesId]?.seasons[seasonNumber]?.episodes[episodeNumber]) { + db.series[seriesId].seasons[seasonNumber].episodes[episodeNumber] = + updatedItem; + } + } else if (baseItem.Id && db.other?.[baseItem.Id]) { + db.other[baseItem.Id] = updatedItem; + } + + saveDownloadsDatabase(db); +} + /** * Clear all downloaded items from the database */ diff --git a/providers/Downloads/hooks/useDownloadEventHandlers.ts b/providers/Downloads/hooks/useDownloadEventHandlers.ts index ee9fffbe..a3a27b8a 100644 --- a/providers/Downloads/hooks/useDownloadEventHandlers.ts +++ b/providers/Downloads/hooks/useDownloadEventHandlers.ts @@ -24,7 +24,7 @@ import { } from "./useDownloadSpeedCalculator"; interface UseDownloadEventHandlersProps { - taskMapRef: MutableRefObject>; + taskMapRef: MutableRefObject>; processes: JobStatus[]; updateProcess: ( processId: string, @@ -59,7 +59,8 @@ export function useDownloadEventHandlers({ // If no mapping exists, find by URL (for queued downloads) if (!processId && event.url) { // Check if we have a URL mapping (queued download) - processId = taskMapRef.current.get(event.url); + const urlKey = event.url; + processId = taskMapRef.current.get(urlKey); if (!processId) { // Fallback: search by matching URL in processes @@ -74,7 +75,7 @@ export function useDownloadEventHandlers({ if (processId) { // Create taskId mapping and remove URL mapping taskMapRef.current.set(event.taskId, processId); - taskMapRef.current.delete(event.url); + taskMapRef.current.delete(urlKey); console.log( `[DPL] Mapped queued download: taskId=${event.taskId} to processId=${processId.slice(0, 8)}...`, ); diff --git a/providers/Downloads/hooks/useDownloadOperations.ts b/providers/Downloads/hooks/useDownloadOperations.ts index 1cf0e51c..f1e4c4ed 100644 --- a/providers/Downloads/hooks/useDownloadOperations.ts +++ b/providers/Downloads/hooks/useDownloadOperations.ts @@ -27,7 +27,7 @@ import type { JobStatus } from "../types"; import { generateFilename, uriToFilePath } from "../utils"; interface UseDownloadOperationsProps { - taskMapRef: MutableRefObject>; + taskMapRef: MutableRefObject>; processes: JobStatus[]; setProcesses: (updater: (prev: JobStatus[]) => JobStatus[]) => void; removeProcess: (id: string) => void; @@ -169,7 +169,7 @@ export function useDownloadOperations({ if (typeof key === "number") { taskId = key; } else { - downloadUrl = key; + downloadUrl = key as string; } } }); diff --git a/providers/PlaySettingsProvider.tsx b/providers/PlaySettingsProvider.tsx index 38806a54..57fdd238 100644 --- a/providers/PlaySettingsProvider.tsx +++ b/providers/PlaySettingsProvider.tsx @@ -94,9 +94,9 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({ console.log(`${data?.url?.slice(0, 100)}...${data?.url?.slice(-50)}`); _setPlaySettings(newSettings); - setPlayUrl(data?.url!); - setPlaySessionId(data?.sessionId!); - setMediaSource(data?.mediaSource!); + if (data?.url) setPlayUrl(data.url); + if (data?.sessionId) setPlaySessionId(data.sessionId); + if (data?.mediaSource) setMediaSource(data.mediaSource); return data; } catch (error) {