|
@@ -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<number, Promise<string>>();
|
|
|
-
|
|
|
- private fileDownloadProgress = new Map<number, number>();
|
|
|
-
|
|
|
- private progressUpdater: (value: Map<number, number>) => void;
|
|
|
-
|
|
|
- setProgressUpdater(progressUpdater: (value: Map<number, number>) => 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(
|
|
@@ -209,10 +58,6 @@ class CastDownloadManager {
|
|
|
// 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 +99,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();
|