Compare commits

...

2 Commits

Author SHA1 Message Date
Alex Kim
ad751fd2c8 WIP 2025-01-05 03:12:01 +11:00
Alex Kim
42e66b39cc WIP 2025-01-05 03:11:36 +11:00
5 changed files with 75 additions and 47 deletions

View File

@@ -13,7 +13,7 @@ import { orientationAtom } from "@/utils/atoms/orientation";
import { Settings, useSettings } from "@/utils/atoms/settings"; import { Settings, useSettings } from "@/utils/atoms/settings";
import { BACKGROUND_FETCH_TASK } from "@/utils/background-tasks"; import { BACKGROUND_FETCH_TASK } from "@/utils/background-tasks";
import { LogProvider, writeToLog } from "@/utils/log"; import { LogProvider, writeToLog } from "@/utils/log";
import { storage } from "@/utils/mmkv"; import { formatItemName, storage } from "@/utils/mmkv";
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server"; import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
import { ActionSheetProvider } from "@expo/react-native-action-sheet"; import { ActionSheetProvider } from "@expo/react-native-action-sheet";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"; import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
@@ -385,18 +385,15 @@ function Layout() {
function saveDownloadedItemInfo(item: BaseItemDto) { function saveDownloadedItemInfo(item: BaseItemDto) {
try { try {
const downloadedItems = storage.getString("downloadedItems"); const downloadedItems = storage.getString("downloadedItems");
let items: BaseItemDto[] = downloadedItems let items: { [key: string]: BaseItemDto } = downloadedItems
? JSON.parse(downloadedItems) ? JSON.parse(downloadedItems)
: []; : {};
const existingItemIndex = items.findIndex((i) => i.Id === item.Id); if (item.Id) {
if (existingItemIndex !== -1) { item.Path = `${FileSystem.documentDirectory}${formatItemName(item)}.mp4`;
items[existingItemIndex] = item; items[item.Id] = item;
} else { storage.set("downloadedItems", JSON.stringify(items));
items.push(item);
} }
storage.set("downloadedItems", JSON.stringify(items));
} catch (error) { } catch (error) {
writeToLog("ERROR", "Failed to save downloaded item information:", error); writeToLog("ERROR", "Failed to save downloaded item information:", error);
console.error("Failed to save downloaded item information:", error); console.error("Failed to save downloaded item information:", error);

View File

@@ -1,5 +1,6 @@
import { usePlaySettings } from "@/providers/PlaySettingsProvider"; import { usePlaySettings } from "@/providers/PlaySettingsProvider";
import { writeToLog } from "@/utils/log"; import { writeToLog } from "@/utils/log";
import { getFilePathFromItemId } from "@/utils/mmkv";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import * as FileSystem from "expo-file-system"; import * as FileSystem from "expo-file-system";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
@@ -17,13 +18,13 @@ export const getDownloadedFileUrl = async (itemId: string): Promise<string> => {
} }
const files = await FileSystem.readDirectoryAsync(directory); const files = await FileSystem.readDirectoryAsync(directory);
const path = itemId!; const filePath = getFilePathFromItemId(itemId);
const matchingFile = files.find((file) => file.startsWith(path));
const matchingFile = files.find((file) => file === filePath);
if (!matchingFile) { if (!matchingFile) {
throw new Error(`No file found for item ${path}`); throw new Error(`No file found for item ${filePath}`);
} }
return `${directory}${matchingFile}`; return `${directory}${matchingFile}`;
}; };

View File

@@ -18,6 +18,7 @@ import useDownloadHelper from "@/utils/download";
import { Api } from "@jellyfin/sdk"; import { Api } from "@jellyfin/sdk";
import { useSettings } from "@/utils/atoms/settings"; import { useSettings } from "@/utils/atoms/settings";
import { JobStatus } from "@/utils/optimize-server"; import { JobStatus } from "@/utils/optimize-server";
import { formatItemName } from "@/utils/mmkv";
const createFFmpegCommand = (url: string, output: string) => [ const createFFmpegCommand = (url: string, output: string) => [
"-y", // overwrite output files without asking "-y", // overwrite output files without asking
@@ -53,7 +54,12 @@ export const useRemuxHlsToMp4 = () => {
const [settings] = useSettings(); const [settings] = useSettings();
const { saveImage } = useImageStorage(); const { saveImage } = useImageStorage();
const { saveSeriesPrimaryImage } = useDownloadHelper(); const { saveSeriesPrimaryImage } = useDownloadHelper();
const { saveDownloadedItemInfo, setProcesses, processes, APP_CACHE_DOWNLOAD_DIRECTORY } = useDownload(); const {
saveDownloadedItemInfo,
setProcesses,
processes,
APP_CACHE_DOWNLOAD_DIRECTORY,
} = useDownload();
const onSaveAssets = async (api: Api, item: BaseItemDto) => { const onSaveAssets = async (api: Api, item: BaseItemDto) => {
await saveSeriesPrimaryImage(item); await saveSeriesPrimaryImage(item);
@@ -73,13 +79,12 @@ export const useRemuxHlsToMp4 = () => {
try { try {
console.log("completeCallback"); console.log("completeCallback");
const returnCode = await session.getReturnCode(); const returnCode = await session.getReturnCode();
if (returnCode.isValueSuccess()) { if (returnCode.isValueSuccess()) {
const stat = await session.getLastReceivedStatistics(); const stat = await session.getLastReceivedStatistics();
await FileSystem.moveAsync({ await FileSystem.moveAsync({
from: `${APP_CACHE_DOWNLOAD_DIRECTORY}${item.Id}.mp4`, from: `${APP_CACHE_DOWNLOAD_DIRECTORY}${item.Id}.mp4`,
to: `${FileSystem.documentDirectory}${item.Id}.mp4` to: `${FileSystem.documentDirectory}${formatItemName(item)}.mp4`,
}) });
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: ["downloadedItems"], queryKey: ["downloadedItems"],
}); });
@@ -131,12 +136,16 @@ export const useRemuxHlsToMp4 = () => {
const startRemuxing = useCallback( const startRemuxing = useCallback(
async (item: BaseItemDto, url: string, mediaSource: MediaSourceInfo) => { async (item: BaseItemDto, url: string, mediaSource: MediaSourceInfo) => {
const cacheDir = await FileSystem.getInfoAsync(APP_CACHE_DOWNLOAD_DIRECTORY); const cacheDir = await FileSystem.getInfoAsync(
APP_CACHE_DOWNLOAD_DIRECTORY
);
if (!cacheDir.exists) { if (!cacheDir.exists) {
await FileSystem.makeDirectoryAsync(APP_CACHE_DOWNLOAD_DIRECTORY, {intermediates: true}) await FileSystem.makeDirectoryAsync(APP_CACHE_DOWNLOAD_DIRECTORY, {
intermediates: true,
});
} }
const output = APP_CACHE_DOWNLOAD_DIRECTORY + `${item.Id}.mp4` const output = APP_CACHE_DOWNLOAD_DIRECTORY + `${item.Id}.mp4`;
if (!api) throw new Error("API is not defined"); if (!api) throw new Error("API is not defined");
if (!item.Id) throw new Error("Item must have an Id"); if (!item.Id) throw new Error("Item must have an Id");

View File

@@ -19,14 +19,7 @@ import {
download, download,
setConfig, setConfig,
} from "@kesha-antonov/react-native-background-downloader"; } from "@kesha-antonov/react-native-background-downloader";
import MMKV from "react-native-mmkv"; import { focusManager, useQuery, useQueryClient } from "@tanstack/react-query";
import {
focusManager,
QueryClient,
QueryClientProvider,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import * as FileSystem from "expo-file-system"; import * as FileSystem from "expo-file-system";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
@@ -45,7 +38,7 @@ import { apiAtom } from "./JellyfinProvider";
import * as Notifications from "expo-notifications"; import * as Notifications from "expo-notifications";
import { getItemImage } from "@/utils/getItemImage"; import { getItemImage } from "@/utils/getItemImage";
import useImageStorage from "@/hooks/useImageStorage"; import useImageStorage from "@/hooks/useImageStorage";
import { storage } from "@/utils/mmkv"; import { formatItemName, saveItemMapping, storage } from "@/utils/mmkv";
import useDownloadHelper from "@/utils/download"; import useDownloadHelper from "@/utils/download";
import { FileInfo } from "expo-file-system"; import { FileInfo } from "expo-file-system";
import * as Haptics from "expo-haptics"; import * as Haptics from "expo-haptics";
@@ -54,8 +47,11 @@ import * as Application from "expo-application";
export type DownloadedItem = { export type DownloadedItem = {
item: Partial<BaseItemDto>; item: Partial<BaseItemDto>;
mediaSource: MediaSourceInfo; mediaSource: MediaSourceInfo;
fileSize: number;
}; };
export type DownloadedItem2 = {};
export const processesAtom = atom<JobStatus[]>([]); export const processesAtom = atom<JobStatus[]>([]);
function onAppStateChange(status: AppStateStatus) { function onAppStateChange(status: AppStateStatus) {
@@ -512,8 +508,10 @@ function useDownloadProvider() {
const downloadedItems = storage.getString("downloadedItems"); const downloadedItems = storage.getString("downloadedItems");
if (downloadedItems) { if (downloadedItems) {
let items = JSON.parse(downloadedItems) as DownloadedItem[]; let items: { [key: string]: BaseItemDto } = downloadedItems
items = items.filter((item) => item.item.Id !== id); ? JSON.parse(downloadedItems)
: {};
delete items[id];
storage.set("downloadedItems", JSON.stringify(items)); storage.set("downloadedItems", JSON.stringify(items));
} }
@@ -586,7 +584,7 @@ function useDownloadProvider() {
const appSizeUsage = useMemo(async () => { const appSizeUsage = useMemo(async () => {
const sizes: number[] = const sizes: number[] =
downloadedFiles?.map((d) => { downloadedFiles?.map((d) => {
return getDownloadedItemSize(d.item.Id!!); return d.fileSize;
}) || []; }) || [];
await forEveryDocumentDirFile( await forEveryDocumentDirFile(
@@ -608,8 +606,10 @@ function useDownloadProvider() {
try { try {
const downloadedItems = storage.getString("downloadedItems"); const downloadedItems = storage.getString("downloadedItems");
if (downloadedItems) { if (downloadedItems) {
const items: DownloadedItem[] = JSON.parse(downloadedItems); const items: { [key: string]: BaseItemDto } = downloadedItems
const item = items.find((i) => i.item.Id === itemId); ? JSON.parse(downloadedItems)
: {};
const item = items[itemId] as DownloadedItem;
return item || null; return item || null;
} }
return null; return null;
@@ -623,7 +623,7 @@ function useDownloadProvider() {
try { try {
const downloadedItems = storage.getString("downloadedItems"); const downloadedItems = storage.getString("downloadedItems");
if (downloadedItems) { if (downloadedItems) {
return JSON.parse(downloadedItems) as DownloadedItem[]; return Object.values(JSON.parse(downloadedItems)) as DownloadedItem[];
} else { } else {
return []; return [];
} }
@@ -636,11 +636,11 @@ function useDownloadProvider() {
function saveDownloadedItemInfo(item: BaseItemDto, size: number = 0) { function saveDownloadedItemInfo(item: BaseItemDto, size: number = 0) {
try { try {
const downloadedItems = storage.getString("downloadedItems"); const downloadedItems = storage.getString("downloadedItems");
let items: DownloadedItem[] = downloadedItems const items: { [key: string]: BaseItemDto } = downloadedItems
? JSON.parse(downloadedItems) ? JSON.parse(downloadedItems)
: []; : {};
const existingItemIndex = items.findIndex((i) => i.item.Id === item.Id); const chosenItem = items[item.Id!] || {};
const data = getDownloadItemInfoFromDiskTmp(item.Id!); const data = getDownloadItemInfoFromDiskTmp(item.Id!);
@@ -651,12 +651,6 @@ function useDownloadProvider() {
const newItem = { item, mediaSource: data.mediaSource }; const newItem = { item, mediaSource: data.mediaSource };
if (existingItemIndex !== -1) {
items[existingItemIndex] = newItem;
} else {
items.push(newItem);
}
deleteDownloadItemInfoFromDiskTmp(item.Id!); deleteDownloadItemInfoFromDiskTmp(item.Id!);
storage.set("downloadedItems", JSON.stringify(items)); storage.set("downloadedItems", JSON.stringify(items));

View File

@@ -1,3 +1,30 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import { MMKV } from "react-native-mmkv"; import { MMKV } from "react-native-mmkv";
export const storage = new MMKV(); const storage = new MMKV();
const saveItemMapping = (itemId: string | undefined, fileName: string) => {
if (!itemId) return;
storage.set(itemId, fileName);
};
const getFilePathFromItemId = (itemId: string): string | undefined => {
return storage.getString(itemId);
};
const formatItemName = (item: BaseItemDto) => {
if (item.Type === "Episode") {
const formattedParentIndexNumber = (item.ParentIndexNumber ?? 0)
.toString()
.padStart(2, "0");
const formattedIndexNumber = (item.IndexNumber ?? 0)
.toString()
.padStart(2, "0");
const formattedString = `S${formattedParentIndexNumber}E${formattedIndexNumber}`;
return `${item.SeriesName} - ${formattedString} - ${item.Name}`;
}
return item.Name;
};
export { saveItemMapping, getFilePathFromItemId, storage, formatItemName };