Skip to content

Commit

Permalink
✨ ooconnector: poll forgotten files, wip: extract code from oocallbac…
Browse files Browse the repository at this point in the history
…k to call here (#525)
  • Loading branch information
ericlinagora committed Jul 16, 2024
1 parent 64b0660 commit 89bb7f8
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 8 deletions.
1 change: 1 addition & 0 deletions tdrive/connectors/onlyoffice-connector/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Documentation of the [API used with OnlyOffice is here](https://api.onlyoffice.c
- The inline configuration provides a read and a write URL to the DocsAPI editor, these point to the routes of `OnlyOfficeController`.
- When all the clients have disconnected from the editing session on the OnlyOffice document editing service, the OnlyOffice server will call the `callbackUrl` of this connector.
- The connector then downloads the new file from Only Office, and creates a new version in Twake Drive.
- Periodically gets a list of forgotten files and updates the backend with them

## Configuration example

Expand Down
4 changes: 4 additions & 0 deletions tdrive/connectors/onlyoffice-connector/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ export const {
SERVER_PREFIX,
SERVER_ORIGIN,
} = process.env;

export const twakeDriveTokenRefrehPeriodMS = 10 * 60 * 1000;
export const onlyOfficeForgottenFilesCheckPeriodMS = 10 * 60 * 1000;
export const onlyOfficeConnectivityCheckPeriodMS = 10 * 60 * 1000;
31 changes: 26 additions & 5 deletions tdrive/connectors/onlyoffice-connector/src/services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,49 @@ import {
IApiServiceApplicationTokenResponse,
} from '@/interfaces/api.interface';
import axios, { Axios, AxiosRequestConfig, AxiosResponse } from 'axios';
import { CREDENTIALS_ENDPOINT, CREDENTIALS_ID, CREDENTIALS_SECRET } from '@config';
import {
CREDENTIALS_ENDPOINT,
CREDENTIALS_ID,
CREDENTIALS_SECRET,
twakeDriveTokenRefrehPeriodMS,
onlyOfficeForgottenFilesCheckPeriodMS,
} from '@config';
import logger from '../lib/logger';
import * as Utils from '@/utils';
import { PolledThingieValue } from '@/lib/polled-thingie-value';
import onlyofficeService from './onlyoffice.service';

/**
* Client for the Twake Drive backend API on behalf of the plugin (or provided token in parameters).
* Periodically updates authorization and adds to requests.
*/
class ApiService implements IApiService {
private readonly poller: PolledThingieValue<Axios>;
private readonly tokenPoller: PolledThingieValue<Axios>;
private readonly forgottenFilesPoller: PolledThingieValue<number>;

constructor() {
this.poller = new PolledThingieValue('Refresh Twake Drive token', async () => this.refreshToken(), 1000 * 60); //TODO: should be Every 10 minutes
this.tokenPoller = new PolledThingieValue('Refresh Twake Drive token', async () => await this.refreshToken(), twakeDriveTokenRefrehPeriodMS);
this.forgottenFilesPoller = new PolledThingieValue(
'Process forgotten files in OO',
async () => await this.processForgottenFiles(),
onlyOfficeForgottenFilesCheckPeriodMS,
);
}

public async hasToken() {
return (await this.poller.latestValueWithTry()) !== undefined;
return (await this.tokenPoller.latestValueWithTry()) !== undefined;
}

private requireAxios() {
return this.poller.requireLatestValueWithTry('Token Kind 538 not ready');
return this.tokenPoller.requireLatestValueWithTry('No Twake Drive app token.');
}

private async processForgottenFiles() {
if (!this.tokenPoller.hasValue()) return -1;
return await onlyofficeService.processForgotten(async (/* key, url */) => {
//TODO: when endpoint decided, call here. See if accept HTTP 202 for ex. to avoid deleting.
return false;
});
}

public get = async <T>(params: IApiServiceRequestParams<T>): Promise<T> => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from 'axios';
import { ONLY_OFFICE_SERVER } from '@config';
import { ONLY_OFFICE_SERVER, onlyOfficeConnectivityCheckPeriodMS } from '@config';
import { PolledThingieValue } from '@/lib/polled-thingie-value';
import logger from '@/lib/logger';
import * as Utils from '@/utils';
Expand Down Expand Up @@ -161,16 +161,35 @@ namespace CommandService {
}
}
}

export namespace License {
export interface Response extends SuccessResponse {
// @see https://api.onlyoffice.com/editors/command/license
license: object;
server: object;
quota: object;
}
export class Request extends BaseRequest<Response> {
constructor() {
super('license');
}
}
}
}

/**
* Exposed OnlyOffice command service
* @see https://api.onlyoffice.com/editors/command/
*/
class OnlyOfficeService {
private readonly poller: PolledThingieValue<string>;
private readonly poller: PolledThingieValue<unknown>;

constructor() {
this.poller = new PolledThingieValue('Connect to Only Office', () => this.getVersion(), 10 * 1000 * 60);
this.poller = new PolledThingieValue(
'Connect to Only Office',
async () => logger.info('Only Office license status', await this.getLicense()),
onlyOfficeConnectivityCheckPeriodMS,
);
}
/** Get the latest Only Office version from polling. If the return is `undefined`
* it probably means there is a connection issue contacting the OnlyOffice server
Expand All @@ -180,13 +199,44 @@ class OnlyOfficeService {
return this.poller.latest();
}

/**
* Iterate over all the forgotten files as returned by OnlyOffice, call the processor for each..
* @param processor Handler to process the forgotten file (available at `url`). If `true` is returned,
* the file is deleted from the forgotten file list in OnlyOffice. If false is returned, the
* same forgotten file will reappear in a future batch
* @returns The number of files processed and deleted
*/
public async processForgotten(processor: (key: string, url: string) => Promise<boolean>): Promise<number> {
const forgottenFiles = await this.getForgottenList();
if (forgottenFiles.length === 0) return 0;
Utils.fisherYattesShuffleInPlace(forgottenFiles);
logger.info(`Forgotten files found: ${forgottenFiles.length}`);
let deleted = 0;
for (const forgottenFileKey of forgottenFiles) {
const forgottenFileURL = await this.getForgotten(forgottenFileKey);
logger.info(`Forgotten file about to process: ${JSON.stringify(forgottenFileKey)}`, { url: forgottenFileURL });
const shouldDelete = await processor(forgottenFileKey, forgottenFileURL);
if (shouldDelete) {
logger.info(`Forgotten file about to be deleted: ${JSON.stringify(forgottenFileKey)}`);
await this.deleteForgotten(forgottenFileKey);
deleted++;
}
}
return deleted;
}

// Note that `async` is important in the functions below. While they avoid the overhead
// of `await`, the `async` is still required to catch the throw in `.post()`

/** Return the version string of OnlyOffice */
async getVersion(): Promise<string> {
return new CommandService.Version.Request().post().then(response => response.version);
}
/** Return the version string of OnlyOffice */
async getLicense(): Promise<CommandService.License.Response> {
//TODO: When typing the response more fully, don't return the response object itself as here
return new CommandService.License.Request().post();
}
/** Force a save in the editing session key provided. `userdata` will be forwarded to the callback */
async forceSave(key: string, userdata = ''): Promise<string> {
return new CommandService.ForceSave.Request(key, userdata).post().then(response => response.key);
Expand Down
11 changes: 11 additions & 0 deletions tdrive/connectors/onlyoffice-connector/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,14 @@ export function splitFilename(filename: string): [string, string] {
const extension = parts.pop();
return [parts.join('.'), extension];
}

/** Shuffle an array in place, returns its parameter for convenience */
export function fisherYattesShuffleInPlace<T>(list: T[]): T[] {
let index = list.length;
while (index) {
const randomIndex = Math.floor(Math.random() * index);
index--;
[list[index], list[randomIndex]] = [list[randomIndex], list[index]];
}
return list;
}

0 comments on commit 89bb7f8

Please sign in to comment.