diff --git a/web/apps/cast/src/components/PhotoAuditorium.tsx b/web/apps/cast/src/components/PhotoAuditorium.tsx new file mode 100644 index 000000000..0042dfe95 --- /dev/null +++ b/web/apps/cast/src/components/PhotoAuditorium.tsx @@ -0,0 +1,95 @@ +import { SlideshowContext } from "pages/slideshow"; +import { useContext, useEffect, useState } from "react"; + +export default function PhotoAuditorium({ + url, + nextSlideUrl, +}: { + url: string; + nextSlideUrl: string; +}) { + const { showNextSlide } = useContext(SlideshowContext); + + const [showPreloadedNextSlide, setShowPreloadedNextSlide] = useState(false); + const [nextSlidePrerendered, setNextSlidePrerendered] = useState(false); + const [prerenderTime, setPrerenderTime] = useState(null); + + useEffect(() => { + let timeout: NodeJS.Timeout; + let timeout2: NodeJS.Timeout; + + if (nextSlidePrerendered) { + const elapsedTime = prerenderTime ? Date.now() - prerenderTime : 0; + const delayTime = Math.max(10000 - elapsedTime, 0); + + if (elapsedTime >= 10000) { + setShowPreloadedNextSlide(true); + } else { + timeout = setTimeout(() => { + setShowPreloadedNextSlide(true); + }, delayTime); + } + + if (showNextSlide) { + timeout2 = setTimeout(() => { + showNextSlide(); + setNextSlidePrerendered(false); + setPrerenderTime(null); + setShowPreloadedNextSlide(false); + }, delayTime); + } + } + + return () => { + if (timeout) clearTimeout(timeout); + if (timeout2) clearTimeout(timeout2); + }; + }, [nextSlidePrerendered, showNextSlide, prerenderTime]); + + return ( +
+
+ + { + setNextSlidePrerendered(true); + setPrerenderTime(Date.now()); + }} + /> +
+
+ ); +} diff --git a/web/apps/cast/src/components/TimerBar.tsx b/web/apps/cast/src/components/TimerBar.tsx deleted file mode 100644 index 7f4d02171..000000000 --- a/web/apps/cast/src/components/TimerBar.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect, useState } from "react"; - -export default function TimerBar({ percentage }: { percentage: number }) { - const okColor = "#75C157"; - const warningColor = "#FFC000"; - const lateColor = "#FF0000"; - - const [backgroundColor, setBackgroundColor] = useState(okColor); - - useEffect(() => { - if (percentage >= 40) { - setBackgroundColor(okColor); - } else if (percentage >= 20) { - setBackgroundColor(warningColor); - } else { - setBackgroundColor(lateColor); - } - }, [percentage]); - - return ( -
- ); -} diff --git a/web/apps/cast/src/constants/apps.ts b/web/apps/cast/src/constants/apps.ts deleted file mode 100644 index f8c3f9657..000000000 --- a/web/apps/cast/src/constants/apps.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { getAlbumsURL } from "@ente/shared/network/api"; -import { runningInBrowser } from "@ente/shared/platform"; -import { PAGES } from "constants/pages"; - -export enum APPS { - PHOTOS = "PHOTOS", - AUTH = "AUTH", - ALBUMS = "ALBUMS", -} - -export const ALLOWED_APP_PAGES = new Map([ - [APPS.ALBUMS, [PAGES.SHARED_ALBUMS, PAGES.ROOT]], - [ - APPS.AUTH, - [ - PAGES.ROOT, - PAGES.LOGIN, - PAGES.SIGNUP, - PAGES.VERIFY, - PAGES.CREDENTIALS, - PAGES.RECOVER, - PAGES.CHANGE_PASSWORD, - PAGES.GENERATE, - PAGES.AUTH, - PAGES.TWO_FACTOR_VERIFY, - PAGES.TWO_FACTOR_RECOVER, - ], - ], -]); - -export const CLIENT_PACKAGE_NAMES = new Map([ - [APPS.ALBUMS, "io.ente.albums.web"], - [APPS.PHOTOS, "io.ente.photos.web"], - [APPS.AUTH, "io.ente.auth.web"], -]); - -export const getAppNameAndTitle = () => { - if (!runningInBrowser()) { - return {}; - } - const currentURL = new URL(window.location.href); - const albumsURL = new URL(getAlbumsURL()); - if (currentURL.origin === albumsURL.origin) { - return { name: APPS.ALBUMS, title: "ente Photos" }; - } else { - return { name: APPS.PHOTOS, title: "ente Photos" }; - } -}; - -export const getAppTitle = () => { - return getAppNameAndTitle().title; -}; - -export const getAppName = () => { - return getAppNameAndTitle().name; -}; diff --git a/web/apps/cast/src/constants/cache.ts b/web/apps/cast/src/constants/cache.ts deleted file mode 100644 index cf88f63a2..000000000 --- a/web/apps/cast/src/constants/cache.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum CACHES { - THUMBS = "thumbs", - FACE_CROPS = "face-crops", - FILES = "files", -} diff --git a/web/apps/cast/src/constants/file.ts b/web/apps/cast/src/constants/file.ts index 46065136c..9be574638 100644 --- a/web/apps/cast/src/constants/file.ts +++ b/web/apps/cast/src/constants/file.ts @@ -1,14 +1,3 @@ -export const MIN_EDITED_CREATION_TIME = new Date(1800, 0, 1); -export const MAX_EDITED_CREATION_TIME = new Date(); - -export const MAX_EDITED_FILE_NAME_LENGTH = 100; -export const MAX_CAPTION_SIZE = 5000; - -export const TYPE_HEIC = "heic"; -export const TYPE_HEIF = "heif"; -export const TYPE_JPEG = "jpeg"; -export const TYPE_JPG = "jpg"; - export enum FILE_TYPE { IMAGE, VIDEO, @@ -29,15 +18,3 @@ export const RAW_FORMATS = [ "dng", "tif", ]; -export const SUPPORTED_RAW_FORMATS = [ - "heic", - "rw2", - "tiff", - "arw", - "cr3", - "cr2", - "nef", - "psd", - "dng", - "tif", -]; diff --git a/web/apps/cast/src/constants/gallery.ts b/web/apps/cast/src/constants/gallery.ts deleted file mode 100644 index 9865d2e80..000000000 --- a/web/apps/cast/src/constants/gallery.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const GAP_BTW_TILES = 4; -export const DATE_CONTAINER_HEIGHT = 48; -export const SIZE_AND_COUNT_CONTAINER_HEIGHT = 72; -export const IMAGE_CONTAINER_MAX_HEIGHT = 180; -export const IMAGE_CONTAINER_MAX_WIDTH = 180; -export const MIN_COLUMNS = 4; -export const SPACE_BTW_DATES = 44; -export const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244; - -export enum PLAN_PERIOD { - MONTH = "month", - YEAR = "year", -} - -export const SYNC_INTERVAL_IN_MICROSECONDS = 1000 * 60 * 5; // 5 minutes diff --git a/web/apps/cast/src/constants/pages.ts b/web/apps/cast/src/constants/pages.ts deleted file mode 100644 index af532801d..000000000 --- a/web/apps/cast/src/constants/pages.ts +++ /dev/null @@ -1,20 +0,0 @@ -export enum PAGES { - CHANGE_EMAIL = "/change-email", - CHANGE_PASSWORD = "/change-password", - CREDENTIALS = "/credentials", - GALLERY = "/gallery", - GENERATE = "/generate", - LOGIN = "/login", - RECOVER = "/recover", - SIGNUP = "/signup", - TWO_FACTOR_SETUP = "/two-factor/setup", - TWO_FACTOR_VERIFY = "/two-factor/verify", - TWO_FACTOR_RECOVER = "/two-factor/recover", - VERIFY = "/verify", - ROOT = "/", - SHARED_ALBUMS = "/shared-albums", - // ML_DEBUG = '/ml-debug', - DEDUPLICATE = "/deduplicate", - // AUTH page is used to show (auth)enticator codes - AUTH = "/auth", -} diff --git a/web/apps/cast/src/constants/upload.ts b/web/apps/cast/src/constants/upload.ts index bc6006e46..63d044fb4 100644 --- a/web/apps/cast/src/constants/upload.ts +++ b/web/apps/cast/src/constants/upload.ts @@ -1,11 +1,5 @@ -import { ENCRYPTION_CHUNK_SIZE } from "@ente/shared/crypto/constants"; import { FILE_TYPE } from "constants/file"; -import { - FileTypeInfo, - ImportSuggestion, - Location, - ParsedExtractedMetadata, -} from "types/upload"; +import { FileTypeInfo } from "types/upload"; // list of format that were missed by type-detection for some files. export const WHITELISTED_FILE_FORMATS: FileTypeInfo[] = [ @@ -45,98 +39,3 @@ export const WHITELISTED_FILE_FORMATS: FileTypeInfo[] = [ ]; export const KNOWN_NON_MEDIA_FORMATS = ["xmp", "html", "txt"]; - -export const EXIFLESS_FORMATS = ["gif", "bmp"]; - -// this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part. -export const MULTIPART_PART_SIZE = 20 * 1024 * 1024; - -export const FILE_READER_CHUNK_SIZE = ENCRYPTION_CHUNK_SIZE; - -export const FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART = Math.floor( - MULTIPART_PART_SIZE / FILE_READER_CHUNK_SIZE, -); - -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_STRATEGY { - SINGLE_COLLECTION, - COLLECTION_PER_FOLDER, -} - -export enum UPLOAD_RESULT { - FAILED, - ALREADY_UPLOADED, - UNSUPPORTED, - BLOCKED, - TOO_LARGE, - LARGER_THAN_AVAILABLE_STORAGE, - UPLOADED, - UPLOADED_WITH_STATIC_THUMBNAIL, - ADDED_SYMLINK, -} - -export enum PICKED_UPLOAD_TYPE { - FILES = "files", - FOLDERS = "folders", - ZIPS = "zips", -} - -export const MAX_FILE_SIZE_SUPPORTED = 4 * 1024 * 1024 * 1024; // 4 GB - -export const LIVE_PHOTO_ASSET_SIZE_LIMIT = 20 * 1024 * 1024; // 20MB - -export const NULL_EXTRACTED_METADATA: ParsedExtractedMetadata = { - location: NULL_LOCATION, - creationTime: null, - width: null, - height: null, -}; - -export const A_SEC_IN_MICROSECONDS = 1e6; - -export const DEFAULT_IMPORT_SUGGESTION: ImportSuggestion = { - rootFolderName: "", - hasNestedFolders: false, - hasRootLevelFileWithFolder: false, -}; - -export const BLACK_THUMBNAIL_BASE64 = - "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB" + - "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ" + - "EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARC" + - "ACWASwDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF" + - "BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk" + - "6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztL" + - "W2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAA" + - "AAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVY" + - "nLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImK" + - "kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oAD" + - "AMBAAIRAxEAPwD/AD/6ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" + - "CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" + - "AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAC" + - "gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" + - "AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" + - "AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" + - "AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" + - "CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" + - "CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA" + - "KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" + - "AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" + - "AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" + - "CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAK" + - "ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA" + - "KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" + - "AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" + - "AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k="; diff --git a/web/apps/cast/src/constants/urls.ts b/web/apps/cast/src/constants/urls.ts deleted file mode 100644 index b5b453c31..000000000 --- a/web/apps/cast/src/constants/urls.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const ENTE_WEBSITE_LINK = "https://ente.io"; - -export const ML_BLOG_LINK = "https://ente.io/blog/desktop-ml-beta"; - -export const FACE_SEARCH_PRIVACY_POLICY_LINK = - "https://ente.io/privacy#8-biometric-information-privacy-policy"; - -export const SUPPORT_EMAIL = "support@ente.io"; - -export const APP_DOWNLOAD_URL = "https://ente.io/download/desktop"; - -export const FEEDBACK_EMAIL = "feedback@ente.io"; - -export const DELETE_ACCOUNT_EMAIL = "account-deletion@ente.io"; - -export const WEB_ROADMAP_URL = "https://github.com/ente-io/ente/discussions"; - -export const DESKTOP_ROADMAP_URL = - "https://github.com/ente-io/ente/discussions"; diff --git a/web/apps/cast/src/services/InMemoryStore.ts b/web/apps/cast/src/services/InMemoryStore.ts deleted file mode 100644 index 88e77b869..000000000 --- a/web/apps/cast/src/services/InMemoryStore.ts +++ /dev/null @@ -1,31 +0,0 @@ -export enum MS_KEYS { - SRP_CONFIGURE_IN_PROGRESS = "srpConfigureInProgress", - REDIRECT_URL = "redirectUrl", -} - -type StoreType = Map, any>; - -class InMemoryStore { - private store: StoreType = new Map(); - - get(key: MS_KEYS) { - return this.store.get(key); - } - - set(key: MS_KEYS, value: any) { - this.store.set(key, value); - } - - delete(key: MS_KEYS) { - this.store.delete(key); - } - - has(key: MS_KEYS) { - return this.store.has(key); - } - clear() { - this.store.clear(); - } -} - -export default new InMemoryStore(); diff --git a/web/apps/cast/src/services/cache/cacheStorageFactory.ts b/web/apps/cast/src/services/cache/cacheStorageFactory.ts deleted file mode 100644 index c4474b29e..000000000 --- a/web/apps/cast/src/services/cache/cacheStorageFactory.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { LimitedCacheStorage } from "types/cache/index"; - -class cacheStorageFactory { - getCacheStorage(): LimitedCacheStorage { - return transformBrowserCacheStorageToLimitedCacheStorage(caches); - } -} - -export const CacheStorageFactory = new cacheStorageFactory(); - -function transformBrowserCacheStorageToLimitedCacheStorage( - caches: CacheStorage, -): LimitedCacheStorage { - return { - async open(cacheName) { - const cache = await caches.open(cacheName); - return { - match: cache.match.bind(cache), - put: cache.put.bind(cache), - delete: cache.delete.bind(cache), - }; - }, - delete: caches.delete.bind(caches), - }; -} diff --git a/web/apps/cast/src/services/cache/cacheStorageService.ts b/web/apps/cast/src/services/cache/cacheStorageService.ts deleted file mode 100644 index 391aefb55..000000000 --- a/web/apps/cast/src/services/cache/cacheStorageService.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { logError } from "@ente/shared/sentry"; -import { CacheStorageFactory } from "./cacheStorageFactory"; - -const SecurityError = "SecurityError"; -const INSECURE_OPERATION = "The operation is insecure."; -async function openCache(cacheName: string) { - try { - return await CacheStorageFactory.getCacheStorage().open(cacheName); - } catch (e) { - // ignoring insecure operation error, as it is thrown in incognito mode in firefox - if (e.name === SecurityError && e.message === INSECURE_OPERATION) { - // no-op - } else { - // log and ignore, we don't want to break the caller flow, when cache is not available - logError(e, "openCache failed"); - } - } -} -async function deleteCache(cacheName: string) { - try { - return await CacheStorageFactory.getCacheStorage().delete(cacheName); - } catch (e) { - // ignoring insecure operation error, as it is thrown in incognito mode in firefox - if (e.name === SecurityError && e.message === INSECURE_OPERATION) { - // no-op - } else { - // log and ignore, we don't want to break the caller flow, when cache is not available - logError(e, "deleteCache failed"); - } - } -} - -export const CacheStorageService = { open: openCache, delete: deleteCache }; diff --git a/web/apps/cast/src/services/castDownloadManager.ts b/web/apps/cast/src/services/castDownloadManager.ts index b56aec928..a99f0481d 100644 --- a/web/apps/cast/src/services/castDownloadManager.ts +++ b/web/apps/cast/src/services/castDownloadManager.ts @@ -1,162 +1,14 @@ -import { EnteFile } from "types/file"; -import { - createTypedObjectURL, - generateStreamFromArrayBuffer, - getRenderableFileURL, -} from "utils/file"; - import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getCastFileURL, getCastThumbnailURL } from "@ente/shared/network/api"; -import { logError } from "@ente/shared/sentry"; -import { CACHES } from "constants/cache"; +import { getCastFileURL } from "@ente/shared/network/api"; import { FILE_TYPE } from "constants/file"; -import { LimitedCache } from "types/cache"; +import { EnteFile } from "types/file"; import ComlinkCryptoWorker from "utils/comlink/ComlinkCryptoWorker"; -import { CacheStorageService } from "./cache/cacheStorageService"; +import { generateStreamFromArrayBuffer } from "utils/file"; class CastDownloadManager { - private fileObjectURLPromise = new Map< - string, - Promise<{ original: string[]; converted: string[] }> - >(); - private thumbnailObjectURLPromise = new Map>(); - - private fileDownloadProgress = new Map(); - - private progressUpdater: (value: Map) => void; - - setProgressUpdater(progressUpdater: (value: Map) => void) { - this.progressUpdater = progressUpdater; - } - - private async getThumbnailCache() { - try { - const thumbnailCache = await CacheStorageService.open( - CACHES.THUMBS, - ); - return thumbnailCache; - } catch (e) { - return null; - // ignore - } - } - - public async getCachedThumbnail( - file: EnteFile, - thumbnailCache?: LimitedCache, - ) { - try { - if (!thumbnailCache) { - thumbnailCache = await this.getThumbnailCache(); - } - const cacheResp: Response = await thumbnailCache?.match( - file.id.toString(), - ); - - if (cacheResp) { - return URL.createObjectURL(await cacheResp.blob()); - } - return null; - } catch (e) { - logError(e, "failed to get cached thumbnail"); - throw e; - } - } - - public async getThumbnail(file: EnteFile, castToken: string) { - try { - if (!this.thumbnailObjectURLPromise.has(file.id)) { - const downloadPromise = async () => { - const thumbnailCache = await this.getThumbnailCache(); - const cachedThumb = await this.getCachedThumbnail( - file, - thumbnailCache, - ); - if (cachedThumb) { - return cachedThumb; - } - - const thumb = await this.downloadThumb(castToken, file); - const thumbBlob = new Blob([thumb]); - try { - await thumbnailCache?.put( - file.id.toString(), - new Response(thumbBlob), - ); - } catch (e) { - // TODO: handle storage full exception. - } - return URL.createObjectURL(thumbBlob); - }; - this.thumbnailObjectURLPromise.set(file.id, downloadPromise()); - } - - return await this.thumbnailObjectURLPromise.get(file.id); - } catch (e) { - this.thumbnailObjectURLPromise.delete(file.id); - logError(e, "get castDownloadManager preview Failed"); - throw e; - } - } - - private downloadThumb = async (castToken: string, file: EnteFile) => { - const resp = await HTTPService.get( - getCastThumbnailURL(file.id), - null, - { - "X-Cast-Access-Token": castToken, - }, - { responseType: "arraybuffer" }, - ); - if (typeof resp.data === "undefined") { - throw Error(CustomError.REQUEST_FAILED); - } - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const decrypted = await cryptoWorker.decryptThumbnail( - new Uint8Array(resp.data), - await cryptoWorker.fromB64(file.thumbnail.decryptionHeader), - file.key, - ); - return decrypted; - }; - - getFile = async (file: EnteFile, castToken: string, forPreview = false) => { - const fileKey = forPreview ? `${file.id}_preview` : `${file.id}`; - try { - const getFilePromise = async () => { - const fileStream = await this.downloadFile(castToken, file); - const fileBlob = await new Response(fileStream).blob(); - if (forPreview) { - return await getRenderableFileURL(file, fileBlob); - } else { - const fileURL = await createTypedObjectURL( - fileBlob, - file.metadata.title, - ); - return { converted: [fileURL], original: [fileURL] }; - } - }; - - if (!this.fileObjectURLPromise.get(fileKey)) { - this.fileObjectURLPromise.set(fileKey, getFilePromise()); - } - const fileURLs = await this.fileObjectURLPromise.get(fileKey); - return fileURLs; - } catch (e) { - this.fileObjectURLPromise.delete(fileKey); - logError(e, "castDownloadManager failed to get file"); - throw e; - } - }; - - public async getCachedOriginalFile(file: EnteFile) { - return await this.fileObjectURLPromise.get(file.id.toString()); - } - async downloadFile(castToken: string, file: EnteFile) { const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const onDownloadProgress = this.trackDownloadProgress(file.id); if ( file.metadata.fileType === FILE_TYPE.IMAGE || @@ -187,9 +39,6 @@ class CastDownloadManager { }); const reader = resp.body.getReader(); - const contentLength = +resp.headers.get("Content-Length"); - let downloadedBytes = 0; - const stream = new ReadableStream({ async start(controller) { const decryptionHeader = await cryptoWorker.fromB64( @@ -208,11 +57,6 @@ class CastDownloadManager { reader.read().then(async ({ done, value }) => { // Is there more data to read? if (!done) { - downloadedBytes += value.byteLength; - onDownloadProgress({ - loaded: downloadedBytes, - total: contentLength, - }); const buffer = new Uint8Array( data.byteLength + value.byteLength, ); @@ -254,20 +98,6 @@ class CastDownloadManager { }); return stream; } - - trackDownloadProgress = (fileID: number) => { - return (event: { loaded: number; total: number }) => { - if (event.loaded === event.total) { - this.fileDownloadProgress.delete(fileID); - } else { - this.fileDownloadProgress.set( - fileID, - Math.round((event.loaded * 100) / event.total), - ); - } - this.progressUpdater(new Map(this.fileDownloadProgress)); - }; - }; } export default new CastDownloadManager(); diff --git a/web/apps/cast/src/services/events.ts b/web/apps/cast/src/services/events.ts deleted file mode 100644 index 32306fc64..000000000 --- a/web/apps/cast/src/services/events.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EventEmitter } from "eventemitter3"; - -// When registering event handlers, -// handle errors to avoid unhandled rejection or propagation to emit call - -export enum Events { - LOGOUT = "logout", - FILE_UPLOADED = "fileUploaded", - LOCAL_FILES_UPDATED = "localFilesUpdated", -} - -export const eventBus = new EventEmitter(); diff --git a/web/apps/cast/src/services/ffmpeg/ffmpegFactory.ts b/web/apps/cast/src/services/ffmpeg/ffmpegFactory.ts index b3c716d99..0f7d226c8 100644 --- a/web/apps/cast/src/services/ffmpeg/ffmpegFactory.ts +++ b/web/apps/cast/src/services/ffmpeg/ffmpegFactory.ts @@ -1,26 +1,19 @@ -// import isElectron from 'is-electron'; -// import { ElectronFFmpeg } from 'services/electron/ffmpeg'; -import { ElectronFile } from "types/upload"; import ComlinkFFmpegWorker from "utils/comlink/ComlinkFFmpegWorker"; export interface IFFmpeg { run: ( cmd: string[], - inputFile: File | ElectronFile, + inputFile: File, outputFilename: string, dontTimeout?: boolean, - ) => Promise; + ) => Promise; } class FFmpegFactory { private client: IFFmpeg; async getFFmpegClient() { if (!this.client) { - // if (isElectron()) { - // this.client = new ElectronFFmpeg(); - // } else { this.client = await ComlinkFFmpegWorker.getInstance(); - // } } return this.client; } diff --git a/web/apps/cast/src/services/ffmpeg/ffmpegService.ts b/web/apps/cast/src/services/ffmpeg/ffmpegService.ts index 85bab9939..325f1d66b 100644 --- a/web/apps/cast/src/services/ffmpeg/ffmpegService.ts +++ b/web/apps/cast/src/services/ffmpeg/ffmpegService.ts @@ -4,10 +4,9 @@ import { INPUT_PATH_PLACEHOLDER, OUTPUT_PATH_PLACEHOLDER, } from "constants/ffmpeg"; -import { ElectronFile } from "types/upload"; import ffmpegFactory from "./ffmpegFactory"; -export async function convertToMP4(file: File | ElectronFile) { +export async function convertToMP4(file: File) { try { const ffmpegClient = await ffmpegFactory.getFFmpegClient(); return await ffmpegClient.run( diff --git a/web/apps/cast/src/services/heicConversionService.ts b/web/apps/cast/src/services/heicConversionService.ts deleted file mode 100644 index f11a9f4a4..000000000 --- a/web/apps/cast/src/services/heicConversionService.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { logError } from "@ente/shared/sentry"; -import WasmHEICConverterService from "./wasmHeicConverter/wasmHEICConverterService"; - -class HeicConversionService { - async convert(heicFileData: Blob): Promise { - try { - return await WasmHEICConverterService.convert(heicFileData); - } catch (e) { - logError(e, "failed to convert heic file"); - throw e; - } - } -} -export default new HeicConversionService(); diff --git a/web/apps/cast/src/services/livePhotoService.ts b/web/apps/cast/src/services/livePhotoService.ts index 4d96e812c..789234bd3 100644 --- a/web/apps/cast/src/services/livePhotoService.ts +++ b/web/apps/cast/src/services/livePhotoService.ts @@ -30,16 +30,3 @@ export const decodeLivePhoto = async (file: EnteFile, zipBlob: Blob) => { } return livePhoto; }; - -export const encodeLivePhoto = async (livePhoto: LivePhoto) => { - const zip = new JSZip(); - zip.file( - "image" + getFileExtensionWithDot(livePhoto.imageNameTitle), - livePhoto.image, - ); - zip.file( - "video" + getFileExtensionWithDot(livePhoto.videoNameTitle), - livePhoto.video, - ); - return await zip.generateAsync({ type: "uint8array" }); -}; diff --git a/web/apps/cast/src/services/readerService.ts b/web/apps/cast/src/services/readerService.ts index 5aa42b6d2..7682f1580 100644 --- a/web/apps/cast/src/services/readerService.ts +++ b/web/apps/cast/src/services/readerService.ts @@ -1,10 +1,7 @@ import { logError } from "@ente/shared/sentry"; import { convertBytesToHumanReadable } from "@ente/shared/utils/size"; -import { ElectronFile } from "types/upload"; -export async function getUint8ArrayView( - file: Blob | ElectronFile, -): Promise { +export async function getUint8ArrayView(file: Blob): Promise { try { return new Uint8Array(await file.arrayBuffer()); } catch (e) { @@ -14,45 +11,3 @@ export async function getUint8ArrayView( throw e; } } - -export function getFileStream(file: File, chunkSize: number) { - const fileChunkReader = fileChunkReaderMaker(file, chunkSize); - - const stream = new ReadableStream({ - async pull(controller: ReadableStreamDefaultController) { - const chunk = await fileChunkReader.next(); - if (chunk.done) { - controller.close(); - } else { - controller.enqueue(chunk.value); - } - }, - }); - const chunkCount = Math.ceil(file.size / chunkSize); - return { - stream, - chunkCount, - }; -} - -export async function getElectronFileStream( - file: ElectronFile, - chunkSize: number, -) { - const chunkCount = Math.ceil(file.size / chunkSize); - return { - stream: await file.stream(), - chunkCount, - }; -} - -async function* fileChunkReaderMaker(file: File, chunkSize: number) { - let offset = 0; - while (offset < file.size) { - const blob = file.slice(offset, chunkSize + offset); - const fileChunk = await getUint8ArrayView(blob); - yield fileChunk; - offset += chunkSize; - } - return null; -} diff --git a/web/apps/cast/src/services/typeDetectionService.ts b/web/apps/cast/src/services/typeDetectionService.ts index c280baf51..826253ce4 100644 --- a/web/apps/cast/src/services/typeDetectionService.ts +++ b/web/apps/cast/src/services/typeDetectionService.ts @@ -6,37 +6,25 @@ import { KNOWN_NON_MEDIA_FORMATS, WHITELISTED_FILE_FORMATS, } from "constants/upload"; -import FileType, { FileTypeResult } from "file-type"; -import { ElectronFile, FileTypeInfo } from "types/upload"; +import FileType from "file-type"; +import { FileTypeInfo } from "types/upload"; import { getFileExtension } from "utils/file"; import { getUint8ArrayView } from "./readerService"; -function getFileSize(file: File | ElectronFile) { - return file.size; -} - const TYPE_VIDEO = "video"; const TYPE_IMAGE = "image"; const CHUNK_SIZE_FOR_TYPE_DETECTION = 4100; -export async function getFileType( - receivedFile: File | ElectronFile, -): Promise { +export async function getFileType(receivedFile: File): Promise { try { let fileType: FILE_TYPE; - let typeResult: FileTypeResult; - - if (receivedFile instanceof File) { - typeResult = await extractFileType(receivedFile); - } else { - typeResult = await extractElectronFileType(receivedFile); - } + const typeResult = await extractFileType(receivedFile); const mimTypeParts: string[] = typeResult.mime?.split("/"); - if (mimTypeParts?.length !== 2) { throw Error(CustomError.INVALID_MIME_TYPE(typeResult.mime)); } + switch (mimTypeParts[0]) { case TYPE_IMAGE: fileType = FILE_TYPE.IMAGE; @@ -54,7 +42,7 @@ export async function getFileType( }; } catch (e) { const fileFormat = getFileExtension(receivedFile.name); - const fileSize = convertBytesToHumanReadable(getFileSize(receivedFile)); + const fileSize = convertBytesToHumanReadable(receivedFile.size); const whiteListedFormat = WHITELISTED_FILE_FORMATS.find( (a) => a.exactType === fileFormat, ); @@ -85,14 +73,6 @@ async function extractFileType(file: File) { return getFileTypeFromBuffer(fileDataChunk); } -async function extractElectronFileType(file: ElectronFile) { - const stream = await file.stream(); - const reader = stream.getReader(); - const { value: fileDataChunk } = await reader.read(); - await reader.cancel(); - return getFileTypeFromBuffer(fileDataChunk); -} - async function getFileTypeFromBuffer(buffer: Uint8Array) { const result = await FileType.fromBuffer(buffer); if (!result?.mime) { diff --git a/web/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterClient.ts b/web/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterClient.ts deleted file mode 100644 index 03b390fb9..000000000 --- a/web/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterClient.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as HeicConvert from "heic-convert"; -import { getUint8ArrayView } from "services/readerService"; - -export async function convertHEIC( - fileBlob: Blob, - format: string, -): Promise { - const filedata = await getUint8ArrayView(fileBlob); - const result = await HeicConvert({ buffer: filedata, format }); - const convertedFileData = new Uint8Array(result); - const convertedFileBlob = new Blob([convertedFileData]); - return convertedFileBlob; -} diff --git a/web/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterService.ts b/web/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterService.ts deleted file mode 100644 index 42790e515..000000000 --- a/web/apps/cast/src/services/wasmHeicConverter/wasmHEICConverterService.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { CustomError } from "@ente/shared/error"; -import { addLogLine } from "@ente/shared/logging"; -import { retryAsyncFunction } from "@ente/shared/promise"; -import { logError } from "@ente/shared/sentry"; -import QueueProcessor from "@ente/shared/utils/queueProcessor"; -import { convertBytesToHumanReadable } from "@ente/shared/utils/size"; -import { getDedicatedConvertWorker } from "utils/comlink/ComlinkConvertWorker"; -import { ComlinkWorker } from "utils/comlink/comlinkWorker"; -import { DedicatedConvertWorker } from "worker/convert.worker"; - -const WORKER_POOL_SIZE = 2; -const MAX_CONVERSION_IN_PARALLEL = 1; -const WAIT_TIME_BEFORE_NEXT_ATTEMPT_IN_MICROSECONDS = [100, 100]; -const WAIT_TIME_IN_MICROSECONDS = 30 * 1000; -const BREATH_TIME_IN_MICROSECONDS = 1000; -const CONVERT_FORMAT = "JPEG"; - -class HEICConverter { - private convertProcessor = new QueueProcessor( - MAX_CONVERSION_IN_PARALLEL, - ); - private workerPool: ComlinkWorker[] = []; - private ready: Promise; - - constructor() { - this.ready = this.init(); - } - private async init() { - this.workerPool = []; - for (let i = 0; i < WORKER_POOL_SIZE; i++) { - this.workerPool.push(getDedicatedConvertWorker()); - } - } - async convert(fileBlob: Blob): Promise { - await this.ready; - const response = this.convertProcessor.queueUpRequest(() => - retryAsyncFunction(async () => { - const convertWorker = this.workerPool.shift(); - const worker = await convertWorker.remote; - try { - const convertedHEIC = await new Promise( - (resolve, reject) => { - const main = async () => { - try { - const timeout = setTimeout(() => { - reject(Error("wait time exceeded")); - }, WAIT_TIME_IN_MICROSECONDS); - const startTime = Date.now(); - const convertedHEIC = - await worker.convertHEIC( - fileBlob, - CONVERT_FORMAT, - ); - addLogLine( - `originalFileSize:${convertBytesToHumanReadable( - fileBlob?.size, - )},convertedFileSize:${convertBytesToHumanReadable( - convertedHEIC?.size, - )}, heic conversion time: ${ - Date.now() - startTime - }ms `, - ); - clearTimeout(timeout); - resolve(convertedHEIC); - } catch (e) { - reject(e); - } - }; - main(); - }, - ); - if (!convertedHEIC || convertedHEIC?.size === 0) { - logError( - Error(`converted heic fileSize is Zero`), - "converted heic fileSize is Zero", - { - originalFileSize: convertBytesToHumanReadable( - fileBlob?.size ?? 0, - ), - convertedFileSize: convertBytesToHumanReadable( - convertedHEIC?.size ?? 0, - ), - }, - ); - } - await new Promise((resolve) => { - setTimeout( - () => resolve(null), - BREATH_TIME_IN_MICROSECONDS, - ); - }); - this.workerPool.push(convertWorker); - return convertedHEIC; - } catch (e) { - logError(e, "heic conversion failed"); - convertWorker.terminate(); - this.workerPool.push(getDedicatedConvertWorker()); - throw e; - } - }, WAIT_TIME_BEFORE_NEXT_ATTEMPT_IN_MICROSECONDS), - ); - try { - return await response.promise; - } catch (e) { - if (e.message === CustomError.REQUEST_CANCELLED) { - // ignore - return null; - } - throw e; - } - } -} - -export default new HEICConverter(); diff --git a/web/apps/cast/src/types/cache/index.ts b/web/apps/cast/src/types/cache/index.ts deleted file mode 100644 index 8980ca08f..000000000 --- a/web/apps/cast/src/types/cache/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface LimitedCacheStorage { - open: (cacheName: string) => Promise; - delete: (cacheName: string) => Promise; -} - -export interface LimitedCache { - match: (key: string) => Promise; - put: (key: string, data: Response) => Promise; - delete: (key: string) => Promise; -} diff --git a/web/apps/cast/src/types/cast/index.ts b/web/apps/cast/src/types/cast/index.ts deleted file mode 100644 index f082e433e..000000000 --- a/web/apps/cast/src/types/cast/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface CastPayload { - collectionID: number; - collectionKey: string; - castToken: string; -} diff --git a/web/apps/cast/src/types/gallery/index.ts b/web/apps/cast/src/types/gallery/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/apps/cast/src/types/upload/index.ts b/web/apps/cast/src/types/upload/index.ts index 0d38f6190..ef44b4a23 100644 --- a/web/apps/cast/src/types/upload/index.ts +++ b/web/apps/cast/src/types/upload/index.ts @@ -3,7 +3,6 @@ import { LocalFileAttributes, } from "@ente/shared/crypto/types"; import { FILE_TYPE } from "constants/file"; -import { Collection } from "types/collection"; import { FilePublicMagicMetadata, FilePublicMagicMetadataProps, @@ -39,24 +38,6 @@ export interface Metadata { deviceFolder?: string; } -export interface Location { - latitude: number; - longitude: number; -} - -export interface ParsedMetadataJSON { - creationTime: number; - modificationTime: number; - latitude: number; - longitude: number; -} - -export interface MultipartUploadURLs { - objectKey: string; - partURLs: string[]; - completeURL: string; -} - export interface FileTypeInfo { fileType: FILE_TYPE; exactType: string; @@ -65,43 +46,6 @@ export interface FileTypeInfo { videoType?: string; } -/* - * ElectronFile is a custom interface that is used to represent - * any file on disk as a File-like object in the Electron desktop app. - * - * This was added to support the auto-resuming of failed uploads - * which needed absolute paths to the files which the - * normal File interface does not provide. - */ -export interface ElectronFile { - name: string; - path: string; - size: number; - lastModified: number; - stream: () => Promise>; - blob: () => Promise; - arrayBuffer: () => Promise; -} - -export interface UploadAsset { - isLivePhoto?: boolean; - file?: File | ElectronFile; - livePhotoAssets?: LivePhotoAssets; - isElectron?: boolean; -} -export interface LivePhotoAssets { - image: globalThis.File | ElectronFile; - video: globalThis.File | ElectronFile; -} - -export interface FileWithCollection extends UploadAsset { - localID: number; - collection?: Collection; - collectionID?: number; -} - -export type ParsedMetadataJSONMap = Map; - export interface UploadURL { url: string; objectKey: string; diff --git a/web/apps/cast/src/types/upload/ui.ts b/web/apps/cast/src/types/upload/ui.ts deleted file mode 100644 index bce381213..000000000 --- a/web/apps/cast/src/types/upload/ui.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { UPLOAD_RESULT, UPLOAD_STAGES } from "constants/upload"; - -export type FileID = number; -export type FileName = string; - -export type PercentageUploaded = number; -export type UploadFileNames = Map; - -export interface UploadCounter { - finished: number; - total: number; -} - -export interface InProgressUpload { - localFileID: FileID; - progress: PercentageUploaded; -} - -export interface FinishedUpload { - localFileID: FileID; - result: UPLOAD_RESULT; -} - -export type InProgressUploads = Map; - -export type FinishedUploads = Map; - -export type SegregatedFinishedUploads = Map; - -export interface ProgressUpdater { - setPercentComplete: React.Dispatch>; - setUploadCounter: React.Dispatch>; - setUploadStage: React.Dispatch>; - setInProgressUploads: React.Dispatch< - React.SetStateAction - >; - setFinishedUploads: React.Dispatch< - React.SetStateAction - >; - setUploadFilenames: React.Dispatch>; - setHasLivePhotos: React.Dispatch>; - setUploadProgressView: React.Dispatch>; -} diff --git a/web/apps/cast/src/utils/collection/index.ts b/web/apps/cast/src/utils/collection/index.ts deleted file mode 100644 index bd6c2791d..000000000 --- a/web/apps/cast/src/utils/collection/index.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; -import { User } from "@ente/shared/user/types"; -import { - CollectionSummaryType, - CollectionType, - HIDE_FROM_COLLECTION_BAR_TYPES, - OPTIONS_NOT_HAVING_COLLECTION_TYPES, -} from "constants/collection"; -import { COLLECTION_ROLE, Collection } from "types/collection"; -import { SUB_TYPE, VISIBILITY_STATE } from "types/magicMetadata"; - -export enum COLLECTION_OPS_TYPE { - ADD, - MOVE, - REMOVE, - RESTORE, - UNHIDE, -} - -export function getSelectedCollection( - collectionID: number, - collections: Collection[], -) { - return collections.find((collection) => collection.id === collectionID); -} - -export const shouldShowOptions = (type: CollectionSummaryType) => { - return !OPTIONS_NOT_HAVING_COLLECTION_TYPES.has(type); -}; -export const showEmptyTrashQuickOption = (type: CollectionSummaryType) => { - return type === CollectionSummaryType.trash; -}; -export const showDownloadQuickOption = (type: CollectionSummaryType) => { - return ( - type === CollectionSummaryType.folder || - type === CollectionSummaryType.favorites || - type === CollectionSummaryType.album || - type === CollectionSummaryType.uncategorized || - type === CollectionSummaryType.hiddenItems || - type === CollectionSummaryType.incomingShareViewer || - type === CollectionSummaryType.incomingShareCollaborator || - type === CollectionSummaryType.outgoingShare || - type === CollectionSummaryType.sharedOnlyViaLink || - type === CollectionSummaryType.archived || - type === CollectionSummaryType.pinned - ); -}; -export const showShareQuickOption = (type: CollectionSummaryType) => { - return ( - type === CollectionSummaryType.folder || - type === CollectionSummaryType.album || - type === CollectionSummaryType.outgoingShare || - type === CollectionSummaryType.sharedOnlyViaLink || - type === CollectionSummaryType.archived || - type === CollectionSummaryType.incomingShareViewer || - type === CollectionSummaryType.incomingShareCollaborator || - type === CollectionSummaryType.pinned - ); -}; -export const shouldBeShownOnCollectionBar = (type: CollectionSummaryType) => { - return !HIDE_FROM_COLLECTION_BAR_TYPES.has(type); -}; - -export const getUserOwnedCollections = (collections: Collection[]) => { - const user: User = getData(LS_KEYS.USER); - if (!user?.id) { - throw Error("user missing"); - } - return collections.filter((collection) => collection.owner.id === user.id); -}; - -export const isDefaultHiddenCollection = (collection: Collection) => - collection.magicMetadata?.data.subType === SUB_TYPE.DEFAULT_HIDDEN; - -export const isHiddenCollection = (collection: Collection) => - collection.magicMetadata?.data.visibility === VISIBILITY_STATE.HIDDEN; - -export const isQuickLinkCollection = (collection: Collection) => - collection.magicMetadata?.data.subType === SUB_TYPE.QUICK_LINK_COLLECTION; - -export function isOutgoingShare(collection: Collection, user: User): boolean { - return collection.owner.id === user.id && collection.sharees?.length > 0; -} - -export function isIncomingShare(collection: Collection, user: User) { - return collection.owner.id !== user.id; -} - -export function isIncomingViewerShare(collection: Collection, user: User) { - const sharee = collection.sharees?.find((sharee) => sharee.id === user.id); - return sharee?.role === COLLECTION_ROLE.VIEWER; -} - -export function isIncomingCollabShare(collection: Collection, user: User) { - const sharee = collection.sharees?.find((sharee) => sharee.id === user.id); - return sharee?.role === COLLECTION_ROLE.COLLABORATOR; -} - -export function isSharedOnlyViaLink(collection: Collection) { - return collection.publicURLs?.length && !collection.sharees?.length; -} - -export function isValidMoveTarget( - sourceCollectionID: number, - targetCollection: Collection, - user: User, -) { - return ( - sourceCollectionID !== targetCollection.id && - !isHiddenCollection(targetCollection) && - !isQuickLinkCollection(targetCollection) && - !isIncomingShare(targetCollection, user) - ); -} - -export function isValidReplacementAlbum( - collection: Collection, - user: User, - wantedCollectionName: string, -) { - return ( - collection.name === wantedCollectionName && - (collection.type === CollectionType.album || - collection.type === CollectionType.folder) && - !isHiddenCollection(collection) && - !isQuickLinkCollection(collection) && - !isIncomingShare(collection, user) - ); -} - -export function getCollectionNameMap( - collections: Collection[], -): Map { - return new Map( - collections.map((collection) => [collection.id, collection.name]), - ); -} - -export function getNonHiddenCollections( - collections: Collection[], -): Collection[] { - return collections.filter((collection) => !isHiddenCollection(collection)); -} - -export function getHiddenCollections(collections: Collection[]): Collection[] { - return collections.filter((collection) => isHiddenCollection(collection)); -} diff --git a/web/apps/cast/src/utils/comlink/ComlinkConvertWorker.ts b/web/apps/cast/src/utils/comlink/ComlinkConvertWorker.ts deleted file mode 100644 index dc15136d9..000000000 --- a/web/apps/cast/src/utils/comlink/ComlinkConvertWorker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { runningInBrowser } from "@ente/shared/platform"; -import { Remote } from "comlink"; -import { DedicatedConvertWorker } from "worker/convert.worker"; -import { ComlinkWorker } from "./comlinkWorker"; - -class ComlinkConvertWorker { - private comlinkWorkerInstance: Remote; - - async getInstance() { - if (!this.comlinkWorkerInstance) { - this.comlinkWorkerInstance = - await getDedicatedConvertWorker().remote; - } - return this.comlinkWorkerInstance; - } -} - -export const getDedicatedConvertWorker = () => { - if (runningInBrowser()) { - const cryptoComlinkWorker = new ComlinkWorker< - typeof DedicatedConvertWorker - >( - "ente-convert-worker", - new Worker(new URL("worker/convert.worker.ts", import.meta.url)), - ); - return cryptoComlinkWorker; - } -}; - -export default new ComlinkConvertWorker(); diff --git a/web/apps/cast/src/utils/comlink/comlinkWorker.ts b/web/apps/cast/src/utils/comlink/comlinkWorker.ts index 9c1aacff6..e924a3a96 100644 --- a/web/apps/cast/src/utils/comlink/comlinkWorker.ts +++ b/web/apps/cast/src/utils/comlink/comlinkWorker.ts @@ -1,6 +1,5 @@ import { addLocalLog } from "@ente/shared/logging"; import { Remote, wrap } from "comlink"; -// import { WorkerElectronCacheStorageClient } from 'services/workerElectronCache/client'; export class ComlinkWorker InstanceType> { public remote: Promise>>; @@ -17,7 +16,6 @@ export class ComlinkWorker InstanceType> { addLocalLog(() => `Initiated ${this.name}`); const comlink = wrap(this.worker); this.remote = new comlink() as Promise>>; - // expose(WorkerElectronCacheStorageClient, this.worker); } public terminate() { diff --git a/web/apps/cast/src/utils/file/blob.ts b/web/apps/cast/src/utils/file/blob.ts deleted file mode 100644 index cb2e8c7a2..000000000 --- a/web/apps/cast/src/utils/file/blob.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const readAsDataURL = (blob) => - new Promise((resolve, reject) => { - const fileReader = new FileReader(); - fileReader.onload = () => resolve(fileReader.result as string); - fileReader.onerror = () => reject(fileReader.error); - fileReader.readAsDataURL(blob); - }); - -export const readAsText = (blob) => - new Promise((resolve, reject) => { - const fileReader = new FileReader(); - fileReader.onload = () => resolve(fileReader.result as string); - fileReader.onerror = () => reject(fileReader.error); - fileReader.readAsText(blob); - }); diff --git a/web/apps/cast/src/utils/file/index.ts b/web/apps/cast/src/utils/file/index.ts index d3de7b76f..9b9c8e21e 100644 --- a/web/apps/cast/src/utils/file/index.ts +++ b/web/apps/cast/src/utils/file/index.ts @@ -1,14 +1,6 @@ import { logError } from "@ente/shared/sentry"; -import { - FILE_TYPE, - RAW_FORMATS, - SUPPORTED_RAW_FORMATS, - TYPE_HEIC, - TYPE_HEIF, -} from "constants/file"; +import { FILE_TYPE, RAW_FORMATS } from "constants/file"; import CastDownloadManager from "services/castDownloadManager"; -import * as ffmpegService from "services/ffmpeg/ffmpegService"; -import heicConversionService from "services/heicConversionService"; import { decodeLivePhoto } from "services/livePhotoService"; import { getFileType } from "services/typeDetectionService"; import { @@ -17,20 +9,8 @@ import { FileMagicMetadata, FilePublicMagicMetadata, } from "types/file"; -import { isArchivedFile } from "utils/magicMetadata"; - -import { CustomError } from "@ente/shared/error"; -import { addLocalLog, addLogLine } from "@ente/shared/logging"; -import { isPlaybackPossible } from "@ente/shared/media/video-playback"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; -import { User } from "@ente/shared/user/types"; -import { convertBytesToHumanReadable } from "@ente/shared/utils/size"; -import isElectron from "is-electron"; -import { FileTypeInfo } from "types/upload"; import ComlinkCryptoWorker from "utils/comlink/ComlinkCryptoWorker"; -const WAIT_TIME_IMAGE_CONVERSION = 30 * 1000; - export function sortFiles(files: EnteFile[], sortAsc = false) { // sort based on the time of creation time of the file, // for files with same creation time, sort based on the time of last modification @@ -46,20 +26,6 @@ export function sortFiles(files: EnteFile[], sortAsc = false) { }); } -export function sortTrashFiles(files: EnteFile[]) { - return files.sort((a, b) => { - if (a.deleteBy === b.deleteBy) { - if (a.metadata.creationTime === b.metadata.creationTime) { - return ( - b.metadata.modificationTime - a.metadata.modificationTime - ); - } - return b.metadata.creationTime - a.metadata.creationTime; - } - return a.deleteBy - b.deleteBy; - }); -} - export async function decryptFile( file: EncryptedEnteFile, collectionKey: string, @@ -154,176 +120,6 @@ export function generateStreamFromArrayBuffer(data: Uint8Array) { }); } -export async function getRenderableFileURL(file: EnteFile, fileBlob: Blob) { - switch (file.metadata.fileType) { - case FILE_TYPE.IMAGE: { - const convertedBlob = await getRenderableImage( - file.metadata.title, - fileBlob, - ); - const { originalURL, convertedURL } = getFileObjectURLs( - fileBlob, - convertedBlob, - ); - return { - converted: [convertedURL], - original: [originalURL], - }; - } - case FILE_TYPE.LIVE_PHOTO: { - return await getRenderableLivePhotoURL(file, fileBlob); - } - case FILE_TYPE.VIDEO: { - const convertedBlob = await getPlayableVideo( - file.metadata.title, - fileBlob, - ); - const { originalURL, convertedURL } = getFileObjectURLs( - fileBlob, - convertedBlob, - ); - return { - converted: [convertedURL], - original: [originalURL], - }; - } - default: { - const previewURL = await createTypedObjectURL( - fileBlob, - file.metadata.title, - ); - return { - converted: [previewURL], - original: [previewURL], - }; - } - } -} - -async function getRenderableLivePhotoURL( - file: EnteFile, - fileBlob: Blob, -): Promise<{ original: string[]; converted: string[] }> { - const livePhoto = await decodeLivePhoto(file, fileBlob); - const imageBlob = new Blob([livePhoto.image]); - const videoBlob = new Blob([livePhoto.video]); - const convertedImageBlob = await getRenderableImage( - livePhoto.imageNameTitle, - imageBlob, - ); - const convertedVideoBlob = await getPlayableVideo( - livePhoto.videoNameTitle, - videoBlob, - true, - ); - const { originalURL: originalImageURL, convertedURL: convertedImageURL } = - getFileObjectURLs(imageBlob, convertedImageBlob); - - const { originalURL: originalVideoURL, convertedURL: convertedVideoURL } = - getFileObjectURLs(videoBlob, convertedVideoBlob); - return { - converted: [convertedImageURL, convertedVideoURL], - original: [originalImageURL, originalVideoURL], - }; -} - -export async function getPlayableVideo( - videoNameTitle: string, - videoBlob: Blob, - forceConvert = false, -) { - try { - const isPlayable = await isPlaybackPossible( - URL.createObjectURL(videoBlob), - ); - if (isPlayable && !forceConvert) { - return videoBlob; - } else { - if (!forceConvert && !isElectron()) { - return null; - } - addLogLine( - "video format not supported, converting it name:", - videoNameTitle, - ); - const mp4ConvertedVideo = await ffmpegService.convertToMP4( - new File([videoBlob], videoNameTitle), - ); - addLogLine("video successfully converted", videoNameTitle); - return new Blob([await mp4ConvertedVideo.arrayBuffer()]); - } - } catch (e) { - addLogLine("video conversion failed", videoNameTitle); - logError(e, "video conversion failed"); - return null; - } -} - -export async function getRenderableImage(fileName: string, imageBlob: Blob) { - let fileTypeInfo: FileTypeInfo; - try { - const tempFile = new File([imageBlob], fileName); - fileTypeInfo = await getFileType(tempFile); - addLocalLog(() => `file type info: ${JSON.stringify(fileTypeInfo)}`); - const { exactType } = fileTypeInfo; - let convertedImageBlob: Blob; - if (isRawFile(exactType)) { - try { - if (!isSupportedRawFormat(exactType)) { - throw Error(CustomError.UNSUPPORTED_RAW_FORMAT); - } - - if (!isElectron()) { - throw Error(CustomError.NOT_AVAILABLE_ON_WEB); - } - addLogLine( - `RawConverter called for ${fileName}-${convertBytesToHumanReadable( - imageBlob.size, - )}`, - ); - // convertedImageBlob = await imageProcessor.convertToJPEG( - // imageBlob, - // fileName - // ); - addLogLine(`${fileName} successfully converted`); - } catch (e) { - try { - if (!isFileHEIC(exactType)) { - throw e; - } - addLogLine( - `HEICConverter called for ${fileName}-${convertBytesToHumanReadable( - imageBlob.size, - )}`, - ); - convertedImageBlob = - await heicConversionService.convert(imageBlob); - addLogLine(`${fileName} successfully converted`); - } catch (e) { - throw Error(CustomError.NON_PREVIEWABLE_FILE); - } - } - return convertedImageBlob; - } else { - return imageBlob; - } - } catch (e) { - logError(e, "get Renderable Image failed", { fileTypeInfo }); - return null; - } -} - -export function isFileHEIC(exactType: string) { - return ( - exactType.toLowerCase().endsWith(TYPE_HEIC) || - exactType.toLowerCase().endsWith(TYPE_HEIF) - ); -} - -export function isRawFile(exactType: string) { - return RAW_FORMATS.includes(exactType.toLowerCase()); -} - export function isRawFileFromFileName(fileName: string) { for (const rawFormat of RAW_FORMATS) { if (fileName.toLowerCase().endsWith(rawFormat)) { @@ -333,10 +129,6 @@ export function isRawFileFromFileName(fileName: string) { return false; } -export function isSupportedRawFormat(exactType: string) { - return SUPPORTED_RAW_FORMATS.includes(exactType.toLowerCase()); -} - export function mergeMetadata(files: EnteFile[]): EnteFile[] { return files.map((file) => { if (file.pubMagicMetadata?.data.editedTime) { @@ -350,187 +142,24 @@ export function mergeMetadata(files: EnteFile[]): EnteFile[] { }); } -export async function getFileFromURL(fileURL: string) { - const fileBlob = await (await fetch(fileURL)).blob(); - const fileFile = new File([fileBlob], "temp"); - return fileFile; -} - -export function getUniqueFiles(files: EnteFile[]) { - const idSet = new Set(); - const uniqueFiles = files.filter((file) => { - if (!idSet.has(file.id)) { - idSet.add(file.id); - return true; - } else { - return false; - } - }); - - return uniqueFiles; -} - -export const isImageOrVideo = (fileType: FILE_TYPE) => - [FILE_TYPE.IMAGE, FILE_TYPE.VIDEO].includes(fileType); - -export const getArchivedFiles = (files: EnteFile[]) => { - return files.filter(isArchivedFile).map((file) => file.id); -}; - -export const createTypedObjectURL = async (blob: Blob, fileName: string) => { - const type = await getFileType(new File([blob], fileName)); - return URL.createObjectURL(new Blob([blob], { type: type.mimeType })); -}; - -export const getUserOwnedFiles = (files: EnteFile[]) => { - const user: User = getData(LS_KEYS.USER); - if (!user?.id) { - throw Error("user missing"); - } - return files.filter((file) => file.ownerID === user.id); -}; - -// doesn't work on firefox -export const copyFileToClipboard = async (fileUrl: string) => { - const canvas = document.createElement("canvas"); - const canvasCTX = canvas.getContext("2d"); - const image = new Image(); - - const blobPromise = new Promise((resolve, reject) => { - let timeout: NodeJS.Timeout = null; - try { - image.setAttribute("src", fileUrl); - image.onload = () => { - canvas.width = image.width; - canvas.height = image.height; - canvasCTX.drawImage(image, 0, 0, image.width, image.height); - canvas.toBlob( - (blob) => { - resolve(blob); - }, - "image/png", - 1, - ); - - clearTimeout(timeout); - }; - } catch (e) { - void logError(e, "failed to copy to clipboard"); - reject(e); - } finally { - clearTimeout(timeout); - } - timeout = setTimeout( - () => reject(Error(CustomError.WAIT_TIME_EXCEEDED)), - WAIT_TIME_IMAGE_CONVERSION, - ); - }); - - const { ClipboardItem } = window; - - await navigator.clipboard - .write([new ClipboardItem({ "image/png": blobPromise })]) - .catch((e) => logError(e, "failed to copy to clipboard")); -}; - -export function getLatestVersionFiles(files: EnteFile[]) { - const latestVersionFiles = new Map(); - files.forEach((file) => { - const uid = `${file.collectionID}-${file.id}`; - if ( - !latestVersionFiles.has(uid) || - latestVersionFiles.get(uid).updationTime < file.updationTime - ) { - latestVersionFiles.set(uid, file); - } - }); - return Array.from(latestVersionFiles.values()).filter( - (file) => !file.isDeleted, - ); -} - -export function getPersonalFiles(files: EnteFile[], user: User) { - if (!user?.id) { - throw Error("user missing"); - } - return files.filter((file) => file.ownerID === user.id); -} - -export function getIDBasedSortedFiles(files: EnteFile[]) { - return files.sort((a, b) => a.id - b.id); -} - -export function constructFileToCollectionMap(files: EnteFile[]) { - const fileToCollectionsMap = new Map(); - (files ?? []).forEach((file) => { - if (!fileToCollectionsMap.get(file.id)) { - fileToCollectionsMap.set(file.id, []); - } - fileToCollectionsMap.get(file.id).push(file.collectionID); - }); - return fileToCollectionsMap; -} - -export const shouldShowAvatar = (file: EnteFile, user: User) => { - if (!file || !user) { - return false; - } - // is Shared file - else if (file.ownerID !== user.id) { - return true; - } - // is public collected file - else if ( - file.ownerID === user.id && - file.pubMagicMetadata?.data?.uploaderName - ) { - return true; - } else { - return false; - } -}; - export const getPreviewableImage = async ( file: EnteFile, castToken: string, ): Promise => { try { - let fileBlob: Blob; - const fileURL = - await CastDownloadManager.getCachedOriginalFile(file)[0]; - if (!fileURL) { - fileBlob = await new Response( - await CastDownloadManager.downloadFile(castToken, file), - ).blob(); - } else { - fileBlob = await (await fetch(fileURL)).blob(); - } + let fileBlob = await new Response( + await CastDownloadManager.downloadFile(castToken, file), + ).blob(); if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { const livePhoto = await decodeLivePhoto(file, fileBlob); fileBlob = new Blob([livePhoto.image]); } - const convertedBlob = await getRenderableImage( - file.metadata.title, - fileBlob, - ); - fileBlob = convertedBlob; const fileType = await getFileType( new File([fileBlob], file.metadata.title), ); - fileBlob = new Blob([fileBlob], { type: fileType.mimeType }); return fileBlob; } catch (e) { logError(e, "failed to download file"); } }; - -const getFileObjectURLs = (originalBlob: Blob, convertedBlob: Blob) => { - const originalURL = URL.createObjectURL(originalBlob); - const convertedURL = convertedBlob - ? convertedBlob === originalBlob - ? originalURL - : URL.createObjectURL(convertedBlob) - : null; - return { originalURL, convertedURL }; -}; diff --git a/web/apps/cast/src/utils/file/livePhoto.ts b/web/apps/cast/src/utils/file/livePhoto.ts deleted file mode 100644 index 7d687217c..000000000 --- a/web/apps/cast/src/utils/file/livePhoto.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { FILE_TYPE } from "constants/file"; -import { getFileExtension } from "utils/file"; - -const IMAGE_EXTENSIONS = [ - "heic", - "heif", - "jpeg", - "jpg", - "png", - "gif", - "bmp", - "tiff", - "webp", -]; - -const VIDEO_EXTENSIONS = [ - "mov", - "mp4", - "m4v", - "avi", - "wmv", - "flv", - "mkv", - "webm", - "3gp", - "3g2", - "avi", - "ogv", - "mpg", - "mp", -]; - -export function getFileTypeFromExtensionForLivePhotoClustering( - filename: string, -) { - const extension = getFileExtension(filename)?.toLowerCase(); - if (IMAGE_EXTENSIONS.includes(extension)) { - return FILE_TYPE.IMAGE; - } else if (VIDEO_EXTENSIONS.includes(extension)) { - return FILE_TYPE.VIDEO; - } -} diff --git a/web/apps/cast/src/utils/magicMetadata/index.ts b/web/apps/cast/src/utils/magicMetadata/index.ts deleted file mode 100644 index 7beb45772..000000000 --- a/web/apps/cast/src/utils/magicMetadata/index.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Collection } from "types/collection"; -import { EnteFile } from "types/file"; -import { MagicMetadataCore, VISIBILITY_STATE } from "types/magicMetadata"; -import ComlinkCryptoWorker from "utils/comlink/ComlinkCryptoWorker"; - -export function isArchivedFile(item: EnteFile): boolean { - if (!item || !item.magicMetadata || !item.magicMetadata.data) { - return false; - } - return item.magicMetadata.data.visibility === VISIBILITY_STATE.ARCHIVED; -} - -export function isArchivedCollection(item: Collection): boolean { - if (!item) { - return false; - } - - if (item.magicMetadata && item.magicMetadata.data) { - return item.magicMetadata.data.visibility === VISIBILITY_STATE.ARCHIVED; - } - - if (item.sharedMagicMetadata && item.sharedMagicMetadata.data) { - return ( - item.sharedMagicMetadata.data.visibility === - VISIBILITY_STATE.ARCHIVED - ); - } - return false; -} - -export function isPinnedCollection(item: Collection) { - if ( - !item || - !item.magicMetadata || - !item.magicMetadata.data || - typeof item.magicMetadata.data === "string" || - typeof item.magicMetadata.data.order === "undefined" - ) { - return false; - } - return item.magicMetadata.data.order !== 0; -} - -export async function updateMagicMetadata( - magicMetadataUpdates: T, - originalMagicMetadata?: MagicMetadataCore, - decryptionKey?: string, -): Promise> { - const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - - if (!originalMagicMetadata) { - originalMagicMetadata = getNewMagicMetadata(); - } - - if (typeof originalMagicMetadata?.data === "string") { - originalMagicMetadata.data = await cryptoWorker.decryptMetadata( - originalMagicMetadata.data, - originalMagicMetadata.header, - decryptionKey, - ); - } - // copies the existing magic metadata properties of the files and updates the visibility value - // The expected behavior while updating magic metadata is to let the existing property as it is and update/add the property you want - const magicMetadataProps: T = { - ...originalMagicMetadata.data, - ...magicMetadataUpdates, - }; - - const nonEmptyMagicMetadataProps = - getNonEmptyMagicMetadataProps(magicMetadataProps); - - const magicMetadata = { - ...originalMagicMetadata, - data: nonEmptyMagicMetadataProps, - count: Object.keys(nonEmptyMagicMetadataProps).length, - }; - - return magicMetadata; -} - -export const getNewMagicMetadata = (): MagicMetadataCore => { - return { - version: 1, - data: null, - header: null, - count: 0, - }; -}; - -export const getNonEmptyMagicMetadataProps = (magicMetadataProps: T): T => { - return Object.fromEntries( - Object.entries(magicMetadataProps).filter( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ([_, v]) => v !== null && v !== undefined, - ), - ) as T; -}; diff --git a/web/apps/cast/src/worker/convert.worker.ts b/web/apps/cast/src/worker/convert.worker.ts deleted file mode 100644 index a805752ac..000000000 --- a/web/apps/cast/src/worker/convert.worker.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as Comlink from "comlink"; -import { convertHEIC } from "services/wasmHeicConverter/wasmHEICConverterClient"; - -export class DedicatedConvertWorker { - async convertHEIC(fileBlob: Blob, format: string) { - return convertHEIC(fileBlob, format); - } -} - -Comlink.expose(DedicatedConvertWorker, self);