2021-05-30 16:56:48 +00:00
|
|
|
import { getEndpoint } from 'utils/common/apiUtil';
|
2021-03-12 06:58:27 +00:00
|
|
|
import localForage from 'utils/storage/localForage';
|
|
|
|
|
2021-04-03 04:36:15 +00:00
|
|
|
import CryptoWorker from 'utils/crypto';
|
2021-05-30 16:56:48 +00:00
|
|
|
import { getToken } from 'utils/common/key';
|
|
|
|
import { DataStream, MetadataObject } from './uploadService';
|
|
|
|
import { Collection } from './collectionService';
|
2021-05-29 06:27:52 +00:00
|
|
|
import HTTPService from './HTTPService';
|
2021-06-12 17:14:21 +00:00
|
|
|
import { logError } from 'utils/sentry';
|
2020-09-27 17:18:57 +00:00
|
|
|
|
2020-09-19 21:20:10 +00:00
|
|
|
const ENDPOINT = getEndpoint();
|
2021-07-09 08:47:30 +00:00
|
|
|
const DIFF_LIMIT: number = 1000;
|
2020-09-19 21:20:10 +00:00
|
|
|
|
2021-02-09 05:43:05 +00:00
|
|
|
const FILES = 'files';
|
|
|
|
|
2020-10-19 03:01:34 +00:00
|
|
|
export interface fileAttribute {
|
2021-03-04 12:14:45 +00:00
|
|
|
encryptedData?: DataStream | Uint8Array;
|
2021-02-16 09:44:25 +00:00
|
|
|
objectKey?: string;
|
2021-01-08 03:51:59 +00:00
|
|
|
decryptionHeader: string;
|
2021-01-05 10:23:28 +00:00
|
|
|
}
|
2020-10-19 03:01:34 +00:00
|
|
|
|
2021-04-27 09:17:38 +00:00
|
|
|
export interface File {
|
2021-01-08 03:51:59 +00:00
|
|
|
id: number;
|
|
|
|
collectionID: number;
|
|
|
|
file: fileAttribute;
|
|
|
|
thumbnail: fileAttribute;
|
2021-02-16 09:44:25 +00:00
|
|
|
metadata: MetadataObject;
|
2021-01-08 03:51:59 +00:00
|
|
|
encryptedKey: string;
|
|
|
|
keyDecryptionNonce: string;
|
2021-01-18 02:26:34 +00:00
|
|
|
key: string;
|
2021-01-08 03:51:59 +00:00
|
|
|
src: string;
|
|
|
|
msrc: string;
|
|
|
|
html: string;
|
|
|
|
w: number;
|
|
|
|
h: number;
|
|
|
|
isDeleted: boolean;
|
|
|
|
dataIndex: number;
|
2021-01-20 13:51:22 +00:00
|
|
|
updationTime: number;
|
2021-01-05 10:23:28 +00:00
|
|
|
}
|
2020-09-19 21:20:10 +00:00
|
|
|
|
2021-04-23 19:16:27 +00:00
|
|
|
export const getLocalFiles = async () => {
|
2021-05-29 06:27:52 +00:00
|
|
|
const files: Array<File> = (await localForage.getItem<File[]>(FILES)) || [];
|
2021-01-27 06:14:02 +00:00
|
|
|
return files;
|
2021-02-08 16:17:17 +00:00
|
|
|
};
|
2021-01-27 06:14:02 +00:00
|
|
|
|
2021-05-30 16:35:12 +00:00
|
|
|
export const syncFiles = async (collections: Collection[], setFiles: (files: File[]) => void) => {
|
2021-04-23 19:16:27 +00:00
|
|
|
const localFiles = await getLocalFiles();
|
|
|
|
let files = await removeDeletedCollectionFiles(collections, localFiles);
|
2021-05-29 06:27:52 +00:00
|
|
|
if (files.length !== localFiles.length) {
|
2021-04-24 13:30:11 +00:00
|
|
|
await localForage.setItem('files', files);
|
2021-04-23 19:16:27 +00:00
|
|
|
}
|
2021-05-29 06:27:52 +00:00
|
|
|
for (const collection of collections) {
|
2021-03-16 06:49:34 +00:00
|
|
|
if (!getToken()) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-05-29 06:27:52 +00:00
|
|
|
const lastSyncTime = (await localForage.getItem<number>(`${collection.id}-time`)) ?? 0;
|
2021-02-08 17:07:17 +00:00
|
|
|
if (collection.updationTime === lastSyncTime) {
|
2021-02-08 07:33:46 +00:00
|
|
|
continue;
|
2021-02-08 11:01:05 +00:00
|
|
|
}
|
2021-06-01 20:42:44 +00:00
|
|
|
const fetchedFiles = (await getFiles(collection, lastSyncTime, DIFF_LIMIT, files, setFiles)) ?? [];
|
2021-02-08 10:56:47 +00:00
|
|
|
files.push(...fetchedFiles);
|
2021-05-29 06:27:52 +00:00
|
|
|
const latestVersionFiles = new Map<string, File>();
|
2021-02-08 10:56:47 +00:00
|
|
|
files.forEach((file) => {
|
2021-03-16 09:41:11 +00:00
|
|
|
const uid = `${file.collectionID}-${file.id}`;
|
2021-02-08 16:17:17 +00:00
|
|
|
if (
|
2021-03-16 09:41:11 +00:00
|
|
|
!latestVersionFiles.has(uid) ||
|
|
|
|
latestVersionFiles.get(uid).updationTime < file.updationTime
|
2021-02-08 16:17:17 +00:00
|
|
|
) {
|
2021-03-16 09:41:11 +00:00
|
|
|
latestVersionFiles.set(uid, file);
|
2021-02-08 10:56:47 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
files = [];
|
2021-05-29 06:27:52 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
2021-02-08 10:56:47 +00:00
|
|
|
for (const [_, file] of latestVersionFiles) {
|
2021-02-08 16:17:17 +00:00
|
|
|
if (file.isDeleted) {
|
2021-02-08 10:56:47 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
files.push(file);
|
2021-02-06 07:54:36 +00:00
|
|
|
}
|
2021-06-10 08:26:02 +00:00
|
|
|
// sort according to modification time first
|
|
|
|
files = files.sort((a, b) => {
|
|
|
|
if (!b.metadata?.modificationTime) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!a.metadata?.modificationTime) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return b.metadata.modificationTime - a.metadata.modificationTime;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// then sort according to creation time, maintaining ordering according to modification time for files with creation time
|
|
|
|
files = files.map((file, index) => ({ index, file })).sort((a, b) => {
|
|
|
|
let diff = b.file.metadata.creationTime - a.file.metadata.creationTime;
|
|
|
|
if (diff === 0) {
|
|
|
|
diff = a.index - b.index;
|
|
|
|
}
|
|
|
|
return diff;
|
|
|
|
}).map((file) => file.file);
|
2021-02-08 10:56:47 +00:00
|
|
|
await localForage.setItem('files', files);
|
2021-02-08 16:17:17 +00:00
|
|
|
await localForage.setItem(
|
|
|
|
`${collection.id}-time`,
|
2021-05-29 06:27:52 +00:00
|
|
|
collection.updationTime,
|
2021-02-08 16:17:17 +00:00
|
|
|
);
|
2021-05-30 16:35:12 +00:00
|
|
|
setFiles(files.map((item) => ({
|
|
|
|
...item,
|
|
|
|
w: window.innerWidth,
|
|
|
|
h: window.innerHeight,
|
|
|
|
})));
|
2021-02-05 18:51:14 +00:00
|
|
|
}
|
2021-04-27 08:40:37 +00:00
|
|
|
return {
|
|
|
|
files: files.map((item) => ({
|
|
|
|
...item,
|
|
|
|
w: window.innerWidth,
|
|
|
|
h: window.innerHeight,
|
|
|
|
})),
|
|
|
|
};
|
2021-01-14 11:31:20 +00:00
|
|
|
};
|
|
|
|
|
2021-02-08 16:17:17 +00:00
|
|
|
export const getFiles = async (
|
2021-04-23 07:14:07 +00:00
|
|
|
collection: Collection,
|
2021-02-09 05:07:46 +00:00
|
|
|
sinceTime: number,
|
2021-05-29 06:27:52 +00:00
|
|
|
limit: number,
|
2021-06-01 20:42:44 +00:00
|
|
|
files: File[],
|
|
|
|
setFiles: (files: File[]) => void,
|
2021-04-27 09:17:38 +00:00
|
|
|
): Promise<File[]> => {
|
2021-01-25 07:05:45 +00:00
|
|
|
try {
|
|
|
|
const worker = await new CryptoWorker();
|
2021-04-27 09:17:38 +00:00
|
|
|
const decryptedFiles: File[] = [];
|
2021-05-29 06:27:52 +00:00
|
|
|
let time = sinceTime ||
|
2021-02-09 05:07:46 +00:00
|
|
|
(await localForage.getItem<number>(`${collection.id}-time`)) ||
|
|
|
|
0;
|
2021-02-05 18:51:14 +00:00
|
|
|
let resp;
|
|
|
|
do {
|
2021-03-16 06:49:34 +00:00
|
|
|
const token = getToken();
|
|
|
|
if (!token) {
|
|
|
|
break;
|
|
|
|
}
|
2021-02-08 16:17:17 +00:00
|
|
|
resp = await HTTPService.get(
|
|
|
|
`${ENDPOINT}/collections/diff`,
|
|
|
|
{
|
2021-02-17 08:35:19 +00:00
|
|
|
collectionID: collection.id,
|
|
|
|
sinceTime: time,
|
2021-05-29 06:27:52 +00:00
|
|
|
limit,
|
2021-02-08 16:17:17 +00:00
|
|
|
},
|
2021-02-05 18:51:14 +00:00
|
|
|
{
|
2021-02-08 16:17:17 +00:00
|
|
|
'X-Auth-Token': token,
|
2021-05-29 06:27:52 +00:00
|
|
|
},
|
2021-02-08 16:17:17 +00:00
|
|
|
);
|
2021-04-23 07:14:07 +00:00
|
|
|
|
|
|
|
decryptedFiles.push(
|
|
|
|
...(await Promise.all(
|
2021-04-27 09:17:38 +00:00
|
|
|
resp.data.diff.map(async (file: File) => {
|
2021-04-23 07:14:07 +00:00
|
|
|
if (!file.isDeleted) {
|
|
|
|
file.key = await worker.decryptB64(
|
|
|
|
file.encryptedKey,
|
|
|
|
file.keyDecryptionNonce,
|
2021-05-29 06:27:52 +00:00
|
|
|
collection.key,
|
2021-04-23 07:14:07 +00:00
|
|
|
);
|
|
|
|
file.metadata = await worker.decryptMetadata(file);
|
|
|
|
}
|
|
|
|
return file;
|
2021-05-29 06:27:52 +00:00
|
|
|
}) as Promise<File>[],
|
|
|
|
)),
|
2021-02-08 16:17:17 +00:00
|
|
|
);
|
2021-02-05 18:51:14 +00:00
|
|
|
|
|
|
|
if (resp.data.diff.length) {
|
2021-02-17 08:35:19 +00:00
|
|
|
time = resp.data.diff.slice(-1)[0].updationTime;
|
2021-02-05 18:51:14 +00:00
|
|
|
}
|
2021-06-01 20:42:44 +00:00
|
|
|
setFiles([...(files || []), ...decryptedFiles].filter((item) => !item.isDeleted).sort(
|
|
|
|
(a, b) => b.metadata.creationTime - a.metadata.creationTime,
|
|
|
|
));
|
2021-02-05 18:51:14 +00:00
|
|
|
} while (resp.data.diff.length === limit);
|
2021-04-23 07:14:07 +00:00
|
|
|
return decryptedFiles;
|
2021-01-25 07:05:45 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'Get files failed');
|
2021-01-14 11:31:20 +00:00
|
|
|
}
|
2021-02-08 16:17:17 +00:00
|
|
|
};
|
2021-01-05 10:23:28 +00:00
|
|
|
|
2021-02-08 17:07:17 +00:00
|
|
|
const removeDeletedCollectionFiles = async (
|
2021-04-23 07:14:07 +00:00
|
|
|
collections: Collection[],
|
2021-05-29 06:27:52 +00:00
|
|
|
files: File[],
|
2021-02-08 17:07:17 +00:00
|
|
|
) => {
|
2021-02-08 16:17:17 +00:00
|
|
|
const syncedCollectionIds = new Set<number>();
|
2021-05-29 06:27:52 +00:00
|
|
|
for (const collection of collections) {
|
2021-02-08 16:17:17 +00:00
|
|
|
syncedCollectionIds.add(collection.id);
|
|
|
|
}
|
|
|
|
files = files.filter((file) => syncedCollectionIds.has(file.collectionID));
|
2021-02-09 05:07:46 +00:00
|
|
|
return files;
|
2021-02-08 16:17:17 +00:00
|
|
|
};
|
2021-03-21 06:53:41 +00:00
|
|
|
|
2021-04-25 15:18:17 +00:00
|
|
|
export const deleteFiles = async (
|
2021-04-27 11:44:26 +00:00
|
|
|
filesToDelete: number[],
|
2021-04-25 15:18:17 +00:00
|
|
|
clearSelection: Function,
|
2021-05-29 06:27:52 +00:00
|
|
|
syncWithRemote: Function,
|
2021-04-25 15:18:17 +00:00
|
|
|
) => {
|
2021-03-21 06:53:41 +00:00
|
|
|
try {
|
|
|
|
const token = getToken();
|
|
|
|
if (!token) {
|
2021-03-30 05:09:43 +00:00
|
|
|
return;
|
2021-03-21 06:53:41 +00:00
|
|
|
}
|
|
|
|
await HTTPService.post(
|
|
|
|
`${ENDPOINT}/files/delete`,
|
2021-05-30 16:56:48 +00:00
|
|
|
{ fileIDs: filesToDelete },
|
2021-03-21 06:53:41 +00:00
|
|
|
null,
|
|
|
|
{
|
|
|
|
'X-Auth-Token': token,
|
2021-05-29 06:27:52 +00:00
|
|
|
},
|
2021-03-21 06:53:41 +00:00
|
|
|
);
|
2021-04-25 15:18:17 +00:00
|
|
|
clearSelection();
|
|
|
|
syncWithRemote();
|
2021-03-21 06:53:41 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'delete failed');
|
2021-05-31 10:58:33 +00:00
|
|
|
throw e;
|
2021-03-21 06:53:41 +00:00
|
|
|
}
|
|
|
|
};
|