Merge branch 'main' into watch

This commit is contained in:
Abhinav 2022-08-15 14:22:35 +05:30
commit 2ee492938c
18 changed files with 255 additions and 157 deletions

View file

@ -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');

View file

@ -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]);

View file

@ -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,

View file

@ -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}

View file

@ -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,

View file

@ -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) {

View 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
}
}

View file

@ -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;

View 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();

View 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();

View file

@ -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();

View file

@ -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),

View file

@ -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;

View file

@ -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 {

View file

@ -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) {

View file

@ -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);
}
};

View file

@ -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) =>

View file

@ -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
</>