etesync/webapp/src/sync/SyncManager.ts

120 lines
3.6 KiB
TypeScript

// SPDX-FileCopyrightText: © 2019 EteSync Authors
// SPDX-License-Identifier: GPL-3.0-only
import * as Etebase from "etebase";
import { store, StoreState } from "../store";
import { credentialsSelector } from "../credentials";
import { setSyncCollection, setSyncGeneral, setCacheCollection, unsetCacheCollection, setCacheItemMulti, appendError } from "../store/actions";
const cachedSyncManager = new Map<string, SyncManager>();
export class SyncManager {
private COLLECTION_TYPES = ["etebase.vcard", "etebase.vevent", "etebase.vtodo"];
private BATCH_SIZE = 40;
public static getManager(etebase: Etebase.Account) {
const cached = cachedSyncManager.get(etebase.user.username);
if (cached) {
return cached;
}
const ret = new SyncManager();
cachedSyncManager.set(etebase.user.username, ret);
return ret;
}
public static removeManager(etebase: Etebase.Account) {
cachedSyncManager.delete(etebase.user.username);
}
protected etebase: Etebase.Account;
protected isSyncing: boolean;
private async fetchCollection(col: Etebase.Collection) {
const storeState = store.getState() as unknown as StoreState;
const etebase = (await credentialsSelector(storeState))!;
const syncCollection = storeState.sync.collections.get(col.uid, undefined);
const colMgr = etebase.getCollectionManager();
const itemMgr = colMgr.getItemManager(col);
let stoken = syncCollection?.stoken;
const limit = this.BATCH_SIZE;
let done = false;
while (!done) {
const items = await itemMgr.list({ stoken, limit });
store.dispatch(setCacheItemMulti(col.uid, itemMgr, items.data));
done = items.done;
stoken = items.stoken;
}
if (syncCollection?.stoken !== stoken) {
store.dispatch(setSyncCollection(col.uid, stoken!));
}
}
private async fetchAllCollections() {
const storeState = store.getState() as unknown as StoreState;
const etebase = (await credentialsSelector(storeState))!;
const syncGeneral = storeState.sync.general;
const colMgr = etebase.getCollectionManager();
const limit = this.BATCH_SIZE;
let stoken = syncGeneral?.stoken;
let done = false;
while (!done) {
const collections = await colMgr.list(this.COLLECTION_TYPES, { stoken, limit });
for (const col of collections.data) {
const collectionType = col.getCollectionType();
if (this.COLLECTION_TYPES.includes(collectionType)) {
store.dispatch(setCacheCollection(colMgr, col));
await this.fetchCollection(col);
}
}
if (collections.removedMemberships) {
for (const removed of collections.removedMemberships) {
store.dispatch(unsetCacheCollection(colMgr, removed.uid));
}
}
done = collections.done;
stoken = collections.stoken;
}
if (syncGeneral?.stoken !== stoken) {
store.dispatch(setSyncGeneral(stoken));
}
return true;
}
public async sync(alwaysThrowErrors = false) {
if (this.isSyncing) {
return false;
}
this.isSyncing = true;
try {
const stoken = await this.fetchAllCollections();
return stoken;
} catch (e) {
if (alwaysThrowErrors) {
throw e;
}
if (e instanceof Etebase.NetworkError || e instanceof Etebase.TemporaryServerError) {
// Ignore network errors
return null;
} else if (e instanceof Etebase.PermissionDeniedError) {
store.dispatch(appendError(e));
return null;
} else if (e instanceof Etebase.HttpError) {
store.dispatch(appendError(e));
return null;
}
throw e;
} finally {
this.isSyncing = false;
}
}
}