2021-05-30 16:56:48 +00:00
|
|
|
import { getToken } from 'utils/common/key';
|
|
|
|
import { getFileUrl, getThumbnailUrl } from 'utils/common/apiUtil';
|
2021-04-03 04:36:15 +00:00
|
|
|
import CryptoWorker from 'utils/crypto';
|
2021-05-30 16:56:48 +00:00
|
|
|
import { fileIsHEIC, convertHEIC2JPEG } from 'utils/file';
|
2021-05-29 06:27:52 +00:00
|
|
|
import HTTPService from './HTTPService';
|
2021-05-30 16:56:48 +00:00
|
|
|
import { File } from './fileService';
|
2021-06-12 17:14:21 +00:00
|
|
|
import { logError } from 'utils/sentry';
|
2021-08-11 08:44:17 +00:00
|
|
|
import JSZip from 'jszip';
|
|
|
|
import { FILE_TYPE } from 'pages/gallery';
|
|
|
|
|
|
|
|
class MotionPhoto {
|
|
|
|
public imageBlob: Promise<Uint8Array>;
|
|
|
|
public videoBlob: Promise<Uint8Array>;
|
|
|
|
|
|
|
|
public static CreateMotitionPhoto(zipBlob: Blob) {
|
|
|
|
return JSZip.loadAsync(zipBlob, { createFolders: true })
|
|
|
|
.then(function (zip) {
|
|
|
|
let instnace = new MotionPhoto();
|
|
|
|
Object.keys(zip.files).forEach(function (filename) {
|
|
|
|
if (filename.startsWith("image")) {
|
|
|
|
instnace.imageBlob = zip.files[filename].async('uint8array');
|
|
|
|
} else if (filename.startsWith("video")) {
|
|
|
|
instnace.videoBlob = zip.files[filename].async('uint8array');
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return instnace;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2021-02-17 05:01:31 +00:00
|
|
|
|
|
|
|
class DownloadManager {
|
2021-07-21 06:08:41 +00:00
|
|
|
private fileDownloads = new Map<string, string>();
|
2021-05-29 06:27:52 +00:00
|
|
|
|
2021-06-13 15:53:33 +00:00
|
|
|
private thumbnailDownloads = new Map<number, string>();
|
2021-02-17 07:17:36 +00:00
|
|
|
|
2021-04-27 09:17:38 +00:00
|
|
|
public async getPreview(file: File) {
|
2021-02-17 05:01:31 +00:00
|
|
|
try {
|
2021-03-16 06:49:34 +00:00
|
|
|
const token = getToken();
|
|
|
|
if (!token) {
|
|
|
|
return null;
|
|
|
|
}
|
2021-02-17 05:01:31 +00:00
|
|
|
const cache = await caches.open('thumbs');
|
|
|
|
const cacheResp: Response = await cache.match(file.id.toString());
|
|
|
|
if (cacheResp) {
|
|
|
|
return URL.createObjectURL(await cacheResp.blob());
|
|
|
|
}
|
2021-02-17 07:17:36 +00:00
|
|
|
if (!this.thumbnailDownloads.get(file.id)) {
|
2021-06-13 15:53:33 +00:00
|
|
|
const download = await this.downloadThumb(token, cache, file);
|
2021-02-17 07:17:36 +00:00
|
|
|
this.thumbnailDownloads.set(file.id, download);
|
2021-02-17 05:01:31 +00:00
|
|
|
}
|
2021-02-17 07:17:36 +00:00
|
|
|
return await this.thumbnailDownloads.get(file.id);
|
2021-02-17 05:01:31 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'get preview Failed');
|
2021-02-17 05:01:31 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-13 15:53:33 +00:00
|
|
|
downloadThumb = async (token: string, cache: Cache, file: File) => {
|
|
|
|
const resp = await HTTPService.get(
|
|
|
|
getThumbnailUrl(file.id),
|
|
|
|
null,
|
|
|
|
{ 'X-Auth-Token': token },
|
|
|
|
{ responseType: 'arraybuffer' },
|
|
|
|
);
|
|
|
|
const worker = await new CryptoWorker();
|
|
|
|
const decrypted: any = await worker.decryptThumbnail(
|
|
|
|
new Uint8Array(resp.data),
|
|
|
|
await worker.fromB64(file.thumbnail.decryptionHeader),
|
|
|
|
file.key,
|
|
|
|
);
|
|
|
|
try {
|
|
|
|
await cache.put(
|
|
|
|
file.id.toString(),
|
|
|
|
new Response(new Blob([decrypted])),
|
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
// TODO: handle storage full exception.
|
|
|
|
}
|
|
|
|
return URL.createObjectURL(new Blob([decrypted]));
|
|
|
|
}
|
2021-02-17 05:01:31 +00:00
|
|
|
|
2021-07-21 06:08:41 +00:00
|
|
|
getFile = async (file: File, forPreview=false) => {
|
2021-03-14 13:20:00 +00:00
|
|
|
try {
|
2021-07-21 06:08:41 +00:00
|
|
|
if (!this.fileDownloads.get(`${file.id}_${forPreview}`)) {
|
2021-06-13 15:53:33 +00:00
|
|
|
const fileStream = await this.downloadFile(file);
|
2021-07-21 06:08:41 +00:00
|
|
|
let fileBlob= await new Response(fileStream).blob();
|
2021-08-11 08:44:17 +00:00
|
|
|
// unzip motion photo and return fileBlob of the image for preview
|
|
|
|
if (forPreview && file.metadata.fileType == FILE_TYPE.LIVE_PHOTO) {
|
|
|
|
let im = await MotionPhoto.CreateMotitionPhoto(fileBlob);
|
|
|
|
fileBlob = await new Response(await im.imageBlob).blob();
|
|
|
|
}
|
2021-07-21 06:08:41 +00:00
|
|
|
if (forPreview) {
|
|
|
|
if (fileIsHEIC(file.metadata.title)) {
|
|
|
|
fileBlob = await convertHEIC2JPEG(fileBlob);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.fileDownloads.set(`${file.id}_${forPreview}`, URL.createObjectURL(fileBlob));
|
2021-03-14 13:20:00 +00:00
|
|
|
}
|
2021-07-21 06:08:41 +00:00
|
|
|
return this.fileDownloads.get(`${file.id}_${forPreview}`);
|
2021-03-14 13:20:00 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'Failed to get File');
|
2021-02-17 05:01:31 +00:00
|
|
|
}
|
|
|
|
};
|
2021-02-23 10:11:46 +00:00
|
|
|
|
2021-04-27 09:17:38 +00:00
|
|
|
async downloadFile(file: File) {
|
2021-03-08 06:30:27 +00:00
|
|
|
const worker = await new CryptoWorker();
|
2021-03-16 06:49:34 +00:00
|
|
|
const token = getToken();
|
|
|
|
if (!token) {
|
|
|
|
return null;
|
|
|
|
}
|
2021-08-11 08:44:17 +00:00
|
|
|
if (file.metadata.fileType === FILE_TYPE.IMAGE || file.metadata.fileType == FILE_TYPE.LIVE_PHOTO) {
|
2021-03-08 06:30:27 +00:00
|
|
|
const resp = await HTTPService.get(
|
|
|
|
getFileUrl(file.id),
|
2021-03-11 07:19:19 +00:00
|
|
|
null,
|
2021-05-30 16:56:48 +00:00
|
|
|
{ 'X-Auth-Token': token },
|
|
|
|
{ responseType: 'arraybuffer' },
|
2021-03-08 06:30:27 +00:00
|
|
|
);
|
|
|
|
const decrypted: any = await worker.decryptFile(
|
|
|
|
new Uint8Array(resp.data),
|
|
|
|
await worker.fromB64(file.file.decryptionHeader),
|
2021-05-29 06:27:52 +00:00
|
|
|
file.key,
|
2021-03-08 06:30:27 +00:00
|
|
|
);
|
2021-03-29 05:13:59 +00:00
|
|
|
return new ReadableStream({
|
2021-03-29 11:29:01 +00:00
|
|
|
async start(controller: ReadableStreamDefaultController) {
|
2021-07-21 06:08:41 +00:00
|
|
|
controller.enqueue(decrypted);
|
2021-03-29 05:13:59 +00:00
|
|
|
controller.close();
|
|
|
|
},
|
|
|
|
});
|
2021-05-29 06:27:52 +00:00
|
|
|
}
|
|
|
|
const resp = await fetch(getFileUrl(file.id), {
|
|
|
|
headers: {
|
|
|
|
'X-Auth-Token': token,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const reader = resp.body.getReader();
|
|
|
|
const stream = new ReadableStream({
|
|
|
|
async start(controller) {
|
|
|
|
const decryptionHeader = await worker.fromB64(
|
|
|
|
file.file.decryptionHeader,
|
|
|
|
);
|
|
|
|
const fileKey = await worker.fromB64(file.key);
|
|
|
|
const {
|
|
|
|
pullState,
|
|
|
|
decryptionChunkSize,
|
|
|
|
} = await worker.initDecryption(decryptionHeader, fileKey);
|
|
|
|
let data = new Uint8Array();
|
|
|
|
// The following function handles each data chunk
|
|
|
|
function push() {
|
|
|
|
// "done" is a Boolean and value a "Uint8Array"
|
2021-05-30 16:56:48 +00:00
|
|
|
reader.read().then(async ({ done, value }) => {
|
2021-05-29 06:27:52 +00:00
|
|
|
// Is there more data to read?
|
|
|
|
if (!done) {
|
|
|
|
const buffer = new Uint8Array(
|
|
|
|
data.byteLength + value.byteLength,
|
|
|
|
);
|
|
|
|
buffer.set(new Uint8Array(data), 0);
|
|
|
|
buffer.set(
|
|
|
|
new Uint8Array(value),
|
|
|
|
data.byteLength,
|
|
|
|
);
|
|
|
|
if (buffer.length > decryptionChunkSize) {
|
|
|
|
const fileData = buffer.slice(
|
|
|
|
0,
|
|
|
|
decryptionChunkSize,
|
2021-03-11 07:19:19 +00:00
|
|
|
);
|
2021-05-29 06:27:52 +00:00
|
|
|
const {
|
|
|
|
decryptedData,
|
|
|
|
} = await worker.decryptChunk(
|
|
|
|
fileData,
|
|
|
|
pullState,
|
2021-03-11 07:19:19 +00:00
|
|
|
);
|
2021-05-29 06:27:52 +00:00
|
|
|
controller.enqueue(decryptedData);
|
|
|
|
data = buffer.slice(decryptionChunkSize);
|
2021-03-08 06:30:27 +00:00
|
|
|
} else {
|
2021-05-29 06:27:52 +00:00
|
|
|
data = buffer;
|
2021-03-08 06:30:27 +00:00
|
|
|
}
|
2021-05-29 06:27:52 +00:00
|
|
|
push();
|
|
|
|
} else {
|
|
|
|
if (data) {
|
|
|
|
const {
|
|
|
|
decryptedData,
|
|
|
|
} = await worker.decryptChunk(
|
|
|
|
data,
|
|
|
|
pullState,
|
|
|
|
);
|
|
|
|
controller.enqueue(decryptedData);
|
|
|
|
data = null;
|
|
|
|
}
|
|
|
|
controller.close();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-03-08 06:30:27 +00:00
|
|
|
|
2021-05-29 06:27:52 +00:00
|
|
|
push();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return stream;
|
2021-03-08 06:30:27 +00:00
|
|
|
}
|
2021-02-17 05:01:31 +00:00
|
|
|
}
|
|
|
|
|
2021-03-11 07:19:19 +00:00
|
|
|
export default new DownloadManager();
|