Merge branch 'main' into watch
This commit is contained in:
commit
2ee492938c
|
@ -4,6 +4,9 @@ import { downloadAsFile } from 'utils/file';
|
|||
import constants from 'utils/strings/constants';
|
||||
import { addLogLine, getDebugLogs } from 'utils/logging';
|
||||
import SidebarButton from './Button';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { User } from 'types/user';
|
||||
import { getSentryUserID } from 'utils/user';
|
||||
|
||||
export default function DebugLogs() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
@ -22,6 +25,11 @@ export default function DebugLogs() {
|
|||
});
|
||||
|
||||
const downloadDebugLogs = () => {
|
||||
addLogLine(
|
||||
'latest commit id :' + process.env.NEXT_PUBLIC_LATEST_COMMIT_HASH
|
||||
);
|
||||
addLogLine(`user sentry id ${getSentryUserID()}`);
|
||||
addLogLine(`ente userID ${(getData(LS_KEYS.USER) as User)?.id}`);
|
||||
addLogLine('exporting logs');
|
||||
const logs = getDebugLogs();
|
||||
const logString = logs.join('\n');
|
||||
|
|
|
@ -27,7 +27,6 @@ import {
|
|||
getRoadmapRedirectURL,
|
||||
} from 'services/userService';
|
||||
import { CustomError } from 'utils/error';
|
||||
import { getSentryUserID } from 'utils/user';
|
||||
|
||||
export const MessageContainer = styled('div')`
|
||||
background-color: #111;
|
||||
|
@ -208,10 +207,6 @@ export default function App({ Component, err }) {
|
|||
|
||||
useEffect(() => {
|
||||
addLogLine(`app started`);
|
||||
addLogLine(
|
||||
`latest commit id :${process.env.NEXT_PUBLIC_LATEST_COMMIT_HASH}`
|
||||
);
|
||||
addLogLine(`user sentry id ${getSentryUserID()}`);
|
||||
}, []);
|
||||
|
||||
useEffect(() => setMessageDialogView(true), [dialogMessage]);
|
||||
|
|
|
@ -25,7 +25,7 @@ import FormPaperFooter from 'components/Form/FormPaper/Footer';
|
|||
import LinkButton from 'components/pages/gallery/LinkButton';
|
||||
import { CustomError } from 'utils/error';
|
||||
import isElectron from 'is-electron';
|
||||
import desktopService from 'services/desktopService';
|
||||
import safeStorageService from 'services/electron/safeStorage';
|
||||
import VerticallyCentered from 'components/Container';
|
||||
import EnteSpinner from 'components/EnteSpinner';
|
||||
import { Input } from '@mui/material';
|
||||
|
@ -43,7 +43,7 @@ export default function Credentials() {
|
|||
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
||||
let key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
if (!key && isElectron()) {
|
||||
key = await desktopService.getEncryptionKey();
|
||||
key = await safeStorageService.getEncryptionKey();
|
||||
if (key) {
|
||||
await saveKeyInSessionStore(
|
||||
SESSION_KEYS.ENCRYPTION_KEY,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
trashFiles,
|
||||
deleteFromTrash,
|
||||
} from 'services/fileService';
|
||||
import { styled } from '@mui/material';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import {
|
||||
syncCollections,
|
||||
getFavItemIds,
|
||||
|
@ -101,6 +101,7 @@ import UploadInputs from 'components/UploadSelectorInputs';
|
|||
import useFileInput from 'hooks/useFileInput';
|
||||
import { User } from 'types/user';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { CenteredFlex } from 'components/Container';
|
||||
|
||||
export const DeadCenter = styled('div')`
|
||||
flex: 1;
|
||||
|
@ -110,12 +111,6 @@ export const DeadCenter = styled('div')`
|
|||
text-align: center;
|
||||
flex-direction: column;
|
||||
`;
|
||||
const AlertContainer = styled('div')`
|
||||
background-color: #111;
|
||||
padding: 5px 0;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const defaultGalleryContext: GalleryContextType = {
|
||||
thumbs: new Map(),
|
||||
|
@ -609,9 +604,11 @@ export default function Gallery() {
|
|||
</LoadingOverlay>
|
||||
)}
|
||||
{isFirstLoad && (
|
||||
<AlertContainer>
|
||||
{constants.INITIAL_LOAD_DELAY_WARNING}
|
||||
</AlertContainer>
|
||||
<CenteredFlex>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
{constants.INITIAL_LOAD_DELAY_WARNING}
|
||||
</Typography>
|
||||
</CenteredFlex>
|
||||
)}
|
||||
<PlanSelector
|
||||
modalView={planModalView}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { logError } from 'utils/sentry';
|
|||
import { getAlbumSiteHost, PAGES } from 'constants/pages';
|
||||
import { EnteLogo } from 'components/EnteLogo';
|
||||
import isElectron from 'is-electron';
|
||||
import desktopService from 'services/desktopService';
|
||||
import safeStorageService from 'services/electron/safeStorage';
|
||||
import { saveKeyInSessionStore } from 'utils/crypto';
|
||||
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
|
||||
|
||||
|
@ -128,7 +128,7 @@ export default function LandingPage() {
|
|||
const user = getData(LS_KEYS.USER);
|
||||
let key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
if (!key && isElectron()) {
|
||||
key = await desktopService.getEncryptionKey();
|
||||
key = await safeStorageService.getEncryptionKey();
|
||||
if (key) {
|
||||
await saveKeyInSessionStore(
|
||||
SESSION_KEYS.ENCRYPTION_KEY,
|
||||
|
|
|
@ -32,12 +32,15 @@ import { useRouter } from 'next/router';
|
|||
import SingleInputForm, {
|
||||
SingleInputFormProps,
|
||||
} from 'components/SingleInputForm';
|
||||
import { Card } from 'react-bootstrap';
|
||||
import { logError } from 'utils/sentry';
|
||||
import SharedAlbumNavbar from 'components/pages/sharedAlbum/Navbar';
|
||||
import { CollectionInfo } from 'components/Collections/CollectionInfo';
|
||||
import { CollectionInfoBarWrapper } from 'components/Collections/styledComponents';
|
||||
import { ITEM_TYPE, TimeStampListItem } from 'components/PhotoList';
|
||||
import FormContainer from 'components/Form/FormContainer';
|
||||
import FormPaper from 'components/Form/FormPaper';
|
||||
import FormPaperTitle from 'components/Form/FormPaper/Title';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
const Loader = () => (
|
||||
<VerticallyCentered>
|
||||
|
@ -267,21 +270,23 @@ export default function PublicCollectionGallery() {
|
|||
}
|
||||
if (isPasswordProtected && !passwordJWTToken.current) {
|
||||
return (
|
||||
<VerticallyCentered>
|
||||
<Card style={{ maxWidth: '332px' }} className="text-center">
|
||||
<Card.Body style={{ padding: '40px 30px' }}>
|
||||
<Card.Subtitle style={{ marginBottom: '2rem' }}>
|
||||
{constants.LINK_PASSWORD}
|
||||
</Card.Subtitle>
|
||||
<SingleInputForm
|
||||
callback={verifyLinkPassword}
|
||||
placeholder={constants.RETURN_PASSPHRASE_HINT}
|
||||
buttonText={'unlock'}
|
||||
fieldType="password"
|
||||
/>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</VerticallyCentered>
|
||||
<FormContainer>
|
||||
<FormPaper>
|
||||
<FormPaperTitle>{constants.PASSWORD}</FormPaperTitle>
|
||||
<Typography
|
||||
color={'text.secondary'}
|
||||
mb={2}
|
||||
variant="body2">
|
||||
{constants.LINK_PASSWORD}
|
||||
</Typography>
|
||||
<SingleInputForm
|
||||
callback={verifyLinkPassword}
|
||||
placeholder={constants.RETURN_PASSPHRASE_HINT}
|
||||
buttonText={'unlock'}
|
||||
fieldType="password"
|
||||
/>
|
||||
</FormPaper>
|
||||
</FormContainer>
|
||||
);
|
||||
}
|
||||
if (!publicFiles) {
|
||||
|
|
31
src/services/cacheService.ts
Normal file
31
src/services/cacheService.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import electronService from './electron/common';
|
||||
import electronCacheService from './electron/cache';
|
||||
import { logError } from 'utils/sentry';
|
||||
|
||||
const THUMB_CACHE = 'thumbs';
|
||||
|
||||
export function getCacheProvider() {
|
||||
if (electronService.checkIsBundledApp()) {
|
||||
return electronCacheService;
|
||||
} else {
|
||||
return caches;
|
||||
}
|
||||
}
|
||||
|
||||
export async function openThumbnailCache() {
|
||||
try {
|
||||
return await getCacheProvider().open(THUMB_CACHE);
|
||||
} catch (e) {
|
||||
logError(e, 'openThumbnailCache failed');
|
||||
// log and ignore
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteThumbnailCache() {
|
||||
try {
|
||||
return await getCacheProvider().delete(THUMB_CACHE);
|
||||
} catch (e) {
|
||||
logError(e, 'deleteThumbnailCache failed');
|
||||
// dont throw
|
||||
}
|
||||
}
|
|
@ -3,8 +3,7 @@ import { getFileURL, getThumbnailURL } from 'utils/common/apiUtil';
|
|||
import CryptoWorker from 'utils/crypto';
|
||||
import {
|
||||
generateStreamFromArrayBuffer,
|
||||
convertForPreview,
|
||||
needsConversionForPreview,
|
||||
getRenderableFileURL,
|
||||
createTypedObjectURL,
|
||||
} from 'utils/file';
|
||||
import HTTPService from './HTTPService';
|
||||
|
@ -13,6 +12,7 @@ import { EnteFile } from 'types/file';
|
|||
import { logError } from 'utils/sentry';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { CustomError } from 'utils/error';
|
||||
import { openThumbnailCache } from './cacheService';
|
||||
|
||||
class DownloadManager {
|
||||
private fileObjectURLPromise = new Map<string, Promise<string[]>>();
|
||||
|
@ -26,14 +26,7 @@ class DownloadManager {
|
|||
}
|
||||
if (!this.thumbnailObjectURLPromise.get(file.id)) {
|
||||
const downloadPromise = async () => {
|
||||
const thumbnailCache = await (async () => {
|
||||
try {
|
||||
return await caches.open('thumbs');
|
||||
} catch (e) {
|
||||
return null;
|
||||
// ignore
|
||||
}
|
||||
})();
|
||||
const thumbnailCache = await openThumbnailCache();
|
||||
|
||||
const cacheResp: Response = await thumbnailCache?.match(
|
||||
file.id.toString()
|
||||
|
@ -43,14 +36,13 @@ class DownloadManager {
|
|||
}
|
||||
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.
|
||||
}
|
||||
|
||||
thumbnailCache
|
||||
?.put(file.id.toString(), new Response(thumbBlob))
|
||||
.catch((e) => {
|
||||
logError(e, 'cache put failed');
|
||||
// TODO: handle storage full exception.
|
||||
});
|
||||
return URL.createObjectURL(thumbBlob);
|
||||
};
|
||||
this.thumbnailObjectURLPromise.set(file.id, downloadPromise());
|
||||
|
@ -84,38 +76,24 @@ class DownloadManager {
|
|||
};
|
||||
|
||||
getFile = async (file: EnteFile, forPreview = false) => {
|
||||
const shouldBeConverted = forPreview && needsConversionForPreview(file);
|
||||
const fileKey = shouldBeConverted
|
||||
? `${file.id}_converted`
|
||||
: `${file.id}`;
|
||||
const fileKey = forPreview ? `${file.id}_preview` : `${file.id}`;
|
||||
try {
|
||||
const getFilePromise = async (convert: boolean) => {
|
||||
const getFilePromise = async () => {
|
||||
const fileStream = await this.downloadFile(file);
|
||||
const fileBlob = await new Response(fileStream).blob();
|
||||
if (convert) {
|
||||
const convertedBlobs = await convertForPreview(
|
||||
file,
|
||||
fileBlob
|
||||
);
|
||||
return await Promise.all(
|
||||
convertedBlobs.map(
|
||||
async (blob) =>
|
||||
await createTypedObjectURL(
|
||||
blob,
|
||||
file.metadata.title
|
||||
)
|
||||
)
|
||||
);
|
||||
if (forPreview) {
|
||||
return await getRenderableFileURL(file, fileBlob);
|
||||
} else {
|
||||
return [
|
||||
await createTypedObjectURL(
|
||||
fileBlob,
|
||||
file.metadata.title
|
||||
),
|
||||
];
|
||||
}
|
||||
return [
|
||||
await createTypedObjectURL(fileBlob, file.metadata.title),
|
||||
];
|
||||
};
|
||||
if (!this.fileObjectURLPromise.get(fileKey)) {
|
||||
this.fileObjectURLPromise.set(
|
||||
fileKey,
|
||||
getFilePromise(shouldBeConverted)
|
||||
);
|
||||
this.fileObjectURLPromise.set(fileKey, getFilePromise());
|
||||
}
|
||||
const fileURLs = await this.fileObjectURLPromise.get(fileKey);
|
||||
return fileURLs;
|
||||
|
|
24
src/services/electron/cache.ts
Normal file
24
src/services/electron/cache.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { runningInBrowser } from 'utils/common';
|
||||
|
||||
class ElectronCacheService {
|
||||
private ElectronAPIs: any;
|
||||
private allElectronAPIsExist: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.ElectronAPIs = runningInBrowser() && window['ElectronAPIs'];
|
||||
this.allElectronAPIsExist = !!this.ElectronAPIs?.openDiskCache;
|
||||
}
|
||||
async open(cacheName: string): Promise<Cache> {
|
||||
if (this.allElectronAPIsExist) {
|
||||
return await this.ElectronAPIs.openDiskCache(cacheName);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(cacheName: string): Promise<boolean> {
|
||||
if (this.allElectronAPIsExist) {
|
||||
return await this.ElectronAPIs.deleteDiskCache(cacheName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ElectronCacheService();
|
18
src/services/electron/common.ts
Normal file
18
src/services/electron/common.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import isElectron from 'is-electron';
|
||||
import { runningInBrowser } from 'utils/common';
|
||||
|
||||
class ElectronService {
|
||||
private ElectronAPIs: any;
|
||||
private isBundledApp: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.ElectronAPIs = runningInBrowser() && window['ElectronAPIs'];
|
||||
this.isBundledApp = !!this.ElectronAPIs?.openDiskCache;
|
||||
}
|
||||
|
||||
checkIsBundledApp() {
|
||||
return isElectron() && this.isBundledApp;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ElectronService();
|
|
@ -1,7 +1,7 @@
|
|||
import { runningInBrowser } from 'utils/common';
|
||||
import { logError } from 'utils/sentry';
|
||||
|
||||
class DesktopService {
|
||||
class SafeStorageService {
|
||||
private ElectronAPIs: any;
|
||||
private allElectronAPIsExist: boolean = false;
|
||||
constructor() {
|
||||
|
@ -35,8 +35,8 @@ class DesktopService {
|
|||
return await this.ElectronAPIs.clearElectronStore();
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'getEncryptionKey failed');
|
||||
logError(e, 'clearElectronStore failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
export default new DesktopService();
|
||||
export default new SafeStorageService();
|
|
@ -4,6 +4,7 @@ import { createNewConvertWorker } from 'utils/heicConverter';
|
|||
import { retryAsyncFunction } from 'utils/network';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { addLogLine } from 'utils/logging';
|
||||
import { makeHumanReadableStorage } from 'utils/billing';
|
||||
|
||||
const WORKER_POOL_SIZE = 2;
|
||||
const MAX_CONVERSION_IN_PARALLEL = 1;
|
||||
|
@ -40,11 +41,21 @@ class HEICConverter {
|
|||
const timeout = setTimeout(() => {
|
||||
reject(Error('wait time exceeded'));
|
||||
}, WAIT_TIME_IN_MICROSECONDS);
|
||||
const convertedHEIC =
|
||||
const startTime = Date.now();
|
||||
const convertedHEIC: Blob =
|
||||
await comlink.convertHEIC(
|
||||
fileBlob,
|
||||
format
|
||||
);
|
||||
addLogLine(
|
||||
`originalFileSize:${makeHumanReadableStorage(
|
||||
fileBlob?.size
|
||||
)},convertedFileSize:${makeHumanReadableStorage(
|
||||
convertedHEIC?.size
|
||||
)}, heic conversion time: ${
|
||||
Date.now() - startTime
|
||||
}ms `
|
||||
);
|
||||
clearTimeout(timeout);
|
||||
resolve(convertedHEIC);
|
||||
} catch (e) {
|
||||
|
@ -54,6 +65,20 @@ class HEICConverter {
|
|||
main();
|
||||
}
|
||||
);
|
||||
if (!convertedHEIC || convertedHEIC?.size === 0) {
|
||||
logError(
|
||||
Error(`converted heic fileSize is Zero`),
|
||||
'converted heic fileSize is Zero',
|
||||
{
|
||||
originalFileSize: makeHumanReadableStorage(
|
||||
fileBlob?.size ?? 0
|
||||
),
|
||||
convertedFileSize: makeHumanReadableStorage(
|
||||
convertedHEIC?.size ?? 0
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(
|
||||
() => resolve(null),
|
||||
|
|
|
@ -5,8 +5,8 @@ import {
|
|||
import CryptoWorker from 'utils/crypto';
|
||||
import {
|
||||
generateStreamFromArrayBuffer,
|
||||
convertForPreview,
|
||||
needsConversionForPreview,
|
||||
getRenderableFileURL,
|
||||
createTypedObjectURL,
|
||||
} from 'utils/file';
|
||||
import HTTPService from './HTTPService';
|
||||
import { EnteFile } from 'types/file';
|
||||
|
@ -107,34 +107,28 @@ class PublicCollectionDownloadManager {
|
|||
passwordToken: string,
|
||||
forPreview = false
|
||||
) => {
|
||||
const shouldBeConverted = forPreview && needsConversionForPreview(file);
|
||||
const fileKey = shouldBeConverted
|
||||
? `${file.id}_converted`
|
||||
: `${file.id}`;
|
||||
const fileKey = forPreview ? `${file.id}_preview` : `${file.id}`;
|
||||
try {
|
||||
const getFilePromise = async (convert: boolean) => {
|
||||
const getFilePromise = async () => {
|
||||
const fileStream = await this.downloadFile(
|
||||
token,
|
||||
passwordToken,
|
||||
file
|
||||
);
|
||||
const fileBlob = await new Response(fileStream).blob();
|
||||
if (convert) {
|
||||
const convertedBlobs = await convertForPreview(
|
||||
file,
|
||||
fileBlob
|
||||
);
|
||||
return convertedBlobs.map((blob) =>
|
||||
URL.createObjectURL(blob)
|
||||
);
|
||||
if (forPreview) {
|
||||
return await getRenderableFileURL(file, fileBlob);
|
||||
} else {
|
||||
return [
|
||||
await createTypedObjectURL(
|
||||
fileBlob,
|
||||
file.metadata.title
|
||||
),
|
||||
];
|
||||
}
|
||||
return [URL.createObjectURL(fileBlob)];
|
||||
};
|
||||
if (!this.fileObjectURLPromise.get(fileKey)) {
|
||||
this.fileObjectURLPromise.set(
|
||||
fileKey,
|
||||
getFilePromise(shouldBeConverted)
|
||||
);
|
||||
this.fileObjectURLPromise.set(fileKey, getFilePromise());
|
||||
}
|
||||
const fileURLs = await this.fileObjectURLPromise.get(fileKey);
|
||||
return fileURLs;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { logError } from 'utils/sentry';
|
|||
import { BLACK_THUMBNAIL_BASE64 } from 'constants/upload';
|
||||
import FFmpegService from 'services/ffmpeg/ffmpegService';
|
||||
import { convertBytesToHumanReadable } from 'utils/file/size';
|
||||
import { isFileHEIC } from 'utils/file';
|
||||
import { isExactTypeHEIC } from 'utils/file';
|
||||
import { ElectronFile, FileTypeInfo } from 'types/upload';
|
||||
import { getUint8ArrayView } from '../readerService';
|
||||
import HEICConverter from 'services/heicConverter/heicConverterService';
|
||||
|
@ -37,7 +37,7 @@ export async function generateThumbnail(
|
|||
file = new File([await file.blob()], file.name);
|
||||
}
|
||||
if (fileTypeInfo.fileType === FILE_TYPE.IMAGE) {
|
||||
const isHEIC = isFileHEIC(fileTypeInfo.exactType);
|
||||
const isHEIC = isExactTypeHEIC(fileTypeInfo.exactType);
|
||||
canvas = await generateImageThumbnail(file, isHEIC);
|
||||
} else {
|
||||
try {
|
||||
|
|
|
@ -20,7 +20,8 @@ import {
|
|||
import { getLocalFamilyData, isPartOfFamily } from 'utils/billing';
|
||||
import { ServerErrorCodes } from 'utils/error';
|
||||
import isElectron from 'is-electron';
|
||||
import desktopService from './desktopService';
|
||||
import safeStorageService from './electron/safeStorage';
|
||||
import { deleteThumbnailCache } from './cacheService';
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
|
||||
|
@ -118,13 +119,13 @@ export const logoutUser = async () => {
|
|||
clearKeys();
|
||||
clearData();
|
||||
try {
|
||||
await caches.delete('thumbs');
|
||||
await deleteThumbnailCache();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
await clearFiles();
|
||||
if (isElectron()) {
|
||||
desktopService.clearElectronStore();
|
||||
safeStorageService.clearElectronStore();
|
||||
}
|
||||
router.push(PAGES.ROOT);
|
||||
} catch (e) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { setRecoveryKey } from 'services/userService';
|
|||
import { logError } from 'utils/sentry';
|
||||
import { ComlinkWorker } from 'utils/comlink';
|
||||
import isElectron from 'is-electron';
|
||||
import desktopService from 'services/desktopService';
|
||||
import safeStorageService from 'services/electron/safeStorage';
|
||||
|
||||
export interface B64EncryptionResult {
|
||||
encryptedData: string;
|
||||
|
@ -103,7 +103,7 @@ export const saveKeyInSessionStore = async (
|
|||
const sessionKeyAttributes = await cryptoWorker.encryptToB64(key);
|
||||
setKey(keyType, sessionKeyAttributes);
|
||||
if (isElectron() && !fromDesktop) {
|
||||
desktopService.setEncryptionKey(key);
|
||||
safeStorageService.setEncryptionKey(key);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ import ffmpegService from 'services/ffmpeg/ffmpegService';
|
|||
import { NEW_FILE_MAGIC_METADATA, VISIBILITY_STATE } from 'types/magicMetadata';
|
||||
import { IsArchived, updateMagicMetadataProps } from 'utils/magicMetadata';
|
||||
import { ARCHIVE_SECTION, TRASH_SECTION } from 'constants/collection';
|
||||
|
||||
import { addLogLine } from 'utils/logging';
|
||||
import { makeHumanReadableStorage } from 'utils/billing';
|
||||
export function downloadAsFile(filename: string, content: string) {
|
||||
const file = new Blob([content], {
|
||||
type: 'text/plain',
|
||||
|
@ -131,14 +132,6 @@ function downloadUsingAnchor(link: string, name: string) {
|
|||
a.remove();
|
||||
}
|
||||
|
||||
export function isFileHEIC(mimeType: string) {
|
||||
return (
|
||||
mimeType &&
|
||||
(mimeType.toLowerCase().endsWith(TYPE_HEIC) ||
|
||||
mimeType.toLowerCase().endsWith(TYPE_HEIF))
|
||||
);
|
||||
}
|
||||
|
||||
export function sortFilesIntoCollections(files: EnteFile[]) {
|
||||
const collectionWiseFiles = new Map<number, EnteFile[]>([
|
||||
[ARCHIVE_SECTION, []],
|
||||
|
@ -327,40 +320,71 @@ export function generateStreamFromArrayBuffer(data: Uint8Array) {
|
|||
});
|
||||
}
|
||||
|
||||
export async function convertForPreview(
|
||||
export async function getRenderableFileURL(file: EnteFile, fileBlob: Blob) {
|
||||
switch (file.metadata.fileType) {
|
||||
case FILE_TYPE.IMAGE: {
|
||||
const convertedBlob = await getRenderableImage(
|
||||
file.metadata.title,
|
||||
fileBlob
|
||||
);
|
||||
return [URL.createObjectURL(convertedBlob)];
|
||||
}
|
||||
case FILE_TYPE.LIVE_PHOTO: {
|
||||
const livePhoto = await getRenderableLivePhoto(file, fileBlob);
|
||||
return livePhoto.map((asset) => URL.createObjectURL(asset));
|
||||
}
|
||||
default:
|
||||
return [URL.createObjectURL(fileBlob)];
|
||||
}
|
||||
}
|
||||
|
||||
async function getRenderableLivePhoto(
|
||||
file: EnteFile,
|
||||
fileBlob: Blob
|
||||
): Promise<Blob[]> {
|
||||
const convertIfHEIC = async (fileName: string, fileBlob: Blob) => {
|
||||
const mimeType = (
|
||||
await getFileType(new File([fileBlob], file.metadata.title))
|
||||
).exactType;
|
||||
if (isFileHEIC(mimeType)) {
|
||||
fileBlob = await HEICConverter.convert(fileBlob);
|
||||
}
|
||||
return fileBlob;
|
||||
};
|
||||
const originalName = fileNameWithoutExtension(file.metadata.title);
|
||||
const motionPhoto = await decodeMotionPhoto(fileBlob, originalName);
|
||||
const imageBlob = new Blob([motionPhoto.image]);
|
||||
return await Promise.all([
|
||||
getRenderableImage(motionPhoto.imageNameTitle, imageBlob),
|
||||
getPlayableVideo(motionPhoto.videoNameTitle, motionPhoto.video),
|
||||
]);
|
||||
}
|
||||
|
||||
if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
|
||||
const originalName = fileNameWithoutExtension(file.metadata.title);
|
||||
const motionPhoto = await decodeMotionPhoto(fileBlob, originalName);
|
||||
let image = new Blob([motionPhoto.image]);
|
||||
async function getPlayableVideo(videoNameTitle: string, video: Uint8Array) {
|
||||
const mp4ConvertedVideo = await ffmpegService.convertToMP4(
|
||||
video,
|
||||
videoNameTitle
|
||||
);
|
||||
return new Blob([mp4ConvertedVideo]);
|
||||
}
|
||||
|
||||
// can run conversion in parellel as video and image
|
||||
// have different processes
|
||||
const convertedVideo = ffmpegService.convertToMP4(
|
||||
motionPhoto.video,
|
||||
motionPhoto.videoNameTitle
|
||||
async function getRenderableImage(fileName: string, imageBlob: Blob) {
|
||||
if (await isFileHEIC(imageBlob, fileName)) {
|
||||
addLogLine(
|
||||
`HEICConverter called for ${fileName}-${makeHumanReadableStorage(
|
||||
imageBlob.size
|
||||
)}`
|
||||
);
|
||||
|
||||
image = await convertIfHEIC(motionPhoto.imageNameTitle, image);
|
||||
const video = new Blob([await convertedVideo]);
|
||||
|
||||
return [image, video];
|
||||
const convertedImageBlob = await HEICConverter.convert(imageBlob);
|
||||
addLogLine(`${fileName} successfully converted`);
|
||||
return convertedImageBlob;
|
||||
} else {
|
||||
return imageBlob;
|
||||
}
|
||||
}
|
||||
|
||||
fileBlob = await convertIfHEIC(file.metadata.title, fileBlob);
|
||||
return [fileBlob];
|
||||
export async function isFileHEIC(fileBlob: Blob, fileName: string) {
|
||||
const tempFile = new File([fileBlob], fileName);
|
||||
const { exactType } = await getFileType(tempFile);
|
||||
return isExactTypeHEIC(exactType);
|
||||
}
|
||||
|
||||
export function isExactTypeHEIC(exactType: string) {
|
||||
return (
|
||||
exactType.toLowerCase().endsWith(TYPE_HEIC) ||
|
||||
exactType.toLowerCase().endsWith(TYPE_HEIF)
|
||||
);
|
||||
}
|
||||
|
||||
export async function changeFilesVisibility(
|
||||
|
@ -510,17 +534,15 @@ export async function downloadFiles(files: EnteFile[]) {
|
|||
}
|
||||
}
|
||||
|
||||
export function needsConversionForPreview(file: EnteFile) {
|
||||
const fileExtension = splitFilenameAndExtension(file.metadata.title)[1];
|
||||
if (
|
||||
export async function needsConversionForPreview(
|
||||
file: EnteFile,
|
||||
fileBlob: Blob
|
||||
) {
|
||||
const isHEIC = await isFileHEIC(fileBlob, file.metadata.title);
|
||||
return (
|
||||
file.metadata.fileType === FILE_TYPE.LIVE_PHOTO ||
|
||||
(file.metadata.fileType === FILE_TYPE.IMAGE &&
|
||||
isFileHEIC(fileExtension))
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
(file.metadata.fileType === FILE_TYPE.IMAGE && isHEIC)
|
||||
);
|
||||
}
|
||||
|
||||
export const isLivePhoto = (file: EnteFile) =>
|
||||
|
|
|
@ -227,7 +227,7 @@ const englishConstants = {
|
|||
target="_blank"
|
||||
style={{ color: '#51cd7c' }}
|
||||
rel="noreferrer">
|
||||
android
|
||||
Android
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
<a
|
||||
|
@ -235,7 +235,7 @@ const englishConstants = {
|
|||
style={{ color: '#51cd7c' }}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
ios app{' '}
|
||||
iOS app{' '}
|
||||
</a>
|
||||
to automatically backup all your photos
|
||||
</>
|
||||
|
|
Loading…
Reference in a new issue