From 157f3696e43efd6a6134aab55db5fed9e5b27423 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 19:58:28 +0530 Subject: [PATCH 01/29] Move --- .../src/components/PhotoViewer/index.tsx | 2 +- .../photos/src/services/download/index.ts | 2 +- web/apps/photos/src/services/fix-exif.ts | 2 +- .../src/services/upload/uploadService.ts | 2 +- web/apps/photos/src/utils/file/index.ts | 111 +---------------- .../new/photos/utils}/detect-type.ts | 2 +- web/packages/new/photos/utils/file.ts | 113 ++++++++++++++++++ 7 files changed, 120 insertions(+), 114 deletions(-) rename web/{apps/photos/src/services => packages/new/photos/utils}/detect-type.ts (97%) diff --git a/web/apps/photos/src/components/PhotoViewer/index.tsx b/web/apps/photos/src/components/PhotoViewer/index.tsx index db3d11b5c2..d5ecb003b4 100644 --- a/web/apps/photos/src/components/PhotoViewer/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/index.tsx @@ -17,6 +17,7 @@ import { import { FILE_TYPE } from "@/media/file-type"; import { isNonWebImageFileExtension } from "@/media/formats"; import type { LoadedLivePhotoSourceURL } from "@/new/photos/types/file"; +import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; import { lowercaseExtension } from "@/next/file"; import { FlexWrapper } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; @@ -44,7 +45,6 @@ import { t } from "i18next"; import isElectron from "is-electron"; import { AppContext } from "pages/_app"; import { GalleryContext } from "pages/gallery"; -import { detectFileTypeInfo } from "services/detect-type"; import downloadManager from "services/download"; import { getParsedExifData } from "services/exif"; import { trashFiles } from "services/fileService"; diff --git a/web/apps/photos/src/services/download/index.ts b/web/apps/photos/src/services/download/index.ts index 179aa7cbc8..a0f99cac2e 100644 --- a/web/apps/photos/src/services/download/index.ts +++ b/web/apps/photos/src/services/download/index.ts @@ -5,6 +5,7 @@ import { type LivePhotoSourceURL, type SourceURLs, } from "@/new/photos/types/file"; +import { getRenderableImage } from "@/new/photos/utils/file"; import { blobCache, type BlobCache } from "@/next/blob-cache"; import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; @@ -14,7 +15,6 @@ import { isPlaybackPossible } from "@ente/shared/media/video-playback"; import type { Remote } from "comlink"; import isElectron from "is-electron"; import * as ffmpeg from "services/ffmpeg"; -import { getRenderableImage } from "utils/file"; import { PhotosDownloadClient } from "./clients/photos"; import { PublicAlbumsDownloadClient } from "./clients/publicAlbums"; diff --git a/web/apps/photos/src/services/fix-exif.ts b/web/apps/photos/src/services/fix-exif.ts index a695f8d3a0..d212b31f11 100644 --- a/web/apps/photos/src/services/fix-exif.ts +++ b/web/apps/photos/src/services/fix-exif.ts @@ -1,9 +1,9 @@ import { FILE_TYPE } from "@/media/file-type"; import { EnteFile } from "@/new/photos/types/file"; +import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; import log from "@/next/log"; import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time"; import type { FixOption } from "components/FixCreationTime"; -import { detectFileTypeInfo } from "services/detect-type"; import { changeFileCreationTime, updateExistingFilePubMetadata, diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index c50407eb94..89a4d96f50 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -2,6 +2,7 @@ import { hasFileHash } from "@/media/file"; import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { encodeLivePhoto } from "@/media/live-photo"; import type { Metadata } from "@/media/types/file"; +import { detectFileTypeInfoFromChunk } from "@/new/photos/services/detect-type"; import { EnteFile, MetadataFileAttributes, @@ -40,7 +41,6 @@ import { } from "utils/magicMetadata"; import { readStream } from "utils/native-stream"; import * as convert from "xml-js"; -import { detectFileTypeInfoFromChunk } from "../detect-type"; import { tryParseEpochMicrosecondsFromFileName } from "./date"; import publicUploadHttpClient from "./publicUploadHttpClient"; import type { ParsedMetadataJSON } from "./takeout"; diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index f9bb04b94c..759117390c 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,6 +1,4 @@ import { FILE_TYPE } from "@/media/file-type"; -import { isNonWebImageFileExtension } from "@/media/formats"; -import { heicToJPEG } from "@/media/heic-convert"; import { decodeLivePhoto } from "@/media/live-photo"; import { EncryptedEnteFile, @@ -12,20 +10,18 @@ import { FileWithUpdatedMagicMetadata, } from "@/new/photos/types/file"; import { VISIBILITY_STATE } from "@/new/photos/types/magicMetadata"; +import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; import { mergeMetadata } from "@/new/photos/utils/file"; import { lowercaseExtension } from "@/next/file"; import log from "@/next/log"; -import { CustomErrorMessage, type Electron } from "@/next/types/ipc"; -import { workerBridge } from "@/next/worker/worker-bridge"; +import { type Electron } from "@/next/types/ipc"; import { withTimeout } from "@/utils/promise"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import { downloadUsingAnchor } from "@ente/shared/utils"; import { t } from "i18next"; -import isElectron from "is-electron"; import { moveToHiddenCollection } from "services/collectionService"; -import { detectFileTypeInfo } from "services/detect-type"; import DownloadManager from "services/download"; import { updateFileCreationDateInEXIF } from "services/exif"; import { @@ -43,19 +39,6 @@ import { isArchivedFile, updateMagicMetadata } from "utils/magicMetadata"; import { safeFileName } from "utils/native-fs"; import { writeStream } from "utils/native-stream"; -const SUPPORTED_RAW_FORMATS = [ - "heic", - "rw2", - "tiff", - "arw", - "cr3", - "cr2", - "nef", - "psd", - "dng", - "tif", -]; - export enum FILE_OPS_TYPE { DOWNLOAD, FIX_TIME, @@ -66,22 +49,6 @@ export enum FILE_OPS_TYPE { DELETE_PERMANENTLY, } -class ModuleState { - /** - * This will be set to true if we get an error from the Node.js side of our - * desktop app telling us that native JPEG conversion is not available for - * the current OS/arch combination. - * - * That way, we can stop pestering it again and again (saving an IPC - * round-trip). - * - * Note the double negative when it is used. - */ - isNativeJPEGConversionNotAvailable = false; -} - -const moduleState = new ModuleState(); - /** * @returns a string to use as an identifier when logging information about the * given {@link enteFile}. The returned string contains the file name (for ease @@ -257,80 +224,6 @@ export async function decryptFile( } } -/** - * The returned blob.type is filled in, whenever possible, with the MIME type of - * the data that we're dealing with. - */ -export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { - try { - const tempFile = new File([imageBlob], fileName); - const fileTypeInfo = await detectFileTypeInfo(tempFile); - log.debug( - () => - `Need renderable image for ${JSON.stringify({ fileName, ...fileTypeInfo })}`, - ); - const { extension } = fileTypeInfo; - - if (!isNonWebImageFileExtension(extension)) { - // Either it is something that the browser already knows how to - // render, or something we don't even about yet. - const mimeType = fileTypeInfo.mimeType; - if (!mimeType) { - log.info( - "Trying to render a file without a MIME type", - fileName, - ); - return imageBlob; - } else { - return new Blob([imageBlob], { type: mimeType }); - } - } - - const available = !moduleState.isNativeJPEGConversionNotAvailable; - if (isElectron() && available && isSupportedRawFormat(extension)) { - // If we're running in our desktop app, see if our Node.js layer can - // convert this into a JPEG using native tools for us. - try { - return await nativeConvertToJPEG(imageBlob); - } catch (e) { - if (e.message.endsWith(CustomErrorMessage.NotAvailable)) { - moduleState.isNativeJPEGConversionNotAvailable = true; - } else { - log.error("Native conversion to JPEG failed", e); - } - } - } - - if (extension == "heic" || extension == "heif") { - // For HEIC/HEIF files we can use our web HEIC converter. - return await heicToJPEG(imageBlob); - } - - return undefined; - } catch (e) { - log.error(`Failed to get renderable image for ${fileName}`, e); - return undefined; - } -}; - -const nativeConvertToJPEG = async (imageBlob: Blob) => { - const startTime = Date.now(); - const imageData = new Uint8Array(await imageBlob.arrayBuffer()); - const electron = globalThis.electron; - // If we're running in a worker, we need to reroute the request back to - // the main thread since workers don't have access to the `window` (and - // thus, to the `window.electron`) object. - const jpegData = electron - ? await electron.convertToJPEG(imageData) - : await workerBridge.convertToJPEG(imageData); - log.debug(() => `Native JPEG conversion took ${Date.now() - startTime} ms`); - return new Blob([jpegData], { type: "image/jpeg" }); -}; - -export function isSupportedRawFormat(exactType: string) { - return SUPPORTED_RAW_FORMATS.includes(exactType.toLowerCase()); -} - export async function changeFilesVisibility( files: EnteFile[], visibility: VISIBILITY_STATE, diff --git a/web/apps/photos/src/services/detect-type.ts b/web/packages/new/photos/utils/detect-type.ts similarity index 97% rename from web/apps/photos/src/services/detect-type.ts rename to web/packages/new/photos/utils/detect-type.ts index e92e10bf82..e7447587d6 100644 --- a/web/apps/photos/src/services/detect-type.ts +++ b/web/packages/new/photos/utils/detect-type.ts @@ -78,7 +78,7 @@ export const detectFileTypeInfoFromChunk = async ( const known = KnownFileTypeInfos.find((f) => f.extension == extension); if (known) return known; - if (KnownNonMediaFileExtensions.includes(extension)) + if (extension && KnownNonMediaFileExtensions.includes(extension)) throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); throw e; diff --git a/web/packages/new/photos/utils/file.ts b/web/packages/new/photos/utils/file.ts index d5d2892254..9674b2f504 100644 --- a/web/packages/new/photos/utils/file.ts +++ b/web/packages/new/photos/utils/file.ts @@ -1,4 +1,40 @@ +import { isNonWebImageFileExtension } from "@/media/formats"; +import { heicToJPEG } from "@/media/heic-convert"; +import log from "@/next/log"; +import { CustomErrorMessage } from "@/next/types/ipc"; +import { workerBridge } from "@/next/worker/worker-bridge"; +import isElectron from "is-electron"; import type { EnteFile } from "../types/file"; +import { detectFileTypeInfo } from "./detect-type"; + +const SUPPORTED_RAW_FORMATS = [ + "heic", + "rw2", + "tiff", + "arw", + "cr3", + "cr2", + "nef", + "psd", + "dng", + "tif", +]; + +class ModuleState { + /** + * This will be set to true if we get an error from the Node.js side of our + * desktop app telling us that native JPEG conversion is not available for + * the current OS/arch combination. + * + * That way, we can stop pestering it again and again (saving an IPC + * round-trip). + * + * Note the double negative when it is used. + */ + isNativeJPEGConversionNotAvailable = false; +} + +const moduleState = new ModuleState(); /** * [Note: File name for local EnteFile objects] @@ -28,3 +64,80 @@ export function mergeMetadata(files: EnteFile[]): EnteFile[] { return file; }); } + +/** + * The returned blob.type is filled in, whenever possible, with the MIME type of + * the data that we're dealing with. + */ +export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { + try { + const tempFile = new File([imageBlob], fileName); + const fileTypeInfo = await detectFileTypeInfo(tempFile); + log.debug( + () => + `Need renderable image for ${JSON.stringify({ fileName, ...fileTypeInfo })}`, + ); + const { extension } = fileTypeInfo; + + if (!isNonWebImageFileExtension(extension)) { + // Either it is something that the browser already knows how to + // render, or something we don't even about yet. + const mimeType = fileTypeInfo.mimeType; + if (!mimeType) { + log.info( + "Trying to render a file without a MIME type", + fileName, + ); + return imageBlob; + } else { + return new Blob([imageBlob], { type: mimeType }); + } + } + + const available = !moduleState.isNativeJPEGConversionNotAvailable; + if (isElectron() && available && isSupportedRawFormat(extension)) { + // If we're running in our desktop app, see if our Node.js layer can + // convert this into a JPEG using native tools for us. + try { + return await nativeConvertToJPEG(imageBlob); + } catch (e) { + if ( + e instanceof Error && + e.message.endsWith(CustomErrorMessage.NotAvailable) + ) { + moduleState.isNativeJPEGConversionNotAvailable = true; + } else { + log.error("Native conversion to JPEG failed", e); + } + } + } + + if (extension == "heic" || extension == "heif") { + // For HEIC/HEIF files we can use our web HEIC converter. + return await heicToJPEG(imageBlob); + } + + return undefined; + } catch (e) { + log.error(`Failed to get renderable image for ${fileName}`, e); + return undefined; + } +}; + +const nativeConvertToJPEG = async (imageBlob: Blob) => { + const startTime = Date.now(); + const imageData = new Uint8Array(await imageBlob.arrayBuffer()); + const electron = globalThis.electron; + // If we're running in a worker, we need to reroute the request back to + // the main thread since workers don't have access to the `window` (and + // thus, to the `window.electron`) object. + const jpegData = electron + ? await electron.convertToJPEG(imageData) + : await workerBridge.convertToJPEG(imageData); + log.debug(() => `Native JPEG conversion took ${Date.now() - startTime} ms`); + return new Blob([jpegData], { type: "image/jpeg" }); +}; + +export function isSupportedRawFormat(exactType: string) { + return SUPPORTED_RAW_FORMATS.includes(exactType.toLowerCase()); +} From 5fcb1de5401f488d7ede75b3c344b68abb5e564e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 20:06:48 +0530 Subject: [PATCH 02/29] Rename etc --- .../src/components/PhotoViewer/index.tsx | 6 ++- web/packages/new/photos/utils/file.ts | 43 +++++++++++-------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/web/apps/photos/src/components/PhotoViewer/index.tsx b/web/apps/photos/src/components/PhotoViewer/index.tsx index d5ecb003b4..aff51460dd 100644 --- a/web/apps/photos/src/components/PhotoViewer/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/index.tsx @@ -11,13 +11,13 @@ import { copyFileToClipboard, downloadSingleFile, getFileFromURL, - isSupportedRawFormat, } from "utils/file"; import { FILE_TYPE } from "@/media/file-type"; import { isNonWebImageFileExtension } from "@/media/formats"; import type { LoadedLivePhotoSourceURL } from "@/new/photos/types/file"; import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; +import { isNativeConvertibleToJPEG } from "@/new/photos/utils/file"; import { lowercaseExtension } from "@/next/file"; import { FlexWrapper } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; @@ -352,7 +352,9 @@ function PhotoViewer(props: Iprops) { const extension = lowercaseExtension(file.metadata.title); const isSupported = !isNonWebImageFileExtension(extension) || - isSupportedRawFormat(extension); + // TODO: This condition doesn't sound correct when running in the + // web app? + isNativeConvertibleToJPEG(extension); setShowEditButton( file.metadata.fileType === FILE_TYPE.IMAGE && isSupported, ); diff --git a/web/packages/new/photos/utils/file.ts b/web/packages/new/photos/utils/file.ts index 9674b2f504..33d93242e2 100644 --- a/web/packages/new/photos/utils/file.ts +++ b/web/packages/new/photos/utils/file.ts @@ -7,19 +7,6 @@ import isElectron from "is-electron"; import type { EnteFile } from "../types/file"; import { detectFileTypeInfo } from "./detect-type"; -const SUPPORTED_RAW_FORMATS = [ - "heic", - "rw2", - "tiff", - "arw", - "cr3", - "cr2", - "nef", - "psd", - "dng", - "tif", -]; - class ModuleState { /** * This will be set to true if we get an error from the Node.js side of our @@ -95,7 +82,7 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { } const available = !moduleState.isNativeJPEGConversionNotAvailable; - if (isElectron() && available && isSupportedRawFormat(extension)) { + if (isElectron() && available && isNativeConvertibleToJPEG(extension)) { // If we're running in our desktop app, see if our Node.js layer can // convert this into a JPEG using native tools for us. try { @@ -124,6 +111,30 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { } }; +/** + * File extensions which our native JPEG conversion code should be able to + * convert to a renderable image. + */ +const convertibleToJPEGExtensions = [ + "heic", + "rw2", + "tiff", + "arw", + "cr3", + "cr2", + "nef", + "psd", + "dng", + "tif", +]; + +/** + * Return true if {@link extension} is amongst the file extensions which we + * expect our native JPEG conversion to be able to process. + */ +export const isNativeConvertibleToJPEG = (extension: string) => + convertibleToJPEGExtensions.includes(extension.toLowerCase()); + const nativeConvertToJPEG = async (imageBlob: Blob) => { const startTime = Date.now(); const imageData = new Uint8Array(await imageBlob.arrayBuffer()); @@ -137,7 +148,3 @@ const nativeConvertToJPEG = async (imageBlob: Blob) => { log.debug(() => `Native JPEG conversion took ${Date.now() - startTime} ms`); return new Blob([jpegData], { type: "image/jpeg" }); }; - -export function isSupportedRawFormat(exactType: string) { - return SUPPORTED_RAW_FORMATS.includes(exactType.toLowerCase()); -} From 03150482f7b5148d0c3055351f625117b20c3612 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 20:07:25 +0530 Subject: [PATCH 03/29] Use our check --- web/packages/new/photos/utils/file.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/packages/new/photos/utils/file.ts b/web/packages/new/photos/utils/file.ts index 33d93242e2..92fb5dfff0 100644 --- a/web/packages/new/photos/utils/file.ts +++ b/web/packages/new/photos/utils/file.ts @@ -1,9 +1,9 @@ import { isNonWebImageFileExtension } from "@/media/formats"; import { heicToJPEG } from "@/media/heic-convert"; +import { isDesktop } from "@/next/app"; import log from "@/next/log"; import { CustomErrorMessage } from "@/next/types/ipc"; import { workerBridge } from "@/next/worker/worker-bridge"; -import isElectron from "is-electron"; import type { EnteFile } from "../types/file"; import { detectFileTypeInfo } from "./detect-type"; @@ -82,7 +82,7 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { } const available = !moduleState.isNativeJPEGConversionNotAvailable; - if (isElectron() && available && isNativeConvertibleToJPEG(extension)) { + if (isDesktop && available && isNativeConvertibleToJPEG(extension)) { // If we're running in our desktop app, see if our Node.js layer can // convert this into a JPEG using native tools for us. try { From 5c0f1837404377f78405284576b2206344ee9ad9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 20:08:41 +0530 Subject: [PATCH 04/29] lf --- web/apps/photos/src/services/face/f-index.ts | 2 +- web/apps/photos/src/services/upload/uploadService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/services/face/f-index.ts b/web/apps/photos/src/services/face/f-index.ts index cf41623e94..539fdc7411 100644 --- a/web/apps/photos/src/services/face/f-index.ts +++ b/web/apps/photos/src/services/face/f-index.ts @@ -8,6 +8,7 @@ import type { } from "@/new/photos/services/face/types"; import { faceIndexingVersion } from "@/new/photos/services/face/types"; import type { EnteFile } from "@/new/photos/types/file"; +import { getRenderableImage } from "@/new/photos/utils/file"; import log from "@/next/log"; import { workerBridge } from "@/next/worker/worker-bridge"; import { Matrix } from "ml-matrix"; @@ -20,7 +21,6 @@ import { scale, translate, } from "transformation-matrix"; -import { getRenderableImage } from "utils/file"; import { saveFaceCrop } from "./crop"; import { clamp, diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 89a4d96f50..9a5885f624 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -2,7 +2,6 @@ import { hasFileHash } from "@/media/file"; import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { encodeLivePhoto } from "@/media/live-photo"; import type { Metadata } from "@/media/types/file"; -import { detectFileTypeInfoFromChunk } from "@/new/photos/services/detect-type"; import { EnteFile, MetadataFileAttributes, @@ -12,6 +11,7 @@ import { type FilePublicMagicMetadataProps, } from "@/new/photos/types/file"; import { EncryptedMagicMetadata } from "@/new/photos/types/magicMetadata"; +import { detectFileTypeInfoFromChunk } from "@/new/photos/utils/detect-type"; import { ensureElectron } from "@/next/electron"; import { basename } from "@/next/file"; import log from "@/next/log"; From cec60520d99d6e4d5ab4c5f59a0ee60ce4db573d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 20:16:57 +0530 Subject: [PATCH 05/29] Move --- web/{apps/photos/src => packages/new/photos}/utils/native-fs.ts | 0 .../photos/src => packages/new/photos}/utils/native-stream.ts | 0 web/{apps/photos/src => packages/new/photos}/utils/units.ts | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename web/{apps/photos/src => packages/new/photos}/utils/native-fs.ts (100%) rename web/{apps/photos/src => packages/new/photos}/utils/native-stream.ts (100%) rename web/{apps/photos/src => packages/new/photos}/utils/units.ts (100%) diff --git a/web/apps/photos/src/utils/native-fs.ts b/web/packages/new/photos/utils/native-fs.ts similarity index 100% rename from web/apps/photos/src/utils/native-fs.ts rename to web/packages/new/photos/utils/native-fs.ts diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/packages/new/photos/utils/native-stream.ts similarity index 100% rename from web/apps/photos/src/utils/native-stream.ts rename to web/packages/new/photos/utils/native-stream.ts diff --git a/web/apps/photos/src/utils/units.ts b/web/packages/new/photos/utils/units.ts similarity index 100% rename from web/apps/photos/src/utils/units.ts rename to web/packages/new/photos/utils/units.ts From a282c6cb40ba49527e98dc651c6080fa52a7e2c8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 20:25:52 +0530 Subject: [PATCH 06/29] Move more --- .../src/components/PhotoList/dedupe.tsx | 2 +- .../photos/src/components/PhotoList/index.tsx | 2 +- .../PhotoViewer/FileInfo/RenderFileName.tsx | 2 +- .../individual/usageSection.tsx | 2 +- .../contentOverlay/storageSection.tsx | 4 ++-- .../photos/src/components/Upload/Uploader.tsx | 2 +- .../pages/gallery/PlanSelector/card.tsx | 2 +- .../gallery/PlanSelector/plans/BfAddOnRow.tsx | 2 +- .../gallery/PlanSelector/plans/index.tsx | 2 +- .../gallery/PlanSelector/plans/planRow.tsx | 2 +- web/apps/photos/src/services/export/index.ts | 20 ++++++------------- .../photos/src/services/export/migration.ts | 10 +++++----- web/apps/photos/src/services/ffmpeg.ts | 10 +++++----- .../photos/src/services/upload/takeout.ts | 2 +- .../src/services/upload/uploadService.ts | 2 +- web/apps/photos/src/utils/collection/index.ts | 2 +- web/apps/photos/src/utils/file/index.ts | 4 ++-- web/packages/new/photos/services/export.ts | 11 ++++++++++ web/packages/new/photos/utils/native-fs.ts | 6 +++--- .../new/photos/utils/native-stream.ts | 14 ++++++++----- 20 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 web/packages/new/photos/services/export.ts diff --git a/web/apps/photos/src/components/PhotoList/dedupe.tsx b/web/apps/photos/src/components/PhotoList/dedupe.tsx index 56a9638e3f..1652ff176f 100644 --- a/web/apps/photos/src/components/PhotoList/dedupe.tsx +++ b/web/apps/photos/src/components/PhotoList/dedupe.tsx @@ -1,4 +1,5 @@ import { EnteFile } from "@/new/photos/types/file"; +import { formattedByteSize } from "@/new/photos/utils/units"; import { FlexWrapper } from "@ente/shared/components/Container"; import { Box, styled } from "@mui/material"; import { @@ -19,7 +20,6 @@ import { areEqual, } from "react-window"; import { Duplicate } from "services/deduplicationService"; -import { formattedByteSize } from "utils/units"; export enum ITEM_TYPE { TIME = "TIME", diff --git a/web/apps/photos/src/components/PhotoList/index.tsx b/web/apps/photos/src/components/PhotoList/index.tsx index 85c6db57cb..1f4b0bfb06 100644 --- a/web/apps/photos/src/components/PhotoList/index.tsx +++ b/web/apps/photos/src/components/PhotoList/index.tsx @@ -1,4 +1,5 @@ import { EnteFile } from "@/new/photos/types/file"; +import { formattedByteSize } from "@/new/photos/utils/units"; import { FlexWrapper } from "@ente/shared/components/Container"; import { formatDate } from "@ente/shared/time/format"; import { Box, Checkbox, Link, Typography, styled } from "@mui/material"; @@ -24,7 +25,6 @@ import { } from "react-window"; import { handleSelectCreator } from "utils/photoFrame"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; -import { formattedByteSize } from "utils/units"; const FOOTER_HEIGHT = 90; const ALBUM_FOOTER_HEIGHT = 75; diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx index caa9be2de4..b4a932b9aa 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx @@ -1,5 +1,6 @@ import { FILE_TYPE } from "@/media/file-type"; import { EnteFile } from "@/new/photos/types/file"; +import { formattedByteSize } from "@/new/photos/utils/units"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import { FlexWrapper } from "@ente/shared/components/Container"; @@ -8,7 +9,6 @@ import VideocamOutlined from "@mui/icons-material/VideocamOutlined"; import Box from "@mui/material/Box"; import { useEffect, useState } from "react"; import { changeFileName, updateExistingFilePubMetadata } from "utils/file"; -import { formattedByteSize } from "utils/units"; import { FileNameEditDialog } from "./FileNameEditDialog"; import InfoItem from "./InfoItem"; diff --git a/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/individual/usageSection.tsx b/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/individual/usageSection.tsx index 8975941ad5..251a58d823 100644 --- a/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/individual/usageSection.tsx +++ b/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/individual/usageSection.tsx @@ -1,7 +1,7 @@ +import { formattedStorageByteSize } from "@/new/photos/utils/units"; import { SpaceBetweenFlex } from "@ente/shared/components/Container"; import { Box, Typography } from "@mui/material"; import { t } from "i18next"; -import { formattedStorageByteSize } from "utils/units"; import { Progressbar } from "../../styledComponents"; diff --git a/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/storageSection.tsx b/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/storageSection.tsx index 7f2712f738..4ad0ed2149 100644 --- a/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/storageSection.tsx +++ b/web/apps/photos/src/components/Sidebar/SubscriptionCard/contentOverlay/storageSection.tsx @@ -1,6 +1,6 @@ -import { Box, styled, Typography } from "@mui/material"; +import { bytesInGB, formattedStorageByteSize } from "@/new/photos/utils/units"; +import { Box, Typography, styled } from "@mui/material"; import { t } from "i18next"; -import { bytesInGB, formattedStorageByteSize } from "utils/units"; const MobileSmallBox = styled(Box)` display: none; diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index 85e660672e..170d2fcdc8 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -1,3 +1,4 @@ +import { exportMetadataDirectoryName } from "@/new/photos/services/export"; import { basename } from "@/next/file"; import log from "@/next/log"; import type { CollectionMapping, Electron, ZipItem } from "@/next/types/ipc"; @@ -15,7 +16,6 @@ import { GalleryContext } from "pages/gallery"; import { useContext, useEffect, useRef, useState } from "react"; import billingService from "services/billingService"; import { getLatestCollections } from "services/collectionService"; -import { exportMetadataDirectoryName } from "services/export"; import { getPublicCollectionUID, getPublicCollectionUploaderName, diff --git a/web/apps/photos/src/components/pages/gallery/PlanSelector/card.tsx b/web/apps/photos/src/components/pages/gallery/PlanSelector/card.tsx index 55e93979de..17f0b6e6e7 100644 --- a/web/apps/photos/src/components/pages/gallery/PlanSelector/card.tsx +++ b/web/apps/photos/src/components/pages/gallery/PlanSelector/card.tsx @@ -1,3 +1,4 @@ +import { bytesInGB } from "@/new/photos/utils/units"; import log from "@/next/log"; import { SpaceBetweenFlex } from "@ente/shared/components/Container"; import { SUPPORT_EMAIL } from "@ente/shared/constants/urls"; @@ -27,7 +28,6 @@ import { planForSubscription, updateSubscription, } from "utils/billing"; -import { bytesInGB } from "utils/units"; import { getLocalUserDetails } from "utils/user"; import { getTotalFamilyUsage, isPartOfFamily } from "utils/user/family"; import { ManageSubscription } from "./manageSubscription"; diff --git a/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/BfAddOnRow.tsx b/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/BfAddOnRow.tsx index 5f7e13deb8..7b375f5d73 100644 --- a/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/BfAddOnRow.tsx +++ b/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/BfAddOnRow.tsx @@ -1,8 +1,8 @@ import { SpaceBetweenFlex } from "@ente/shared/components/Container"; import { Box, styled, Typography } from "@mui/material"; +import { formattedStorageByteSize } from "@/new/photos/utils/units"; import { Trans } from "react-i18next"; -import { formattedStorageByteSize } from "utils/units"; const RowContainer = styled(SpaceBetweenFlex)(({ theme }) => ({ // gap: theme.spacing(1.5), diff --git a/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/index.tsx b/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/index.tsx index 31e97c68e6..e83ca781ee 100644 --- a/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/index.tsx +++ b/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/index.tsx @@ -1,3 +1,4 @@ +import { formattedStorageByteSize } from "@/new/photos/utils/units"; import { SpaceBetweenFlex } from "@ente/shared/components/Container"; import ArrowForward from "@mui/icons-material/ArrowForward"; import { Box, IconButton, Stack, Typography, styled } from "@mui/material"; @@ -12,7 +13,6 @@ import { isPopularPlan, isUserSubscribedPlan, } from "utils/billing"; -import { formattedStorageByteSize } from "utils/units"; import { PlanRow } from "./planRow"; interface Iprops { diff --git a/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/planRow.tsx b/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/planRow.tsx index 9f1351b120..9701baf01a 100644 --- a/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/planRow.tsx +++ b/web/apps/photos/src/components/pages/gallery/PlanSelector/plans/planRow.tsx @@ -1,3 +1,4 @@ +import { bytesInGB } from "@/new/photos/utils/units"; import { FlexWrapper, FluidContainer } from "@ente/shared/components/Container"; import ArrowForward from "@mui/icons-material/ArrowForward"; import Done from "@mui/icons-material/Done"; @@ -7,7 +8,6 @@ import { PLAN_PERIOD } from "constants/gallery"; import { t } from "i18next"; import { Plan, Subscription } from "types/billing"; import { hasPaidSubscription, isUserSubscribedPlan } from "utils/billing"; -import { bytesInGB } from "utils/units"; interface Iprops { plan: Plan; diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index df7d23eddf..fcf387fef3 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -1,9 +1,15 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import type { Metadata } from "@/media/types/file"; +import { + exportMetadataDirectoryName, + exportTrashDirectoryName, +} from "@/new/photos/services/export"; import { getAllLocalFiles } from "@/new/photos/services/files"; import { EnteFile } from "@/new/photos/types/file"; import { mergeMetadata } from "@/new/photos/utils/file"; +import { safeDirectoryName, safeFileName } from "@/new/photos/utils/native-fs"; +import { writeStream } from "@/new/photos/utils/native-stream"; import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; import { wait } from "@/utils/promise"; @@ -31,8 +37,6 @@ import { getNonEmptyPersonalCollections, } from "utils/collection"; import { getPersonalFiles, getUpdatedEXIFFileForDownload } from "utils/file"; -import { safeDirectoryName, safeFileName } from "utils/native-fs"; -import { writeStream } from "utils/native-stream"; import { getAllLocalCollections } from "../collectionService"; import downloadManager from "../download"; import { migrateExport } from "./migration"; @@ -46,18 +50,6 @@ const exportRecordFileName = "export_status.json"; */ const exportDirectoryName = "Ente Photos"; -/** - * Name of the directory in which we put our metadata when exporting to the file - * system. - */ -export const exportMetadataDirectoryName = "metadata"; - -/** - * Name of the directory in which we keep trash items when deleting files that - * have been exported to the local disk previously. - */ -export const exportTrashDirectoryName = "Trash"; - export enum ExportStage { INIT = 0, MIGRATION = 1, diff --git a/web/apps/photos/src/services/export/migration.ts b/web/apps/photos/src/services/export/migration.ts index 75ab0e2f11..f46fc0a48e 100644 --- a/web/apps/photos/src/services/export/migration.ts +++ b/web/apps/photos/src/services/export/migration.ts @@ -3,6 +3,11 @@ import { decodeLivePhoto } from "@/media/live-photo"; import { getAllLocalFiles } from "@/new/photos/services/files"; import { EnteFile } from "@/new/photos/types/file"; import { mergeMetadata } from "@/new/photos/utils/file"; +import { + safeDirectoryName, + safeFileName, + sanitizeFilename, +} from "@/new/photos/utils/native-fs"; import { ensureElectron } from "@/next/electron"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; @@ -24,11 +29,6 @@ import { } from "types/export"; import { getNonEmptyPersonalCollections } from "utils/collection"; import { getIDBasedSortedFiles, getPersonalFiles } from "utils/file"; -import { - safeDirectoryName, - safeFileName, - sanitizeFilename, -} from "utils/native-fs"; import { exportMetadataDirectoryName, getCollectionIDFromFileUID, diff --git a/web/apps/photos/src/services/ffmpeg.ts b/web/apps/photos/src/services/ffmpeg.ts index 800df1e953..342a6fca57 100644 --- a/web/apps/photos/src/services/ffmpeg.ts +++ b/web/apps/photos/src/services/ffmpeg.ts @@ -1,3 +1,8 @@ +import { + readConvertToMP4Done, + readConvertToMP4Stream, + writeConvertToMP4Stream, +} from "@/new/photos/utils/native-stream"; import type { Electron } from "@/next/types/ipc"; import { ComlinkWorker } from "@/next/worker/comlink-worker"; import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time"; @@ -9,11 +14,6 @@ import { } from "constants/ffmpeg"; import { NULL_LOCATION } from "constants/upload"; import type { ParsedExtractedMetadata } from "types/metadata"; -import { - readConvertToMP4Done, - readConvertToMP4Stream, - writeConvertToMP4Stream, -} from "utils/native-stream"; import type { DedicatedFFmpegWorker } from "worker/ffmpeg.worker"; import { toDataOrPathOrZipEntry, diff --git a/web/apps/photos/src/services/upload/takeout.ts b/web/apps/photos/src/services/upload/takeout.ts index 24c0a9d267..2cd0f342d6 100644 --- a/web/apps/photos/src/services/upload/takeout.ts +++ b/web/apps/photos/src/services/upload/takeout.ts @@ -1,11 +1,11 @@ /** @file Dealing with the JSON metadata in Google Takeouts */ +import { readStream } from "@/new/photos/utils/native-stream"; import { ensureElectron } from "@/next/electron"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import { NULL_LOCATION } from "constants/upload"; import type { Location } from "types/metadata"; -import { readStream } from "utils/native-stream"; import type { UploadItem } from "./types"; export interface ParsedMetadataJSON { diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 9a5885f624..bfe682cb1b 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -12,6 +12,7 @@ import { } from "@/new/photos/types/file"; import { EncryptedMagicMetadata } from "@/new/photos/types/magicMetadata"; import { detectFileTypeInfoFromChunk } from "@/new/photos/utils/detect-type"; +import { readStream } from "@/new/photos/utils/native-stream"; import { ensureElectron } from "@/next/electron"; import { basename } from "@/next/file"; import log from "@/next/log"; @@ -39,7 +40,6 @@ import { getNonEmptyMagicMetadataProps, updateMagicMetadata, } from "utils/magicMetadata"; -import { readStream } from "utils/native-stream"; import * as convert from "xml-js"; import { tryParseEpochMicrosecondsFromFileName } from "./date"; import publicUploadHttpClient from "./publicUploadHttpClient"; diff --git a/web/apps/photos/src/utils/collection/index.ts b/web/apps/photos/src/utils/collection/index.ts index 12fffe6bfa..d6081567f3 100644 --- a/web/apps/photos/src/utils/collection/index.ts +++ b/web/apps/photos/src/utils/collection/index.ts @@ -1,6 +1,7 @@ import { getAllLocalFiles, getLocalFiles } from "@/new/photos/services/files"; import { EnteFile } from "@/new/photos/types/file"; import { SUB_TYPE, VISIBILITY_STATE } from "@/new/photos/types/magicMetadata"; +import { safeDirectoryName } from "@/new/photos/utils/native-fs"; import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; import { CustomError } from "@ente/shared/error"; @@ -43,7 +44,6 @@ import { import { SetFilesDownloadProgressAttributes } from "types/gallery"; import { downloadFilesWithProgress } from "utils/file"; import { isArchivedCollection, updateMagicMetadata } from "utils/magicMetadata"; -import { safeDirectoryName } from "utils/native-fs"; export enum COLLECTION_OPS_TYPE { ADD, diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 759117390c..13b3c2fe61 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -12,6 +12,8 @@ import { import { VISIBILITY_STATE } from "@/new/photos/types/magicMetadata"; import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; import { mergeMetadata } from "@/new/photos/utils/file"; +import { safeFileName } from "@/new/photos/utils/native-fs"; +import { writeStream } from "@/new/photos/utils/native-stream"; import { lowercaseExtension } from "@/next/file"; import log from "@/next/log"; import { type Electron } from "@/next/types/ipc"; @@ -36,8 +38,6 @@ import { SetFilesDownloadProgressAttributesCreator, } from "types/gallery"; import { isArchivedFile, updateMagicMetadata } from "utils/magicMetadata"; -import { safeFileName } from "utils/native-fs"; -import { writeStream } from "utils/native-stream"; export enum FILE_OPS_TYPE { DOWNLOAD, diff --git a/web/packages/new/photos/services/export.ts b/web/packages/new/photos/services/export.ts new file mode 100644 index 0000000000..40229741ed --- /dev/null +++ b/web/packages/new/photos/services/export.ts @@ -0,0 +1,11 @@ +/** + * Name of the directory in which we put our metadata when exporting to the file + * system. + */ +export const exportMetadataDirectoryName = "metadata"; + +/** + * Name of the directory in which we keep trash items when deleting files that + * have been exported to the local disk previously. + */ +export const exportTrashDirectoryName = "Trash"; diff --git a/web/packages/new/photos/utils/native-fs.ts b/web/packages/new/photos/utils/native-fs.ts index 27ebdd1c12..666efc4fca 100644 --- a/web/packages/new/photos/utils/native-fs.ts +++ b/web/packages/new/photos/utils/native-fs.ts @@ -5,12 +5,12 @@ * written for use by the code that runs in our desktop app. */ -import { nameAndExtension } from "@/next/file"; -import sanitize from "sanitize-filename"; import { exportMetadataDirectoryName, exportTrashDirectoryName, -} from "services/export"; +} from "@/new/photos/services/export"; +import { nameAndExtension } from "@/next/file"; +import sanitize from "sanitize-filename"; /** * Sanitize string for use as file or directory name. diff --git a/web/packages/new/photos/utils/native-stream.ts b/web/packages/new/photos/utils/native-stream.ts index e922c26219..9c38897367 100644 --- a/web/packages/new/photos/utils/native-stream.ts +++ b/web/packages/new/photos/utils/native-stream.ts @@ -52,7 +52,7 @@ export const readStream = async ( const res = await fetch(req); if (!res.ok) throw new Error( - `Failed to read stream from ${url}: HTTP ${res.status}`, + `Failed to read stream from ${url.href}: HTTP ${res.status}`, ); const size = readNumericHeader(res, "Content-Length"); @@ -63,7 +63,7 @@ export const readStream = async ( const readNumericHeader = (res: Response, key: string) => { const valueText = res.headers.get(key); - const value = +valueText; + const value = valueText === null ? NaN : +valueText; if (isNaN(value)) throw new Error( `Expected a numeric ${key} when reading a stream response, instead got ${valueText}`, @@ -111,7 +111,9 @@ export const writeStream = async ( const res = await fetch(req); if (!res.ok) - throw new Error(`Failed to write stream to ${url}: HTTP ${res.status}`); + throw new Error( + `Failed to write stream to ${url.href}: HTTP ${res.status}`, + ); }; /** @@ -161,7 +163,7 @@ export const readConvertToMP4Stream = async ( const res = await fetch(req); if (!res.ok) throw new Error( - `Failed to read stream from ${url}: HTTP ${res.status}`, + `Failed to read stream from ${url.href}: HTTP ${res.status}`, ); return res.blob(); @@ -185,5 +187,7 @@ export const readConvertToMP4Done = async ( const req = new Request(url, { method: "GET" }); const res = await fetch(req); if (!res.ok) - throw new Error(`Failed to close stream at ${url}: HTTP ${res.status}`); + throw new Error( + `Failed to close stream at ${url.href}: HTTP ${res.status}`, + ); }; From 920590758843debb6a68fec094166d1296bcf7e7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 20:33:28 +0530 Subject: [PATCH 07/29] Inline --- .../src/services/download/clients/photos.ts | 149 --------- .../services/download/clients/publicAlbums.ts | 145 --------- .../photos/src/services/download/index.ts | 287 +++++++++++++++++- 3 files changed, 285 insertions(+), 296 deletions(-) delete mode 100644 web/apps/photos/src/services/download/clients/photos.ts delete mode 100644 web/apps/photos/src/services/download/clients/publicAlbums.ts diff --git a/web/apps/photos/src/services/download/clients/photos.ts b/web/apps/photos/src/services/download/clients/photos.ts deleted file mode 100644 index 110c51a093..0000000000 --- a/web/apps/photos/src/services/download/clients/photos.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { EnteFile } from "@/new/photos/types/file"; -import { customAPIOrigin } from "@/next/origins"; -import { CustomError } from "@ente/shared/error"; -import HTTPService from "@ente/shared/network/HTTPService"; -import { retryAsyncFunction } from "@ente/shared/utils"; -import { DownloadClient } from "services/download"; - -export class PhotosDownloadClient implements DownloadClient { - constructor( - private token: string, - private timeout: number, - ) {} - - updateTokens(token: string) { - this.token = token; - } - - async downloadThumbnail(file: EnteFile): Promise { - const token = this.token; - if (!token) throw Error(CustomError.TOKEN_MISSING); - - const customOrigin = await customAPIOrigin(); - - // See: [Note: Passing credentials for self-hosted file fetches] - const getThumbnail = () => { - const opts = { responseType: "arraybuffer", timeout: this.timeout }; - if (customOrigin) { - const params = new URLSearchParams({ token }); - return HTTPService.get( - `${customOrigin}/files/preview/${file.id}?${params.toString()}`, - undefined, - undefined, - opts, - ); - } else { - return HTTPService.get( - `https://thumbnails.ente.io/?fileID=${file.id}`, - undefined, - { "X-Auth-Token": token }, - opts, - ); - } - }; - - const resp = await retryAsyncFunction(getThumbnail); - if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); - } - - async downloadFile( - file: EnteFile, - onDownloadProgress: (event: { loaded: number; total: number }) => void, - ): Promise { - const token = this.token; - if (!token) throw Error(CustomError.TOKEN_MISSING); - - const customOrigin = await customAPIOrigin(); - - // See: [Note: Passing credentials for self-hosted file fetches] - const getFile = () => { - const opts = { - responseType: "arraybuffer", - timeout: this.timeout, - onDownloadProgress, - }; - - if (customOrigin) { - const params = new URLSearchParams({ token }); - return HTTPService.get( - `${customOrigin}/files/download/${file.id}?${params.toString()}`, - undefined, - undefined, - opts, - ); - } else { - return HTTPService.get( - `https://files.ente.io/?fileID=${file.id}`, - undefined, - { "X-Auth-Token": token }, - opts, - ); - } - }; - - const resp = await retryAsyncFunction(getFile); - if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); - } - - async downloadFileStream(file: EnteFile): Promise { - const token = this.token; - if (!token) throw Error(CustomError.TOKEN_MISSING); - - const customOrigin = await customAPIOrigin(); - - // [Note: Passing credentials for self-hosted file fetches] - // - // Fetching files (or thumbnails) in the default self-hosted Ente - // configuration involves a redirection: - // - // 1. The browser makes a HTTP GET to a museum with credentials. Museum - // inspects the credentials, in this case the auth token, and if - // they're valid, returns a HTTP 307 redirect to the pre-signed S3 - // URL that to the file in the configured S3 bucket. - // - // 2. The browser follows the redirect to get the actual file. The URL - // is pre-signed, i.e. already has all credentials needed to prove to - // the S3 object storage that it should serve this response. - // - // For the first step normally we'd pass the auth the token via the - // "X-Auth-Token" HTTP header. In this case though, that would be - // problematic because the browser preserves the request headers when it - // follows the HTTP 307 redirect, and the "X-Auth-Token" header also - // gets sent to the redirected S3 request made in second step. - // - // To avoid this, we pass the token as a query parameter. Generally this - // is not a good idea, but in this case (a) the URL is not a user - // visible one and (b) even if it gets logged, it'll be in the - // self-hosters own service. - // - // Note that Ente's own servers don't have these concerns because we use - // a slightly different flow involving a proxy instead of directly - // connecting to the S3 storage. - // - // 1. The web browser makes a HTTP GET request to a proxy passing it the - // credentials in the "X-Auth-Token". - // - // 2. The proxy then does both the original steps: (a). Use the - // credentials to get the pre signed URL, and (b) fetch that pre - // signed URL and stream back the response. - - const getFile = () => { - if (customOrigin) { - const params = new URLSearchParams({ token }); - return fetch( - `${customOrigin}/files/download/${file.id}?${params.toString()}`, - ); - } else { - return fetch(`https://files.ente.io/?fileID=${file.id}`, { - headers: { - "X-Auth-Token": token, - }, - }); - } - }; - - return retryAsyncFunction(getFile); - } -} diff --git a/web/apps/photos/src/services/download/clients/publicAlbums.ts b/web/apps/photos/src/services/download/clients/publicAlbums.ts deleted file mode 100644 index d471591e63..0000000000 --- a/web/apps/photos/src/services/download/clients/publicAlbums.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { EnteFile } from "@/new/photos/types/file"; -import { customAPIOrigin } from "@/next/origins"; -import { CustomError } from "@ente/shared/error"; -import HTTPService from "@ente/shared/network/HTTPService"; -import { retryAsyncFunction } from "@ente/shared/utils"; -import { DownloadClient } from "services/download"; - -export class PublicAlbumsDownloadClient implements DownloadClient { - private token: string; - private passwordToken: string; - - constructor(private timeout: number) {} - - updateTokens(token: string, passwordToken: string) { - this.token = token; - this.passwordToken = passwordToken; - } - - downloadThumbnail = async (file: EnteFile) => { - const accessToken = this.token; - const accessTokenJWT = this.passwordToken; - if (!accessToken) throw Error(CustomError.TOKEN_MISSING); - const customOrigin = await customAPIOrigin(); - - // See: [Note: Passing credentials for self-hosted file fetches] - const getThumbnail = () => { - const opts = { - responseType: "arraybuffer", - }; - - if (customOrigin) { - const params = new URLSearchParams({ - accessToken, - ...(accessTokenJWT && { accessTokenJWT }), - }); - return HTTPService.get( - `${customOrigin}/public-collection/files/preview/${file.id}?${params.toString()}`, - undefined, - undefined, - opts, - ); - } else { - return HTTPService.get( - `https://public-albums.ente.io/preview/?fileID=${file.id}`, - undefined, - { - "X-Auth-Access-Token": accessToken, - ...(accessTokenJWT && { - "X-Auth-Access-Token-JWT": accessTokenJWT, - }), - }, - opts, - ); - } - }; - - const resp = await getThumbnail(); - if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); - }; - - downloadFile = async ( - file: EnteFile, - onDownloadProgress: (event: { loaded: number; total: number }) => void, - ) => { - const accessToken = this.token; - const accessTokenJWT = this.passwordToken; - if (!accessToken) throw Error(CustomError.TOKEN_MISSING); - - const customOrigin = await customAPIOrigin(); - - // See: [Note: Passing credentials for self-hosted file fetches] - const getFile = () => { - const opts = { - responseType: "arraybuffer", - timeout: this.timeout, - onDownloadProgress, - }; - - if (customOrigin) { - const params = new URLSearchParams({ - accessToken, - ...(accessTokenJWT && { accessTokenJWT }), - }); - return HTTPService.get( - `${customOrigin}/public-collection/files/download/${file.id}?${params.toString()}`, - undefined, - undefined, - opts, - ); - } else { - return HTTPService.get( - `https://public-albums.ente.io/download/?fileID=${file.id}`, - undefined, - { - "X-Auth-Access-Token": accessToken, - ...(accessTokenJWT && { - "X-Auth-Access-Token-JWT": accessTokenJWT, - }), - }, - opts, - ); - } - }; - - const resp = await retryAsyncFunction(getFile); - if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); - }; - - async downloadFileStream(file: EnteFile): Promise { - const accessToken = this.token; - const accessTokenJWT = this.passwordToken; - if (!accessToken) throw Error(CustomError.TOKEN_MISSING); - - const customOrigin = await customAPIOrigin(); - - // See: [Note: Passing credentials for self-hosted file fetches] - const getFile = () => { - if (customOrigin) { - const params = new URLSearchParams({ - accessToken, - ...(accessTokenJWT && { accessTokenJWT }), - }); - return fetch( - `${customOrigin}/public-collection/files/download/${file.id}?${params.toString()}`, - ); - } else { - return fetch( - `https://public-albums.ente.io/download/?fileID=${file.id}`, - { - headers: { - "X-Auth-Access-Token": accessToken, - ...(accessTokenJWT && { - "X-Auth-Access-Token-JWT": accessTokenJWT, - }), - }, - }, - ); - } - }; - - return retryAsyncFunction(getFile); - } -} diff --git a/web/apps/photos/src/services/download/index.ts b/web/apps/photos/src/services/download/index.ts index a0f99cac2e..e656c54484 100644 --- a/web/apps/photos/src/services/download/index.ts +++ b/web/apps/photos/src/services/download/index.ts @@ -8,15 +8,16 @@ import { import { getRenderableImage } from "@/new/photos/utils/file"; import { blobCache, type BlobCache } from "@/next/blob-cache"; import log from "@/next/log"; +import { customAPIOrigin } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker"; import { CustomError } from "@ente/shared/error"; import { isPlaybackPossible } from "@ente/shared/media/video-playback"; +import HTTPService from "@ente/shared/network/HTTPService"; +import { retryAsyncFunction } from "@ente/shared/utils"; import type { Remote } from "comlink"; import isElectron from "is-electron"; import * as ffmpeg from "services/ffmpeg"; -import { PhotosDownloadClient } from "./clients/photos"; -import { PublicAlbumsDownloadClient } from "./clients/publicAlbums"; export type OnDownloadProgress = (event: { loaded: number; @@ -536,3 +537,285 @@ async function getPlayableVideo( return null; } } + +class PhotosDownloadClient implements DownloadClient { + constructor( + private token: string, + private timeout: number, + ) {} + + updateTokens(token: string) { + this.token = token; + } + + async downloadThumbnail(file: EnteFile): Promise { + const token = this.token; + if (!token) throw Error(CustomError.TOKEN_MISSING); + + const customOrigin = await customAPIOrigin(); + + // See: [Note: Passing credentials for self-hosted file fetches] + const getThumbnail = () => { + const opts = { responseType: "arraybuffer", timeout: this.timeout }; + if (customOrigin) { + const params = new URLSearchParams({ token }); + return HTTPService.get( + `${customOrigin}/files/preview/${file.id}?${params.toString()}`, + undefined, + undefined, + opts, + ); + } else { + return HTTPService.get( + `https://thumbnails.ente.io/?fileID=${file.id}`, + undefined, + { "X-Auth-Token": token }, + opts, + ); + } + }; + + const resp = await retryAsyncFunction(getThumbnail); + if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); + return new Uint8Array(resp.data); + } + + async downloadFile( + file: EnteFile, + onDownloadProgress: (event: { loaded: number; total: number }) => void, + ): Promise { + const token = this.token; + if (!token) throw Error(CustomError.TOKEN_MISSING); + + const customOrigin = await customAPIOrigin(); + + // See: [Note: Passing credentials for self-hosted file fetches] + const getFile = () => { + const opts = { + responseType: "arraybuffer", + timeout: this.timeout, + onDownloadProgress, + }; + + if (customOrigin) { + const params = new URLSearchParams({ token }); + return HTTPService.get( + `${customOrigin}/files/download/${file.id}?${params.toString()}`, + undefined, + undefined, + opts, + ); + } else { + return HTTPService.get( + `https://files.ente.io/?fileID=${file.id}`, + undefined, + { "X-Auth-Token": token }, + opts, + ); + } + }; + + const resp = await retryAsyncFunction(getFile); + if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); + return new Uint8Array(resp.data); + } + + async downloadFileStream(file: EnteFile): Promise { + const token = this.token; + if (!token) throw Error(CustomError.TOKEN_MISSING); + + const customOrigin = await customAPIOrigin(); + + // [Note: Passing credentials for self-hosted file fetches] + // + // Fetching files (or thumbnails) in the default self-hosted Ente + // configuration involves a redirection: + // + // 1. The browser makes a HTTP GET to a museum with credentials. Museum + // inspects the credentials, in this case the auth token, and if + // they're valid, returns a HTTP 307 redirect to the pre-signed S3 + // URL that to the file in the configured S3 bucket. + // + // 2. The browser follows the redirect to get the actual file. The URL + // is pre-signed, i.e. already has all credentials needed to prove to + // the S3 object storage that it should serve this response. + // + // For the first step normally we'd pass the auth the token via the + // "X-Auth-Token" HTTP header. In this case though, that would be + // problematic because the browser preserves the request headers when it + // follows the HTTP 307 redirect, and the "X-Auth-Token" header also + // gets sent to the redirected S3 request made in second step. + // + // To avoid this, we pass the token as a query parameter. Generally this + // is not a good idea, but in this case (a) the URL is not a user + // visible one and (b) even if it gets logged, it'll be in the + // self-hosters own service. + // + // Note that Ente's own servers don't have these concerns because we use + // a slightly different flow involving a proxy instead of directly + // connecting to the S3 storage. + // + // 1. The web browser makes a HTTP GET request to a proxy passing it the + // credentials in the "X-Auth-Token". + // + // 2. The proxy then does both the original steps: (a). Use the + // credentials to get the pre signed URL, and (b) fetch that pre + // signed URL and stream back the response. + + const getFile = () => { + if (customOrigin) { + const params = new URLSearchParams({ token }); + return fetch( + `${customOrigin}/files/download/${file.id}?${params.toString()}`, + ); + } else { + return fetch(`https://files.ente.io/?fileID=${file.id}`, { + headers: { + "X-Auth-Token": token, + }, + }); + } + }; + + return retryAsyncFunction(getFile); + } +} + +class PublicAlbumsDownloadClient implements DownloadClient { + private token: string; + private passwordToken: string; + + constructor(private timeout: number) {} + + updateTokens(token: string, passwordToken: string) { + this.token = token; + this.passwordToken = passwordToken; + } + + downloadThumbnail = async (file: EnteFile) => { + const accessToken = this.token; + const accessTokenJWT = this.passwordToken; + if (!accessToken) throw Error(CustomError.TOKEN_MISSING); + const customOrigin = await customAPIOrigin(); + + // See: [Note: Passing credentials for self-hosted file fetches] + const getThumbnail = () => { + const opts = { + responseType: "arraybuffer", + }; + + if (customOrigin) { + const params = new URLSearchParams({ + accessToken, + ...(accessTokenJWT && { accessTokenJWT }), + }); + return HTTPService.get( + `${customOrigin}/public-collection/files/preview/${file.id}?${params.toString()}`, + undefined, + undefined, + opts, + ); + } else { + return HTTPService.get( + `https://public-albums.ente.io/preview/?fileID=${file.id}`, + undefined, + { + "X-Auth-Access-Token": accessToken, + ...(accessTokenJWT && { + "X-Auth-Access-Token-JWT": accessTokenJWT, + }), + }, + opts, + ); + } + }; + + const resp = await getThumbnail(); + if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); + return new Uint8Array(resp.data); + }; + + downloadFile = async ( + file: EnteFile, + onDownloadProgress: (event: { loaded: number; total: number }) => void, + ) => { + const accessToken = this.token; + const accessTokenJWT = this.passwordToken; + if (!accessToken) throw Error(CustomError.TOKEN_MISSING); + + const customOrigin = await customAPIOrigin(); + + // See: [Note: Passing credentials for self-hosted file fetches] + const getFile = () => { + const opts = { + responseType: "arraybuffer", + timeout: this.timeout, + onDownloadProgress, + }; + + if (customOrigin) { + const params = new URLSearchParams({ + accessToken, + ...(accessTokenJWT && { accessTokenJWT }), + }); + return HTTPService.get( + `${customOrigin}/public-collection/files/download/${file.id}?${params.toString()}`, + undefined, + undefined, + opts, + ); + } else { + return HTTPService.get( + `https://public-albums.ente.io/download/?fileID=${file.id}`, + undefined, + { + "X-Auth-Access-Token": accessToken, + ...(accessTokenJWT && { + "X-Auth-Access-Token-JWT": accessTokenJWT, + }), + }, + opts, + ); + } + }; + + const resp = await retryAsyncFunction(getFile); + if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); + return new Uint8Array(resp.data); + }; + + async downloadFileStream(file: EnteFile): Promise { + const accessToken = this.token; + const accessTokenJWT = this.passwordToken; + if (!accessToken) throw Error(CustomError.TOKEN_MISSING); + + const customOrigin = await customAPIOrigin(); + + // See: [Note: Passing credentials for self-hosted file fetches] + const getFile = () => { + if (customOrigin) { + const params = new URLSearchParams({ + accessToken, + ...(accessTokenJWT && { accessTokenJWT }), + }); + return fetch( + `${customOrigin}/public-collection/files/download/${file.id}?${params.toString()}`, + ); + } else { + return fetch( + `https://public-albums.ente.io/download/?fileID=${file.id}`, + { + headers: { + "X-Auth-Access-Token": accessToken, + ...(accessTokenJWT && { + "X-Auth-Access-Token-JWT": accessTokenJWT, + }), + }, + }, + ); + } + }; + + return retryAsyncFunction(getFile); + } +} From 2cce5fb17cd27504c64ac7eaacb03158d6f339e0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 1 Jul 2024 20:42:14 +0530 Subject: [PATCH 08/29] Move more --- .../src/components/Upload/UploadProgress/dialog.tsx | 2 +- .../src/components/Upload/UploadProgress/footer.tsx | 2 +- .../Upload/UploadProgress/inProgressSection.tsx | 2 +- .../src/components/Upload/UploadProgress/index.tsx | 2 +- .../components/Upload/UploadProgress/progressBar.tsx | 2 +- .../Upload/UploadProgress/resultSection.tsx | 2 +- .../src/components/Upload/UploadProgress/title.tsx | 2 +- web/apps/photos/src/components/Upload/Uploader.tsx | 2 +- web/apps/photos/src/contexts/uploadProgress.tsx | 2 +- web/apps/photos/src/services/download/index.ts | 2 +- web/apps/photos/src/services/exif.ts | 7 +++++-- web/apps/photos/src/services/export/migration.ts | 2 +- web/apps/photos/src/services/ffmpeg.ts | 4 ++-- .../photos/src/services/locationSearchService.ts | 4 ++-- web/apps/photos/src/services/upload/takeout.ts | 4 ++-- web/apps/photos/src/services/upload/uploadManager.ts | 10 +++++----- web/apps/photos/src/services/upload/uploadService.ts | 12 ++++++------ web/apps/photos/src/services/watch.ts | 2 +- web/apps/photos/src/types/entity.ts | 2 +- .../src => packages/new/photos}/constants/upload.ts | 2 +- .../src => packages/new/photos}/types/metadata.ts | 4 ++-- 21 files changed, 38 insertions(+), 35 deletions(-) rename web/{apps/photos/src => packages/new/photos}/constants/upload.ts (91%) rename web/{apps/photos/src => packages/new/photos}/types/metadata.ts (73%) diff --git a/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx b/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx index 1367b57ad5..da7f488f25 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx @@ -1,8 +1,8 @@ import { Dialog, DialogContent, Link } from "@mui/material"; import { t } from "i18next"; +import { UPLOAD_RESULT, UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { dialogCloseHandler } from "@ente/shared/components/DialogBox/TitleWithCloseButton"; -import { UPLOAD_RESULT, UPLOAD_STAGES } from "constants/upload"; import UploadProgressContext from "contexts/uploadProgress"; import { useContext, useEffect, useState } from "react"; import { Trans } from "react-i18next"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx b/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx index 5a5bebd201..6359009a9a 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx @@ -1,5 +1,5 @@ +import { UPLOAD_RESULT, UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { Button, DialogActions } from "@mui/material"; -import { UPLOAD_RESULT, UPLOAD_STAGES } from "constants/upload"; import { t } from "i18next"; import { useContext } from "react"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx b/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx index 128e280abb..fba1a51db5 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx @@ -11,8 +11,8 @@ import { } from "./section"; import { InProgressItemContainer } from "./styledComponents"; +import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { CaptionedText } from "components/CaptionedText"; -import { UPLOAD_STAGES } from "constants/upload"; export const InProgressSection = () => { const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadStage } = diff --git a/web/apps/photos/src/components/Upload/UploadProgress/index.tsx b/web/apps/photos/src/components/Upload/UploadProgress/index.tsx index 1acffd561e..da5fde0950 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/index.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/index.tsx @@ -1,4 +1,4 @@ -import { UPLOAD_STAGES } from "constants/upload"; +import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; import UploadProgressContext from "contexts/uploadProgress"; import { t } from "i18next"; import { AppContext } from "pages/_app"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx b/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx index 6173829d7e..ba28ac7e19 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx @@ -1,5 +1,5 @@ +import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { Box, Divider, LinearProgress } from "@mui/material"; -import { UPLOAD_STAGES } from "constants/upload"; import UploadProgressContext from "contexts/uploadProgress"; import { useContext } from "react"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx b/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx index 1be6ca8317..93926fcf0e 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx @@ -1,7 +1,7 @@ +import { UPLOAD_RESULT } from "@/new/photos/constants/upload"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { CaptionedText } from "components/CaptionedText"; import ItemList from "components/ItemList"; -import { UPLOAD_RESULT } from "constants/upload"; import UploadProgressContext from "contexts/uploadProgress"; import { useContext } from "react"; import { diff --git a/web/apps/photos/src/components/Upload/UploadProgress/title.tsx b/web/apps/photos/src/components/Upload/UploadProgress/title.tsx index 1b97b9b437..a123b4c009 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/title.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/title.tsx @@ -1,10 +1,10 @@ +import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { IconButtonWithBG, SpaceBetweenFlex, } from "@ente/shared/components/Container"; import Close from "@mui/icons-material/Close"; import { Box, DialogTitle, Stack, Typography } from "@mui/material"; -import { UPLOAD_STAGES } from "constants/upload"; import { t } from "i18next"; import { useContext } from "react"; diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index 170d2fcdc8..8afe1a41d1 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -1,3 +1,4 @@ +import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { exportMetadataDirectoryName } from "@/new/photos/services/export"; import { basename } from "@/next/file"; import log from "@/next/log"; @@ -8,7 +9,6 @@ import { CustomError } from "@ente/shared/error"; import { isPromise } from "@ente/shared/utils"; import DiscFullIcon from "@mui/icons-material/DiscFull"; import UserNameInputDialog from "components/UserNameInputDialog"; -import { UPLOAD_STAGES } from "constants/upload"; import { t } from "i18next"; import isElectron from "is-electron"; import { AppContext } from "pages/_app"; diff --git a/web/apps/photos/src/contexts/uploadProgress.tsx b/web/apps/photos/src/contexts/uploadProgress.tsx index b25df7d65b..440ec75c6d 100644 --- a/web/apps/photos/src/contexts/uploadProgress.tsx +++ b/web/apps/photos/src/contexts/uploadProgress.tsx @@ -1,4 +1,4 @@ -import { UPLOAD_STAGES } from "constants/upload"; +import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { createContext } from "react"; import type { InProgressUpload, diff --git a/web/apps/photos/src/services/download/index.ts b/web/apps/photos/src/services/download/index.ts index e656c54484..421a540a5c 100644 --- a/web/apps/photos/src/services/download/index.ts +++ b/web/apps/photos/src/services/download/index.ts @@ -24,7 +24,7 @@ export type OnDownloadProgress = (event: { total: number; }) => void; -export interface DownloadClient { +interface DownloadClient { updateTokens: (token: string, passwordToken?: string) => void; downloadThumbnail: ( file: EnteFile, diff --git a/web/apps/photos/src/services/exif.ts b/web/apps/photos/src/services/exif.ts index 073a695f75..eed1f5ee17 100644 --- a/web/apps/photos/src/services/exif.ts +++ b/web/apps/photos/src/services/exif.ts @@ -1,10 +1,13 @@ import { type FileTypeInfo } from "@/media/file-type"; +import { NULL_LOCATION } from "@/new/photos/constants/upload"; +import type { + Location, + ParsedExtractedMetadata, +} from "@/new/photos/types/metadata"; import log from "@/next/log"; import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time"; -import { NULL_LOCATION } from "constants/upload"; import exifr from "exifr"; import piexif from "piexifjs"; -import type { Location, ParsedExtractedMetadata } from "types/metadata"; type ParsedEXIFData = Record & Partial<{ diff --git a/web/apps/photos/src/services/export/migration.ts b/web/apps/photos/src/services/export/migration.ts index f46fc0a48e..da506e11e1 100644 --- a/web/apps/photos/src/services/export/migration.ts +++ b/web/apps/photos/src/services/export/migration.ts @@ -1,5 +1,6 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; +import { exportMetadataDirectoryName } from "@/new/photos/services/export"; import { getAllLocalFiles } from "@/new/photos/services/files"; import { EnteFile } from "@/new/photos/types/file"; import { mergeMetadata } from "@/new/photos/utils/file"; @@ -30,7 +31,6 @@ import { import { getNonEmptyPersonalCollections } from "utils/collection"; import { getIDBasedSortedFiles, getPersonalFiles } from "utils/file"; import { - exportMetadataDirectoryName, getCollectionIDFromFileUID, getExportRecordFileUID, getLivePhotoExportName, diff --git a/web/apps/photos/src/services/ffmpeg.ts b/web/apps/photos/src/services/ffmpeg.ts index 342a6fca57..ded7a5baff 100644 --- a/web/apps/photos/src/services/ffmpeg.ts +++ b/web/apps/photos/src/services/ffmpeg.ts @@ -1,3 +1,5 @@ +import { NULL_LOCATION } from "@/new/photos/constants/upload"; +import type { ParsedExtractedMetadata } from "@/new/photos/types/metadata"; import { readConvertToMP4Done, readConvertToMP4Stream, @@ -12,8 +14,6 @@ import { inputPathPlaceholder, outputPathPlaceholder, } from "constants/ffmpeg"; -import { NULL_LOCATION } from "constants/upload"; -import type { ParsedExtractedMetadata } from "types/metadata"; import type { DedicatedFFmpegWorker } from "worker/ffmpeg.worker"; import { toDataOrPathOrZipEntry, diff --git a/web/apps/photos/src/services/locationSearchService.ts b/web/apps/photos/src/services/locationSearchService.ts index 354c87a712..07c805bac0 100644 --- a/web/apps/photos/src/services/locationSearchService.ts +++ b/web/apps/photos/src/services/locationSearchService.ts @@ -1,6 +1,6 @@ +import type { Location } from "@/new/photos/types/metadata"; import log from "@/next/log"; -import { LocationTagData } from "types/entity"; -import { Location } from "types/metadata"; +import type { LocationTagData } from "types/entity"; export interface City { city: string; diff --git a/web/apps/photos/src/services/upload/takeout.ts b/web/apps/photos/src/services/upload/takeout.ts index 2cd0f342d6..f2bcdee38b 100644 --- a/web/apps/photos/src/services/upload/takeout.ts +++ b/web/apps/photos/src/services/upload/takeout.ts @@ -1,11 +1,11 @@ /** @file Dealing with the JSON metadata in Google Takeouts */ +import { NULL_LOCATION } from "@/new/photos/constants/upload"; +import type { Location } from "@/new/photos/types/metadata"; import { readStream } from "@/new/photos/utils/native-stream"; import { ensureElectron } from "@/next/electron"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; -import { NULL_LOCATION } from "constants/upload"; -import type { Location } from "types/metadata"; import type { UploadItem } from "./types"; export interface ParsedMetadataJSON { diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts index 869bbbad27..be4ced8a92 100644 --- a/web/apps/photos/src/services/upload/uploadManager.ts +++ b/web/apps/photos/src/services/upload/uploadManager.ts @@ -1,5 +1,10 @@ import { FILE_TYPE } from "@/media/file-type"; import { potentialFileTypeFromExtension } from "@/media/live-photo"; +import { + RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, + UPLOAD_RESULT, + UPLOAD_STAGES, +} from "@/new/photos/constants/upload"; import { getLocalFiles } from "@/new/photos/services/files"; import { EncryptedEnteFile, EnteFile } from "@/new/photos/types/file"; import { ensureElectron } from "@/next/electron"; @@ -15,11 +20,6 @@ import { CustomError } from "@ente/shared/error"; import { Events, eventBus } from "@ente/shared/events"; import { Canceler } from "axios"; import type { Remote } from "comlink"; -import { - RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, - UPLOAD_RESULT, - UPLOAD_STAGES, -} from "constants/upload"; import isElectron from "is-electron"; import { getLocalPublicFiles, diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index bfe682cb1b..81cf6645f8 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -2,6 +2,11 @@ import { hasFileHash } from "@/media/file"; import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { encodeLivePhoto } from "@/media/live-photo"; import type { Metadata } from "@/media/types/file"; +import { + NULL_LOCATION, + RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, + UPLOAD_RESULT, +} from "@/new/photos/constants/upload"; import { EnteFile, MetadataFileAttributes, @@ -11,6 +16,7 @@ import { type FilePublicMagicMetadataProps, } from "@/new/photos/types/file"; import { EncryptedMagicMetadata } from "@/new/photos/types/magicMetadata"; +import type { ParsedExtractedMetadata } from "@/new/photos/types/metadata"; import { detectFileTypeInfoFromChunk } from "@/new/photos/utils/detect-type"; import { readStream } from "@/new/photos/utils/native-stream"; import { ensureElectron } from "@/next/electron"; @@ -23,11 +29,6 @@ import type { B64EncryptionResult } from "@ente/shared/crypto/internal/libsodium import { ENCRYPTION_CHUNK_SIZE } from "@ente/shared/crypto/internal/libsodium"; import { CustomError, handleUploadError } from "@ente/shared/error"; import type { Remote } from "comlink"; -import { - NULL_LOCATION, - RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, - UPLOAD_RESULT, -} from "constants/upload"; import { addToCollection } from "services/collectionService"; import { parseImageMetadata } from "services/exif"; import * as ffmpeg from "services/ffmpeg"; @@ -35,7 +36,6 @@ import { PublicUploadProps, type LivePhotoAssets, } from "services/upload/uploadManager"; -import type { ParsedExtractedMetadata } from "types/metadata"; import { getNonEmptyMagicMetadataProps, updateMagicMetadata, diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index 597bbe29cc..2e9519dbf5 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -3,6 +3,7 @@ * watch folders functionality. */ +import { UPLOAD_RESULT } from "@/new/photos/constants/upload"; import { getLocalFiles } from "@/new/photos/services/files"; import { EncryptedEnteFile } from "@/new/photos/types/file"; import { ensureElectron } from "@/next/electron"; @@ -14,7 +15,6 @@ import type { FolderWatchSyncedFile, } from "@/next/types/ipc"; import { ensureString } from "@/utils/ensure"; -import { UPLOAD_RESULT } from "constants/upload"; import debounce from "debounce"; import uploadManager, { type UploadItemWithCollection, diff --git a/web/apps/photos/src/types/entity.ts b/web/apps/photos/src/types/entity.ts index 60844ce466..0f22973d21 100644 --- a/web/apps/photos/src/types/entity.ts +++ b/web/apps/photos/src/types/entity.ts @@ -1,4 +1,4 @@ -import { Location } from "types/metadata"; +import { Location } from "@/new/photos/types/metadata"; export enum EntityType { LOCATION_TAG = "location", diff --git a/web/apps/photos/src/constants/upload.ts b/web/packages/new/photos/constants/upload.ts similarity index 91% rename from web/apps/photos/src/constants/upload.ts rename to web/packages/new/photos/constants/upload.ts index a0103cb6e6..fcee4df18c 100644 --- a/web/apps/photos/src/constants/upload.ts +++ b/web/packages/new/photos/constants/upload.ts @@ -1,4 +1,4 @@ -import { Location } from "types/metadata"; +import type { Location } from "../types/metadata"; export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random(); diff --git a/web/apps/photos/src/types/metadata.ts b/web/packages/new/photos/types/metadata.ts similarity index 73% rename from web/apps/photos/src/types/metadata.ts rename to web/packages/new/photos/types/metadata.ts index 7994e62479..626493211f 100644 --- a/web/apps/photos/src/types/metadata.ts +++ b/web/packages/new/photos/types/metadata.ts @@ -1,6 +1,6 @@ export interface Location { - latitude: number; - longitude: number; + latitude: number | null; + longitude: number | null; } export interface ParsedExtractedMetadata { From 93488e149d7a866e3b5342810da990963e6b809b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:13:30 +0530 Subject: [PATCH 09/29] move and merge --- .../Upload/UploadProgress/dialog.tsx | 5 +++- .../Upload/UploadProgress/footer.tsx | 5 +++- .../UploadProgress/inProgressSection.tsx | 2 +- .../Upload/UploadProgress/index.tsx | 2 +- .../Upload/UploadProgress/progressBar.tsx | 2 +- .../Upload/UploadProgress/resultSection.tsx | 2 +- .../Upload/UploadProgress/title.tsx | 2 +- .../photos/src/components/Upload/Uploader.tsx | 7 +++-- .../photos/src/contexts/uploadProgress.tsx | 2 +- web/apps/photos/src/services/exif.ts | 2 +- web/apps/photos/src/services/ffmpeg.ts | 12 ++++----- .../photos/src/services/upload/takeout.ts | 4 +-- .../photos/src/services/upload/thumbnail.ts | 5 +++- .../src/services/upload/uploadManager.ts | 6 ++--- .../src/services/upload/uploadService.ts | 4 +-- web/apps/photos/src/services/watch.ts | 2 +- web/packages/new/photos/constants/upload.ts | 26 ------------------- .../new/photos}/services/upload/types.ts | 26 +++++++++++++++++++ 18 files changed, 64 insertions(+), 52 deletions(-) delete mode 100644 web/packages/new/photos/constants/upload.ts rename web/{apps/photos/src => packages/new/photos}/services/upload/types.ts (81%) diff --git a/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx b/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx index da7f488f25..ded9e2c17f 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/dialog.tsx @@ -1,7 +1,10 @@ import { Dialog, DialogContent, Link } from "@mui/material"; import { t } from "i18next"; -import { UPLOAD_RESULT, UPLOAD_STAGES } from "@/new/photos/constants/upload"; +import { + UPLOAD_RESULT, + UPLOAD_STAGES, +} from "@/new/photos/services/upload/types"; import { dialogCloseHandler } from "@ente/shared/components/DialogBox/TitleWithCloseButton"; import UploadProgressContext from "contexts/uploadProgress"; import { useContext, useEffect, useState } from "react"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx b/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx index 6359009a9a..7372392032 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/footer.tsx @@ -1,4 +1,7 @@ -import { UPLOAD_RESULT, UPLOAD_STAGES } from "@/new/photos/constants/upload"; +import { + UPLOAD_RESULT, + UPLOAD_STAGES, +} from "@/new/photos/services/upload/types"; import { Button, DialogActions } from "@mui/material"; import { t } from "i18next"; import { useContext } from "react"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx b/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx index fba1a51db5..327cc43723 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx @@ -11,7 +11,7 @@ import { } from "./section"; import { InProgressItemContainer } from "./styledComponents"; -import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; +import { UPLOAD_STAGES } from "@/new/photos/services/upload/types"; import { CaptionedText } from "components/CaptionedText"; export const InProgressSection = () => { diff --git a/web/apps/photos/src/components/Upload/UploadProgress/index.tsx b/web/apps/photos/src/components/Upload/UploadProgress/index.tsx index da5fde0950..3dc9d6cea8 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/index.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/index.tsx @@ -1,4 +1,4 @@ -import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; +import { UPLOAD_STAGES } from "@/new/photos/services/upload/types"; import UploadProgressContext from "contexts/uploadProgress"; import { t } from "i18next"; import { AppContext } from "pages/_app"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx b/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx index ba28ac7e19..a18d9d7aa9 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/progressBar.tsx @@ -1,4 +1,4 @@ -import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; +import { UPLOAD_STAGES } from "@/new/photos/services/upload/types"; import { Box, Divider, LinearProgress } from "@mui/material"; import UploadProgressContext from "contexts/uploadProgress"; import { useContext } from "react"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx b/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx index 93926fcf0e..6c483bf49c 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx @@ -1,4 +1,4 @@ -import { UPLOAD_RESULT } from "@/new/photos/constants/upload"; +import { UPLOAD_RESULT } from "@/new/photos/services/upload/types"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { CaptionedText } from "components/CaptionedText"; import ItemList from "components/ItemList"; diff --git a/web/apps/photos/src/components/Upload/UploadProgress/title.tsx b/web/apps/photos/src/components/Upload/UploadProgress/title.tsx index a123b4c009..332ef57483 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/title.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/title.tsx @@ -1,4 +1,4 @@ -import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; +import { UPLOAD_STAGES } from "@/new/photos/services/upload/types"; import { IconButtonWithBG, SpaceBetweenFlex, diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx index 8afe1a41d1..767b32c35d 100644 --- a/web/apps/photos/src/components/Upload/Uploader.tsx +++ b/web/apps/photos/src/components/Upload/Uploader.tsx @@ -1,5 +1,9 @@ -import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; import { exportMetadataDirectoryName } from "@/new/photos/services/export"; +import type { + FileAndPath, + UploadItem, +} from "@/new/photos/services/upload/types"; +import { UPLOAD_STAGES } from "@/new/photos/services/upload/types"; import { basename } from "@/next/file"; import log from "@/next/log"; import type { CollectionMapping, Electron, ZipItem } from "@/next/types/ipc"; @@ -21,7 +25,6 @@ import { getPublicCollectionUploaderName, savePublicCollectionUploaderName, } from "services/publicCollectionService"; -import type { FileAndPath, UploadItem } from "services/upload/types"; import type { InProgressUpload, SegregatedFinishedUploads, diff --git a/web/apps/photos/src/contexts/uploadProgress.tsx b/web/apps/photos/src/contexts/uploadProgress.tsx index 440ec75c6d..1c98569b03 100644 --- a/web/apps/photos/src/contexts/uploadProgress.tsx +++ b/web/apps/photos/src/contexts/uploadProgress.tsx @@ -1,4 +1,4 @@ -import { UPLOAD_STAGES } from "@/new/photos/constants/upload"; +import { UPLOAD_STAGES } from "@/new/photos/services/upload/types"; import { createContext } from "react"; import type { InProgressUpload, diff --git a/web/apps/photos/src/services/exif.ts b/web/apps/photos/src/services/exif.ts index eed1f5ee17..d483dec745 100644 --- a/web/apps/photos/src/services/exif.ts +++ b/web/apps/photos/src/services/exif.ts @@ -1,5 +1,5 @@ import { type FileTypeInfo } from "@/media/file-type"; -import { NULL_LOCATION } from "@/new/photos/constants/upload"; +import { NULL_LOCATION } from "@/new/photos/services/upload/types"; import type { Location, ParsedExtractedMetadata, diff --git a/web/apps/photos/src/services/ffmpeg.ts b/web/apps/photos/src/services/ffmpeg.ts index ded7a5baff..0d98e96a29 100644 --- a/web/apps/photos/src/services/ffmpeg.ts +++ b/web/apps/photos/src/services/ffmpeg.ts @@ -1,4 +1,9 @@ -import { NULL_LOCATION } from "@/new/photos/constants/upload"; +import { + NULL_LOCATION, + toDataOrPathOrZipEntry, + type DesktopUploadItem, + type UploadItem, +} from "@/new/photos/services/upload/types"; import type { ParsedExtractedMetadata } from "@/new/photos/types/metadata"; import { readConvertToMP4Done, @@ -15,11 +20,6 @@ import { outputPathPlaceholder, } from "constants/ffmpeg"; import type { DedicatedFFmpegWorker } from "worker/ffmpeg.worker"; -import { - toDataOrPathOrZipEntry, - type DesktopUploadItem, - type UploadItem, -} from "./upload/types"; /** * Generate a thumbnail for the given video using a wasm FFmpeg running in a web diff --git a/web/apps/photos/src/services/upload/takeout.ts b/web/apps/photos/src/services/upload/takeout.ts index f2bcdee38b..e0b1307ed9 100644 --- a/web/apps/photos/src/services/upload/takeout.ts +++ b/web/apps/photos/src/services/upload/takeout.ts @@ -1,12 +1,12 @@ /** @file Dealing with the JSON metadata in Google Takeouts */ -import { NULL_LOCATION } from "@/new/photos/constants/upload"; +import type { UploadItem } from "@/new/photos/services/upload/types"; +import { NULL_LOCATION } from "@/new/photos/services/upload/types"; import type { Location } from "@/new/photos/types/metadata"; import { readStream } from "@/new/photos/utils/native-stream"; import { ensureElectron } from "@/next/electron"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; -import type { UploadItem } from "./types"; export interface ParsedMetadataJSON { creationTime: number; diff --git a/web/apps/photos/src/services/upload/thumbnail.ts b/web/apps/photos/src/services/upload/thumbnail.ts index 91260131d3..f40e1fefeb 100644 --- a/web/apps/photos/src/services/upload/thumbnail.ts +++ b/web/apps/photos/src/services/upload/thumbnail.ts @@ -1,12 +1,15 @@ import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { heicToJPEG } from "@/media/heic-convert"; import { scaledImageDimensions } from "@/media/image"; +import { + toDataOrPathOrZipEntry, + type DesktopUploadItem, +} from "@/new/photos/services/upload/types"; import log from "@/next/log"; import { type Electron } from "@/next/types/ipc"; import { ensure } from "@/utils/ensure"; import { withTimeout } from "@/utils/promise"; import * as ffmpeg from "services/ffmpeg"; -import { toDataOrPathOrZipEntry, type DesktopUploadItem } from "./types"; /** Maximum width or height of the generated thumbnail */ const maxThumbnailDimension = 720; diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts index be4ced8a92..b66b2ebf8f 100644 --- a/web/apps/photos/src/services/upload/uploadManager.ts +++ b/web/apps/photos/src/services/upload/uploadManager.ts @@ -1,11 +1,12 @@ import { FILE_TYPE } from "@/media/file-type"; import { potentialFileTypeFromExtension } from "@/media/live-photo"; +import { getLocalFiles } from "@/new/photos/services/files"; +import type { UploadItem } from "@/new/photos/services/upload/types"; import { RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, UPLOAD_RESULT, UPLOAD_STAGES, -} from "@/new/photos/constants/upload"; -import { getLocalFiles } from "@/new/photos/services/files"; +} from "@/new/photos/services/upload/types"; import { EncryptedEnteFile, EnteFile } from "@/new/photos/types/file"; import { ensureElectron } from "@/next/electron"; import { lowercaseExtension, nameAndExtension } from "@/next/file"; @@ -35,7 +36,6 @@ import { tryParseTakeoutMetadataJSON, type ParsedMetadataJSON, } from "./takeout"; -import type { UploadItem } from "./types"; import UploadService, { uploadItemFileName, uploader } from "./uploadService"; export type FileID = number; diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 81cf6645f8..c4057feff5 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -2,11 +2,12 @@ import { hasFileHash } from "@/media/file"; import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { encodeLivePhoto } from "@/media/live-photo"; import type { Metadata } from "@/media/types/file"; +import type { UploadItem } from "@/new/photos/services/upload/types"; import { NULL_LOCATION, RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, UPLOAD_RESULT, -} from "@/new/photos/constants/upload"; +} from "@/new/photos/services/upload/types"; import { EnteFile, MetadataFileAttributes, @@ -50,7 +51,6 @@ import { generateThumbnailNative, generateThumbnailWeb, } from "./thumbnail"; -import type { UploadItem } from "./types"; import UploadHttpClient from "./uploadHttpClient"; import type { UploadableUploadItem } from "./uploadManager"; diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index 2e9519dbf5..8c367ee71c 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -3,8 +3,8 @@ * watch folders functionality. */ -import { UPLOAD_RESULT } from "@/new/photos/constants/upload"; import { getLocalFiles } from "@/new/photos/services/files"; +import { UPLOAD_RESULT } from "@/new/photos/services/upload/types"; import { EncryptedEnteFile } from "@/new/photos/types/file"; import { ensureElectron } from "@/next/electron"; import { basename, dirname } from "@/next/file"; diff --git a/web/packages/new/photos/constants/upload.ts b/web/packages/new/photos/constants/upload.ts deleted file mode 100644 index fcee4df18c..0000000000 --- a/web/packages/new/photos/constants/upload.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Location } from "../types/metadata"; - -export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random(); - -export const NULL_LOCATION: Location = { latitude: null, longitude: null }; - -export enum UPLOAD_STAGES { - START, - READING_GOOGLE_METADATA_FILES, - EXTRACTING_METADATA, - UPLOADING, - CANCELLING, - FINISH, -} - -export enum UPLOAD_RESULT { - FAILED, - ALREADY_UPLOADED, - UNSUPPORTED, - BLOCKED, - TOO_LARGE, - LARGER_THAN_AVAILABLE_STORAGE, - UPLOADED, - UPLOADED_WITH_STATIC_THUMBNAIL, - ADDED_SYMLINK, -} diff --git a/web/apps/photos/src/services/upload/types.ts b/web/packages/new/photos/services/upload/types.ts similarity index 81% rename from web/apps/photos/src/services/upload/types.ts rename to web/packages/new/photos/services/upload/types.ts index 25e2ab408a..f11ba90961 100644 --- a/web/apps/photos/src/services/upload/types.ts +++ b/web/packages/new/photos/services/upload/types.ts @@ -1,4 +1,5 @@ import type { ZipItem } from "@/next/types/ipc"; +import type { Location } from "../../types/metadata"; /** * An item to upload is one of the following: @@ -55,3 +56,28 @@ export const toDataOrPathOrZipEntry = (desktopUploadItem: DesktopUploadItem) => typeof desktopUploadItem == "string" || Array.isArray(desktopUploadItem) ? desktopUploadItem : desktopUploadItem.path; + +export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random(); + +export const NULL_LOCATION: Location = { latitude: null, longitude: null }; + +export enum UPLOAD_STAGES { + START, + READING_GOOGLE_METADATA_FILES, + EXTRACTING_METADATA, + UPLOADING, + CANCELLING, + FINISH, +} + +export enum UPLOAD_RESULT { + FAILED, + ALREADY_UPLOADED, + UNSUPPORTED, + BLOCKED, + TOO_LARGE, + LARGER_THAN_AVAILABLE_STORAGE, + UPLOADED, + UPLOADED_WITH_STATIC_THUMBNAIL, + ADDED_SYMLINK, +} From f7324d5388d7a41b3887cf7a284a657766f840b5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:20:13 +0530 Subject: [PATCH 10/29] Move --- web/apps/photos/src/services/download/index.ts | 2 +- web/apps/photos/src/services/upload/thumbnail.ts | 2 +- web/apps/photos/src/services/upload/uploadService.ts | 2 +- .../new/photos/services/ffmpeg/constants.ts} | 0 .../new/photos/services/ffmpeg/index.ts} | 10 +++++++--- .../new/photos/services/ffmpeg/worker.ts} | 4 ++-- 6 files changed, 12 insertions(+), 8 deletions(-) rename web/{apps/photos/src/constants/ffmpeg.ts => packages/new/photos/services/ffmpeg/constants.ts} (100%) rename web/{apps/photos/src/services/ffmpeg.ts => packages/new/photos/services/ffmpeg/index.ts} (96%) rename web/{apps/photos/src/worker/ffmpeg.worker.ts => packages/new/photos/services/ffmpeg/worker.ts} (96%) diff --git a/web/apps/photos/src/services/download/index.ts b/web/apps/photos/src/services/download/index.ts index 421a540a5c..bbf17ca21f 100644 --- a/web/apps/photos/src/services/download/index.ts +++ b/web/apps/photos/src/services/download/index.ts @@ -1,5 +1,6 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; +import * as ffmpeg from "@/new/photos/services/ffmpeg"; import { EnteFile, type LivePhotoSourceURL, @@ -17,7 +18,6 @@ import HTTPService from "@ente/shared/network/HTTPService"; import { retryAsyncFunction } from "@ente/shared/utils"; import type { Remote } from "comlink"; import isElectron from "is-electron"; -import * as ffmpeg from "services/ffmpeg"; export type OnDownloadProgress = (event: { loaded: number; diff --git a/web/apps/photos/src/services/upload/thumbnail.ts b/web/apps/photos/src/services/upload/thumbnail.ts index f40e1fefeb..255bc68ca3 100644 --- a/web/apps/photos/src/services/upload/thumbnail.ts +++ b/web/apps/photos/src/services/upload/thumbnail.ts @@ -1,6 +1,7 @@ import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { heicToJPEG } from "@/media/heic-convert"; import { scaledImageDimensions } from "@/media/image"; +import * as ffmpeg from "@/new/photos/services/ffmpeg"; import { toDataOrPathOrZipEntry, type DesktopUploadItem, @@ -9,7 +10,6 @@ import log from "@/next/log"; import { type Electron } from "@/next/types/ipc"; import { ensure } from "@/utils/ensure"; import { withTimeout } from "@/utils/promise"; -import * as ffmpeg from "services/ffmpeg"; /** Maximum width or height of the generated thumbnail */ const maxThumbnailDimension = 720; diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index c4057feff5..846c2d6f59 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -2,6 +2,7 @@ import { hasFileHash } from "@/media/file"; import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { encodeLivePhoto } from "@/media/live-photo"; import type { Metadata } from "@/media/types/file"; +import * as ffmpeg from "@/new/photos/services/ffmpeg"; import type { UploadItem } from "@/new/photos/services/upload/types"; import { NULL_LOCATION, @@ -32,7 +33,6 @@ import { CustomError, handleUploadError } from "@ente/shared/error"; import type { Remote } from "comlink"; import { addToCollection } from "services/collectionService"; import { parseImageMetadata } from "services/exif"; -import * as ffmpeg from "services/ffmpeg"; import { PublicUploadProps, type LivePhotoAssets, diff --git a/web/apps/photos/src/constants/ffmpeg.ts b/web/packages/new/photos/services/ffmpeg/constants.ts similarity index 100% rename from web/apps/photos/src/constants/ffmpeg.ts rename to web/packages/new/photos/services/ffmpeg/constants.ts diff --git a/web/apps/photos/src/services/ffmpeg.ts b/web/packages/new/photos/services/ffmpeg/index.ts similarity index 96% rename from web/apps/photos/src/services/ffmpeg.ts rename to web/packages/new/photos/services/ffmpeg/index.ts index 0d98e96a29..4e8e660978 100644 --- a/web/apps/photos/src/services/ffmpeg.ts +++ b/web/packages/new/photos/services/ffmpeg/index.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/array-type */ +/* @ts-nocheck */ + import { NULL_LOCATION, toDataOrPathOrZipEntry, @@ -18,8 +22,8 @@ import { ffmpegPathPlaceholder, inputPathPlaceholder, outputPathPlaceholder, -} from "constants/ffmpeg"; -import type { DedicatedFFmpegWorker } from "worker/ffmpeg.worker"; +} from "./constants"; +import type { DedicatedFFmpegWorker } from "./worker"; /** * Generate a thumbnail for the given video using a wasm FFmpeg running in a web @@ -269,7 +273,7 @@ class WorkerFactory { private createComlinkWorker = () => new ComlinkWorker( "ffmpeg-worker", - new Worker(new URL("worker/ffmpeg.worker.ts", import.meta.url)), + new Worker(new URL("worker.ts", import.meta.url)), ); async lazy() { diff --git a/web/apps/photos/src/worker/ffmpeg.worker.ts b/web/packages/new/photos/services/ffmpeg/worker.ts similarity index 96% rename from web/apps/photos/src/worker/ffmpeg.worker.ts rename to web/packages/new/photos/services/ffmpeg/worker.ts index 06ba05be9e..49db8791c8 100644 --- a/web/apps/photos/src/worker/ffmpeg.worker.ts +++ b/web/packages/new/photos/services/ffmpeg/worker.ts @@ -5,7 +5,7 @@ import { ffmpegPathPlaceholder, inputPathPlaceholder, outputPathPlaceholder, -} from "constants/ffmpeg"; +} from "./constants"; // When we run tsc on CI, the line below errors out // @@ -22,7 +22,7 @@ import { // Note that we can't use @ts-expect-error since it doesn't error out when // actually building! // -// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore import { FFmpeg, createFFmpeg } from "ffmpeg-wasm"; From 18194dc61a62f298e1d73662a57e29ebba5e7014 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:35:29 +0530 Subject: [PATCH 11/29] Fix tsc --- web/packages/new/photos/services/ffmpeg/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/packages/new/photos/services/ffmpeg/index.ts b/web/packages/new/photos/services/ffmpeg/index.ts index 4e8e660978..2d29c5ad9b 100644 --- a/web/packages/new/photos/services/ffmpeg/index.ts +++ b/web/packages/new/photos/services/ffmpeg/index.ts @@ -14,6 +14,7 @@ import { readConvertToMP4Stream, writeConvertToMP4Stream, } from "@/new/photos/utils/native-stream"; +import { ensureElectron } from "@/next/electron"; import type { Electron } from "@/next/types/ipc"; import { ComlinkWorker } from "@/next/worker/comlink-worker"; import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time"; @@ -116,7 +117,7 @@ export const extractVideoMetadata = async ( const outputData = uploadItem instanceof File ? await ffmpegExecWeb(command, uploadItem, "txt") - : await electron.ffmpegExec( + : await ensureElectron().ffmpegExec( command, toDataOrPathOrZipEntry(uploadItem), "txt", @@ -268,7 +269,7 @@ const convertToMP4Native = async (electron: Electron, blob: Blob) => { /** Lazily create a singleton instance of our worker */ class WorkerFactory { - private instance: Promise>; + private instance: Promise> | undefined; private createComlinkWorker = () => new ComlinkWorker( From b88d6d26a64f088a2a10c0fc1c09670e3d7b107f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:37:44 +0530 Subject: [PATCH 12/29] Fix tsc --- web/packages/new/photos/services/ffmpeg/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/packages/new/photos/services/ffmpeg/index.ts b/web/packages/new/photos/services/ffmpeg/index.ts index 2d29c5ad9b..578f74cceb 100644 --- a/web/packages/new/photos/services/ffmpeg/index.ts +++ b/web/packages/new/photos/services/ffmpeg/index.ts @@ -195,17 +195,17 @@ function parseFFmpegExtractedMetadata(encodedMetadata: Uint8Array) { return parsedMetadata; } -function parseAppleISOLocation(isoLocation: string) { +const parseAppleISOLocation = (isoLocation: string | undefined) => { let location = { ...NULL_LOCATION }; if (isoLocation) { - const [latitude, longitude] = isoLocation + const m = isoLocation .match(/(\+|-)\d+\.*\d+/g) - .map((x) => parseFloat(x)); + ?.map((x) => parseFloat(x)); - location = { latitude, longitude }; + location = { latitude: m?.at(0) ?? null, longitude: m?.at(1) ?? null }; } return location; -} +}; function parseCreationTime(creationTime: string) { let dateTime = null; From f2ea1a05c1a7fd672ac9c783b40a00478b282fb3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:38:17 +0530 Subject: [PATCH 13/29] Fix tsc --- web/packages/new/photos/services/ffmpeg/index.ts | 5 ++--- web/packages/new/photos/types/metadata.ts | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/web/packages/new/photos/services/ffmpeg/index.ts b/web/packages/new/photos/services/ffmpeg/index.ts index 578f74cceb..f22de9fe9c 100644 --- a/web/packages/new/photos/services/ffmpeg/index.ts +++ b/web/packages/new/photos/services/ffmpeg/index.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/array-type */ -/* @ts-nocheck */ import { NULL_LOCATION, @@ -207,7 +206,7 @@ const parseAppleISOLocation = (isoLocation: string | undefined) => { return location; }; -function parseCreationTime(creationTime: string) { +const parseCreationTime = (creationTime: string | undefined) => { let dateTime = null; if (creationTime) { dateTime = validateAndGetCreationUnixTimeInMicroSeconds( @@ -215,7 +214,7 @@ function parseCreationTime(creationTime: string) { ); } return dateTime; -} +}; /** * Run the given FFmpeg command using a wasm FFmpeg running in a web worker. diff --git a/web/packages/new/photos/types/metadata.ts b/web/packages/new/photos/types/metadata.ts index 626493211f..8c7ee8088e 100644 --- a/web/packages/new/photos/types/metadata.ts +++ b/web/packages/new/photos/types/metadata.ts @@ -5,7 +5,7 @@ export interface Location { export interface ParsedExtractedMetadata { location: Location; - creationTime: number; - width: number; - height: number; + creationTime: number | null; + width: number | null; + height: number | null; } From 94f179ebabb8a170bdcc76f662ef7f2f5889ee2a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:40:07 +0530 Subject: [PATCH 14/29] Fix esl --- web/packages/new/photos/services/ffmpeg/index.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/web/packages/new/photos/services/ffmpeg/index.ts b/web/packages/new/photos/services/ffmpeg/index.ts index f22de9fe9c..02913ed797 100644 --- a/web/packages/new/photos/services/ffmpeg/index.ts +++ b/web/packages/new/photos/services/ffmpeg/index.ts @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -/* eslint-disable @typescript-eslint/array-type */ - import { NULL_LOCATION, toDataOrPathOrZipEntry, @@ -168,8 +165,8 @@ function parseFFmpegExtractedMetadata(encodedMetadata: Uint8Array) { property.split("="), ); const validKeyValuePairs = metadataKeyValueArray.filter( - (keyValueArray) => keyValueArray.length === 2, - ) as Array<[string, string]>; + (keyValueArray) => keyValueArray.length == 2, + ) as [string, string][]; const metadataMap = Object.fromEntries(validKeyValuePairs); @@ -262,7 +259,7 @@ export const convertToMP4 = async (blob: Blob): Promise => { const convertToMP4Native = async (electron: Electron, blob: Blob) => { const token = await writeConvertToMP4Stream(electron, blob); const mp4Blob = await readConvertToMP4Stream(electron, token); - readConvertToMP4Done(electron, token); + await readConvertToMP4Done(electron, token); return mp4Blob; }; From 892a90d83be8afd6e44ec72ecbdd15c95f75ced5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:47:25 +0530 Subject: [PATCH 15/29] esl --- web/packages/new/photos/services/ffmpeg/worker.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/packages/new/photos/services/ffmpeg/worker.ts b/web/packages/new/photos/services/ffmpeg/worker.ts index 49db8791c8..fc05abcbd2 100644 --- a/web/packages/new/photos/services/ffmpeg/worker.ts +++ b/web/packages/new/photos/services/ffmpeg/worker.ts @@ -24,7 +24,8 @@ import { // // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore -import { FFmpeg, createFFmpeg } from "ffmpeg-wasm"; +import { ensure } from "@/utils/ensure"; +import { createFFmpeg, type FFmpeg } from "ffmpeg-wasm"; export class DedicatedFFmpegWorker { private ffmpeg: FFmpeg; @@ -106,7 +107,7 @@ const randomPrefix = () => { let result = ""; for (let i = 0; i < 10; i++) - result += alphabet[Math.floor(Math.random() * alphabet.length)]; + result += ensure(alphabet[Math.floor(Math.random() * alphabet.length)]); return result; }; @@ -127,4 +128,5 @@ const substitutePlaceholders = ( return segment; } }) - .filter((c) => !!c); + // TODO: The type guard should automatically get deduced with TS 5.5 + .filter((s): s is string => !!s); From 025fe3599966a880d083abce4dcab0e5082301ae Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:55:43 +0530 Subject: [PATCH 16/29] tsc transitive --- web/packages/shared/utils/queueProcessor.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/packages/shared/utils/queueProcessor.ts b/web/packages/shared/utils/queueProcessor.ts index b185281994..d490794469 100644 --- a/web/packages/shared/utils/queueProcessor.ts +++ b/web/packages/shared/utils/queueProcessor.ts @@ -1,9 +1,10 @@ +import { ensure } from "@/utils/ensure"; import { CustomError } from "@ente/shared/error"; interface RequestQueueItem { request: (canceller?: RequestCanceller) => Promise; successCallback: (response: any) => void; - failureCallback: (error: Error) => void; + failureCallback: (error: unknown) => void; isCanceled: { status: boolean }; canceller: { exec: () => void }; } @@ -50,7 +51,7 @@ export default class QueueProcessor { this.isProcessingRequest = true; while (this.requestQueue.length > 0) { - const queueItem = this.requestQueue.shift(); + const queueItem = ensure(this.requestQueue.shift()); let response = null; if (queueItem.isCanceled.status) { From 1c7f25723e68191c95c7fa659cd8fd7d9128d7c1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:58:27 +0530 Subject: [PATCH 17/29] Conv --- web/apps/photos/src/services/download/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/services/download/index.ts b/web/apps/photos/src/services/download/index.ts index bbf17ca21f..bf911362ee 100644 --- a/web/apps/photos/src/services/download/index.ts +++ b/web/apps/photos/src/services/download/index.ts @@ -7,6 +7,7 @@ import { type SourceURLs, } from "@/new/photos/types/file"; import { getRenderableImage } from "@/new/photos/utils/file"; +import { isDesktop } from "@/next/app"; import { blobCache, type BlobCache } from "@/next/blob-cache"; import log from "@/next/log"; import { customAPIOrigin } from "@/next/origins"; @@ -17,7 +18,6 @@ import { isPlaybackPossible } from "@ente/shared/media/video-playback"; import HTTPService from "@ente/shared/network/HTTPService"; import { retryAsyncFunction } from "@ente/shared/utils"; import type { Remote } from "comlink"; -import isElectron from "is-electron"; export type OnDownloadProgress = (event: { loaded: number; @@ -525,7 +525,7 @@ async function getPlayableVideo( if (isPlayable && !forceConvert) { return videoBlob; } else { - if (!forceConvert && !runOnWeb && !isElectron()) { + if (!forceConvert && !runOnWeb && !isDesktop) { return null; } log.info(`Converting video ${videoNameTitle} to mp4`); From cd27168f5fbe5d8eadb9197cce0edf8730f1a4e4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 10:58:59 +0530 Subject: [PATCH 18/29] Move --- .../index.ts => packages/new/photos/services/download.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web/{apps/photos/src/services/download/index.ts => packages/new/photos/services/download.ts} (100%) diff --git a/web/apps/photos/src/services/download/index.ts b/web/packages/new/photos/services/download.ts similarity index 100% rename from web/apps/photos/src/services/download/index.ts rename to web/packages/new/photos/services/download.ts From 16e197455b8861e1a7bdaa67f506f3aa7849c6eb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 11:00:08 +0530 Subject: [PATCH 19/29] Update imports --- web/apps/photos/src/components/Collections/CollectionCard.tsx | 2 +- web/apps/photos/src/components/PhotoFrame.tsx | 2 +- .../src/components/PhotoViewer/ImageEditorOverlay/index.tsx | 2 +- web/apps/photos/src/components/PhotoViewer/index.tsx | 2 +- web/apps/photos/src/components/pages/gallery/PreviewCard.tsx | 2 +- web/apps/photos/src/pages/_app.tsx | 2 +- web/apps/photos/src/pages/gallery/index.tsx | 2 +- web/apps/photos/src/pages/shared-albums/index.tsx | 2 +- web/apps/photos/src/services/clip-service.ts | 2 +- web/apps/photos/src/services/export/migration.ts | 2 +- web/apps/photos/src/services/face/f-index.ts | 2 +- web/apps/photos/src/services/face/face.worker.ts | 2 +- web/apps/photos/src/services/fix-exif.ts | 2 +- web/apps/photos/src/services/logout.ts | 2 +- web/apps/photos/src/utils/file/index.ts | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/web/apps/photos/src/components/Collections/CollectionCard.tsx b/web/apps/photos/src/components/Collections/CollectionCard.tsx index 5bf247020e..7d757561ba 100644 --- a/web/apps/photos/src/components/Collections/CollectionCard.tsx +++ b/web/apps/photos/src/components/Collections/CollectionCard.tsx @@ -1,10 +1,10 @@ +import downloadManager from "@/new/photos/services/download"; import { EnteFile } from "@/new/photos/types/file"; import { LoadingThumbnail, StaticThumbnail, } from "components/PlaceholderThumbnails"; import { useEffect, useState } from "react"; -import downloadManager from "services/download"; export default function CollectionCard(props: { children?: any; diff --git a/web/apps/photos/src/components/PhotoFrame.tsx b/web/apps/photos/src/components/PhotoFrame.tsx index 6095a31765..a6f8606a08 100644 --- a/web/apps/photos/src/components/PhotoFrame.tsx +++ b/web/apps/photos/src/components/PhotoFrame.tsx @@ -1,4 +1,5 @@ import { FILE_TYPE } from "@/media/file-type"; +import DownloadManager from "@/new/photos/services/download"; import type { LivePhotoSourceURL, SourceURLs } from "@/new/photos/types/file"; import { EnteFile } from "@/new/photos/types/file"; import log from "@/next/log"; @@ -14,7 +15,6 @@ import PhotoSwipe from "photoswipe"; import { useContext, useEffect, useState } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; import { Duplicate } from "services/deduplicationService"; -import DownloadManager from "services/download"; import { SelectedState, SetFilesDownloadProgressAttributesCreator, diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx index 0d41aba55d..3bb010d708 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx @@ -1,3 +1,4 @@ +import downloadManager from "@/new/photos/services/download"; import { EnteFile } from "@/new/photos/types/file"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; @@ -35,7 +36,6 @@ import { AppContext } from "pages/_app"; import type { Dispatch, MutableRefObject, SetStateAction } from "react"; import { createContext, useContext, useEffect, useRef, useState } from "react"; import { getLocalCollections } from "services/collectionService"; -import downloadManager from "services/download"; import uploadManager from "services/upload/uploadManager"; import { getEditorCloseConfirmationMessage } from "utils/ui"; import ColoursMenu from "./ColoursMenu"; diff --git a/web/apps/photos/src/components/PhotoViewer/index.tsx b/web/apps/photos/src/components/PhotoViewer/index.tsx index aff51460dd..56ad5475ec 100644 --- a/web/apps/photos/src/components/PhotoViewer/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/index.tsx @@ -15,6 +15,7 @@ import { import { FILE_TYPE } from "@/media/file-type"; import { isNonWebImageFileExtension } from "@/media/formats"; +import downloadManager from "@/new/photos/services/download"; import type { LoadedLivePhotoSourceURL } from "@/new/photos/types/file"; import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; import { isNativeConvertibleToJPEG } from "@/new/photos/utils/file"; @@ -45,7 +46,6 @@ import { t } from "i18next"; import isElectron from "is-electron"; import { AppContext } from "pages/_app"; import { GalleryContext } from "pages/gallery"; -import downloadManager from "services/download"; import { getParsedExifData } from "services/exif"; import { trashFiles } from "services/fileService"; import { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; diff --git a/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx b/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx index 0b4c386c4b..6a5e8128bb 100644 --- a/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx +++ b/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx @@ -1,4 +1,5 @@ import { FILE_TYPE } from "@/media/file-type"; +import DownloadManager from "@/new/photos/services/download"; import { EnteFile } from "@/new/photos/types/file"; import log from "@/next/log"; import { Overlay } from "@ente/shared/components/Container"; @@ -17,7 +18,6 @@ import i18n from "i18next"; import { DeduplicateContext } from "pages/deduplicate"; import { GalleryContext } from "pages/gallery"; import React, { useContext, useEffect, useRef, useState } from "react"; -import DownloadManager from "services/download"; import { shouldShowAvatar } from "utils/file"; import Avatar from "./Avatar"; diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 98af7f9d67..a74c2b3ab1 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -1,3 +1,4 @@ +import DownloadManager from "@/new/photos/services/download"; import { clientPackageName, staticAppTitle } from "@/next/app"; import { CustomHead } from "@/next/components/Head"; import { setupI18n } from "@/next/i18n"; @@ -48,7 +49,6 @@ import { useRouter } from "next/router"; import "photoswipe/dist/photoswipe.css"; import { createContext, useContext, useEffect, useRef, useState } from "react"; import LoadingBar from "react-top-loading-bar"; -import DownloadManager from "services/download"; import { resumeExportsIfNeeded } from "services/export"; import { isFaceIndexingEnabled, diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index 8bac954cbb..42f3aed2c4 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/web/apps/photos/src/pages/gallery/index.tsx @@ -1,5 +1,6 @@ import { WhatsNew } from "@/new/photos/components/WhatsNew"; import { shouldShowWhatsNew } from "@/new/photos/services/changelog"; +import downloadManager from "@/new/photos/services/download"; import { getLocalFiles, getLocalTrashedFiles, @@ -92,7 +93,6 @@ import { getHiddenItemsSummary, getSectionSummaries, } from "services/collectionService"; -import downloadManager from "services/download"; import { syncFiles } from "services/fileService"; import locationSearchService from "services/locationSearchService"; import { sync } from "services/sync"; diff --git a/web/apps/photos/src/pages/shared-albums/index.tsx b/web/apps/photos/src/pages/shared-albums/index.tsx index 4e479457b1..b4786e1798 100644 --- a/web/apps/photos/src/pages/shared-albums/index.tsx +++ b/web/apps/photos/src/pages/shared-albums/index.tsx @@ -1,3 +1,4 @@ +import downloadManager from "@/new/photos/services/download"; import { EnteFile } from "@/new/photos/types/file"; import { mergeMetadata } from "@/new/photos/utils/file"; import log from "@/next/log"; @@ -45,7 +46,6 @@ import { useRouter } from "next/router"; import { AppContext } from "pages/_app"; import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useDropzone } from "react-dropzone"; -import downloadManager from "services/download"; import { getLocalPublicCollection, getLocalPublicCollectionPassword, diff --git a/web/apps/photos/src/services/clip-service.ts b/web/apps/photos/src/services/clip-service.ts index be59a2aeda..4b9900aed5 100644 --- a/web/apps/photos/src/services/clip-service.ts +++ b/web/apps/photos/src/services/clip-service.ts @@ -1,4 +1,5 @@ import { FILE_TYPE } from "@/media/file-type"; +import downloadManager from "@/new/photos/services/download"; import { getAllLocalFiles, getLocalFiles } from "@/new/photos/services/files"; import { EnteFile } from "@/new/photos/types/file"; import { ensureElectron } from "@/next/electron"; @@ -10,7 +11,6 @@ import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import PQueue from "p-queue"; import { Embedding } from "types/embedding"; import { getPersonalFiles } from "utils/file"; -import downloadManager from "./download"; import { localCLIPEmbeddings, putEmbedding } from "./embeddingService"; /** Status of CLIP indexing on the images in the user's local library. */ diff --git a/web/apps/photos/src/services/export/migration.ts b/web/apps/photos/src/services/export/migration.ts index da506e11e1..1dfbe49d60 100644 --- a/web/apps/photos/src/services/export/migration.ts +++ b/web/apps/photos/src/services/export/migration.ts @@ -1,5 +1,6 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; +import downloadManager from "@/new/photos/services/download"; import { exportMetadataDirectoryName } from "@/new/photos/services/export"; import { getAllLocalFiles } from "@/new/photos/services/files"; import { EnteFile } from "@/new/photos/types/file"; @@ -16,7 +17,6 @@ import { wait } from "@/utils/promise"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import { getLocalCollections } from "services/collectionService"; -import downloadManager from "services/download"; import { Collection } from "types/collection"; import { CollectionExportNames, diff --git a/web/apps/photos/src/services/face/f-index.ts b/web/apps/photos/src/services/face/f-index.ts index 539fdc7411..48b37acb52 100644 --- a/web/apps/photos/src/services/face/f-index.ts +++ b/web/apps/photos/src/services/face/f-index.ts @@ -1,5 +1,6 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; +import DownloadManager from "@/new/photos/services/download"; import type { Box, Dimensions, @@ -12,7 +13,6 @@ import { getRenderableImage } from "@/new/photos/utils/file"; import log from "@/next/log"; import { workerBridge } from "@/next/worker/worker-bridge"; import { Matrix } from "ml-matrix"; -import DownloadManager from "services/download"; import { getSimilarityTransformation } from "similarity-transformation"; import { Matrix as TransformationMatrix, diff --git a/web/apps/photos/src/services/face/face.worker.ts b/web/apps/photos/src/services/face/face.worker.ts index d74c235cc0..f5cc4b8b6f 100644 --- a/web/apps/photos/src/services/face/face.worker.ts +++ b/web/apps/photos/src/services/face/face.worker.ts @@ -1,6 +1,6 @@ +import downloadManager from "@/new/photos/services/download"; import { EnteFile } from "@/new/photos/types/file"; import { expose } from "comlink"; -import downloadManager from "services/download"; import mlService from "services/machineLearning/machineLearningService"; export class DedicatedMLWorker { diff --git a/web/apps/photos/src/services/fix-exif.ts b/web/apps/photos/src/services/fix-exif.ts index d212b31f11..fc504ea622 100644 --- a/web/apps/photos/src/services/fix-exif.ts +++ b/web/apps/photos/src/services/fix-exif.ts @@ -1,4 +1,5 @@ import { FILE_TYPE } from "@/media/file-type"; +import downloadManager from "@/new/photos/services/download"; import { EnteFile } from "@/new/photos/types/file"; import { detectFileTypeInfo } from "@/new/photos/utils/detect-type"; import log from "@/next/log"; @@ -8,7 +9,6 @@ import { changeFileCreationTime, updateExistingFilePubMetadata, } from "utils/file"; -import downloadManager from "./download"; import { getParsedExifData } from "./exif"; const EXIF_TIME_TAGS = [ diff --git a/web/apps/photos/src/services/logout.ts b/web/apps/photos/src/services/logout.ts index 266247ca98..b754a6e445 100644 --- a/web/apps/photos/src/services/logout.ts +++ b/web/apps/photos/src/services/logout.ts @@ -1,10 +1,10 @@ +import DownloadManager from "@/new/photos/services/download"; import { terminateFaceWorker } from "@/new/photos/services/face"; import { clearFaceData } from "@/new/photos/services/face/db"; import { clearFeatureFlagSessionState } from "@/new/photos/services/feature-flags"; import log from "@/next/log"; import { accountLogout } from "@ente/accounts/services/logout"; import { clipService } from "services/clip-service"; -import DownloadManager from "./download"; import exportService from "./export"; import mlWorkManager from "./face/mlWorkManager"; diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 13b3c2fe61..6f2b3b89fb 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,5 +1,6 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; +import DownloadManager from "@/new/photos/services/download"; import { EncryptedEnteFile, EnteFile, @@ -24,7 +25,6 @@ import type { User } from "@ente/shared/user/types"; import { downloadUsingAnchor } from "@ente/shared/utils"; import { t } from "i18next"; import { moveToHiddenCollection } from "services/collectionService"; -import DownloadManager from "services/download"; import { updateFileCreationDateInEXIF } from "services/exif"; import { deleteFromTrash, From 16b79bcf4ab7d4a1a39ee931c52566a925eda212 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 11:11:06 +0530 Subject: [PATCH 20/29] tsc --- web/apps/photos/src/services/logout.ts | 2 +- web/packages/new/photos/services/download.ts | 50 ++++++++++++-------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/web/apps/photos/src/services/logout.ts b/web/apps/photos/src/services/logout.ts index b754a6e445..fbf8249df2 100644 --- a/web/apps/photos/src/services/logout.ts +++ b/web/apps/photos/src/services/logout.ts @@ -31,7 +31,7 @@ export const photosLogout = async () => { } try { - await DownloadManager.logout(); + DownloadManager.logout(); } catch (e) { ignoreError("download", e); } diff --git a/web/packages/new/photos/services/download.ts b/web/packages/new/photos/services/download.ts index bf911362ee..ef41873f39 100644 --- a/web/packages/new/photos/services/download.ts +++ b/web/packages/new/photos/services/download.ts @@ -1,16 +1,20 @@ +// TODO: Remove this override +/* eslint-disable @typescript-eslint/no-empty-function */ + import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import * as ffmpeg from "@/new/photos/services/ffmpeg"; -import { +import type { EnteFile, - type LivePhotoSourceURL, - type SourceURLs, + LivePhotoSourceURL, + SourceURLs, } from "@/new/photos/types/file"; import { getRenderableImage } from "@/new/photos/utils/file"; import { isDesktop } from "@/next/app"; import { blobCache, type BlobCache } from "@/next/blob-cache"; import log from "@/next/log"; import { customAPIOrigin } from "@/next/origins"; +import { ensure } from "@/utils/ensure"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker"; import { CustomError } from "@ente/shared/error"; @@ -38,8 +42,8 @@ interface DownloadClient { } class DownloadManagerImpl { - private ready: boolean = false; - private downloadClient: DownloadClient; + private ready = false; + private downloadClient: DownloadClient | undefined; /** Local cache for thumbnails. Might not be available. */ private thumbnailCache?: BlobCache; /** @@ -48,7 +52,7 @@ class DownloadManagerImpl { * Only available when we're running in the desktop app. */ private fileCache?: BlobCache; - private cryptoWorker: Remote; + private cryptoWorker: Remote | undefined; private fileObjectURLPromises = new Map>(); private fileConversionPromises = new Map>(); @@ -58,6 +62,10 @@ class DownloadManagerImpl { private progressUpdater: (value: Map) => void = () => {}; + private ensureClient() { + return ensure(this.downloadClient); + } + async init(token?: string) { if (this.ready) { log.info("DownloadManager already initialized"); @@ -87,12 +95,17 @@ class DownloadManagerImpl { throw new Error( "Attempting to use an uninitialized download manager", ); + + return { + downloadClient: ensure(this.downloadClient), + cryptoWorker: ensure(this.cryptoWorker), + }; } - async logout() { + logout() { this.ready = false; - this.cryptoWorker = null; - this.downloadClient = null; + this.cryptoWorker = undefined; + this.downloadClient = undefined; this.fileObjectURLPromises.clear(); this.fileConversionPromises.clear(); this.thumbnailObjectURLPromises.clear(); @@ -101,11 +114,8 @@ class DownloadManagerImpl { } updateToken(token: string, passwordToken?: string) { - this.downloadClient.updateTokens(token, passwordToken); - } - - updateCryptoWorker(cryptoWorker: Remote) { - this.cryptoWorker = cryptoWorker; + const { downloadClient } = this.ensureInitialized(); + downloadClient.updateTokens(token, passwordToken); } setProgressUpdater(progressUpdater: (value: Map) => void) { @@ -113,10 +123,12 @@ class DownloadManagerImpl { } private downloadThumb = async (file: EnteFile) => { - const encrypted = await this.downloadClient.downloadThumbnail(file); - const decrypted = await this.cryptoWorker.decryptThumbnail( + const { downloadClient, cryptoWorker } = this.ensureInitialized(); + + const encrypted = await downloadClient.downloadThumbnail(file); + const decrypted = await cryptoWorker.decryptThumbnail( encrypted, - await this.cryptoWorker.fromB64(file.thumbnail.decryptionHeader), + await cryptoWorker.fromB64(file.thumbnail.decryptionHeader), file.key, ); return decrypted; @@ -131,7 +143,7 @@ class DownloadManagerImpl { if (localOnly) return null; const thumb = await this.downloadThumb(file); - this.thumbnailCache?.put(key, new Blob([thumb])); + await this.thumbnailCache?.put(key, new Blob([thumb])); return thumb; } @@ -396,7 +408,7 @@ const DownloadManager = new DownloadManagerImpl(); export default DownloadManager; -const createDownloadClient = (token: string): DownloadClient => { +const createDownloadClient = (token: string | undefined): DownloadClient => { const timeout = 300000; // 5 minute if (token) { return new PhotosDownloadClient(token, timeout); From b49d1323dba277a679650b62ebd4526dba4b7035 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 11:14:35 +0530 Subject: [PATCH 21/29] tsc --- web/packages/new/photos/services/download.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/web/packages/new/photos/services/download.ts b/web/packages/new/photos/services/download.ts index ef41873f39..c9cfa38802 100644 --- a/web/packages/new/photos/services/download.ts +++ b/web/packages/new/photos/services/download.ts @@ -56,16 +56,12 @@ class DownloadManagerImpl { private fileObjectURLPromises = new Map>(); private fileConversionPromises = new Map>(); - private thumbnailObjectURLPromises = new Map>(); + private thumbnailObjectURLPromises = new Map>(); private fileDownloadProgress = new Map(); private progressUpdater: (value: Map) => void = () => {}; - private ensureClient() { - return ensure(this.downloadClient); - } - async init(token?: string) { if (this.ready) { log.info("DownloadManager already initialized"); @@ -140,14 +136,14 @@ class DownloadManagerImpl { const key = file.id.toString(); const cached = await this.thumbnailCache?.get(key); if (cached) return new Uint8Array(await cached.arrayBuffer()); - if (localOnly) return null; + if (localOnly) return undefined; const thumb = await this.downloadThumb(file); await this.thumbnailCache?.put(key, new Blob([thumb])); return thumb; } - async getThumbnailForPreview(file: EnteFile, localOnly = false) { + async getThumbnailForPreview(file: EnteFile, localOnly = false): Promise { this.ensureInitialized(); try { if (!this.thumbnailObjectURLPromises.has(file.id)) { From a7f0dc74fdbcd99c384c442bbecd9ebd83c833f2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 11:26:11 +0530 Subject: [PATCH 22/29] tsc --- web/packages/new/photos/services/download.ts | 74 +++++++++++++------- web/packages/new/photos/types/file.ts | 2 +- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/web/packages/new/photos/services/download.ts b/web/packages/new/photos/services/download.ts index c9cfa38802..ef49ce73b4 100644 --- a/web/packages/new/photos/services/download.ts +++ b/web/packages/new/photos/services/download.ts @@ -56,7 +56,10 @@ class DownloadManagerImpl { private fileObjectURLPromises = new Map>(); private fileConversionPromises = new Map>(); - private thumbnailObjectURLPromises = new Map>(); + private thumbnailObjectURLPromises = new Map< + number, + Promise + >(); private fileDownloadProgress = new Map(); @@ -143,7 +146,10 @@ class DownloadManagerImpl { return thumb; } - async getThumbnailForPreview(file: EnteFile, localOnly = false): Promise { + async getThumbnailForPreview( + file: EnteFile, + localOnly = false, + ): Promise { this.ensureInitialized(); try { if (!this.thumbnailObjectURLPromises.has(file.id)) { @@ -169,15 +175,19 @@ class DownloadManagerImpl { getFileForPreview = async ( file: EnteFile, forceConvert = false, - ): Promise => { + ): Promise => { this.ensureInitialized(); try { const getFileForPreviewPromise = async () => { const fileBlob = await new Response( await this.getFile(file, true), ).blob(); - const { url: originalFileURL } = - await this.fileObjectURLPromises.get(file.id); + // TODO: Is this ensure valid? + // The existing code was already dereferencing, so it shouldn't + // affect behaviour. + const { url: originalFileURL } = ensure( + await this.fileObjectURLPromises.get(file.id), + ); const converted = await getRenderableFileURL( file, @@ -205,7 +215,7 @@ class DownloadManagerImpl { async getFile( file: EnteFile, cacheInMemory = false, - ): Promise> { + ): Promise | null> { this.ensureInitialized(); try { const getFilePromise = async (): Promise => { @@ -224,7 +234,12 @@ class DownloadManagerImpl { } this.fileObjectURLPromises.set(file.id, getFilePromise()); } - const fileURLs = await this.fileObjectURLPromises.get(file.id); + // TODO: Is this ensure valid? + // The existing code was already dereferencing, so it shouldn't + // affect behaviour. + const fileURLs = ensure( + await this.fileObjectURLPromises.get(file.id), + ); if (fileURLs.isOriginal) { const fileStream = (await fetch(fileURLs.url as string)).body; return fileStream; @@ -240,12 +255,15 @@ class DownloadManagerImpl { private async downloadFile( file: EnteFile, - ): Promise> { + ): Promise | null> { + const { downloadClient, cryptoWorker } = this.ensureInitialized(); + log.info(`download attempted for file id ${file.id}`); const onDownloadProgress = this.trackDownloadProgress( file.id, - file.info?.fileSize, + // TODO: Is info supposed to be optional though? + file.info?.fileSize ?? 0, ); const cacheKey = file.id.toString(); @@ -257,23 +275,29 @@ class DownloadManagerImpl { const cachedBlob = await this.fileCache?.get(cacheKey); let encryptedArrayBuffer = await cachedBlob?.arrayBuffer(); if (!encryptedArrayBuffer) { - const array = await this.downloadClient.downloadFile( + const array = await downloadClient.downloadFile( file, onDownloadProgress, ); encryptedArrayBuffer = array.buffer; - this.fileCache?.put(cacheKey, new Blob([encryptedArrayBuffer])); + await this.fileCache?.put( + cacheKey, + new Blob([encryptedArrayBuffer]), + ); } this.clearDownloadProgress(file.id); try { - const decrypted = await this.cryptoWorker.decryptFile( + const decrypted = await cryptoWorker.decryptFile( new Uint8Array(encryptedArrayBuffer), - await this.cryptoWorker.fromB64(file.file.decryptionHeader), + await cryptoWorker.fromB64(file.file.decryptionHeader), file.key, ); return new Response(decrypted).body; } catch (e) { - if (e.message === CustomError.PROCESSING_FAILED) { + if ( + e instanceof Error && + e.message == CustomError.PROCESSING_FAILED + ) { log.error( `Failed to process file with fileID:${file.id}, localID: ${file.metadata.localID}, version: ${file.metadata.version}, deviceFolder:${file.metadata.deviceFolder}`, e, @@ -287,7 +311,7 @@ class DownloadManagerImpl { let res: Response; if (cachedBlob) res = new Response(cachedBlob); else { - res = await this.downloadClient.downloadFileStream(file); + res = await downloadClient.downloadFileStream(file); // We don't have a files cache currently, so this was already a // no-op. But even if we had a cache, this seems sus, because // res.blob() will read the stream and I'd think then trying to do @@ -295,20 +319,20 @@ class DownloadManagerImpl { // this.fileCache?.put(cacheKey, await res.blob()); } - const reader = res.body.getReader(); + const body = res.body; + if (!body) return null; + const reader = body.getReader(); - const contentLength = +res.headers.get("Content-Length") ?? 0; + const contentLength = + parseInt(res.headers.get("Content-Length") ?? "") || 0; let downloadedBytes = 0; - const decryptionHeader = await this.cryptoWorker.fromB64( + const decryptionHeader = await cryptoWorker.fromB64( file.file.decryptionHeader, ); - const fileKey = await this.cryptoWorker.fromB64(file.key); + const fileKey = await cryptoWorker.fromB64(file.key); const { pullState, decryptionChunkSize } = - await this.cryptoWorker.initChunkDecryption( - decryptionHeader, - fileKey, - ); + await cryptoWorker.initChunkDecryption(decryptionHeader, fileKey); let leftoverBytes = new Uint8Array(); @@ -342,7 +366,7 @@ class DownloadManagerImpl { // and we might need multiple iterations to drain it all. while (data.length >= decryptionChunkSize) { const { decryptedData } = - await this.cryptoWorker.decryptFileChunk( + await cryptoWorker.decryptFileChunk( data.slice(0, decryptionChunkSize), pullState, ); @@ -356,7 +380,7 @@ class DownloadManagerImpl { // full chunk, no more bytes are going to come. if (data.length) { const { decryptedData } = - await this.cryptoWorker.decryptFileChunk( + await cryptoWorker.decryptFileChunk( data, pullState, ); diff --git a/web/packages/new/photos/types/file.ts b/web/packages/new/photos/types/file.ts index e76e527393..ce671704f8 100644 --- a/web/packages/new/photos/types/file.ts +++ b/web/packages/new/photos/types/file.ts @@ -26,7 +26,7 @@ export interface EncryptedEnteFile { file: S3FileAttributes; thumbnail: S3FileAttributes; metadata: MetadataFileAttributes; - info: FileInfo; + info: FileInfo | undefined; magicMetadata: EncryptedMagicMetadata; pubMagicMetadata: EncryptedMagicMetadata; encryptedKey: string; From 406e7bd5bddae34a5650edd6814341ad41dee59c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 11:33:33 +0530 Subject: [PATCH 23/29] tsc --- web/packages/new/photos/services/download.ts | 37 +++++++++++++------- web/packages/new/photos/types/file.ts | 4 +-- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/web/packages/new/photos/services/download.ts b/web/packages/new/photos/services/download.ts index ef49ce73b4..0a26c47502 100644 --- a/web/packages/new/photos/services/download.ts +++ b/web/packages/new/photos/services/download.ts @@ -443,14 +443,14 @@ async function getRenderableFileURL( originalFileURL: string, forceConvert: boolean, ): Promise { - const existingOrNewObjectURL = (convertedBlob: Blob) => + const existingOrNewObjectURL = (convertedBlob: Blob | null | undefined) => convertedBlob ? convertedBlob === fileBlob ? originalFileURL : URL.createObjectURL(convertedBlob) : undefined; - let url: SourceURLs["url"]; + let url: SourceURLs["url"] | undefined; let isOriginal: boolean; let isRenderable: boolean; let type: SourceURLs["type"] = "normal"; @@ -497,14 +497,15 @@ async function getRenderableFileURL( } } - return { url, isOriginal, isRenderable, type, mimeType }; + // TODO: Can we remove this ensure and reflect it in the types? + return { url: ensure(url), isOriginal, isRenderable, type, mimeType }; } async function getRenderableLivePhotoURL( file: EnteFile, fileBlob: Blob, forceConvert: boolean, -): Promise { +): Promise { const livePhoto = await decodeLivePhoto(file.metadata.title, fileBlob); const getRenderableLivePhotoImageURL = async () => { @@ -514,11 +515,12 @@ async function getRenderableLivePhotoURL( livePhoto.imageFileName, imageBlob, ); + if (!convertedImageBlob) return undefined; return URL.createObjectURL(convertedImageBlob); } catch (e) { //ignore and return null - return null; + return undefined; } }; @@ -531,10 +533,11 @@ async function getRenderableLivePhotoURL( forceConvert, true, ); + if (!convertedVideoBlob) return undefined; return URL.createObjectURL(convertedVideoBlob); } catch (e) { //ignore and return null - return null; + return undefined; } }; @@ -609,7 +612,9 @@ class PhotosDownloadClient implements DownloadClient { const resp = await retryAsyncFunction(getThumbnail); if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); + // TODO: Remove this cast (it won't be needed when we migrate this from + // axios to fetch). + return new Uint8Array(resp.data as ArrayBuffer); } async downloadFile( @@ -649,7 +654,9 @@ class PhotosDownloadClient implements DownloadClient { const resp = await retryAsyncFunction(getFile); if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); + // TODO: Remove this cast (it won't be needed when we migrate this from + // axios to fetch). + return new Uint8Array(resp.data as ArrayBuffer); } async downloadFileStream(file: EnteFile): Promise { @@ -714,12 +721,12 @@ class PhotosDownloadClient implements DownloadClient { } class PublicAlbumsDownloadClient implements DownloadClient { - private token: string; - private passwordToken: string; + private token: string | undefined; + private passwordToken: string | undefined; constructor(private timeout: number) {} - updateTokens(token: string, passwordToken: string) { + updateTokens(token: string, passwordToken?: string) { this.token = token; this.passwordToken = passwordToken; } @@ -764,7 +771,9 @@ class PublicAlbumsDownloadClient implements DownloadClient { const resp = await getThumbnail(); if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); + // TODO: Remove this cast (it won't be needed when we migrate this from + // axios to fetch). + return new Uint8Array(resp.data as ArrayBuffer); }; downloadFile = async ( @@ -813,7 +822,9 @@ class PublicAlbumsDownloadClient implements DownloadClient { const resp = await retryAsyncFunction(getFile); if (resp.data === undefined) throw Error(CustomError.REQUEST_FAILED); - return new Uint8Array(resp.data); + // TODO: Remove this cast (it won't be needed when we migrate this from + // axios to fetch). + return new Uint8Array(resp.data as ArrayBuffer); }; async downloadFileStream(file: EnteFile): Promise { diff --git a/web/packages/new/photos/types/file.ts b/web/packages/new/photos/types/file.ts index ce671704f8..7cbbb8eb38 100644 --- a/web/packages/new/photos/types/file.ts +++ b/web/packages/new/photos/types/file.ts @@ -63,8 +63,8 @@ export interface EnteFile } export interface LivePhotoSourceURL { - image: () => Promise; - video: () => Promise; + image: () => Promise; + video: () => Promise; } export interface LoadedLivePhotoSourceURL { From a65e0ddfa4ef7429d8840b945b1af4e25a4aeeee Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 11:43:56 +0530 Subject: [PATCH 24/29] lf --- web/apps/cast/src/services/render.ts | 4 +++- web/apps/photos/src/services/export/index.ts | 2 +- web/packages/shared/utils/index.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 5e86b3b8bc..acacdc88d2 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -243,12 +243,14 @@ const decryptEnteFile = async ( file.metadata.title = file.pubMagicMetadata.data.editedName; } // @ts-expect-error TODO: The core types need to be updated to allow the - // possibility of missing metadata fiels. + // possibility of missing metadata fields. return file; }; const isFileEligible = (file: EnteFile) => { if (!isImageOrLivePhoto(file)) return false; + // @ts-expect-error TODO: The core types need to be updated to allow the + // possibility of missing info fields (or do they?) if (file.info.fileSize > 100 * 1024 * 1024) return false; // This check is fast but potentially incorrect because in practice we do diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index fcf387fef3..80f866312e 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -1,6 +1,7 @@ import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import type { Metadata } from "@/media/types/file"; +import downloadManager from "@/new/photos/services/download"; import { exportMetadataDirectoryName, exportTrashDirectoryName, @@ -38,7 +39,6 @@ import { } from "utils/collection"; import { getPersonalFiles, getUpdatedEXIFFileForDownload } from "utils/file"; import { getAllLocalCollections } from "../collectionService"; -import downloadManager from "../download"; import { migrateExport } from "./migration"; /** Name of the JSON file in which we keep the state of the export. */ diff --git a/web/packages/shared/utils/index.ts b/web/packages/shared/utils/index.ts index b71808d466..101e6eb27f 100644 --- a/web/packages/shared/utils/index.ts +++ b/web/packages/shared/utils/index.ts @@ -1,3 +1,4 @@ +import { ensure } from "@/utils/ensure"; import { wait } from "@/utils/promise"; export function downloadAsFile(filename: string, content: string) { @@ -47,7 +48,7 @@ export async function retryAsyncFunction( if (attemptNumber === waitTimeBeforeNextTry.length) { throw e; } - await wait(waitTimeBeforeNextTry[attemptNumber]); + await wait(ensure(waitTimeBeforeNextTry[attemptNumber])); } } } From 8abcd39966e17f7c3f18f0135500b0897616228b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 11:48:48 +0530 Subject: [PATCH 25/29] Fix warning --- web/packages/eslint-config/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/packages/eslint-config/package.json b/web/packages/eslint-config/package.json index 699a1ed4d3..fe1006ca94 100644 --- a/web/packages/eslint-config/package.json +++ b/web/packages/eslint-config/package.json @@ -1,6 +1,7 @@ { "name": "@ente/eslint-config", - "version": "1.0.0", + "version": "0.0.0", + "private": "true", "main": "index.js", "dependencies": {}, "devDependencies": { From f8e5bd3d66d56480240e6d068c41cde2ae2c9aab Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 12:07:45 +0530 Subject: [PATCH 26/29] Workaround failures on GitHub action --- web/packages/new/photos/services/ffmpeg/worker.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/packages/new/photos/services/ffmpeg/worker.ts b/web/packages/new/photos/services/ffmpeg/worker.ts index fc05abcbd2..aeaaee4bd7 100644 --- a/web/packages/new/photos/services/ffmpeg/worker.ts +++ b/web/packages/new/photos/services/ffmpeg/worker.ts @@ -1,3 +1,10 @@ +// TODO: These can be removed when we start using ffmpeg upstream. For an reason +// I haven't investigated much, when we run eslint on our CI, it seems to behave +// differently than locally and give a lot of warnings that possibly arise from +// it not being able to locate ffmpeg-wasm. + +/* eslint-disable */ + import log from "@/next/log"; import QueueProcessor from "@ente/shared/utils/queueProcessor"; import { expose } from "comlink"; From dd80b2174f60ec3085ba55bb2207607c560c1e98 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 12:18:32 +0530 Subject: [PATCH 27/29] Try once more --- web/packages/new/.eslintrc.js | 5 +++++ web/packages/new/photos/services/ffmpeg/worker.ts | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/web/packages/new/.eslintrc.js b/web/packages/new/.eslintrc.js index 53a0075961..655f23ff94 100644 --- a/web/packages/new/.eslintrc.js +++ b/web/packages/new/.eslintrc.js @@ -1,3 +1,8 @@ module.exports = { extends: ["@/build-config/eslintrc-react"], + // TODO: These can be removed when we start using ffmpeg upstream. For an reason + // I haven't investigated much, when we run eslint on our CI, it seems to behave + // differently than locally and give a lot of warnings that possibly arise from + // it not being able to locate ffmpeg-wasm. + ignorePatterns: ["ffmpeg/worker.ts"], }; diff --git a/web/packages/new/photos/services/ffmpeg/worker.ts b/web/packages/new/photos/services/ffmpeg/worker.ts index aeaaee4bd7..fc05abcbd2 100644 --- a/web/packages/new/photos/services/ffmpeg/worker.ts +++ b/web/packages/new/photos/services/ffmpeg/worker.ts @@ -1,10 +1,3 @@ -// TODO: These can be removed when we start using ffmpeg upstream. For an reason -// I haven't investigated much, when we run eslint on our CI, it seems to behave -// differently than locally and give a lot of warnings that possibly arise from -// it not being able to locate ffmpeg-wasm. - -/* eslint-disable */ - import log from "@/next/log"; import QueueProcessor from "@ente/shared/utils/queueProcessor"; import { expose } from "comlink"; From 47e84744668e04b93fb11f32c34da1e2ed7103bb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 12:21:39 +0530 Subject: [PATCH 28/29] wc --- web/packages/new/.eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/new/.eslintrc.js b/web/packages/new/.eslintrc.js index 655f23ff94..37111028d5 100644 --- a/web/packages/new/.eslintrc.js +++ b/web/packages/new/.eslintrc.js @@ -4,5 +4,5 @@ module.exports = { // I haven't investigated much, when we run eslint on our CI, it seems to behave // differently than locally and give a lot of warnings that possibly arise from // it not being able to locate ffmpeg-wasm. - ignorePatterns: ["ffmpeg/worker.ts"], + ignorePatterns: ["**/ffmpeg/worker.ts"], }; From 93b264443ce41f6f9e28eabc0d10a7d035aab634 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 2 Jul 2024 12:25:21 +0530 Subject: [PATCH 29/29] next line --- web/packages/new/photos/services/ffmpeg/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/new/photos/services/ffmpeg/worker.ts b/web/packages/new/photos/services/ffmpeg/worker.ts index fc05abcbd2..e293b9aed7 100644 --- a/web/packages/new/photos/services/ffmpeg/worker.ts +++ b/web/packages/new/photos/services/ffmpeg/worker.ts @@ -1,4 +1,5 @@ import log from "@/next/log"; +import { ensure } from "@/utils/ensure"; import QueueProcessor from "@ente/shared/utils/queueProcessor"; import { expose } from "comlink"; import { @@ -24,7 +25,6 @@ import { // // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error // @ts-ignore -import { ensure } from "@/utils/ensure"; import { createFFmpeg, type FFmpeg } from "ffmpeg-wasm"; export class DedicatedFFmpegWorker {