Merge pull request #11 from ente-io/collection-delete

handling collection deleted from remote
This commit is contained in:
Abhinav-grd 2021-02-09 12:02:16 +05:30 committed by GitHub
commit 5b17b2a322
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 345 additions and 227 deletions

View file

@ -51,7 +51,6 @@ function PhotoSwipe(props: Iprops) {
}, [isOpen]); }, [isOpen]);
function updateFavButton() { function updateFavButton() {
console.log(this.currItem.id, props.favItemIds)
setIsFav(isInFav(this?.currItem)); setIsFav(isInFav(this?.currItem));
} }
@ -113,14 +112,12 @@ function PhotoSwipe(props: Iprops) {
if (!isInFav(file)) { if (!isInFav(file)) {
favItemIds.add(file.id); favItemIds.add(file.id);
await addToFavorites(file); await addToFavorites(file);
console.log("added to Favorites");
setIsFav(true); setIsFav(true);
setFavItemIds(favItemIds); setFavItemIds(favItemIds);
} }
else { else {
favItemIds.delete(file.id); favItemIds.delete(file.id);
await removeFromFavorites(file) await removeFromFavorites(file)
console.log("removed from Favorites");
setIsFav(false); setIsFav(false);
setFavItemIds(favItemIds); setFavItemIds(favItemIds);

View file

@ -9,7 +9,7 @@ function CollectionDropZone({
closeModal, closeModal,
showModal, showModal,
refetchData, refetchData,
collectionLatestFile, collectionAndItsLatestFile,
setProgressView, setProgressView,
progressBarProps progressBarProps
@ -21,7 +21,7 @@ function CollectionDropZone({
progressBarProps.setPercentComplete(0); progressBarProps.setPercentComplete(0);
setProgressView(true); setProgressView(true);
await UploadService.uploadFiles(acceptedFiles, collectionLatestFile, token, progressBarProps); await UploadService.uploadFiles(acceptedFiles, collectionAndItsLatestFile, token, progressBarProps);
refetchData(); refetchData();
setProgressView(false); setProgressView(false);
} }

View file

@ -10,17 +10,16 @@ function CollectionSelector(props) {
uploadModalView, uploadModalView,
closeUploadModal, closeUploadModal,
showUploadModal, showUploadModal,
collectionLatestFile, collectionAndItsLatestFile,
...rest ...rest
} = props; } = props;
const CollectionIcons = collectionAndItsLatestFile?.map((item) => (
const CollectionIcons = collectionLatestFile?.map((item) => (
<CollectionDropZone key={item.collection.id} <CollectionDropZone key={item.collection.id}
{...rest} {...rest}
closeModal={closeUploadModal} closeModal={closeUploadModal}
showModal={showUploadModal} showModal={showUploadModal}
collectionLatestFile={item} collectionAndItsLatestFile={item}
> >
<Card> <Card>
<PreviewCard data={item.file} updateUrl={() => { }} forcedEnable /> <PreviewCard data={item.file} updateUrl={() => { }} forcedEnable />

View file

@ -5,7 +5,7 @@ import styled from 'styled-components';
interface CollectionProps { interface CollectionProps {
collections: collection[]; collections: collection[];
selected?: string; selected?: string;
selectCollection: (id?: string) => void; selectCollection: (id?: number) => void;
} }
const Container = styled.div` const Container = styled.div`
@ -51,7 +51,7 @@ const Chip = styled.button<{ active: boolean }>`
export default function Collections(props: CollectionProps) { export default function Collections(props: CollectionProps) {
const { selected, collections, selectCollection } = props; const { selected, collections, selectCollection } = props;
const clickHandler = (id?: string) => () => selectCollection(id); const clickHandler = (id?: number) => () => selectCollection(id);
return <Container> return <Container>
<Wrapper> <Wrapper>

View file

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Button, Form, Modal } from 'react-bootstrap'; import { Button, Form, Modal } from 'react-bootstrap';
import { createAlbum } from 'services/collectionService'; import { createAlbum } from 'services/collectionService';
import UploadService from 'services/uploadService'; import UploadService from 'services/uploadService';
import { collectionLatestFile } from 'services/collectionService' import { CollectionAndItsLatestFile } from 'services/collectionService'
import { getToken } from 'utils/common/key'; import { getToken } from 'utils/common/key';
export default function CreateCollection(props) { export default function CreateCollection(props) {
@ -35,12 +35,12 @@ export default function CreateCollection(props) {
const collection = await createAlbum(albumName); const collection = await createAlbum(albumName);
const collectionLatestFile: collectionLatestFile = { collection, file: null } const collectionAndItsLatestFile: CollectionAndItsLatestFile = { collection, file: null }
progressBarProps.setPercentComplete(0); progressBarProps.setPercentComplete(0);
setProgressView(true); setProgressView(true);
await UploadService.uploadFiles(acceptedFiles, collectionLatestFile, token, progressBarProps); await UploadService.uploadFiles(acceptedFiles, collectionAndItsLatestFile, token, progressBarProps);
refetchData(); refetchData();
setProgressView(false); setProgressView(false);
} }

View file

@ -6,7 +6,7 @@ import {
file, file,
getFile, getFile,
getPreview, getPreview,
fetchData, syncData,
localFiles, localFiles,
} from 'services/fileService'; } from 'services/fileService';
import { getData, LS_KEYS } from 'utils/storage/localStorage'; import { getData, LS_KEYS } from 'utils/storage/localStorage';
@ -22,9 +22,9 @@ import Collections from './components/Collections';
import Upload from './components/Upload'; import Upload from './components/Upload';
import { import {
collection, collection,
fetchUpdatedCollections, syncCollections,
collectionLatestFile, CollectionAndItsLatestFile,
getCollectionLatestFile, getCollectionAndItsLatestFile,
getFavItemIds, getFavItemIds,
getLocalCollections, getLocalCollections,
} from 'services/collectionService'; } from 'services/collectionService';
@ -108,9 +108,10 @@ export default function Gallery(props) {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [collections, setCollections] = useState<collection[]>([]); const [collections, setCollections] = useState<collection[]>([]);
const [collectionLatestFile, setCollectionLatestFile] = useState< const [
collectionLatestFile[] collectionAndItsLatestFile,
>([]); setCollectionAndItsLatestFile,
] = useState<CollectionAndItsLatestFile[]>([]);
const [data, setData] = useState<file[]>(); const [data, setData] = useState<file[]>();
const [favItemIds, setFavItemIds] = useState<Set<number>>(); const [favItemIds, setFavItemIds] = useState<Set<number>>();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -132,8 +133,16 @@ export default function Gallery(props) {
setLoading(true); setLoading(true);
const data = await localFiles(); const data = await localFiles();
const collections = await getLocalCollections(); const collections = await getLocalCollections();
const collectionAndItsLatestFile = await getCollectionAndItsLatestFile(
collections,
data
);
setData(data); setData(data);
setCollections(collections); setCollections(collections);
setCollectionAndItsLatestFile(collectionAndItsLatestFile);
const favItemIds = await getFavItemIds(data);
setFavItemIds(favItemIds);
setLoading(false); setLoading(false);
setProgress(80); setProgress(80);
await syncWithRemote(); await syncWithRemote();
@ -146,22 +155,18 @@ export default function Gallery(props) {
const syncWithRemote = async () => { const syncWithRemote = async () => {
const token = getToken(); const token = getToken();
const encryptionKey = await getActualKey(); const encryptionKey = await getActualKey();
const updatedCollections = await fetchUpdatedCollections( const collections = await syncCollections(token, encryptionKey);
token, const { data, isUpdated } = await syncData(token, collections);
encryptionKey const collectionAndItsLatestFile = await getCollectionAndItsLatestFile(
);
const data = await fetchData(token, updatedCollections);
const collections = await getLocalCollections();
const collectionLatestFile = await getCollectionLatestFile(
collections, collections,
data data
); );
const favItemIds = await getFavItemIds(data); const favItemIds = await getFavItemIds(data);
if (updatedCollections.length > 0) { setCollections(collections);
setCollections(collections); if (isUpdated) {
setData(data); setData(data);
} }
setCollectionLatestFile(collectionLatestFile); setCollectionAndItsLatestFile(collectionAndItsLatestFile);
setFavItemIds(favItemIds); setFavItemIds(favItemIds);
setSinceTime(new Date().getTime()); setSinceTime(new Date().getTime());
props.setUploadButtonView(true); props.setUploadButtonView(true);
@ -295,7 +300,7 @@ export default function Gallery(props) {
); );
} }
const selectCollection = (id?: string) => { const selectCollection = (id?: number) => {
const href = `/gallery?collection=${id || ''}`; const href = `/gallery?collection=${id || ''}`;
router.push(href, undefined, { shallow: true }); router.push(href, undefined, { shallow: true });
}; };
@ -344,7 +349,7 @@ export default function Gallery(props) {
uploadModalView={props.uploadModalView} uploadModalView={props.uploadModalView}
closeUploadModal={props.closeUploadModal} closeUploadModal={props.closeUploadModal}
showUploadModal={props.showUploadModal} showUploadModal={props.showUploadModal}
collectionLatestFile={collectionLatestFile} collectionAndItsLatestFile={collectionAndItsLatestFile}
refetchData={syncWithRemote} refetchData={syncWithRemote}
/> />
{filteredData.length ? ( {filteredData.length ? (

View file

@ -1,35 +1,37 @@
import { getEndpoint } from "utils/common/apiUtil"; import { getEndpoint } from 'utils/common/apiUtil';
import { getData, LS_KEYS } from "utils/storage/localStorage"; import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { file, user, getFiles } from "./fileService"; import { file, user, getFiles } from './fileService';
import localForage from 'localforage'; import localForage from 'localforage';
import HTTPService from "./HTTPService"; import HTTPService from './HTTPService';
import * as Comlink from 'comlink'; import * as Comlink from 'comlink';
import { keyEncryptionResult } from "./uploadService"; import { keyEncryptionResult } from './uploadService';
import { getActualKey, getToken } from "utils/common/key"; import { getActualKey, getToken } from 'utils/common/key';
const CryptoWorker: any = const CryptoWorker: any =
typeof window !== 'undefined' && typeof window !== 'undefined' &&
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' })); Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
const ENDPOINT = getEndpoint(); const ENDPOINT = getEndpoint();
enum CollectionType { enum CollectionType {
folder = "folder", folder = 'folder',
favorites = "favorites", favorites = 'favorites',
album = "album", album = 'album',
} }
const COLLECTION_UPDATION_TIME = 'collection-updation-time';
const FAV_COLLECTION = 'fav-collection';
const COLLECTIONS = 'collections';
export interface collection { export interface collection {
id: string; id: number;
owner: user; owner: user;
key?: string; key?: string;
name?: string; name?: string;
encryptedName?: string; encryptedName?: string;
nameDecryptionNonce?: string; nameDecryptionNonce?: string;
type: string; type: string;
attributes: collectionAttributes attributes: collectionAttributes;
sharees: user[]; sharees: user[];
updationTime: number; updationTime: number;
encryptedKey: string; encryptedKey: string;
@ -39,16 +41,18 @@ export interface collection {
interface collectionAttributes { interface collectionAttributes {
encryptedPath?: string; encryptedPath?: string;
pathDecryptionNonce?: string pathDecryptionNonce?: string;
}; }
export interface collectionLatestFile { export interface CollectionAndItsLatestFile {
collection: collection collection: collection;
file: file; file: file;
} }
const getCollectionSecrets = async (
const getCollectionSecrets = async (collection: collection, masterKey: string) => { collection: collection,
masterKey: string
) => {
const worker = await new CryptoWorker(); const worker = await new CryptoWorker();
const userID = getData(LS_KEYS.USER).id; const userID = getData(LS_KEYS.USER).id;
let decryptedKey: string; let decryptedKey: string;
@ -58,7 +62,6 @@ const getCollectionSecrets = async (collection: collection, masterKey: string) =
collection.keyDecryptionNonce, collection.keyDecryptionNonce,
masterKey masterKey
); );
} else { } else {
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
const secretKey = await worker.decryptB64( const secretKey = await worker.decryptB64(
@ -72,10 +75,13 @@ const getCollectionSecrets = async (collection: collection, masterKey: string) =
secretKey secretKey
); );
} }
collection.name = collection.name || await worker.decryptString( collection.name =
collection.encryptedName, collection.name ||
collection.nameDecryptionNonce, (await worker.decryptString(
decryptedKey); collection.encryptedName,
collection.nameDecryptionNonce,
decryptedKey
));
return { return {
...collection, ...collection,
key: decryptedKey, key: decryptedKey,
@ -88,85 +94,131 @@ const getCollections = async (
key: string key: string
): Promise<collection[]> => { ): Promise<collection[]> => {
try { try {
const resp = await HTTPService.get(`${ENDPOINT}/collections`, { const resp = await HTTPService.get(
sinceTime: sinceTime, `${ENDPOINT}/collections`,
}, { 'X-Auth-Token': token, }); {
sinceTime: sinceTime,
},
{ 'X-Auth-Token': token }
);
const promises: Promise<collection>[] = resp.data.collections.map( const promises: Promise<collection>[] = resp.data.collections.map(
(collection: collection) => getCollectionSecrets(collection, key) (collection: collection) => getCollectionSecrets(collection, key)
); );
return await Promise.all(promises); return await Promise.all(promises);
} } catch (e) {
catch (e) { console.log('getCollections failed- ' + e);
console.log("getCollections failed- " + e);
} }
}; };
export const getLocalCollections = async (): Promise<collection[]> => { export const getLocalCollections = async (): Promise<collection[]> => {
const collections = await localForage.getItem('collections') as collection[] ?? []; const collections: collection[] =
(await localForage.getItem(COLLECTIONS)) ?? [];
return collections; return collections;
} };
export const fetchUpdatedCollections = async (token: string, key: string) => {
const collectionUpdateTime = await localForage.getItem('collection-update-time') as string; export const syncCollections = async (token: string, key: string) => {
const updatedCollections = await getCollections(token, collectionUpdateTime ?? '0', key) || [];
const favCollection = await localForage.getItem('fav-collection') as collection[] ?? updatedCollections.filter(collection => collection.type === CollectionType.favorites);
const localCollections = await getLocalCollections(); const localCollections = await getLocalCollections();
const allCollectionsInstances = [...localCollections, ...updatedCollections]; const lastCollectionUpdationTime =
var latestCollectionsInstances = new Map<string, collection>(); (await localForage.getItem<string>(COLLECTION_UPDATION_TIME)) ?? '0';
const updatedCollections =
(await getCollections(token, lastCollectionUpdationTime, key)) || [];
if (updatedCollections.length == 0) {
return localCollections;
}
setLocalFavoriteCollection(updatedCollections);
const allCollectionsInstances = [
...localCollections,
...updatedCollections,
];
var latestCollectionsInstances = new Map<number, collection>();
allCollectionsInstances.forEach((collection) => { allCollectionsInstances.forEach((collection) => {
if (!latestCollectionsInstances.has(collection.id) || latestCollectionsInstances.get(collection.id).updationTime < collection.updationTime) { if (
!latestCollectionsInstances.has(collection.id) ||
latestCollectionsInstances.get(collection.id).updationTime <
collection.updationTime
) {
latestCollectionsInstances.set(collection.id, collection); latestCollectionsInstances.set(collection.id, collection);
} }
}); });
let collections = [];
let collections = [],
updationTime = await localForage.getItem<number>(
COLLECTION_UPDATION_TIME
);
for (const [_, collection] of latestCollectionsInstances) { for (const [_, collection] of latestCollectionsInstances) {
collections.push(collection); if (!collection.isDeleted) {
collections.push(collection);
updationTime = Math.max(updationTime, collection.updationTime);
}
} }
await localForage.setItem('fav-collection', favCollection); await localForage.setItem(COLLECTION_UPDATION_TIME, updationTime);
await localForage.setItem('collections', collections); await localForage.setItem(COLLECTIONS, collections);
return updatedCollections; return collections;
}; };
export const getCollectionLatestFile = ( export const getCollectionAndItsLatestFile = (
collections: collection[], collections: collection[],
files: file[] files: file[]
): collectionLatestFile[] => { ): CollectionAndItsLatestFile[] => {
const latestFile = new Map<number, file>(); const latestFile = new Map<number, file>();
const collectionMap = new Map<number, collection>(); const collectionMap = new Map<number, collection>();
collections.forEach(collection => collectionMap.set(Number(collection.id), collection)); collections.forEach((collection) =>
files.forEach(file => { collectionMap.set(collection.id, collection)
);
files.forEach((file) => {
if (!latestFile.has(file.collectionID)) { if (!latestFile.has(file.collectionID)) {
latestFile.set(file.collectionID, file) latestFile.set(file.collectionID, file);
} }
}); });
let allCollectionLatestFile: collectionLatestFile[] = []; let allCollectionAndItsLatestFile: CollectionAndItsLatestFile[] = [];
for (const [collectionID, file] of latestFile) { for (const [collectionID, file] of latestFile) {
allCollectionLatestFile.push({ collection: collectionMap.get(collectionID), file }); allCollectionAndItsLatestFile.push({
collection: collectionMap.get(collectionID),
file,
});
} }
return allCollectionLatestFile; return allCollectionAndItsLatestFile;
} };
export const getFavItemIds = async (files: file[]): Promise<Set<number>> => { export const getFavItemIds = async (files: file[]): Promise<Set<number>> => {
let favCollection = await localForage.getItem<collection>(FAV_COLLECTION);
if (!favCollection) return new Set();
let favCollection: collection = (await localForage.getItem<collection>('fav-collection'))[0]; return new Set(
if (!favCollection) files
return new Set(); .filter((file) => file.collectionID === favCollection.id)
.map((file): number => file.id)
return new Set(files.filter(file => file.collectionID === Number(favCollection.id)).map((file): number => file.id)); );
} };
export const createAlbum = async (albumName: string) => { export const createAlbum = async (albumName: string) => {
return AddCollection(albumName, CollectionType.album); return AddCollection(albumName, CollectionType.album);
} };
export const AddCollection = async (
export const AddCollection = async (collectionName: string, type: CollectionType) => { collectionName: string,
type: CollectionType
) => {
const worker = await new CryptoWorker(); const worker = await new CryptoWorker();
const encryptionKey = await getActualKey(); const encryptionKey = await getActualKey();
const token = getToken(); const token = getToken();
const collectionKey: string = await worker.generateMasterKey(); const collectionKey: string = await worker.generateMasterKey();
const { encryptedData: encryptedKey, nonce: keyDecryptionNonce }: keyEncryptionResult = await worker.encryptToB64(collectionKey, encryptionKey); const {
const { encryptedData: encryptedName, nonce: nameDecryptionNonce }: keyEncryptionResult = await worker.encryptToB64(collectionName, collectionKey); encryptedData: encryptedKey,
nonce: keyDecryptionNonce,
}: keyEncryptionResult = await worker.encryptToB64(
collectionKey,
encryptionKey
);
const {
encryptedData: encryptedName,
nonce: nameDecryptionNonce,
}: keyEncryptionResult = await worker.encryptToB64(
collectionName,
collectionKey
);
const newCollection: collection = { const newCollection: collection = {
id: null, id: null,
owner: null, owner: null,
@ -178,76 +230,126 @@ export const AddCollection = async (collectionName: string, type: CollectionType
attributes: {}, attributes: {},
sharees: null, sharees: null,
updationTime: null, updationTime: null,
isDeleted: false isDeleted: false,
}; };
let createdCollection: collection = await createCollection(newCollection, token); let createdCollection: collection = await createCollection(
createdCollection = await getCollectionSecrets(createdCollection, encryptionKey); newCollection,
token
);
createdCollection = await getCollectionSecrets(
createdCollection,
encryptionKey
);
return createdCollection; return createdCollection;
} };
const createCollection = async (collectionData: collection, token: string): Promise<collection> => { const createCollection = async (
collectionData: collection,
token: string
): Promise<collection> => {
try { try {
const response = await HTTPService.post(`${ENDPOINT}/collections`, collectionData, null, { 'X-Auth-Token': token }); const response = await HTTPService.post(
`${ENDPOINT}/collections`,
collectionData,
null,
{ 'X-Auth-Token': token }
);
return response.data.collection; return response.data.collection;
} catch (e) { } catch (e) {
console.log("create Collection failed " + e); console.log('create Collection failed ' + e);
} }
} };
export const addToFavorites = async (file: file) => { export const addToFavorites = async (file: file) => {
let favCollection: collection = (await localForage.getItem<collection>('fav-collection'))[0]; let favCollection: collection = await localForage.getItem<collection>(
FAV_COLLECTION
);
if (!favCollection) { if (!favCollection) {
favCollection = await AddCollection("Favorites", CollectionType.favorites); favCollection = await AddCollection(
await localForage.setItem('fav-collection', favCollection); 'Favorites',
CollectionType.favorites
);
await localForage.setItem(FAV_COLLECTION, favCollection);
} }
await addtoCollection(favCollection, [file]) await addToCollection(favCollection, [file]);
} };
export const removeFromFavorites = async (file: file) => { export const removeFromFavorites = async (file: file) => {
let favCollection: collection = (await localForage.getItem<collection>('fav-collection'))[0]; let favCollection: collection = await localForage.getItem<collection>(
await removeFromCollection(favCollection, [file]) FAV_COLLECTION
} );
await removeFromCollection(favCollection, [file]);
};
const addtoCollection = async (collection: collection, files: file[]) => { const addToCollection = async (collection: collection, files: file[]) => {
try { try {
const params = new Object(); const params = new Object();
const worker = await new CryptoWorker(); const worker = await new CryptoWorker();
const token = getToken(); const token = getToken();
params["collectionID"] = collection.id; params['collectionID'] = collection.id;
await Promise.all(files.map(async file => { await Promise.all(
file.collectionID = Number(collection.id); files.map(async (file) => {
const newEncryptedKey: keyEncryptionResult = await worker.encryptToB64(file.key, collection.key); file.collectionID = collection.id;
file.encryptedKey = newEncryptedKey.encryptedData; const newEncryptedKey: keyEncryptionResult = await worker.encryptToB64(
file.keyDecryptionNonce = newEncryptedKey.nonce; file.key,
if (params["files"] == undefined) { collection.key
params["files"] = []; );
} file.encryptedKey = newEncryptedKey.encryptedData;
params["files"].push({ file.keyDecryptionNonce = newEncryptedKey.nonce;
id: file.id, if (params['files'] == undefined) {
encryptedKey: file.encryptedKey, params['files'] = [];
keyDecryptionNonce: file.keyDecryptionNonce }
params['files'].push({
id: file.id,
encryptedKey: file.encryptedKey,
keyDecryptionNonce: file.keyDecryptionNonce,
});
return file;
}) })
return file; );
})); await HTTPService.post(
await HTTPService.post(`${ENDPOINT}/collections/add-files`, params, null, { 'X-Auth-Token': token }); `${ENDPOINT}/collections/add-files`,
params,
null,
{ 'X-Auth-Token': token }
);
} catch (e) { } catch (e) {
console.log("Add to collection Failed " + e); console.log('Add to collection Failed ' + e);
} }
} };
const removeFromCollection = async (collection: collection, files: file[]) => { const removeFromCollection = async (collection: collection, files: file[]) => {
try { try {
const params = new Object(); const params = new Object();
const token = getToken(); const token = getToken();
params["collectionID"] = collection.id; params['collectionID'] = collection.id;
await Promise.all(files.map(async file => { await Promise.all(
if (params["fileIDs"] == undefined) { files.map(async (file) => {
params["fileIDs"] = []; if (params['fileIDs'] == undefined) {
} params['fileIDs'] = [];
params["fileIDs"].push(file.id); }
})); params['fileIDs'].push(file.id);
await HTTPService.post(`${ENDPOINT}/collections/remove-files`, params, null, { 'X-Auth-Token': token }); })
);
await HTTPService.post(
`${ENDPOINT}/collections/remove-files`,
params,
null,
{ 'X-Auth-Token': token }
);
} catch (e) { } catch (e) {
console.log("remove from collection failed " + e); console.log('remove from collection failed ' + e);
} }
} };
const setLocalFavoriteCollection = async (collections: collection[]) => {
const localFavCollection = await localForage.getItem(FAV_COLLECTION);
if (localFavCollection) {
return;
}
const favCollection = collections.filter(
(collection) => collection.type == CollectionType.favorites
);
if (favCollection.length > 0) {
await localForage.setItem(FAV_COLLECTION, favCollection[0]);
}
};

View file

@ -16,6 +16,8 @@ localForage.config({
storeName: 'files', storeName: 'files',
}); });
const FILES = 'files';
export interface fileAttribute { export interface fileAttribute {
encryptedData: Uint8Array | string; encryptedData: Uint8Array | string;
decryptionHeader: string; decryptionHeader: string;
@ -29,7 +31,6 @@ export interface user {
email: string; email: string;
} }
export interface file { export interface file {
id: number; id: number;
collectionID: number; collectionID: number;
@ -49,92 +50,95 @@ export interface file {
updationTime: number; updationTime: number;
} }
export const syncData = async (token, collections) => {
const { files: resp, isUpdated } = await syncFiles(token, collections);
return {
export const fetchData = async (token, collections) => { data: resp.map((item) => ({
const resp = await fetchFiles(
token,
collections
);
return (
resp.map((item) => ({
...item, ...item,
w: window.innerWidth, w: window.innerWidth,
h: window.innerHeight, h: window.innerHeight,
})) })),
); isUpdated,
} };
};
export const localFiles = async () => { export const localFiles = async () => {
let files: Array<file> = (await localForage.getItem<file[]>('files')) || []; let files: Array<file> = (await localForage.getItem<file[]>(FILES)) || [];
return files;
}
export const fetchFiles = async (
token: string,
collections: collection[]
) => {
let files = await localFiles();
const collectionUpdationTime = new Map<string, string>();
let fetchedFiles = [];
for (let collection of collections) {
const files = await getFiles(collection, null, 100, token);
fetchedFiles.push(...files);
collectionUpdationTime.set(collection.id, files.length > 0 ? files.slice(-1)[0].updationTime.toString() : "0");
}
files.push(...fetchedFiles);
var latestFiles = new Map<string, file>();
files.forEach((file) => {
let uid = `${file.collectionID}-${file.id}`;
if (!latestFiles.has(uid) || latestFiles.get(uid).updationTime < file.updationTime) {
latestFiles.set(uid, file);
}
});
files = [];
for (const [_, file] of latestFiles) {
if (!file.isDeleted)
files.push(file);
}
files = files.sort(
(a, b) => b.metadata.creationTime - a.metadata.creationTime
);
await localForage.setItem('files', files);
for (let [collectionID, updationTime] of collectionUpdationTime) {
await localForage.setItem(`${collectionID}-time`, updationTime);
}
let updationTime = await localForage.getItem('collection-update-time') as number;
for (let collection of collections) {
updationTime = Math.max(updationTime, collection.updationTime);
}
await localForage.setItem('collection-update-time', updationTime);
return files; return files;
}; };
export const getFiles = async (collection: collection, sinceTime: string, limit: number, token: string): Promise<file[]> => { export const syncFiles = async (token: string, collections: collection[]) => {
let files = await localFiles();
let isUpdated = false;
files = await removeDeletedCollectionFiles(collections, files);
for (let collection of collections) {
const lastSyncTime =
(await localForage.getItem<number>(`${collection.id}-time`)) ?? 0;
if (collection.updationTime === lastSyncTime) {
continue;
}
isUpdated = true;
let fetchedFiles =
(await getFiles(collection, lastSyncTime, 100, token)) ?? [];
files.push(...fetchedFiles);
var latestVersionFiles = new Map<number, file>();
files.forEach((file) => {
if (
!latestVersionFiles.has(file.id) ||
latestVersionFiles.get(file.id).updationTime < file.updationTime
) {
latestVersionFiles.set(file.id, file);
}
});
files = [];
for (const [_, file] of latestVersionFiles) {
if (file.isDeleted) {
continue;
}
files.push(file);
}
files = files.sort(
(a, b) => b.metadata.creationTime - a.metadata.creationTime
);
await localForage.setItem('files', files);
await localForage.setItem(
`${collection.id}-time`,
collection.updationTime
);
}
return { files, isUpdated };
};
export const getFiles = async (
collection: collection,
sinceTime: number,
limit: number,
token: string
): Promise<file[]> => {
try { try {
const worker = await new CryptoWorker(); const worker = await new CryptoWorker();
let promises: Promise<file>[] = []; let promises: Promise<file>[] = [];
if (collection.isDeleted) {
// TODO: Remove files in this collection from localForage and cache
return;
}
let time = let time =
sinceTime || (await localForage.getItem<string>(`${collection.id}-time`)) || "0"; sinceTime ||
(await localForage.getItem<number>(`${collection.id}-time`)) ||
0;
let resp; let resp;
do { do {
resp = await HTTPService.get(`${ENDPOINT}/collections/diff`, { resp = await HTTPService.get(
collectionID: collection.id, `${ENDPOINT}/collections/diff`,
sinceTime: time,
limit: limit.toString(),
},
{ {
'X-Auth-Token': token collectionID: collection.id.toString(),
}); sinceTime: time.toString(),
promises.push(...resp.data.diff.map( limit: limit.toString(),
async (file: file) => { },
{
'X-Auth-Token': token,
}
);
promises.push(
...resp.data.diff.map(async (file: file) => {
if (!file.isDeleted) { if (!file.isDeleted) {
file.key = await worker.decryptB64( file.key = await worker.decryptB64(
file.encryptedKey, file.encryptedKey,
file.keyDecryptionNonce, file.keyDecryptionNonce,
@ -143,8 +147,8 @@ export const getFiles = async (collection: collection, sinceTime: string, limit:
file.metadata = await worker.decryptMetadata(file); file.metadata = await worker.decryptMetadata(file);
} }
return file; return file;
} })
)); );
if (resp.data.diff.length) { if (resp.data.diff.length) {
time = resp.data.diff.slice(-1)[0].updationTime.toString(); time = resp.data.diff.slice(-1)[0].updationTime.toString();
@ -152,9 +156,9 @@ export const getFiles = async (collection: collection, sinceTime: string, limit:
} while (resp.data.diff.length === limit); } while (resp.data.diff.length === limit);
return await Promise.all(promises); return await Promise.all(promises);
} catch (e) { } catch (e) {
console.log("Get files failed" + e); console.log('Get files failed' + e);
} }
} };
export const getPreview = async (token: string, file: file) => { export const getPreview = async (token: string, file: file) => {
try { try {
const cache = await caches.open('thumbs'); const cache = await caches.open('thumbs');
@ -175,13 +179,16 @@ export const getPreview = async (token: string, file: file) => {
file.key file.key
); );
try { try {
await cache.put(file.id.toString(), new Response(new Blob([decrypted]))); await cache.put(
file.id.toString(),
new Response(new Blob([decrypted]))
);
} catch (e) { } catch (e) {
// TODO: handle storage full exception. // TODO: handle storage full exception.
} }
return URL.createObjectURL(new Blob([decrypted])); return URL.createObjectURL(new Blob([decrypted]));
} catch (e) { } catch (e) {
console.log("get preview Failed" + e); console.log('get preview Failed' + e);
} }
}; };
@ -200,9 +207,19 @@ export const getFile = async (token: string, file: file) => {
file.key file.key
); );
return URL.createObjectURL(new Blob([decrypted])); return URL.createObjectURL(new Blob([decrypted]));
} } catch (e) {
catch (e) { console.log('get file failed ' + e);
console.log("get file failed " + e);
} }
}; };
const removeDeletedCollectionFiles = async (
collections: collection[],
files: file[]
) => {
const syncedCollectionIds = new Set<number>();
for (let collection of collections) {
syncedCollectionIds.add(collection.id);
}
files = files.filter((file) => syncedCollectionIds.has(file.collectionID));
return files;
};

View file

@ -3,7 +3,7 @@ import HTTPService from './HTTPService';
import * as Comlink from 'comlink'; import * as Comlink from 'comlink';
import EXIF from "exif-js"; import EXIF from "exif-js";
import { fileAttribute } from './fileService'; import { fileAttribute } from './fileService';
import { collection, collectionLatestFile } from "./collectionService" import { collection, CollectionAndItsLatestFile } from "./collectionService"
import { FILE_TYPE } from 'pages/gallery'; import { FILE_TYPE } from 'pages/gallery';
const CryptoWorker: any = const CryptoWorker: any =
typeof window !== 'undefined' && typeof window !== 'undefined' &&
@ -49,7 +49,7 @@ interface objectKeys {
} }
interface uploadFile extends objectKeys { interface uploadFile extends objectKeys {
collectionID: string, collectionID: number,
encryptedKey: string; encryptedKey: string;
keyDecryptionNonce: string; keyDecryptionNonce: string;
metadata?: { metadata?: {
@ -80,7 +80,7 @@ class UploadService {
private totalFilesCount: number private totalFilesCount: number
private metadataMap: Map<string, Object>; private metadataMap: Map<string, Object>;
public async uploadFiles(recievedFiles: File[], collectionLatestFile: collectionLatestFile, token: string, progressBarProps) { public async uploadFiles(recievedFiles: File[], collectionAndItsLatestFile: CollectionAndItsLatestFile, token: string, progressBarProps) {
try { try {
const worker = await new CryptoWorker(); const worker = await new CryptoWorker();
this.stepsCompleted = 0; this.stepsCompleted = 0;
@ -107,7 +107,7 @@ class UploadService {
while (actualFiles.length > 0) { while (actualFiles.length > 0) {
var promises = []; var promises = [];
for (var i = 0; i < 5 && actualFiles.length > 0; i++) for (var i = 0; i < 5 && actualFiles.length > 0; i++)
promises.push(this.uploadHelper(progressBarProps, actualFiles.pop(), collectionLatestFile.collection, token)); promises.push(this.uploadHelper(progressBarProps, actualFiles.pop(), collectionAndItsLatestFile.collection, token));
uploadFilesWithoutMetaData.push(...await Promise.all(promises)); uploadFilesWithoutMetaData.push(...await Promise.all(promises));
} }
@ -364,7 +364,6 @@ class UploadService {
resolve(blob); resolve(blob);
}), 'image/jpeg', 0.4 }), 'image/jpeg', 0.4
}); });
console.log(URL.createObjectURL(thumbnailBlob));
const thumbnail = this.getUint8ArrayView(thumbnailBlob); const thumbnail = this.getUint8ArrayView(thumbnailBlob);
return thumbnail; return thumbnail;
} catch (e) { } catch (e) {

View file

@ -1,5 +1,4 @@
export const getEndpoint = () => { export const getEndpoint = () => {
const endPoint = process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? "https://api.ente.io"; const endPoint = process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? "https://api.ente.io";
console.log(endPoint);
return endPoint; return endPoint;
} }