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({
title: constants.REPORT_SUBMIT_SUCCESS_TITLE,
content: constants.REPORT_SUBMIT_SUCCESS_CONTENT,
close: { text: constants.CLOSE },
close: { text: constants.OK },
});
} catch (e) {
setFieldError('signature', constants.REPORT_SUBMIT_FAILED);

View file

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

View file

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

View file

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

View file

@ -368,7 +368,8 @@ class MachineLearningService {
let error = e;
console.error('Error in ml sync, fileId: ', enteFile.id, 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
// sync job run should stop after these errors

View file

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

View file

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

View file

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

View file

@ -630,10 +630,10 @@ const englishConstants = {
OPEN_PLAN_SELECTOR_MODAL_FAILED: 'failed to open plans',
COMMENT: 'comment',
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:
'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_FAILED: 'failed to sent report, try again',
INSTALL: 'install',