improve deletion of many files
Mwebclient/src/components/DownloadButton.tsx
@@ -31,6 +31,7 @@ export function DownloadButton(props: DownloadButtonProps) {
>
{(() => {
const progress = downloadStatusMap[fullPath];
if (progress === "UnspecificProgress") return <LoadingSpinner center={true} />;
if (props.isOffline()) {
return (
<button title="Purge this file/these files from local sync storage">
@@ -45,7 +46,6 @@ export function DownloadButton(props: DownloadButtonProps) {
</button>
);
}
if (progress === "DirectoryProgress") return <LoadingSpinner center={true} />;
if (progress === "Synced") {
return (
<button title="Re-sync this file">
Mwebclient/src/offline.ts
@@ -17,7 +17,7 @@ interface Progress {
bytes: number;
}
export type DownloadStatus = Progress | "DirectoryProgress" | "Synced" | undefined;
export type DownloadStatus = Progress | "UnspecificProgress" | "Synced" | undefined;
//used to display download status for directories/files in the filebrowser view
export const [downloadStatusMap, setDownloadStatusMap] = createStore<Record<string, DownloadStatus>>({});
@@ -280,6 +280,22 @@ export async function deletePlaylist(name: string) {
await db.delete(PlaylistStore, name);
}
function collectMediaFilePaths(listing: FileListing, basePath: string): string[] {
const paths: string[] = [];
for (const [name, item] of listing.entries()) {
const currentPath = joinPath(basePath, name);
if ("metadata" in item) {
paths.push(currentPath);
} else if ("files" in item) {
paths.push(...collectMediaFilePaths(item.files, currentPath));
}
}
return paths;
}
//deletes file or directory(+recursive contents) from indexedDB and updates their download statuses
export async function deleteFile(
fileName: string,
@@ -288,21 +304,27 @@ export async function deleteFile(
fetchFiles: ResourceActions<FileListing>,
) {
const fullPath = joinPath(basePath, fileName);
if ("metadata" in item && !downloadStatusMap[fullPath]) return;
setDownloadStatusMap(fullPath, "UnspecificProgress");
try {
if ("metadata" in item && !downloadStatusMap[fullPath]) return;
if (!("metadata" in item)) {
const newBasePath = joinPath(basePath, fileName);
const result = listOfflineFiles(newBasePath);
for (const [childName, childItem] of result) {
await deleteFile(childName, childItem, newBasePath, fetchFiles);
let toDelete: string[];
if ("files" in item) {
toDelete = collectMediaFilePaths(listOfflineFiles(fullPath), fullPath);
} else {
toDelete = [fullPath];
}
}
deleteOfflinePath(fullPath);
const db = await openDB();
await db.delete(FileStore, fullPath);
setDownloadStatusMap(fullPath, undefined);
fetchFiles.refetch();
const db = await openDB();
await db.deleteAll(FileStore, toDelete);
deleteOfflinePath(fullPath);
for (const path of toDelete) {
setDownloadStatusMap(path, undefined);
}
fetchFiles.refetch();
} finally {
setDownloadStatusMap(fullPath, undefined);
}
}
const downloadSemaphore = new AsyncSemaphore(1);
@@ -394,9 +416,9 @@ export async function download(
if ("metadata" in item) {
await downloadFile(fullPath, currentDir, options, item, fetchFiles);
} else {
setDownloadStatusMap(fullPath, "DirectoryProgress");
await getCover(fullPath, false);
setDownloadStatusMap(fullPath, "UnspecificProgress");
try {
await getCover(fullPath, false);
//start downloads for all children to allow them to run in parallel, then wait for the results
const promises: Promise<unknown>[] = [];
for (const [childName, childItem] of await listFiles(fullPath, false, false)) {
@@ -455,7 +477,8 @@ interface DatabaseWrapper {
put(store: typeof CoverStore, value: StoredCover): Promise<void>;
put(store: typeof PlaylistStore, value: Playlist): Promise<void>;
add(store: typeof PlaylistStore, value: Playlist): Promise<void>;
delete(store: typeof FileStore | typeof CoverStore | typeof PlaylistStore, key: string): Promise<void>;
delete(store: typeof CoverStore | typeof PlaylistStore, key: string): Promise<void>;
deleteAll(store: typeof FileStore, keys: string[]): Promise<void>;
clear(store: string): Promise<void>;
listFiles(store: typeof FileStore): Promise<StoredFile[]>;
listPlaylists(store: typeof PlaylistStore): Promise<Playlist[]>;
@@ -492,6 +515,16 @@ function wrapDB(db: IDBDatabase): DatabaseWrapper {
req.onsuccess = () => res();
req.onerror = () => rej(req.error);
}),
deleteAll: (store: typeof FileStore, keys: string[]) =>
new Promise<void>((res, rej) => {
const tx = db.transaction(store, "readwrite");
const objectStore = tx.objectStore(store);
for (const key of keys) {
objectStore.delete(key);
}
tx.oncomplete = () => res();
tx.onerror = () => rej(tx.error);
}),
clear: (store: string) =>
new Promise<void>((res, rej) => {
const tx = db.transaction(store, "readwrite");