mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
New delete options & storage visibility
- Added react-native-progress dependency - Added bottom sheet to downloads page to handle actions for deleting items by type - Added ability to long press to delete a single series - Added ability to delete by season - Refactored delete helpers in DownloadProvider.tsx - Display storage usage inside downloads & settings page - Fixed Delete all downloaded files from delting user data in mmkv
This commit is contained in:
@@ -47,6 +47,8 @@ import { getItemImage } from "@/utils/getItemImage";
|
||||
import useImageStorage from "@/hooks/useImageStorage";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import useDownloadHelper from "@/utils/download";
|
||||
import {FileInfo} from "expo-file-system";
|
||||
import * as Haptics from "expo-haptics";
|
||||
|
||||
export type DownloadedItem = {
|
||||
item: Partial<BaseItemDto>;
|
||||
@@ -388,19 +390,20 @@ function useDownloadProvider() {
|
||||
);
|
||||
|
||||
const deleteAllFiles = async (): Promise<void> => {
|
||||
try {
|
||||
await deleteLocalFiles();
|
||||
removeDownloadedItemsFromStorage();
|
||||
await cancelAllServerJobs();
|
||||
queryClient.invalidateQueries({ queryKey: ["downloadedItems"] });
|
||||
toast.success("All files, folders, and jobs deleted successfully");
|
||||
} catch (error) {
|
||||
console.error("Failed to delete all files, folders, and jobs:", error);
|
||||
Promise.all([
|
||||
deleteLocalFiles(),
|
||||
removeDownloadedItemsFromStorage(),
|
||||
cancelAllServerJobs(),
|
||||
queryClient.invalidateQueries({queryKey: ["downloadedItems"]}),
|
||||
]).then(() =>
|
||||
toast.success("All files, folders, and jobs deleted successfully")
|
||||
).catch((reason) => {
|
||||
console.error("Failed to delete all files, folders, and jobs:", reason);
|
||||
toast.error("An error occurred while deleting files and jobs");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteLocalFiles = async (): Promise<void> => {
|
||||
const forEveryDirectory = async (callback: (dir: FileInfo) => void) => {
|
||||
const baseDirectory = FileSystem.documentDirectory;
|
||||
if (!baseDirectory) {
|
||||
throw new Error("Base directory not found");
|
||||
@@ -408,25 +411,36 @@ function useDownloadProvider() {
|
||||
|
||||
const dirContents = await FileSystem.readDirectoryAsync(baseDirectory);
|
||||
for (const item of dirContents) {
|
||||
const itemPath = `${baseDirectory}${item}`;
|
||||
const itemInfo = await FileSystem.getInfoAsync(itemPath);
|
||||
// Exclude mmkv directory.
|
||||
// Deleting this deletes all user information as well. Logout should handle this.
|
||||
if (item == "mmkv")
|
||||
continue
|
||||
const itemInfo = await FileSystem.getInfoAsync(`${baseDirectory}${item}`);
|
||||
if (itemInfo.exists) {
|
||||
if (itemInfo.isDirectory) {
|
||||
await FileSystem.deleteAsync(itemPath, { idempotent: true });
|
||||
} else {
|
||||
await FileSystem.deleteAsync(itemPath, { idempotent: true });
|
||||
}
|
||||
callback(itemInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteLocalFiles = async (): Promise<void> => {
|
||||
await forEveryDirectory((dir) => {
|
||||
console.warn("Deleting file", dir.uri)
|
||||
FileSystem.deleteAsync(dir.uri, {idempotent: true})
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
const removeDownloadedItemsFromStorage = (): void => {
|
||||
try {
|
||||
storage.delete("downloadedItems");
|
||||
} catch (error) {
|
||||
console.error("Failed to remove downloadedItems from storage:", error);
|
||||
throw error;
|
||||
}
|
||||
const removeDownloadedItemsFromStorage = async () => {
|
||||
// delete any saved images first
|
||||
Promise.all([
|
||||
deleteFileByType("Movie"),
|
||||
deleteFileByType("Episode"),
|
||||
]).then(() =>
|
||||
storage.delete("downloadedItems")
|
||||
).catch((reason) => {
|
||||
console.error("Failed to remove downloadedItems from storage:", reason);
|
||||
throw reason
|
||||
})
|
||||
};
|
||||
|
||||
const cancelAllServerJobs = async (): Promise<void> => {
|
||||
@@ -434,7 +448,8 @@ function useDownloadProvider() {
|
||||
throw new Error("No auth header available");
|
||||
}
|
||||
if (!settings?.optimizedVersionsServerUrl) {
|
||||
throw new Error("No server URL configured");
|
||||
console.error("No server URL configured");
|
||||
return
|
||||
}
|
||||
|
||||
const deviceId = await getOrSetDeviceId();
|
||||
@@ -494,6 +509,41 @@ function useDownloadProvider() {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteItems = async (items: BaseItemDto[]) => {
|
||||
Promise.all(items.map(i => {
|
||||
if (i.Id)
|
||||
return deleteFile(i.Id)
|
||||
return
|
||||
})).then(() =>
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
|
||||
)
|
||||
}
|
||||
|
||||
const deleteFileByType = async (type: BaseItemDto['Type']) => {
|
||||
await Promise.all(
|
||||
downloadedFiles
|
||||
?.filter(file => file.item.Type == type)
|
||||
?.flatMap(file => {
|
||||
const promises = [];
|
||||
if (type == "Episode" && file.item.SeriesId)
|
||||
promises.push(deleteFile(file.item.SeriesId))
|
||||
promises.push(deleteFile(file.item.Id!))
|
||||
return promises;
|
||||
})
|
||||
|| []
|
||||
);
|
||||
}
|
||||
|
||||
const getAppSizeUsage = async () => {
|
||||
const sizes: number[] = [];
|
||||
await forEveryDirectory(dir => {
|
||||
if (dir.exists)
|
||||
sizes.push(dir.size)
|
||||
})
|
||||
|
||||
return sizes.reduce((sum, size) => sum + size, 0);
|
||||
}
|
||||
|
||||
function getDownloadedItem(itemId: string): DownloadedItem | null {
|
||||
try {
|
||||
const downloadedItems = storage.getString("downloadedItems");
|
||||
@@ -566,11 +616,14 @@ function useDownloadProvider() {
|
||||
downloadedFiles,
|
||||
deleteAllFiles,
|
||||
deleteFile,
|
||||
deleteItems,
|
||||
saveDownloadedItemInfo,
|
||||
removeProcess,
|
||||
setProcesses,
|
||||
startDownload,
|
||||
getDownloadedItem,
|
||||
deleteFileByType,
|
||||
getAppSizeUsage
|
||||
};
|
||||
}
|
||||
|
||||
@@ -591,3 +644,11 @@ export function useDownload() {
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function bytesToReadable(bytes: number): string {
|
||||
const gb = bytes / 1e+9;
|
||||
|
||||
if (gb >= 1)
|
||||
return `${gb.toFixed(2)} GB`
|
||||
return `${(bytes / 1024 / 1024).toFixed(2)} MB`
|
||||
}
|
||||
Reference in New Issue
Block a user