♻️ oo-connector: centralising forgotten file batch management with session key check, bit of health refactoring (#525)
This commit is contained in:
@@ -5,6 +5,9 @@ import { Stream } from 'stream';
|
||||
import FormData from 'form-data';
|
||||
import { INSTANCE_ID } from '@/config';
|
||||
|
||||
/** Thrown when Twake Drive returns a 404 for a key */
|
||||
export class UnknownKeyInDriveError extends Error {}
|
||||
|
||||
/**
|
||||
* Client for Twake Drive's APIs dealing with `DriveItem`s, using {@see apiService}
|
||||
* to handle authorization
|
||||
@@ -107,8 +110,8 @@ class DriveService implements IDriveService {
|
||||
// the key anymore, there's not much we can do, and we should get OO
|
||||
// to clean up and stop trying to upload it.
|
||||
// but for today:
|
||||
logger.fatal("Losing OO document because Twake Drive doesn't know that Key.", { editing_session_key, url });
|
||||
throw new Error(`Unknown key ${JSON.stringify(editing_session_key)}`);
|
||||
logger.error("Forgotten OO document that Twake Drive doesn't know the key of.", { editing_session_key, url });
|
||||
throw new UnknownKeyInDriveError(`Unknown key ${JSON.stringify(editing_session_key)}`);
|
||||
// TODO: Distinguish this case from a long disconnected browser tab waking up
|
||||
}
|
||||
|
||||
|
||||
+48
-15
@@ -1,10 +1,11 @@
|
||||
import logger from '@/lib/logger';
|
||||
import { onlyOfficeForgottenFilesCheckPeriodMS } from '@/config';
|
||||
import { PolledThingieValue } from '@/lib/polled-thingie-value';
|
||||
import { createSingleProcessorLock } from '@/lib/single-processor-lock';
|
||||
|
||||
import apiService from './api.service';
|
||||
import onlyofficeService from './onlyoffice.service';
|
||||
import driveService from './drive.service';
|
||||
import logger from '@/lib/logger';
|
||||
import driveService, { UnknownKeyInDriveError } from './drive.service';
|
||||
|
||||
/**
|
||||
* Periodically poll the Only Office document server for forgotten
|
||||
@@ -12,6 +13,7 @@ import logger from '@/lib/logger';
|
||||
*/
|
||||
class ForgottenProcessor {
|
||||
private readonly forgottenFilesPoller: PolledThingieValue<number>;
|
||||
public readonly forgottenSynchroniser = createSingleProcessorLock<boolean>();
|
||||
private lastStart = 0;
|
||||
|
||||
constructor() {
|
||||
@@ -23,34 +25,65 @@ class ForgottenProcessor {
|
||||
);
|
||||
}
|
||||
|
||||
/** Get the number of seconds since the last time this process started */
|
||||
public getLastStartTimeAgoS() {
|
||||
return ~~((new Date().getTime() - this.lastStart) / 1000);
|
||||
public async getHealthData() {
|
||||
const keys = await onlyofficeService.getForgottenList();
|
||||
return {
|
||||
forgotten: {
|
||||
timeSinceLastStartS: ~~((new Date().getTime() - this.lastStart) / 1000),
|
||||
count: keys?.length ?? 0,
|
||||
locks: this.forgottenSynchroniser.getWorstStats(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The point of this is to ensure this file is imported,
|
||||
* which is needed for side-effect of starting this timer.
|
||||
* The only other use being the health stuff it could easily
|
||||
* be refactored out as unused.
|
||||
*/
|
||||
public makeSureItsLoaded() {
|
||||
// The point of this is to ensure this file is imported,
|
||||
// which is needed for side-effect of starting this timer
|
||||
return true;
|
||||
return 'yup this module is loaded !';
|
||||
}
|
||||
|
||||
private async processForgottenFiles() {
|
||||
this.lastStart = new Date().getTime();
|
||||
if (!(await apiService.hasToken())) return -1;
|
||||
return await onlyofficeService.processForgotten(async (key, url) => {
|
||||
/**
|
||||
* Try to upload the forgotten file, optionally delete it from OO, will return if it was
|
||||
* (or should be) deleted. Does not throw unless the OO deletion itself threw.
|
||||
*/
|
||||
private async safeEndEditing(key: string, url: string, deleteForgotten: boolean) {
|
||||
return this.forgottenSynchroniser.runWithLock(key, async () => {
|
||||
let succeded = false;
|
||||
try {
|
||||
await driveService.endEditing(key, url);
|
||||
return true;
|
||||
succeded = true;
|
||||
} catch (error) {
|
||||
logger.error(`Error processing forgotten file by key ${JSON.stringify(key)}: ${error}`, { key, url, error });
|
||||
if (!(error instanceof UnknownKeyInDriveError))
|
||||
logger.error(`Error processing forgotten file by key ${JSON.stringify(key)}: ${error}`, { key, url, error });
|
||||
// Can't do much about it here, hope it goes in retry, but don't
|
||||
// throw to keep processing
|
||||
//TODO: Maybe make a date string, compare to key, if old enough, delete...
|
||||
// this logic should probably in Twake Drive backend though
|
||||
}
|
||||
return false;
|
||||
if (succeded && deleteForgotten) await onlyofficeService.deleteForgotten(key);
|
||||
return succeded;
|
||||
});
|
||||
}
|
||||
|
||||
private async processForgottenFiles() {
|
||||
this.lastStart = new Date().getTime();
|
||||
if (!(await apiService.hasToken())) return -1;
|
||||
return await onlyofficeService.processForgotten((key, url) =>
|
||||
this.safeEndEditing(key, url, false /* `onlyofficeService.processForgotten` will do it */),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to upload the forgotten OO file, only throws if deleting it failed,
|
||||
* returns wether it was deleted.
|
||||
*/
|
||||
public async processForgottenFile(key: string, url: string) {
|
||||
return this.safeEndEditing(key, url, true);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ForgottenProcessor();
|
||||
|
||||
Reference in New Issue
Block a user