Merge branch 'master' into ml-search-beta

This commit is contained in:
Shailesh Pandit 2022-02-01 10:11:13 +05:30
commit c52c8004f9
9 changed files with 169 additions and 123 deletions

View file

@ -86,7 +86,7 @@ export function AbuseReportForm({ show, close, url }: Iprops) {
publicCollectionGalleryContent.setDialogMessage({ publicCollectionGalleryContent.setDialogMessage({
title: constants.REPORT_SUBMIT_SUCCESS_TITLE, title: constants.REPORT_SUBMIT_SUCCESS_TITLE,
content: constants.REPORT_SUBMIT_SUCCESS_CONTENT, content: constants.REPORT_SUBMIT_SUCCESS_CONTENT,
close: { text: constants.CLOSE }, close: { text: constants.OK },
}); });
} catch (e) { } catch (e) {
setFieldError('signature', constants.REPORT_SUBMIT_FAILED); setFieldError('signature', constants.REPORT_SUBMIT_FAILED);

View file

@ -348,7 +348,7 @@ export default function Gallery() {
isLoadingBarRunning.current = true; isLoadingBarRunning.current = true;
}; };
const finishLoading = () => { const finishLoading = () => {
loadingBar.current?.complete(); isLoadingBarRunning.current && loadingBar.current?.complete();
isLoadingBarRunning.current = false; isLoadingBarRunning.current = false;
}; };

View file

@ -5,6 +5,7 @@ import {
getLocalPublicCollection, getLocalPublicCollection,
getLocalPublicFiles, getLocalPublicFiles,
getPublicCollection, getPublicCollection,
getPublicCollectionUID,
removePublicCollectionWithFiles, removePublicCollectionWithFiles,
syncPublicFiles, syncPublicFiles,
} from 'services/publicCollectionService'; } from 'services/publicCollectionService';
@ -19,7 +20,7 @@ import {
defaultPublicCollectionGalleryContext, defaultPublicCollectionGalleryContext,
PublicCollectionGalleryContext, PublicCollectionGalleryContext,
} from 'utils/publicCollectionGallery'; } from 'utils/publicCollectionGallery';
import { CustomError } from 'utils/error'; import { CustomError, parseSharingErrorCodes } from 'utils/error';
import Container from 'components/Container'; import Container from 'components/Container';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import EnteSpinner from 'components/EnteSpinner'; import EnteSpinner from 'components/EnteSpinner';
@ -28,11 +29,18 @@ import CryptoWorker from 'utils/crypto';
import { PAGES } from 'constants/pages'; import { PAGES } from 'constants/pages';
import router from 'next/router'; import router from 'next/router';
const Loader = () => (
<Container>
<EnteSpinner>
<span className="sr-only">Loading...</span>
</EnteSpinner>
</Container>
);
export default function PublicCollectionGallery() { export default function PublicCollectionGallery() {
const token = useRef<string>(null); const token = useRef<string>(null);
const collectionKey = useRef<string>(null); const collectionKey = useRef<string>(null);
const url = useRef<string>(null); const url = useRef<string>(null);
const [publicFiles, setPublicFiles] = useState<EnteFile[]>([]); const [publicFiles, setPublicFiles] = useState<EnteFile[]>(null);
const [publicCollection, setPublicCollection] = useState<Collection>(null); const [publicCollection, setPublicCollection] = useState<Collection>(null);
const appContext = useContext(AppContext); const appContext = useContext(AppContext);
const [abuseReportFormView, setAbuseReportFormView] = useState(false); const [abuseReportFormView, setAbuseReportFormView] = useState(false);
@ -42,23 +50,22 @@ export default function PublicCollectionGallery() {
const openReportForm = () => setAbuseReportFormView(true); const openReportForm = () => setAbuseReportFormView(true);
const closeReportForm = () => setAbuseReportFormView(false); const closeReportForm = () => setAbuseReportFormView(false);
const loadingBar = useRef(null); const loadingBar = useRef(null);
const [isLoadingBarRunning, setIsLoadingBarRunning] = useState(false); const isLoadingBarRunning = useRef(false);
const openMessageDialog = () => setMessageDialogView(true); const openMessageDialog = () => setMessageDialogView(true);
const closeMessageDialog = () => setMessageDialogView(false); const closeMessageDialog = () => setMessageDialogView(false);
const startLoading = () => { const startLoadingBar = () => {
!isLoadingBarRunning && loadingBar.current?.continuousStart(); !isLoadingBarRunning.current && loadingBar.current?.continuousStart();
setIsLoadingBarRunning(true); isLoadingBarRunning.current = true;
}; };
const finishLoading = () => { const finishLoadingBar = () => {
loadingBar.current?.complete(); isLoadingBarRunning.current && loadingBar.current?.complete();
setIsLoadingBarRunning(false); isLoadingBarRunning.current = false;
}; };
useEffect(() => { useEffect(() => {
appContext.showNavBar(true); appContext.showNavBar(true);
setLoading(false);
const currentURL = new URL(window.location.href); const currentURL = new URL(window.location.href);
if (currentURL.pathname !== PAGES.ROOT) { if (currentURL.pathname !== PAGES.ROOT) {
router.push( router.push(
@ -75,6 +82,7 @@ export default function PublicCollectionGallery() {
); );
} }
const main = async () => { const main = async () => {
try {
const worker = await new CryptoWorker(); const worker = await new CryptoWorker();
url.current = window.location.href; url.current = window.location.href;
const currentURL = new URL(url.current); const currentURL = new URL(url.current);
@ -82,7 +90,6 @@ export default function PublicCollectionGallery() {
const ck = currentURL.hash.slice(1); const ck = currentURL.hash.slice(1);
const dck = await worker.fromHex(ck); const dck = await worker.fromHex(ck);
if (!t || !dck) { if (!t || !dck) {
setLoading(false);
return; return;
} }
token.current = t; token.current = t;
@ -93,12 +100,20 @@ export default function PublicCollectionGallery() {
); );
if (localCollection) { if (localCollection) {
setPublicCollection(localCollection); setPublicCollection(localCollection);
const collectionUID = getPublicCollectionUID(
collectionKey.current,
token.current
);
const localFiles = await getLocalPublicFiles(collectionUID);
const localPublicFiles = sortFiles( const localPublicFiles = sortFiles(
mergeMetadata(await getLocalPublicFiles(localCollection)) mergeMetadata(localFiles)
); );
setPublicFiles(localPublicFiles); setPublicFiles(localPublicFiles);
} }
syncWithRemote(); await syncWithRemote();
} finally {
setLoading(false);
}
}; };
main(); main();
}, []); }, []);
@ -107,7 +122,7 @@ export default function PublicCollectionGallery() {
const syncWithRemote = async () => { const syncWithRemote = async () => {
try { try {
startLoading(); startLoadingBar();
const collection = await getPublicCollection( const collection = await getPublicCollection(
token.current, token.current,
collectionKey.current collectionKey.current
@ -116,29 +131,29 @@ export default function PublicCollectionGallery() {
await syncPublicFiles(token.current, collection, setPublicFiles); await syncPublicFiles(token.current, collection, setPublicFiles);
} catch (e) { } catch (e) {
if (e.message === CustomError.TOKEN_EXPIRED) { const parsedError = parseSharingErrorCodes(e);
if (parsedError.message === CustomError.TOKEN_EXPIRED) {
// share has been disabled // share has been disabled
// local cache should be cleared // local cache should be cleared
removePublicCollectionWithFiles(collectionKey.current); removePublicCollectionWithFiles(
token.current,
collectionKey.current
);
setPublicCollection(null); setPublicCollection(null);
setPublicFiles([]); setPublicFiles([]);
} }
} finally { } finally {
finishLoading(); finishLoadingBar();
} }
}; };
if (loading) {
return ( if (!publicFiles && loading) {
<Container> return <Loader />;
<EnteSpinner>
<span className="sr-only">Loading...</span>
</EnteSpinner>
</Container>
);
} }
if (!isLoadingBarRunning && !publicFiles) { if (!publicFiles?.length && !loading) {
return <Container>{constants.NOT_FOUND}</Container>; return <Container>{constants.NOT_FOUND}</Container>;
} }
return ( return (
<PublicCollectionGalleryContext.Provider <PublicCollectionGalleryContext.Provider
value={{ value={{

View file

@ -23,12 +23,7 @@ import {
CreatePublicAccessTokenRequest, CreatePublicAccessTokenRequest,
PublicURL, PublicURL,
} from 'types/collection'; } from 'types/collection';
import { import { COLLECTION_SORT_BY, CollectionType } from 'constants/collection';
COLLECTION_SORT_BY,
CollectionType,
COLLECTION_SHARE_DEFAULT_DEVICE_LIMIT,
COLLECTION_SHARE_DEFAULT_VALID_DURATION,
} from 'constants/collection';
const ENDPOINT = getEndpoint(); const ENDPOINT = getEndpoint();
const COLLECTION_TABLE = 'collections'; const COLLECTION_TABLE = 'collections';
@ -588,9 +583,6 @@ export const createShareableURL = async (collection: Collection) => {
} }
const createPublicAccessTokenRequest: CreatePublicAccessTokenRequest = { const createPublicAccessTokenRequest: CreatePublicAccessTokenRequest = {
collectionID: collection.id, collectionID: collection.id,
validTill:
Date.now() * 1000 + COLLECTION_SHARE_DEFAULT_VALID_DURATION,
deviceLimit: COLLECTION_SHARE_DEFAULT_DEVICE_LIMIT,
}; };
const resp = await HTTPService.post( const resp = await HTTPService.post(
`${ENDPOINT}/collections/share-url`, `${ENDPOINT}/collections/share-url`,

View file

@ -368,7 +368,8 @@ class MachineLearningService {
let error = e; let error = e;
console.error('Error in ml sync, fileId: ', enteFile.id, error); console.error('Error in ml sync, fileId: ', enteFile.id, error);
if ('status' in error) { if ('status' in error) {
error = parseServerError(error).parsedError || error; const parsedMessage = parseServerError(error);
error = parsedMessage ? new Error(parsedMessage) : error;
} }
// TODO: throw errors not related to specific file // TODO: throw errors not related to specific file
// sync job run should stop after these errors // sync job run should stop after these errors

View file

@ -12,17 +12,18 @@ import {
} from 'types/publicCollection'; } from 'types/publicCollection';
import CryptoWorker from 'utils/crypto'; import CryptoWorker from 'utils/crypto';
import { REPORT_REASON } from 'constants/publicCollection'; import { REPORT_REASON } from 'constants/publicCollection';
import { CustomError, ServerErrorCodes } from 'utils/error';
const ENDPOINT = getEndpoint(); const ENDPOINT = getEndpoint();
const PUBLIC_COLLECTION_FILES_TABLE = 'public-collection-files'; const PUBLIC_COLLECTION_FILES_TABLE = 'public-collection-files';
const PUBLIC_COLLECTIONS_TABLE = 'public-collections'; const PUBLIC_COLLECTIONS_TABLE = 'public-collections';
const getCollectionUID = (collection: Collection) => `${collection.key}`; export const getPublicCollectionUID = (collectionKey: string, token: string) =>
const getCollectionSyncTimeUID = (collectionUID: string) => `${collectionKey.slice(0, 8)}-${token.slice(0, 8)}`;
const getPublicCollectionSyncTimeUID = (collectionUID: string) =>
`public-${collectionUID}-time`; `public-${collectionUID}-time`;
export const getLocalPublicFiles = async (collection: Collection) => { export const getLocalPublicFiles = async (collectionUID: string) => {
const localSavedPublicCollectionFiles = const localSavedPublicCollectionFiles =
( (
(await localForage.getItem<LocalSavedPublicCollectionFiles[]>( (await localForage.getItem<LocalSavedPublicCollectionFiles[]>(
@ -30,8 +31,7 @@ export const getLocalPublicFiles = async (collection: Collection) => {
)) || [] )) || []
).find( ).find(
(localSavedPublicCollectionFiles) => (localSavedPublicCollectionFiles) =>
localSavedPublicCollectionFiles.collectionUID === localSavedPublicCollectionFiles.collectionUID === collectionUID
getCollectionUID(collection)
) || ) ||
({ ({
collectionUID: null, collectionUID: null,
@ -106,13 +106,17 @@ const dedupeCollectionFiles = (
const getPublicCollectionLastSyncTime = async (collectionUID: string) => const getPublicCollectionLastSyncTime = async (collectionUID: string) =>
(await localForage.getItem<number>( (await localForage.getItem<number>(
getCollectionSyncTimeUID(collectionUID) getPublicCollectionSyncTimeUID(collectionUID)
)) ?? 0; )) ?? 0;
const setPublicCollectionLastSyncTime = async ( const setPublicCollectionLastSyncTime = async (
collectionUID: string, collectionUID: string,
time: number time: number
) => await localForage.setItem(getCollectionSyncTimeUID(collectionUID), time); ) =>
await localForage.setItem(
getPublicCollectionSyncTimeUID(collectionUID),
time
);
export const syncPublicFiles = async ( export const syncPublicFiles = async (
token: string, token: string,
@ -121,14 +125,15 @@ export const syncPublicFiles = async (
) => { ) => {
try { try {
let files: EnteFile[] = []; let files: EnteFile[] = [];
const localFiles = await getLocalPublicFiles(collection); const collectionUID = getPublicCollectionUID(collection.key, token);
const localFiles = await getLocalPublicFiles(collectionUID);
files.push(...localFiles); files.push(...localFiles);
try { try {
if (!token) { if (!token) {
return files; return files;
} }
const lastSyncTime = await getPublicCollectionLastSyncTime( const lastSyncTime = await getPublicCollectionLastSyncTime(
getCollectionUID(collection) collectionUID
); );
if (collection.updationTime === lastSyncTime) { if (collection.updationTime === lastSyncTime) {
return files; return files;
@ -160,12 +165,9 @@ export const syncPublicFiles = async (
} }
files.push(file); files.push(file);
} }
await savePublicCollectionFiles( await savePublicCollectionFiles(collectionUID, files);
getCollectionUID(collection),
files
);
await setPublicCollectionLastSyncTime( await setPublicCollectionLastSyncTime(
getCollectionUID(collection), collectionUID,
collection.updationTime collection.updationTime
); );
setPublicFiles([...sortFiles(mergeMetadata(files))]); setPublicFiles([...sortFiles(mergeMetadata(files))]);
@ -175,7 +177,7 @@ export const syncPublicFiles = async (
return [...sortFiles(mergeMetadata(files))]; return [...sortFiles(mergeMetadata(files))];
} catch (e) { } catch (e) {
logError(e, 'failed to get local or sync shared collection files'); logError(e, 'failed to get local or sync shared collection files');
return []; throw e;
} }
}; };
@ -260,20 +262,12 @@ export const getPublicCollection = async (
}; };
await savePublicCollection(collection); await savePublicCollection(collection);
return collection; return collection;
} catch (error) { } catch (e) {
logError(error, 'failed to get public collection', { logError(e, 'failed to get public collection', {
collectionKey, collectionKey,
token, token,
}); });
if ('status' in error) { throw e;
const errorCode = error.status.toString();
if (
errorCode === ServerErrorCodes.SESSION_EXPIRED ||
errorCode === ServerErrorCodes.TOKEN_EXPIRED
) {
throw Error(CustomError.TOKEN_EXPIRED);
}
}
} }
}; };
@ -317,21 +311,17 @@ export const reportAbuse = async (
}; };
export const removePublicCollectionWithFiles = async ( export const removePublicCollectionWithFiles = async (
token: string,
collectionKey: string collectionKey: string
) => { ) => {
const collectionUID = getPublicCollectionUID(collectionKey, token);
const publicCollections = const publicCollections =
(await localForage.getItem<Collection[]>(PUBLIC_COLLECTIONS_TABLE)) ?? (await localForage.getItem<Collection[]>(PUBLIC_COLLECTIONS_TABLE)) ||
[]; [];
const collectionToRemove = publicCollections.find(
(collection) => (collection.key = collectionKey)
);
if (!collectionToRemove) {
return;
}
await localForage.setItem( await localForage.setItem(
PUBLIC_COLLECTIONS_TABLE, PUBLIC_COLLECTIONS_TABLE,
publicCollections.filter( publicCollections.filter(
(collection) => collection.id !== collectionToRemove.id (collection) => collection.key !== collectionKey
) )
); );
@ -342,9 +332,7 @@ export const removePublicCollectionWithFiles = async (
await localForage.setItem( await localForage.setItem(
PUBLIC_COLLECTION_FILES_TABLE, PUBLIC_COLLECTION_FILES_TABLE,
publicCollectionFiles.filter( publicCollectionFiles.filter(
(collectionFiles) => (collectionFiles) => collectionFiles.collectionUID !== collectionUID
collectionFiles.collectionUID ===
getCollectionUID(collectionToRemove)
) )
); );
}; };

View file

@ -28,8 +28,8 @@ export interface PublicURL {
export interface CreatePublicAccessTokenRequest { export interface CreatePublicAccessTokenRequest {
collectionID: number; collectionID: number;
validTill: number; validTill?: number;
deviceLimit: number; deviceLimit?: number;
} }
export interface EncryptedFileKey { export interface EncryptedFileKey {

View file

@ -8,6 +8,10 @@ export const ServerErrorCodes = {
STORAGE_LIMIT_EXCEEDED: '426', STORAGE_LIMIT_EXCEEDED: '426',
FILE_TOO_LARGE: '413', FILE_TOO_LARGE: '413',
TOKEN_EXPIRED: '410', TOKEN_EXPIRED: '410',
TOO_MANY_REQUEST: '429',
BAD_REQUEST: '400',
PAYMENT_REQUIRED: '402',
NOT_FOUND: '404',
}; };
export enum CustomError { export enum CustomError {
@ -32,9 +36,35 @@ export enum CustomError {
REQUEST_CANCELLED = 'request canceled', REQUEST_CANCELLED = 'request canceled',
NETWORK_ERROR = 'Network Error', NETWORK_ERROR = 'Network Error',
TOKEN_EXPIRED = 'token expired', TOKEN_EXPIRED = 'token expired',
BAD_REQUEST = 'bad request',
SUBSCRIPTION_NEEDED = 'subscription not present',
NOT_FOUND = 'not found ',
} }
export function parseServerError(error: AxiosResponse) { export function parseServerError(error: AxiosResponse): string {
let parsedMessage: string = null;
const errorCode = error.status.toString();
switch (errorCode) {
case ServerErrorCodes.NO_ACTIVE_SUBSCRIPTION:
parsedMessage = CustomError.SUBSCRIPTION_EXPIRED;
break;
case ServerErrorCodes.STORAGE_LIMIT_EXCEEDED:
parsedMessage = CustomError.STORAGE_QUOTA_EXCEEDED;
break;
case ServerErrorCodes.SESSION_EXPIRED:
parsedMessage = CustomError.SESSION_EXPIRED_MESSAGE;
break;
case ServerErrorCodes.FILE_TOO_LARGE:
parsedMessage = CustomError.FILE_TOO_LARGE;
break;
default:
parsedMessage = `${constants.UNKNOWN_ERROR} statusCode:${errorCode}`;
}
return parsedMessage;
}
function parseUploadErrorCodes(error) {
let parsedMessage = null; let parsedMessage = null;
if (error?.status) { if (error?.status) {
const errorCode = error.status.toString(); const errorCode = error.status.toString();
@ -54,19 +84,15 @@ export function parseServerError(error: AxiosResponse) {
default: default:
parsedMessage = `${constants.UNKNOWN_ERROR} statusCode:${errorCode}`; parsedMessage = `${constants.UNKNOWN_ERROR} statusCode:${errorCode}`;
} }
} else {
parsedMessage = error.message;
} }
return { return new Error(parsedMessage);
parsedError: new Error(parsedMessage),
};
} }
export function handleUploadError(error: AxiosResponse | Error): Error { export function handleUploadError(error): Error {
let parsedError: Error = null; const parsedError = parseUploadErrorCodes(error);
if ('status' in error) {
parsedError = parseServerError(error).parsedError;
} else {
parsedError = error;
}
// breaking errors // breaking errors
switch (parsedError.message) { switch (parsedError.message) {
case CustomError.SUBSCRIPTION_EXPIRED: case CustomError.SUBSCRIPTION_EXPIRED:
@ -101,25 +127,49 @@ export function errorWithContext(originalError: Error, context: string) {
originalError.stack; originalError.stack;
return errorWithContext; return errorWithContext;
} }
export const parseSharingErrorCodes = (error) => {
let parsedMessage = null;
if (error?.status) {
const errorCode = error.status.toString();
switch (errorCode) {
case ServerErrorCodes.BAD_REQUEST:
parsedMessage = CustomError.BAD_REQUEST;
break;
case ServerErrorCodes.PAYMENT_REQUIRED:
parsedMessage = CustomError.SUBSCRIPTION_NEEDED;
break;
case ServerErrorCodes.NOT_FOUND:
parsedMessage = CustomError.NOT_FOUND;
break;
case ServerErrorCodes.SESSION_EXPIRED:
case ServerErrorCodes.TOKEN_EXPIRED:
case ServerErrorCodes.TOO_MANY_REQUEST:
parsedMessage = CustomError.TOKEN_EXPIRED;
break;
default:
parsedMessage = `${constants.UNKNOWN_ERROR} statusCode:${errorCode}`;
}
} else {
parsedMessage = error.message;
}
return new Error(parsedMessage);
};
export const handleSharingErrors = (e) => { export const handleSharingErrors = (error) => {
let errorMessage = null; const parsedError = parseSharingErrorCodes(error);
if ('status' in e) { let errorMessage = '';
switch (e?.status) { switch (parsedError.message) {
case 400: case CustomError.BAD_REQUEST:
errorMessage = constants.SHARING_BAD_REQUEST_ERROR; errorMessage = constants.SHARING_BAD_REQUEST_ERROR;
break; break;
case 402: case CustomError.SUBSCRIPTION_NEEDED:
errorMessage = constants.SHARING_DISABLED_FOR_FREE_ACCOUNTS; errorMessage = constants.SHARING_DISABLED_FOR_FREE_ACCOUNTS;
break; break;
case 404: case CustomError.NOT_FOUND:
errorMessage = constants.USER_DOES_NOT_EXIST; errorMessage = constants.USER_DOES_NOT_EXIST;
break; break;
default: default:
errorMessage = `${constants.UNKNOWN_ERROR} statusCode:${e.status}`; errorMessage = parsedError.message;
}
} else {
errorMessage = e.message;
} }
return errorMessage; return errorMessage;
}; };

View file

@ -630,10 +630,10 @@ const englishConstants = {
OPEN_PLAN_SELECTOR_MODAL_FAILED: 'failed to open plans', OPEN_PLAN_SELECTOR_MODAL_FAILED: 'failed to open plans',
COMMENT: 'comment', COMMENT: 'comment',
ABUSE_REPORT_DESCRIPTION: ABUSE_REPORT_DESCRIPTION:
'fill out the following form to submit the abuse report. Refer the dmca section for more detail for privacy policy of submitting a report ', 'Note: Submitting this report will notify the album owner.',
OTHER_REASON_REQUIRES_COMMENTS: OTHER_REASON_REQUIRES_COMMENTS:
'reason = other, require a mandatory comment ', 'reason = other, require a mandatory comment ',
REPORT_SUBMIT_SUCCESS_CONTENT: 'thank you, your report have been submitted', REPORT_SUBMIT_SUCCESS_CONTENT: 'your report has been submitted',
REPORT_SUBMIT_SUCCESS_TITLE: 'report sent', REPORT_SUBMIT_SUCCESS_TITLE: 'report sent',
REPORT_SUBMIT_FAILED: 'failed to sent report, try again', REPORT_SUBMIT_FAILED: 'failed to sent report, try again',
INSTALL: 'install', INSTALL: 'install',