Files
streamyfin/providers/Downloads/fileOperations.ts
Gauvain 1dd1940334 fix(downloads): compute storage usage from live file sizes
The storage bar showed 0.00% because calculateTotalDownloadedSize
summed the stored videoFileSize, which is 0 for items downloaded before
the size was recorded (or when fileInfo.size was undefined). Stat the
file on disk and fall back to the stored value.
2026-06-11 00:42:51 +02:00

117 lines
3.6 KiB
TypeScript

import { Directory, File, Paths } from "expo-file-system";
import { getAllDownloadedItems, getDownloadedItemById } from "./database";
import type { DownloadedItem } from "./types";
import { filePathToUri } from "./utils";
/**
* Delete a video file and all associated files (subtitles, trickplay, etc.)
*/
export function deleteVideoFile(filePath: string): void {
try {
const videoFile = new File(filePathToUri(filePath));
if (videoFile.exists) {
videoFile.delete();
console.log(`[DELETE] Video file deleted: ${filePath}`);
}
} catch (error) {
console.error("Failed to delete video file:", error);
throw error;
}
}
/**
* Delete all associated files for a downloaded item
* Includes: video, subtitles, trickplay images
*/
export function deleteAllAssociatedFiles(item: DownloadedItem): void {
try {
// Delete video file
if (item.videoFilePath) {
deleteVideoFile(item.videoFilePath);
}
// Delete subtitle files
if (item.mediaSource?.MediaStreams) {
for (const stream of item.mediaSource.MediaStreams) {
if (
stream.Type === "Subtitle" &&
stream.DeliveryMethod === "External" &&
stream.DeliveryUrl
) {
try {
const subtitleFilename = stream.DeliveryUrl.split("/").pop();
if (subtitleFilename) {
const subtitleFile = new File(Paths.document, subtitleFilename);
if (subtitleFile.exists) {
subtitleFile.delete();
console.log(`[DELETE] Subtitle deleted: ${subtitleFilename}`);
}
}
} catch (error) {
console.error("[DELETE] Failed to delete subtitle:", error);
}
}
}
}
// Delete trickplay directory
if (item.trickPlayData?.path) {
try {
const trickplayDirName = item.trickPlayData.path.split("/").pop();
if (trickplayDirName) {
const trickplayDir = new Directory(Paths.document, trickplayDirName);
if (trickplayDir.exists) {
trickplayDir.delete();
console.log(
`[DELETE] Trickplay directory deleted: ${trickplayDirName}`,
);
}
}
} catch (error) {
console.error("[DELETE] Failed to delete trickplay directory:", error);
}
}
} catch (error) {
console.error("[DELETE] Error deleting associated files:", error);
throw error;
}
}
/**
* Get the size of a downloaded item by ID
* Includes video file size and trickplay data size
*/
export function getDownloadedItemSize(id: string): number {
const item = getDownloadedItemById(id);
if (!item) return 0;
const videoSize = item.videoFileSize || 0;
const trickplaySize = item.trickPlayData?.size || 0;
return videoSize + trickplaySize;
}
/**
* Calculate total size of all downloaded items
*/
export function calculateTotalDownloadedSize(): number {
const items = getAllDownloadedItems();
return items.reduce((sum, item) => {
// Read the live file size on disk so the total reflects actual usage and
// self-heals items whose stored videoFileSize is 0 (old schema, or
// `fileInfo.size` was undefined at download time). Fall back to the stored
// value if the file can't be stat'd.
if (item.videoFilePath) {
try {
const file = new File(filePathToUri(item.videoFilePath));
if (file.exists) {
return sum + (file.size ?? item.videoFileSize ?? 0);
}
} catch (error) {
console.warn("Failed to stat downloaded file for size:", error);
}
}
return sum + (item.videoFileSize ?? 0);
}, 0);
}