ente/src/services/fileService.ts

209 lines
6 KiB
TypeScript
Raw Normal View History

import { getEndpoint } from 'utils/common/apiUtil';
import HTTPService from './HTTPService';
2021-03-12 06:58:27 +00:00
import localForage from 'utils/storage/localForage';
import { Collection } from './collectionService';
2021-03-04 12:14:45 +00:00
import { DataStream, MetadataObject } from './uploadService';
2021-04-03 04:36:15 +00:00
import CryptoWorker from 'utils/crypto';
import { getToken } from 'utils/common/key';
2021-03-21 06:53:41 +00:00
import { selectedState } from 'pages/gallery';
2021-03-30 08:23:36 +00:00
import { ErrorHandler } from 'utils/common/errorUtil';
2020-09-27 17:18:57 +00:00
const ENDPOINT = getEndpoint();
2021-02-24 16:05:26 +00:00
const DIFF_LIMIT: number = 2500;
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;
decryptionHeader: string;
}
2020-10-19 03:01:34 +00:00
export interface file {
id: number;
collectionID: number;
file: fileAttribute;
thumbnail: fileAttribute;
2021-02-16 09:44:25 +00:00
metadata: MetadataObject;
encryptedKey: string;
keyDecryptionNonce: string;
2021-01-18 02:26:34 +00:00
key: string;
src: string;
msrc: string;
html: string;
w: number;
h: number;
isDeleted: boolean;
dataIndex: number;
updationTime: number;
}
export const syncData = async (collections) => {
const { files: resp, isUpdated } = await syncFiles(collections);
return {
data: resp.map((item) => ({
...item,
w: window.innerWidth,
h: window.innerHeight,
})),
isUpdated,
};
};
export const getLocalFiles = async () => {
2021-02-09 05:43:05 +00:00
let files: Array<file> = (await localForage.getItem<file[]>(FILES)) || [];
return files;
};
export const syncFiles = async (collections: Collection[]) => {
const localFiles = await getLocalFiles();
let isUpdated = false;
let files = await removeDeletedCollectionFiles(collections, localFiles);
if (files.length != localFiles.length) {
isUpdated = true;
2021-04-24 13:30:11 +00:00
await localForage.setItem('files', files);
}
for (let collection of collections) {
if (!getToken()) {
continue;
}
const lastSyncTime =
(await localForage.getItem<number>(`${collection.id}-time`)) ?? 0;
if (collection.updationTime === lastSyncTime) {
2021-02-08 07:33:46 +00:00
continue;
2021-02-08 11:01:05 +00:00
}
isUpdated = true;
let fetchedFiles =
(await getFiles(collection, lastSyncTime, DIFF_LIMIT)) ?? [];
2021-02-08 10:56:47 +00:00
files.push(...fetchedFiles);
2021-03-16 09:41:11 +00:00
var 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}`;
if (
2021-03-16 09:41:11 +00:00
!latestVersionFiles.has(uid) ||
latestVersionFiles.get(uid).updationTime < file.updationTime
) {
2021-03-16 09:41:11 +00:00
latestVersionFiles.set(uid, file);
2021-02-08 10:56:47 +00:00
}
});
files = [];
for (const [_, file] of latestVersionFiles) {
if (file.isDeleted) {
2021-02-08 10:56:47 +00:00
continue;
}
files.push(file);
2021-02-06 07:54:36 +00:00
}
2021-02-08 10:56:47 +00:00
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,
2021-02-09 05:07:46 +00:00
sinceTime: number,
limit: number
): Promise<file[]> => {
2021-01-25 07:05:45 +00:00
try {
const worker = await new CryptoWorker();
const decryptedFiles: file[] = [];
let time =
sinceTime ||
2021-02-09 05:07:46 +00:00
(await localForage.getItem<number>(`${collection.id}-time`)) ||
0;
let resp;
do {
const token = getToken();
if (!token) {
break;
}
resp = await HTTPService.get(
`${ENDPOINT}/collections/diff`,
{
2021-02-17 08:35:19 +00:00
collectionID: collection.id,
sinceTime: time,
limit: limit,
},
{
'X-Auth-Token': token,
}
);
decryptedFiles.push(
...(await Promise.all(
resp.data.diff.map(async (file: file) => {
if (!file.isDeleted) {
file.key = await worker.decryptB64(
file.encryptedKey,
file.keyDecryptionNonce,
collection.key
);
file.metadata = await worker.decryptMetadata(file);
}
return file;
}) as Promise<file>[]
))
);
if (resp.data.diff.length) {
2021-02-17 08:35:19 +00:00
time = resp.data.diff.slice(-1)[0].updationTime;
}
} while (resp.data.diff.length === limit);
return decryptedFiles;
2021-01-25 07:05:45 +00:00
} catch (e) {
console.error('Get files failed', e);
2021-03-30 08:23:36 +00:00
ErrorHandler(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));
2021-02-09 05:07:46 +00:00
return files;
};
2021-03-21 06:53:41 +00:00
2021-04-25 15:18:17 +00:00
export const deleteFiles = async (
clickedFiles: selectedState,
clearSelection: Function,
syncWithRemote: Function
) => {
2021-03-21 06:53:41 +00:00
try {
let filesToDelete = [];
for (let [key, val] of Object.entries(clickedFiles)) {
if (typeof val === 'boolean' && val) {
filesToDelete.push(Number(key));
}
}
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`,
{ fileIDs: filesToDelete },
null,
{
'X-Auth-Token': token,
}
);
2021-04-25 15:18:17 +00:00
clearSelection();
syncWithRemote();
2021-03-21 06:53:41 +00:00
} catch (e) {
console.error('delete failed');
}
};