Merge branch 'master' into export-v2

This commit is contained in:
Abhinav 2021-11-29 14:56:22 +05:30
commit cff0959cf3
9 changed files with 148 additions and 60 deletions

View file

@ -354,7 +354,7 @@ const PhotoFrame = ({
if (galleryContext.thumbs.has(item.id)) {
url = galleryContext.thumbs.get(item.id);
} else {
url = await DownloadManager.getPreview(item);
url = await DownloadManager.getThumbnail(item);
galleryContext.thumbs.set(item.id, url);
}
updateUrl(item.dataIndex)(url);

View file

@ -0,0 +1,25 @@
import React from 'react';
export default function DownloadIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox={props.viewBox}
width={props.width}
fill="currentColor">
<g>
<rect fill="none" height="24" width="24" />
</g>
<g>
<path d="M5,20h14v-2H5V20z M19,9h-4V3H9v6H5l7,7L19,9z" />
</g>
</svg>
);
}
DownloadIcon.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
};

View file

@ -177,7 +177,7 @@ export default function PreviewCard(props: IProps) {
if (file && !file.msrc) {
const main = async () => {
try {
const url = await DownloadManager.getPreview(file);
const url = await DownloadManager.getThumbnail(file);
if (isMounted.current) {
setImgSrc(url);
thumbs.set(file.id, url);

View file

@ -23,6 +23,7 @@ import {
FIX_CREATION_TIME_VISIBLE_TO_USER_IDS,
User,
} from 'services/userService';
import DownloadIcon from 'components/icons/DownloadIcon';
interface Props {
addToCollectionHelper: (collection: Collection) => void;
@ -34,6 +35,7 @@ interface Props {
deleteFileHelper: (permanent?: boolean) => void;
removeFromCollectionHelper: () => void;
fixTimeHelper: () => void;
downloadHelper: () => void;
count: number;
clearSelection: () => void;
archiveFilesHelper: () => void;
@ -79,6 +81,7 @@ const SelectedFileOptions = ({
setDialogMessage,
setCollectionSelectorAttributes,
deleteFileHelper,
downloadHelper,
count,
clearSelection,
archiveFilesHelper,
@ -190,6 +193,11 @@ const SelectedFileOptions = ({
</IconButton>
</IconWithMessage>
)}
<IconWithMessage message={constants.DOWNLOAD}>
<IconButton onClick={downloadHelper}>
<DownloadIcon />
</IconButton>
</IconWithMessage>
<IconWithMessage message={constants.ADD}>
<IconButton onClick={addToCollection}>
<AddIcon />

View file

@ -50,6 +50,7 @@ import { LoadingOverlay } from 'components/LoadingOverlay';
import PhotoFrame from 'components/PhotoFrame';
import {
changeFilesVisibility,
downloadFiles,
getNonTrashedUniqueUserFiles,
getSelectedFiles,
mergeMetadata,
@ -545,6 +546,14 @@ export default function Gallery() {
clearSelection();
};
const downloadHelper = async () => {
const selectedFiles = getSelectedFiles(selected, files);
clearSelection();
!syncInProgress.current && loadingBar.current?.continuousStart();
await downloadFiles(selectedFiles);
!syncInProgress.current && loadingBar.current.complete();
};
return (
<GalleryContext.Provider
value={{
@ -714,6 +723,7 @@ export default function Gallery() {
)
}
fixTimeHelper={fixTimeHelper}
downloadHelper={downloadHelper}
count={selected.count}
clearSelection={clearSelection}
activeCollection={activeCollection}

View file

@ -1,7 +1,11 @@
import { getToken } from 'utils/common/key';
import { getFileUrl, getThumbnailUrl } from 'utils/common/apiUtil';
import CryptoWorker from 'utils/crypto';
import { generateStreamFromArrayBuffer, convertForPreview } from 'utils/file';
import {
generateStreamFromArrayBuffer,
convertForPreview,
needsConversionForPreview,
} from 'utils/file';
import HTTPService from './HTTPService';
import { File, FILE_TYPE } from './fileService';
import { logError } from 'utils/sentry';
@ -10,27 +14,36 @@ class DownloadManager {
private fileObjectUrlPromise = new Map<string, Promise<string>>();
private thumbnailObjectUrlPromise = new Map<number, Promise<string>>();
public async getPreview(file: File) {
public async getThumbnail(file: File) {
try {
const token = getToken();
if (!token) {
return null;
}
const thumbnailCache = await caches.open('thumbs');
const cacheResp: Response = await thumbnailCache.match(
file.id.toString()
);
if (cacheResp) {
return URL.createObjectURL(await cacheResp.blob());
}
if (!this.thumbnailObjectUrlPromise.get(file.id)) {
const downloadPromise = this.downloadThumb(
token,
thumbnailCache,
file
);
this.thumbnailObjectUrlPromise.set(file.id, downloadPromise);
const downloadPromise = async () => {
const thumbnailCache = await caches.open('thumbs');
const cacheResp: Response = await thumbnailCache.match(
file.id.toString()
);
if (cacheResp) {
return URL.createObjectURL(await cacheResp.blob());
}
const thumb = await this.downloadThumb(token, 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);
@ -39,24 +52,7 @@ class DownloadManager {
}
}
private downloadThumb = async (
token: string,
thumbnailCache: Cache,
file: File
) => {
const thumb = await this.getThumbnail(token, file);
try {
await thumbnailCache.put(
file.id.toString(),
new Response(new Blob([thumb]))
);
} catch (e) {
// TODO: handle storage full exception.
}
return URL.createObjectURL(new Blob([thumb]));
};
getThumbnail = async (token: string, file: File) => {
downloadThumb = async (token: string, file: File) => {
const resp = await HTTPService.get(
getThumbnailUrl(file.id),
null,
@ -73,32 +69,38 @@ class DownloadManager {
};
getFile = async (file: File, forPreview = false) => {
let fileUID: string;
if (file.metadata.fileType === FILE_TYPE.VIDEO) {
fileUID = file.id.toString();
} else {
fileUID = `${file.id}_forPreview=${forPreview}`;
}
const shouldBeConverted = forPreview && needsConversionForPreview(file);
const fileKey = shouldBeConverted
? `${file.id}_converted`
: `${file.id}`;
try {
const getFilePromise = async () => {
const getFilePromise = async (convert: boolean) => {
const fileStream = await this.downloadFile(file);
let fileBlob = await new Response(fileStream).blob();
if (forPreview) {
if (convert) {
fileBlob = await convertForPreview(file, fileBlob);
}
return URL.createObjectURL(fileBlob);
};
if (!this.fileObjectUrlPromise.get(fileUID)) {
this.fileObjectUrlPromise.set(fileUID, getFilePromise());
if (!this.fileObjectUrlPromise.get(fileKey)) {
this.fileObjectUrlPromise.set(
fileKey,
getFilePromise(shouldBeConverted)
);
}
return await this.fileObjectUrlPromise.get(fileUID);
const fileURL = await this.fileObjectUrlPromise.get(fileKey);
return fileURL;
} catch (e) {
this.fileObjectUrlPromise.delete(fileUID);
this.fileObjectUrlPromise.delete(fileKey);
logError(e, 'Failed to get File');
throw e;
}
};
public async getCachedOriginalFile(file: File) {
return await this.fileObjectUrlPromise.get(file.id.toString());
}
async downloadFile(file: File) {
const worker = await new CryptoWorker();
const token = getToken();

View file

@ -67,7 +67,7 @@ export async function replaceThumbnail(
current: idx,
total: largeThumbnailFiles.length,
});
const originalThumbnail = await downloadManager.getThumbnail(
const originalThumbnail = await downloadManager.downloadThumb(
token,
file
);

View file

@ -4,7 +4,7 @@ import { logError } from 'utils/sentry';
import { BLACK_THUMBNAIL_BASE64 } from '../../../public/images/black-thumbnail-b64';
import FFmpegService from 'services/ffmpegService';
import { convertToHumanReadable } from 'utils/billingUtil';
import { fileIsHEIC } from 'utils/file';
import { isFileHEIC } from 'utils/file';
import { FileTypeInfo } from './readFileService';
const MAX_THUMBNAIL_DIMENSION = 720;
@ -31,7 +31,7 @@ export async function generateThumbnail(
let thumbnail: Uint8Array;
try {
if (fileTypeInfo.fileType === FILE_TYPE.IMAGE) {
const isHEIC = fileIsHEIC(fileTypeInfo.exactType);
const isHEIC = isFileHEIC(fileTypeInfo.exactType);
canvas = await generateImageThumbnail(worker, file, isHEIC);
} else {
try {

View file

@ -11,7 +11,7 @@ import {
} from 'services/fileService';
import { decodeMotionPhoto } from 'services/motionPhotoService';
import { getMimeTypeFromBlob } from 'services/upload/readFileService';
import DownloadManger from 'services/downloadManager';
import DownloadManager from 'services/downloadManager';
import { logError } from 'utils/sentry';
import { User } from 'services/userService';
import CryptoWorker from 'utils/crypto';
@ -40,18 +40,35 @@ export function downloadAsFile(filename: string, content: string) {
a.remove();
}
export async function downloadFile(file) {
export async function downloadFile(file: File) {
const a = document.createElement('a');
a.style.display = 'none';
const fileURL = await DownloadManger.getFile(file);
let fileBlob = await (await fetch(fileURL)).blob();
if (file.pubMagicMetadata?.data.editedTime) {
let fileURL = await DownloadManager.getCachedOriginalFile(file);
let tempURL;
if (!fileURL) {
tempURL = URL.createObjectURL(
await new Response(await DownloadManager.downloadFile(file)).blob()
);
fileURL = tempURL;
}
const fileType = getFileExtension(file.metadata.title);
let tempEditedFileURL;
if (
file.pubMagicMetadata?.data.editedTime &&
(fileType === TYPE_JPEG || fileType === TYPE_JPG)
) {
let fileBlob = await (await fetch(fileURL)).blob();
fileBlob = await updateFileCreationDateInEXIF(
fileBlob,
new Date(file.pubMagicMetadata.data.editedTime / 1000)
);
tempEditedFileURL = URL.createObjectURL(fileBlob);
fileURL = tempEditedFileURL;
}
a.href = URL.createObjectURL(fileBlob);
a.href = fileURL;
if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
a.download = fileNameWithoutExtension(file.metadata.title) + '.zip';
} else {
@ -60,12 +77,15 @@ export async function downloadFile(file) {
document.body.appendChild(a);
a.click();
a.remove();
tempURL && URL.revokeObjectURL(tempURL);
tempEditedFileURL && URL.revokeObjectURL(tempEditedFileURL);
}
export function fileIsHEIC(mimeType: string) {
export function isFileHEIC(mimeType: string) {
return (
mimeType.toLowerCase().endsWith(TYPE_HEIC) ||
mimeType.toLowerCase().endsWith(TYPE_HEIF)
mimeType &&
(mimeType.toLowerCase().endsWith(TYPE_HEIC) ||
mimeType.toLowerCase().endsWith(TYPE_HEIF))
);
}
@ -286,7 +306,7 @@ export async function convertForPreview(file: File, fileBlob: Blob) {
const mimeType =
(await getMimeTypeFromBlob(worker, fileBlob)) ?? typeFromExtension;
if (fileIsHEIC(mimeType)) {
if (isFileHEIC(mimeType)) {
fileBlob = await worker.convertHEIC2JPEG(fileBlob);
}
return fileBlob;
@ -481,3 +501,26 @@ export function getNonTrashedUniqueUserFiles(files: File[]) {
)
);
}
export async function downloadFiles(files: File[]) {
for (const file of files) {
try {
await downloadFile(file);
} catch (e) {
logError(e, 'download fail for file');
}
}
}
export function needsConversionForPreview(file: File) {
const fileExtension = splitFilenameAndExtension(file.metadata.title)[1];
if (
file.metadata.fileType === FILE_TYPE.LIVE_PHOTO ||
(file.metadata.fileType === FILE_TYPE.IMAGE &&
isFileHEIC(fileExtension))
) {
return true;
} else {
return false;
}
}