/* * CloudBeaver - Cloud Database Manager * Copyright (C) 2020-2022 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ import { observable, makeObservable } from 'mobx'; import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor'; import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { uuid } from '@cloudbeaver/core-utils'; import type { IDatabaseDataModel, IRequestEventData } from './IDatabaseDataModel'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; import type { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource'; export class DatabaseDataModel implements IDatabaseDataModel { id: string; name: string | null; source: IDatabaseDataSource; countGain: number; get requestInfo(): IRequestInfo { return this.source.requestInfo; } get supportedDataFormats(): ResultDataFormat[] { return this.source.supportedDataFormats; } readonly onOptionsChange: IExecutor; readonly onRequest: IExecutor>; private currentTask: Promise | null; constructor(source: IDatabaseDataSource) { this.id = uuid(); this.name = null; this.source = source; this.countGain = 0; this.onOptionsChange = new Executor(); this.onRequest = new Executor(); this.currentTask = null; makeObservable(this, { countGain: observable, }); } isLoading(): boolean { return this.source.isLoading(); } isDisabled(resultIndex: number): boolean { return this.source.isDisabled(resultIndex); } isReadonly(resultIndex: number): boolean { return this.source.isReadonly(resultIndex); } isDataAvailable(offset: number, count: number): boolean { return this.source.offset <= offset && this.source.count >= count; } getResults(): TResult[] { return this.source.results; } getResult(index: number): TResult | null { return this.source.getResult(index); } setName(name: string | null) { this.name = name; return this; } setResults(results: TResult[]): this { this.source.setResults(results); return this; } setAccess(access: DatabaseDataAccessMode): this { this.source.setAccess(access); return this; } setCountGain(count: number): this { this.countGain = count; return this; } setSlice(offset: number, count = this.countGain): this { this.source.setSlice(offset, count); return this; } setDataFormat(dataFormat: ResultDataFormat): this { this.source.setDataFormat(dataFormat); return this; } setSupportedDataFormats(dataFormats: ResultDataFormat[]): this { this.source.setSupportedDataFormats(dataFormats); return this; } setOptions(options: TOptions): this { this.source.setOptions(options); return this; } async requestOptionsChange(): Promise { const contexts = await this.onOptionsChange.execute(); return !ExecutorInterrupter.isInterrupted(contexts); } async save(): Promise { await this.requestSaveAction(() => this.source.saveData()); } async retry(): Promise { await this.requestDataAction(() => this.source.retry()); } async refresh(concurrent?: boolean): Promise { if (concurrent) { await this.source.refreshData(); return; } await this.requestDataAction(() => this.source.refreshData()); } async request(concurrent?: boolean): Promise { if (concurrent) { await this.source.requestData(); return; } await this.requestDataAction(() => this.source.requestData()); } async reload(): Promise { await this.requestDataAction(() => this.source .setSlice(0, this.countGain) .requestData() ); } async requestDataPortion(offset: number, count: number): Promise { if (!this.isDataAvailable(offset, count)) { await this.requestDataAction(() => this.source .setSlice(offset, count) .requestData() ); } } cancel(): Promise | void { return this.source.cancel(); } resetData(): void { this.source.resetData(); } async dispose(): Promise { await this.source.dispose(); } async requestSaveAction(action: () => Promise | void): Promise { return action(); } async requestDataAction(action: () => Promise | void): Promise { if (this.currentTask) { return this.currentTask; } try { this.currentTask = this.requestDataActionTask(action); return await this.currentTask; } finally { this.currentTask = null; } } private async requestDataActionTask(action: () => Promise | void): Promise { let contexts = await this.onRequest.execute({ type: 'on', model: this }); if (ExecutorInterrupter.isInterrupted(contexts)) { return; } contexts = await this.onRequest.execute({ type: 'before', model: this }); if (ExecutorInterrupter.isInterrupted(contexts)) { return; } await action(); await this.onRequest.execute({ type: 'after', model: this }); } }