[web] General code improvements (#6314)
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -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 videoType = await detectFileTypeInfo(video);
|
||||
const tempVideoURL = URL.createObjectURL(
|
||||
new Blob([videoData], { type: videoType.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,41 @@ 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) =>
|
||||
createTypedObjectURL(blobPart, fileName).then((url) =>
|
||||
saveAsFileAndRevokeObjectURL(url, fileName),
|
||||
);
|
||||
|
||||
const createTypedObjectURL = async (blobPart: BlobPart, fileName: string) => {
|
||||
const blob = blobPart instanceof Blob ? blobPart : new Blob([blobPart]);
|
||||
const { mimeType } = await detectFileTypeInfo(new File([blob], fileName));
|
||||
return URL.createObjectURL(new Blob([blob], { type: mimeType }));
|
||||
};
|
||||
|
||||
async function downloadFilesDesktop(
|
||||
electron: Electron,
|
||||
files: EnteFile[],
|
||||
@@ -332,11 +331,6 @@ export const getArchivedFiles = (files: EnteFile[]) => {
|
||||
return files.filter(isArchivedFile).map((file) => file.id);
|
||||
};
|
||||
|
||||
export const createTypedObjectURL = async (blob: Blob, fileName: string) => {
|
||||
const type = await detectFileTypeInfo(new File([blob], fileName));
|
||||
return URL.createObjectURL(new Blob([blob], { type: type.mimeType }));
|
||||
};
|
||||
|
||||
export const getUserOwnedFiles = (files: EnteFile[]) => {
|
||||
const user: User = getData("user");
|
||||
if (!user?.id) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -704,7 +704,7 @@ export const upload = async (
|
||||
|
||||
const { metadata, publicMagicMetadata } = await extractAssetMetadata(
|
||||
uploadAsset,
|
||||
fileTypeInfo,
|
||||
fileTypeInfo.fileType,
|
||||
lastModifiedMs,
|
||||
collection.id,
|
||||
parsedMetadataJSONMap,
|
||||
@@ -1000,9 +1000,9 @@ const readLivePhotoDetails = async ({ image, video }: LivePhotoAssets) => {
|
||||
return {
|
||||
fileTypeInfo: {
|
||||
fileType: FileType.livePhoto,
|
||||
extension: `${img.fileTypeInfo.extension}+${vid.fileTypeInfo.extension}`,
|
||||
imageType: img.fileTypeInfo.extension,
|
||||
videoType: vid.fileTypeInfo.extension,
|
||||
// Use the extension of the image component as the extension of the
|
||||
// live photo.
|
||||
extension: img.fileTypeInfo.extension,
|
||||
},
|
||||
fileSize: img.fileSize + vid.fileSize,
|
||||
lastModifiedMs: img.lastModifiedMs,
|
||||
@@ -1062,7 +1062,7 @@ const extractAssetMetadata = async (
|
||||
pathPrefix,
|
||||
externalParsedMetadata,
|
||||
}: UploadAsset,
|
||||
fileTypeInfo: FileTypeInfo,
|
||||
fileType: FileType,
|
||||
lastModifiedMs: number,
|
||||
collectionID: number,
|
||||
parsedMetadataJSONMap: Map<string, ParsedMetadataJSON>,
|
||||
@@ -1073,7 +1073,6 @@ const extractAssetMetadata = async (
|
||||
// @ts-ignore
|
||||
livePhotoAssets,
|
||||
pathPrefix,
|
||||
fileTypeInfo,
|
||||
lastModifiedMs,
|
||||
collectionID,
|
||||
parsedMetadataJSONMap,
|
||||
@@ -1084,7 +1083,7 @@ const extractAssetMetadata = async (
|
||||
uploadItem,
|
||||
pathPrefix,
|
||||
externalParsedMetadata,
|
||||
fileTypeInfo,
|
||||
fileType,
|
||||
lastModifiedMs,
|
||||
collectionID,
|
||||
parsedMetadataJSONMap,
|
||||
@@ -1094,23 +1093,17 @@ const extractAssetMetadata = async (
|
||||
const extractLivePhotoMetadata = async (
|
||||
livePhotoAssets: LivePhotoAssets,
|
||||
pathPrefix: UploadPathPrefix | undefined,
|
||||
fileTypeInfo: FileTypeInfo,
|
||||
lastModifiedMs: number,
|
||||
collectionID: number,
|
||||
parsedMetadataJSONMap: Map<string, ParsedMetadataJSON>,
|
||||
worker: CryptoWorker,
|
||||
) => {
|
||||
const imageFileTypeInfo: FileTypeInfo = {
|
||||
fileType: FileType.image,
|
||||
// @ts-ignore
|
||||
extension: fileTypeInfo.imageType,
|
||||
};
|
||||
const { metadata: imageMetadata, publicMagicMetadata } =
|
||||
await extractImageOrVideoMetadata(
|
||||
livePhotoAssets.image,
|
||||
pathPrefix,
|
||||
undefined,
|
||||
imageFileTypeInfo,
|
||||
FileType.image,
|
||||
lastModifiedMs,
|
||||
collectionID,
|
||||
parsedMetadataJSONMap,
|
||||
@@ -1137,14 +1130,13 @@ const extractImageOrVideoMetadata = async (
|
||||
uploadItem: UploadItem,
|
||||
pathPrefix: UploadPathPrefix | undefined,
|
||||
externalParsedMetadata: ExternalParsedMetadata | undefined,
|
||||
fileTypeInfo: FileTypeInfo,
|
||||
fileType: FileType,
|
||||
lastModifiedMs: number,
|
||||
collectionID: number,
|
||||
parsedMetadataJSONMap: Map<string, ParsedMetadataJSON>,
|
||||
worker: CryptoWorker,
|
||||
) => {
|
||||
const fileName = uploadItemFileName(uploadItem);
|
||||
const { fileType } = fileTypeInfo;
|
||||
|
||||
let parsedMetadata: (ParsedMetadata & ExternalParsedMetadata) | undefined;
|
||||
if (fileType == FileType.image) {
|
||||
@@ -1364,11 +1356,11 @@ const readLivePhoto = async (
|
||||
fileStreamOrData: imageFileStreamOrData,
|
||||
thumbnail,
|
||||
hasStaticThumbnail,
|
||||
} = await withThumbnail(
|
||||
} = await augmentWithThumbnail(
|
||||
livePhotoAssets.image,
|
||||
// TODO: Update underlying type
|
||||
// @ts-ignore
|
||||
{ extension: fileTypeInfo.imageType, fileType: FileType.image },
|
||||
// For live photos, the extension field in the file type info is the
|
||||
// extension of the image component of the live photo.
|
||||
{ fileType: FileType.image, extension: fileTypeInfo.extension },
|
||||
await readUploadItem(livePhotoAssets.image),
|
||||
);
|
||||
const videoFileStreamOrData = await readUploadItem(livePhotoAssets.video);
|
||||
@@ -1403,7 +1395,7 @@ const readImageOrVideo = async (
|
||||
fileTypeInfo: FileTypeInfo,
|
||||
) => {
|
||||
const fileStream = await readUploadItem(uploadItem);
|
||||
return withThumbnail(uploadItem, fileTypeInfo, fileStream);
|
||||
return augmentWithThumbnail(uploadItem, fileTypeInfo, fileStream);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1418,7 +1410,7 @@ const readImageOrVideo = async (
|
||||
* Note: The `fileStream` in the returned {@link ThumbnailedFile} may be
|
||||
* different from the one passed to the function.
|
||||
*/
|
||||
const withThumbnail = async (
|
||||
const augmentWithThumbnail = async (
|
||||
uploadItem: UploadItem,
|
||||
fileTypeInfo: FileTypeInfo,
|
||||
fileStream: FileStream,
|
||||
|
||||
@@ -21,15 +21,6 @@ export const FileType = {
|
||||
* containing both the parts.
|
||||
*/
|
||||
livePhoto: 2,
|
||||
/**
|
||||
* An unknown file type.
|
||||
*
|
||||
* The exact value here doesn't matter (and won't likely match what we get
|
||||
* from remote). This instead is serving as a placeholder, forcing us to
|
||||
* deal with the scenario that an EnteFile's type can be different from one
|
||||
* of the above.
|
||||
*/
|
||||
other: 3,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -45,12 +36,11 @@ export interface FileTypeInfo {
|
||||
/**
|
||||
* A lowercased, standardized extension for files of the current type.
|
||||
*
|
||||
* TODO(MR): This in not valid for live photos.
|
||||
* For live photos, this is set to the extension of the image component of
|
||||
* the live photo.
|
||||
*/
|
||||
extension: string;
|
||||
mimeType?: string;
|
||||
imageType?: string;
|
||||
videoType?: string;
|
||||
}
|
||||
|
||||
// list of format that were missed by type-detection for some files.
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user