This commit is contained in:
Manav Rathi
2025-06-20 05:46:49 +05:30
parent 0c81d2ff56
commit 6e5fb95e8f
5 changed files with 51 additions and 52 deletions

View File

@@ -59,7 +59,7 @@ import {
import log from "ente-base/log";
import { savedLogs } from "ente-base/log-web";
import { customAPIHost } from "ente-base/origins";
import { downloadString } from "ente-base/utils/web";
import { saveStringAsFile } from "ente-base/utils/web";
import {
isHLSGenerationSupported,
toggleHLSGeneration,
@@ -1079,7 +1079,7 @@ const Help: React.FC<NestedSidebarDrawerVisibilityProps> = ({
log.info("Viewing logs");
const electron = globalThis.electron;
if (electron) electron.openLogDirectory();
else downloadString(savedLogs(), `ente-web-logs-${Date.now()}.txt`);
else saveStringAsFile(savedLogs(), `ente-web-logs-${Date.now()}.txt`);
};
return (

View File

@@ -2,7 +2,7 @@ import type { User } from "ente-accounts/services/user";
import { joinPath } from "ente-base/file-name";
import log from "ente-base/log";
import { type Electron } from "ente-base/types/ipc";
import { downloadAndRevokeObjectURL } from "ente-base/utils/web";
import { saveAsFileAndRevokeObjectURL } from "ente-base/utils/web";
import { downloadManager } from "ente-gallery/services/download";
import { updateFileMagicMetadata } from "ente-gallery/services/file";
import { updateMagicMetadata } from "ente-gallery/services/magic-metadata";
@@ -48,42 +48,6 @@ export type FileOp =
| "trash"
| "deletePermanently";
export async function downloadFile(file: EnteFile) {
try {
let fileBlob = await downloadManager.fileBlob(file);
const fileName = fileFileName(file);
if (file.metadata.fileType == FileType.livePhoto) {
const { imageFileName, imageData, videoFileName, videoData } =
await decodeLivePhoto(fileName, fileBlob);
const image = new File([imageData], imageFileName);
const imageType = await detectFileTypeInfo(image);
const tempImageURL = URL.createObjectURL(
new Blob([imageData], { type: imageType.mimeType }),
);
const video = new File([videoData], videoFileName);
const { mimeType } = await detectFileTypeInfo(video);
const tempVideoURL = URL.createObjectURL(
new Blob([videoData], { type: mimeType }),
);
downloadAndRevokeObjectURL(tempImageURL, imageFileName);
// Downloading multiple works everywhere except, you guessed it,
// Safari. Make up for their incompetence by adding a setTimeout.
await wait(300) /* arbitrary constant, 300ms */;
downloadAndRevokeObjectURL(tempVideoURL, videoFileName);
} else {
const { mimeType } = await detectFileTypeInfo(
new File([fileBlob], fileName),
);
fileBlob = new Blob([fileBlob], { type: mimeType });
const tempURL = URL.createObjectURL(fileBlob);
downloadAndRevokeObjectURL(tempURL, fileName);
}
} catch (e) {
log.error("failed to download file", e);
throw e;
}
}
function getSelectedFileIds(selectedFiles: SelectedState) {
const filesIDs: number[] = [];
for (const [key, val] of Object.entries(selectedFiles)) {
@@ -240,7 +204,7 @@ export async function downloadFiles(
if (progressBarUpdater?.isCancelled()) {
return;
}
await downloadFile(file);
await saveAsFile(file);
progressBarUpdater?.increaseSuccess();
} catch (e) {
log.error("download fail for file", e);
@@ -249,6 +213,40 @@ export async function downloadFiles(
}
}
/**
* Save the given {@link EnteFile} as a file in the user's download folder.
*/
const saveAsFile = async (file: EnteFile) => {
const fileBlob = await downloadManager.fileBlob(file);
const fileName = fileFileName(file);
if (file.metadata.fileType == FileType.livePhoto) {
const { imageFileName, imageData, videoFileName, videoData } =
await decodeLivePhoto(fileName, fileBlob);
await saveBlobPartAsFile(imageData, imageFileName);
// Downloading multiple works everywhere except, you guessed it,
// Safari. Make up for their incompetence by adding a setTimeout.
await wait(300) /* arbitrary constant, 300ms */;
await saveBlobPartAsFile(videoData, videoFileName);
} else {
await saveBlobPartAsFile(fileBlob, fileName);
}
};
/**
* Save the given {@link blob} as a file in the user's download folder.
*/
const saveBlobPartAsFile = async (blobPart: BlobPart, fileName: string) => {
const { mimeType } = await detectFileTypeInfo(
new File([blobPart], fileName),
);
saveAsFileAndRevokeObjectURL(
URL.createObjectURL(new Blob([blobPart], { type: mimeType })),
fileName,
);
};
async function downloadFilesDesktop(
electron: Electron,
files: EnteFile[],

View File

@@ -14,7 +14,7 @@ import { errorDialogAttributes } from "ente-base/components/utils/dialog";
import { useIsSmallWidth } from "ente-base/components/utils/hooks";
import type { ModalVisibilityProps } from "ente-base/components/utils/modal";
import log from "ente-base/log";
import { downloadString } from "ente-base/utils/web";
import { saveStringAsFile } from "ente-base/utils/web";
import { t } from "i18next";
import { useCallback, useEffect, useState } from "react";
import {
@@ -55,7 +55,7 @@ export const RecoveryKey: React.FC<RecoveryKeyProps> = ({
}, [open, handleLoadError]);
const handleSaveClick = () => {
downloadRecoveryKeyMnemonic(recoveryKey!);
saveRecoveryKeyMnemonicAsFile(recoveryKey!);
onClose();
};
@@ -117,5 +117,5 @@ export const RecoveryKey: React.FC<RecoveryKeyProps> = ({
const getUserRecoveryKeyMnemonic = async () =>
recoveryKeyToMnemonic(await getUserRecoveryKey());
const downloadRecoveryKeyMnemonic = (key: string) =>
downloadString(key, "ente-recovery-key.txt");
const saveRecoveryKeyMnemonicAsFile = (key: string) =>
saveStringAsFile(key, "ente-recovery-key.txt");

View File

@@ -2,13 +2,14 @@
* Download the asset at the given {@link url} to a file on the user's download
* folder by appending a temporary <a> element to the DOM.
*
* @param url The URL that we want to download. See also
* {@link downloadAndRevokeObjectURL} and {@link downloadString}. The URL is
* revoked after initiating the download.
* @param url The URL that we want to download. The URL is revoked after
* initiating the download.
*
* @param fileName The name of downloaded file.
*
* See also {@link saveStringAsFile}.
*/
export const downloadAndRevokeObjectURL = (url: string, fileName: string) => {
export const saveAsFileAndRevokeObjectURL = (url: string, fileName: string) => {
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
@@ -26,8 +27,8 @@ export const downloadAndRevokeObjectURL = (url: string, fileName: string) => {
*
* @param fileName The name of the file that gets saved.
*/
export const downloadString = (s: string, fileName: string) => {
export const saveStringAsFile = (s: string, fileName: string) => {
const file = new Blob([s], { type: "text/plain" });
const fileURL = URL.createObjectURL(file);
downloadAndRevokeObjectURL(fileURL, fileName);
saveAsFileAndRevokeObjectURL(fileURL, fileName);
};

View File

@@ -45,7 +45,7 @@ import type { ModalVisibilityProps } from "ente-base/components/utils/modal";
import { useBaseContext } from "ente-base/context";
import { nameAndExtension } from "ente-base/file-name";
import log from "ente-base/log";
import { downloadAndRevokeObjectURL } from "ente-base/utils/web";
import { saveAsFileAndRevokeObjectURL } from "ente-base/utils/web";
import { downloadManager } from "ente-gallery/services/download";
import type { Collection } from "ente-media/collection";
import type { EnteFile } from "ente-media/file";
@@ -472,7 +472,7 @@ export const ImageEditorOverlay: React.FC<ImageEditorOverlayProps> = ({
if (!canvasRef.current) return;
const f = await getEditedFile();
downloadAndRevokeObjectURL(URL.createObjectURL(f), f.name);
saveAsFileAndRevokeObjectURL(URL.createObjectURL(f), f.name);
};
const saveCopyToEnte = async () => {