mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: high cpu usage
This commit is contained in:
parent
2e4df44c29
commit
c35db5c2a2
@ -10,7 +10,7 @@ pub fn init_flowy_core() -> AppFlowyCore {
|
||||
}
|
||||
data_path.push("data");
|
||||
|
||||
std::env::set_var("RUST_LOG", "debug");
|
||||
std::env::set_var("RUST_LOG", "trace");
|
||||
let server_config = get_client_server_configuration().unwrap();
|
||||
let config = AppFlowyCoreConfig::new(
|
||||
data_path.to_str().unwrap(),
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
SelectOptionCellController,
|
||||
TextCellController,
|
||||
} from '../../stores/effects/database/cell/controller_builder';
|
||||
import assert from 'assert';
|
||||
import { None, Option, Some } from 'ts-results';
|
||||
import { TypeOptionBackendService } from '../../stores/effects/database/field/type_option/type_option_bd_svc';
|
||||
import { DatabaseBackendService } from '../../stores/effects/database/database_bd_svc';
|
||||
@ -29,9 +28,16 @@ export async function openTestDatabase(viewId: string): Promise<DatabaseControll
|
||||
return new DatabaseController(viewId);
|
||||
}
|
||||
|
||||
export async function assertTextCell(rowInfo: RowInfo, databaseController: DatabaseController, expectedContent: string) {
|
||||
const cellController = await makeTextCellController(rowInfo, databaseController).then((result) => result.unwrap());
|
||||
cellController.subscribeChanged({
|
||||
export async function assertTextCell(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
databaseController: DatabaseController,
|
||||
expectedContent: string
|
||||
) {
|
||||
const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
|
||||
result.unwrap()
|
||||
);
|
||||
await cellController.subscribeChanged({
|
||||
onCellChanged: (value) => {
|
||||
const cellContent = value.unwrap();
|
||||
if (cellContent !== expectedContent) {
|
||||
@ -39,55 +45,78 @@ export async function assertTextCell(rowInfo: RowInfo, databaseController: Datab
|
||||
}
|
||||
},
|
||||
});
|
||||
cellController.getCellData();
|
||||
await cellController.getCellData();
|
||||
}
|
||||
|
||||
export async function editTextCell(rowInfo: RowInfo, databaseController: DatabaseController, content: string) {
|
||||
const cellController = await makeTextCellController(rowInfo, databaseController).then((result) => result.unwrap());
|
||||
export async function editTextCell(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
databaseController: DatabaseController,
|
||||
content: string
|
||||
) {
|
||||
const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
|
||||
result.unwrap()
|
||||
);
|
||||
await cellController.saveCellData(content);
|
||||
}
|
||||
|
||||
export async function makeTextCellController(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
databaseController: DatabaseController
|
||||
): Promise<Option<TextCellController>> {
|
||||
const builder = await makeCellControllerBuilder(rowInfo, FieldType.RichText, databaseController).then((result) =>
|
||||
result.unwrap()
|
||||
const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.RichText, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
return Some(builder.build() as TextCellController);
|
||||
}
|
||||
|
||||
export async function makeNumberCellController(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
databaseController: DatabaseController
|
||||
): Promise<Option<NumberCellController>> {
|
||||
const builder = await makeCellControllerBuilder(rowInfo, FieldType.Number, databaseController).then((result) =>
|
||||
result.unwrap()
|
||||
const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.Number, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
return Some(builder.build() as NumberCellController);
|
||||
}
|
||||
|
||||
export async function makeSingleSelectCellController(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
databaseController: DatabaseController
|
||||
): Promise<Option<SelectOptionCellController>> {
|
||||
const builder = await makeCellControllerBuilder(rowInfo, FieldType.SingleSelect, databaseController).then((result) =>
|
||||
result.unwrap()
|
||||
const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.SingleSelect, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
return Some(builder.build() as SelectOptionCellController);
|
||||
}
|
||||
|
||||
export async function makeMultiSelectCellController(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
databaseController: DatabaseController
|
||||
): Promise<Option<SelectOptionCellController>> {
|
||||
const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.MultiSelect, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
return Some(builder.build() as SelectOptionCellController);
|
||||
}
|
||||
|
||||
export async function makeDateCellController(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
databaseController: DatabaseController
|
||||
): Promise<Option<DateCellController>> {
|
||||
const builder = await makeCellControllerBuilder(rowInfo, FieldType.DateTime, databaseController).then((result) =>
|
||||
result.unwrap()
|
||||
const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.DateTime, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
return Some(builder.build() as DateCellController);
|
||||
}
|
||||
|
||||
export async function makeCellControllerBuilder(
|
||||
fieldId: string,
|
||||
rowInfo: RowInfo,
|
||||
fieldType: FieldType,
|
||||
databaseController: DatabaseController
|
||||
@ -99,7 +128,7 @@ export async function makeCellControllerBuilder(
|
||||
const cellByFieldId = await rowController.loadCells();
|
||||
for (const cellIdentifier of cellByFieldId.values()) {
|
||||
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
|
||||
if (cellIdentifier.fieldType === fieldType) {
|
||||
if (cellIdentifier.fieldId === fieldId) {
|
||||
return Some(builder);
|
||||
}
|
||||
}
|
||||
@ -107,6 +136,15 @@ export async function makeCellControllerBuilder(
|
||||
return None;
|
||||
}
|
||||
|
||||
export function findFirstFieldInfoWithFieldType(rowInfo: RowInfo, fieldType: FieldType) {
|
||||
const fieldInfo = rowInfo.fieldInfos.find((element) => element.field.field_type === fieldType);
|
||||
if (fieldInfo === undefined) {
|
||||
return None;
|
||||
} else {
|
||||
return Some(fieldInfo);
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertFieldName(viewId: string, fieldId: string, fieldType: FieldType, expected: string) {
|
||||
const svc = new TypeOptionBackendService(viewId);
|
||||
const typeOptionPB = await svc.getTypeOption(fieldId, fieldType).then((result) => result.unwrap());
|
||||
|
@ -10,6 +10,8 @@ import {
|
||||
TestEditCell,
|
||||
TestEditField,
|
||||
TestGetSingleSelectFieldData,
|
||||
TestSwitchFromMultiSelectToText,
|
||||
TestSwitchFromSingleSelectToNumber,
|
||||
} from './TestGrid';
|
||||
|
||||
export const TestAPI = () => {
|
||||
@ -26,6 +28,8 @@ export const TestAPI = () => {
|
||||
<TestEditField></TestEditField>
|
||||
<TestCreateNewField></TestCreateNewField>
|
||||
<TestDeleteField></TestDeleteField>
|
||||
<TestSwitchFromSingleSelectToNumber></TestSwitchFromSingleSelectToNumber>
|
||||
<TestSwitchFromMultiSelectToText></TestSwitchFromMultiSelectToText>
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
FieldType,
|
||||
NumberFormat,
|
||||
NumberTypeOptionPB,
|
||||
SelectOptionCellDataPB,
|
||||
SingleSelectTypeOptionPB,
|
||||
ViewLayoutTypePB,
|
||||
@ -13,7 +15,10 @@ import {
|
||||
assertTextCell,
|
||||
createTestDatabaseView,
|
||||
editTextCell,
|
||||
findFirstFieldInfoWithFieldType,
|
||||
makeMultiSelectCellController,
|
||||
makeSingleSelectCellController,
|
||||
makeTextCellController,
|
||||
openTestDatabase,
|
||||
} from './DatabaseTestHelper';
|
||||
import {
|
||||
@ -23,7 +28,10 @@ import {
|
||||
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
|
||||
import { None, Some } from 'ts-results';
|
||||
import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
|
||||
import { makeSingleSelectTypeOptionContext } from '../../stores/effects/database/field/type_option/type_option_context';
|
||||
import {
|
||||
makeNumberTypeOptionContext,
|
||||
makeSingleSelectTypeOptionContext,
|
||||
} from '../../stores/effects/database/field/type_option/type_option_context';
|
||||
|
||||
export const TestCreateGrid = () => {
|
||||
async function createBuildInGrid() {
|
||||
@ -33,11 +41,11 @@ export const TestCreateGrid = () => {
|
||||
onViewChanged: (databasePB) => {
|
||||
Log.debug('Did receive database:' + databasePB);
|
||||
},
|
||||
onRowsChanged: async (rows) => {
|
||||
if (rows.length !== 3) {
|
||||
throw Error('Expected number of rows is 3, but receive ' + rows.length + view.id);
|
||||
}
|
||||
},
|
||||
// onRowsChanged: async (rows) => {
|
||||
// if (rows.length !== 3) {
|
||||
// throw Error('Expected number of rows is 3, but receive ' + rows.length);
|
||||
// }
|
||||
// },
|
||||
onFieldsChanged: (fields) => {
|
||||
if (fields.length !== 3) {
|
||||
throw Error('Expected number of fields is 3, but receive ' + fields.length);
|
||||
@ -59,8 +67,9 @@ export const TestEditCell = () => {
|
||||
|
||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||
const cellContent = index.toString();
|
||||
await editTextCell(row, databaseController, cellContent);
|
||||
await assertTextCell(row, databaseController, cellContent);
|
||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
|
||||
await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||
await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,10 +120,11 @@ export const TestCreateSelectOptionInCell = () => {
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||
if (index === 0) {
|
||||
const cellController = await makeSingleSelectCellController(row, databaseController).then((result) =>
|
||||
result.unwrap()
|
||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
|
||||
const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
cellController.subscribeChanged({
|
||||
await cellController.subscribeChanged({
|
||||
onCellChanged: (value) => {
|
||||
const option: SelectOptionCellDataPB = value.unwrap();
|
||||
console.log(option);
|
||||
@ -167,6 +177,89 @@ export const TestGetSingleSelectFieldData = () => {
|
||||
return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
|
||||
};
|
||||
|
||||
export const TestSwitchFromSingleSelectToNumber = () => {
|
||||
async function testSwitchFromSingleSelectToNumber() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Find the single select column
|
||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||
)!;
|
||||
const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
|
||||
await typeOptionController.switchToField(FieldType.Number);
|
||||
|
||||
// Check the number type option
|
||||
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||
const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
|
||||
.getTypeOption()
|
||||
.then((result) => result.unwrap());
|
||||
const format: NumberFormat = numberTypeOption.format;
|
||||
if (format !== NumberFormat.Num) {
|
||||
throw Error('The default format should be number');
|
||||
}
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
|
||||
};
|
||||
|
||||
export const TestSwitchFromMultiSelectToText = () => {
|
||||
async function testSwitchFromMultiSelectToRichText() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Create multi-select field
|
||||
const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
|
||||
await typeOptionController.initialize();
|
||||
|
||||
// Insert options to first row
|
||||
const row = databaseController.databaseViewCache.rowInfos[0];
|
||||
const multiSelectField = typeOptionController.getFieldInfo();
|
||||
// const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
|
||||
const selectOptionCellController = await makeMultiSelectCellController(
|
||||
multiSelectField.field.id,
|
||||
row,
|
||||
databaseController
|
||||
).then((result) => result.unwrap());
|
||||
const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
|
||||
await backendSvc.createOption({ name: 'A' });
|
||||
await backendSvc.createOption({ name: 'B' });
|
||||
await backendSvc.createOption({ name: 'C' });
|
||||
|
||||
const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
|
||||
if (selectOptionCellData.options.length !== 3) {
|
||||
throw Error('The options should equal to 3');
|
||||
}
|
||||
|
||||
if (selectOptionCellData.select_options.length !== 3) {
|
||||
throw Error('The selected options should equal to 3');
|
||||
}
|
||||
await selectOptionCellController.dispose();
|
||||
|
||||
// Switch to RichText field type
|
||||
await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
|
||||
if (typeOptionController.fieldType !== FieldType.RichText) {
|
||||
throw Error('The field type should be text');
|
||||
}
|
||||
|
||||
const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
const cellContent = await textCellController.getCellData();
|
||||
if (cellContent.unwrap() !== 'A,B,C') {
|
||||
throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
|
||||
}
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
|
||||
};
|
||||
|
||||
export const TestEditField = () => {
|
||||
async function testEditField() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
|
@ -35,18 +35,19 @@ export class CellCache {
|
||||
};
|
||||
|
||||
get<T>(key: CellCacheKey): Option<T> {
|
||||
const inner = this.cellDataByFieldId.get(key.fieldId);
|
||||
if (inner === undefined) {
|
||||
const cellDataByRowId = this.cellDataByFieldId.get(key.fieldId);
|
||||
if (cellDataByRowId === undefined) {
|
||||
return None;
|
||||
} else {
|
||||
const value = inner.get(key.rowId);
|
||||
if (typeof value === typeof undefined || typeof value === typeof null) {
|
||||
const value = cellDataByRowId.get(key.rowId);
|
||||
if (typeof value === typeof undefined) {
|
||||
return None;
|
||||
}
|
||||
if (value satisfies T) {
|
||||
return Some(value as T);
|
||||
}
|
||||
return None;
|
||||
|
||||
// if (value satisfies T) {
|
||||
// return Some(value as T);
|
||||
// }
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,28 +8,29 @@ import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { CellObserver } from './cell_observer';
|
||||
import { Log } from '../../../../utils/log';
|
||||
import { Err, None, Ok, Option, Some } from 'ts-results';
|
||||
import { DatabaseFieldObserver } from '../field/field_observer';
|
||||
|
||||
export abstract class CellFieldNotifier {
|
||||
abstract subscribeOnFieldChanged(callback: () => void): void;
|
||||
}
|
||||
type Callbacks<T> = { onCellChanged: (value: Option<T>) => void; onFieldChanged?: () => void };
|
||||
|
||||
export class CellController<T, D> {
|
||||
private fieldBackendService: FieldBackendService;
|
||||
private cellDataNotifier: CellDataNotifier<Option<T>>;
|
||||
private cellObserver: CellObserver;
|
||||
private readonly cacheKey: CellCacheKey;
|
||||
private readonly fieldNotifier: DatabaseFieldObserver;
|
||||
private subscribeCallbacks?: Callbacks<T>;
|
||||
|
||||
constructor(
|
||||
public readonly cellIdentifier: CellIdentifier,
|
||||
private readonly cellCache: CellCache,
|
||||
private readonly fieldNotifier: CellFieldNotifier,
|
||||
private readonly cellDataLoader: CellDataLoader<T>,
|
||||
private readonly cellDataPersistence: CellDataPersistence<D>
|
||||
) {
|
||||
this.fieldBackendService = new FieldBackendService(cellIdentifier.viewId, cellIdentifier.fieldId);
|
||||
this.cacheKey = new CellCacheKey(cellIdentifier.rowId, cellIdentifier.fieldId);
|
||||
this.cacheKey = new CellCacheKey(cellIdentifier.fieldId, cellIdentifier.rowId);
|
||||
this.cellDataNotifier = new CellDataNotifier(cellCache.get<T>(this.cacheKey));
|
||||
this.cellObserver = new CellObserver(cellIdentifier.rowId, cellIdentifier.fieldId);
|
||||
this.fieldNotifier = new DatabaseFieldObserver(cellIdentifier.fieldId);
|
||||
void this.cellObserver.subscribe({
|
||||
/// 1.Listen on user edit event and load the new cell data if needed.
|
||||
/// For example:
|
||||
@ -40,21 +41,23 @@ export class CellController<T, D> {
|
||||
await this._loadCellData();
|
||||
},
|
||||
});
|
||||
|
||||
/// 2.Listen on the field event and load the cell data if needed.
|
||||
void this.fieldNotifier.subscribe({
|
||||
onFieldChanged: () => {
|
||||
this.subscribeCallbacks?.onFieldChanged?.();
|
||||
/// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
|
||||
/// For example:
|
||||
/// ¥12 -> $12
|
||||
if (this.cellDataLoader.reloadOnFieldChanged) {
|
||||
void this._loadCellData();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
subscribeChanged = (callbacks: { onCellChanged: (value: Option<T>) => void; onFieldChanged?: () => void }) => {
|
||||
/// 2.Listen on the field event and load the cell data if needed.
|
||||
this.fieldNotifier.subscribeOnFieldChanged(async () => {
|
||||
callbacks.onFieldChanged?.();
|
||||
|
||||
/// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
|
||||
/// For example:
|
||||
/// ¥12 -> $12
|
||||
if (this.cellDataLoader.reloadOnFieldChanged) {
|
||||
await this._loadCellData();
|
||||
}
|
||||
});
|
||||
|
||||
subscribeChanged = async (callbacks: Callbacks<T>) => {
|
||||
this.subscribeCallbacks = callbacks;
|
||||
this.cellDataNotifier.observer.subscribe((cellData) => {
|
||||
if (cellData !== null) {
|
||||
callbacks.onCellChanged(cellData);
|
||||
@ -78,21 +81,21 @@ export class CellController<T, D> {
|
||||
}
|
||||
};
|
||||
|
||||
/// Return the cell data if it exists in the cache
|
||||
/// If the cell data is not exist, it will load the cell
|
||||
/// data from the backend and then the [onCellChanged] will
|
||||
/// get called
|
||||
getCellData = (): Option<T> => {
|
||||
/// Return the cell data immediately if it exists in the cache
|
||||
/// Otherwise, it will load the cell data from the backend. The
|
||||
/// subscribers of the [onCellChanged] will get noticed
|
||||
getCellData = async (): Promise<Option<T>> => {
|
||||
const cellData = this.cellCache.get<T>(this.cacheKey);
|
||||
if (cellData.none) {
|
||||
void this._loadCellData();
|
||||
await this._loadCellData();
|
||||
return this.cellCache.get<T>(this.cacheKey);
|
||||
}
|
||||
return cellData;
|
||||
};
|
||||
|
||||
private _loadCellData = () => {
|
||||
return this.cellDataLoader.loadData().then((result) => {
|
||||
if (result.ok && result.val !== undefined) {
|
||||
if (result.ok) {
|
||||
this.cellCache.insert(this.cacheKey, result.val);
|
||||
this.cellDataNotifier.cellData = Some(result.val);
|
||||
} else {
|
||||
@ -104,19 +107,10 @@ export class CellController<T, D> {
|
||||
|
||||
dispose = async () => {
|
||||
await this.cellObserver.unsubscribe();
|
||||
await this.fieldNotifier.unsubscribe();
|
||||
};
|
||||
}
|
||||
|
||||
export class CellFieldNotifierImpl extends CellFieldNotifier {
|
||||
constructor(private readonly fieldController: FieldController) {
|
||||
super();
|
||||
}
|
||||
|
||||
subscribeOnFieldChanged(callback: () => void): void {
|
||||
this.fieldController.subscribeOnFieldsChanged(callback);
|
||||
}
|
||||
}
|
||||
|
||||
class CellDataNotifier<T> extends ChangeNotifier<T | null> {
|
||||
_cellData: T | null;
|
||||
|
||||
|
@ -8,24 +8,27 @@ type UpdateCellNotifiedValue = Result<void, FlowyError>;
|
||||
export type CellChangedCallback = (value: UpdateCellNotifiedValue) => void;
|
||||
|
||||
export class CellObserver {
|
||||
private _notifier?: ChangeNotifier<UpdateCellNotifiedValue>;
|
||||
private _listener?: DatabaseNotificationObserver;
|
||||
private notifier?: ChangeNotifier<UpdateCellNotifiedValue>;
|
||||
private listener?: DatabaseNotificationObserver;
|
||||
|
||||
constructor(public readonly rowId: string, public readonly fieldId: string) {}
|
||||
|
||||
subscribe = async (callbacks: { onCellChanged: CellChangedCallback }) => {
|
||||
this._notifier = new ChangeNotifier();
|
||||
this._notifier?.observer.subscribe(callbacks.onCellChanged);
|
||||
this.notifier = new ChangeNotifier();
|
||||
this.notifier?.observer.subscribe(callbacks.onCellChanged);
|
||||
|
||||
this._listener = new DatabaseNotificationObserver({
|
||||
viewId: this.rowId + ':' + this.fieldId,
|
||||
this.listener = new DatabaseNotificationObserver({
|
||||
// The rowId combine with fieldId can identifier the cell.
|
||||
// This format rowId:fieldId is also defined in the backend,
|
||||
// so don't change this.
|
||||
id: this.rowId + ':' + this.fieldId,
|
||||
parserHandler: (notification, result) => {
|
||||
switch (notification) {
|
||||
case DatabaseNotification.DidUpdateCell:
|
||||
if (result.ok) {
|
||||
this._notifier?.notify(Ok.EMPTY);
|
||||
this.notifier?.notify(Ok.EMPTY);
|
||||
} else {
|
||||
this._notifier?.notify(result);
|
||||
this.notifier?.notify(result);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
@ -33,11 +36,11 @@ export class CellObserver {
|
||||
}
|
||||
},
|
||||
});
|
||||
await this._listener.start();
|
||||
await this.listener.start();
|
||||
};
|
||||
|
||||
unsubscribe = async () => {
|
||||
this._notifier?.unsubscribe();
|
||||
await this._listener?.stop();
|
||||
this.notifier?.unsubscribe();
|
||||
await this.listener?.stop();
|
||||
};
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
import {
|
||||
DateCellDataPB,
|
||||
FieldType,
|
||||
SelectOptionCellDataPB,
|
||||
URLCellDataPB,
|
||||
} from '../../../../../services/backend/models/flowy-database';
|
||||
import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '../../../../../services/backend';
|
||||
import { CellIdentifier } from './cell_bd_svc';
|
||||
import { CellController, CellFieldNotifierImpl } from './cell_controller';
|
||||
import { CellController } from './cell_controller';
|
||||
import {
|
||||
CellDataLoader,
|
||||
DateCellDataParser,
|
||||
@ -34,15 +29,11 @@ export class CalendarData {
|
||||
export type URLCellController = CellController<URLCellDataPB, string>;
|
||||
|
||||
export class CellControllerBuilder {
|
||||
_fieldNotifier: CellFieldNotifierImpl;
|
||||
|
||||
constructor(
|
||||
public readonly cellIdentifier: CellIdentifier,
|
||||
public readonly cellCache: CellCache,
|
||||
public readonly fieldController: FieldController
|
||||
) {
|
||||
this._fieldNotifier = new CellFieldNotifierImpl(this.fieldController);
|
||||
}
|
||||
) {}
|
||||
|
||||
///
|
||||
build = () => {
|
||||
@ -68,77 +59,41 @@ export class CellControllerBuilder {
|
||||
const loader = new CellDataLoader(this.cellIdentifier, new SelectOptionCellDataParser(), true);
|
||||
const persistence = new TextCellDataPersistence(this.cellIdentifier);
|
||||
|
||||
return new CellController<SelectOptionCellDataPB, string>(
|
||||
this.cellIdentifier,
|
||||
this.cellCache,
|
||||
this._fieldNotifier,
|
||||
loader,
|
||||
persistence
|
||||
);
|
||||
return new CellController<SelectOptionCellDataPB, string>(this.cellIdentifier, this.cellCache, loader, persistence);
|
||||
};
|
||||
|
||||
makeURLCellController = (): URLCellController => {
|
||||
const loader = new CellDataLoader(this.cellIdentifier, new URLCellDataParser());
|
||||
const persistence = new TextCellDataPersistence(this.cellIdentifier);
|
||||
|
||||
return new CellController<URLCellDataPB, string>(
|
||||
this.cellIdentifier,
|
||||
this.cellCache,
|
||||
this._fieldNotifier,
|
||||
loader,
|
||||
persistence
|
||||
);
|
||||
return new CellController<URLCellDataPB, string>(this.cellIdentifier, this.cellCache, loader, persistence);
|
||||
};
|
||||
|
||||
makeDateCellController = (): DateCellController => {
|
||||
const loader = new CellDataLoader(this.cellIdentifier, new DateCellDataParser(), true);
|
||||
const persistence = new DateCellDataPersistence(this.cellIdentifier);
|
||||
|
||||
return new CellController<DateCellDataPB, CalendarData>(
|
||||
this.cellIdentifier,
|
||||
this.cellCache,
|
||||
this._fieldNotifier,
|
||||
loader,
|
||||
persistence
|
||||
);
|
||||
return new CellController<DateCellDataPB, CalendarData>(this.cellIdentifier, this.cellCache, loader, persistence);
|
||||
};
|
||||
|
||||
makeNumberCellController = (): NumberCellController => {
|
||||
const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser(), true);
|
||||
const persistence = new TextCellDataPersistence(this.cellIdentifier);
|
||||
|
||||
return new CellController<string, string>(
|
||||
this.cellIdentifier,
|
||||
this.cellCache,
|
||||
this._fieldNotifier,
|
||||
loader,
|
||||
persistence
|
||||
);
|
||||
return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
|
||||
};
|
||||
|
||||
makeTextCellController = (): TextCellController => {
|
||||
const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser());
|
||||
const persistence = new TextCellDataPersistence(this.cellIdentifier);
|
||||
|
||||
return new CellController<string, string>(
|
||||
this.cellIdentifier,
|
||||
this.cellCache,
|
||||
this._fieldNotifier,
|
||||
loader,
|
||||
persistence
|
||||
);
|
||||
return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
|
||||
};
|
||||
|
||||
makeCheckboxCellController = (): CheckboxCellController => {
|
||||
const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser());
|
||||
const persistence = new TextCellDataPersistence(this.cellIdentifier);
|
||||
|
||||
return new CellController<string, string>(
|
||||
this.cellIdentifier,
|
||||
this.cellCache,
|
||||
this._fieldNotifier,
|
||||
loader,
|
||||
persistence
|
||||
);
|
||||
return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
|
||||
};
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ import { Err, Ok } from 'ts-results';
|
||||
import { Log } from '../../../../utils/log';
|
||||
|
||||
abstract class CellDataParser<T> {
|
||||
abstract parserData(data: Uint8Array): T | undefined;
|
||||
abstract parserData(data: Uint8Array): T;
|
||||
}
|
||||
|
||||
class CellDataLoader<T> {
|
||||
_service = new CellBackendService();
|
||||
private service = new CellBackendService();
|
||||
|
||||
constructor(
|
||||
readonly cellId: CellIdentifier,
|
||||
@ -20,7 +20,7 @@ class CellDataLoader<T> {
|
||||
) {}
|
||||
|
||||
loadData = async () => {
|
||||
const result = await this._service.getCell(this.cellId);
|
||||
const result = await this.service.getCell(this.cellId);
|
||||
if (result.ok) {
|
||||
return Ok(this.parser.parserData(result.val.data));
|
||||
} else {
|
||||
|
@ -27,10 +27,8 @@ export class DateCellDataPersistence extends CellDataPersistence<CalendarData> {
|
||||
|
||||
save(data: CalendarData): Promise<Result<void, FlowyError>> {
|
||||
const payload = DateChangesetPB.fromObject({ cell_path: _makeCellPath(this.cellIdentifier) });
|
||||
|
||||
payload.date = data.date.getUTCMilliseconds.toString();
|
||||
payload.is_utc = true;
|
||||
|
||||
if (data.time !== undefined) {
|
||||
payload.time = data.time;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ export class SelectOptionBackendService {
|
||||
export class SelectOptionCellBackendService {
|
||||
constructor(public readonly cellIdentifier: CellIdentifier) {}
|
||||
|
||||
// Creates a new option and insert this option to the cell
|
||||
createOption = async (params: { name: string; isSelect?: boolean }) => {
|
||||
const payload = CreateSelectOptionPayloadPB.fromObject({
|
||||
option_name: params.name,
|
||||
@ -39,7 +40,7 @@ export class SelectOptionCellBackendService {
|
||||
|
||||
const result = await DatabaseEventCreateSelectOption(payload);
|
||||
if (result.ok) {
|
||||
return this._insertOption(result.val, params.isSelect || true);
|
||||
return await this._insertOption(result.val, params.isSelect || true);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { DatabasePB } from '../../../../services/backend/models/flowy-database/g
|
||||
import { RowChangedReason, RowInfo } from './row/row_cache';
|
||||
import { Err, Ok } from 'ts-results';
|
||||
|
||||
export type SubscribeCallback = {
|
||||
export type SubscribeCallbacks = {
|
||||
onViewChanged?: (data: DatabasePB) => void;
|
||||
onRowsChanged?: (rowInfos: readonly RowInfo[], reason: RowChangedReason) => void;
|
||||
onFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void;
|
||||
@ -15,7 +15,7 @@ export class DatabaseController {
|
||||
private backendService: DatabaseBackendService;
|
||||
fieldController: FieldController;
|
||||
databaseViewCache: DatabaseViewCache;
|
||||
private _callback?: SubscribeCallback;
|
||||
private _callback?: SubscribeCallbacks;
|
||||
|
||||
constructor(public readonly viewId: string) {
|
||||
this.backendService = new DatabaseBackendService(viewId);
|
||||
@ -23,9 +23,9 @@ export class DatabaseController {
|
||||
this.databaseViewCache = new DatabaseViewCache(viewId, this.fieldController);
|
||||
}
|
||||
|
||||
subscribe = (callbacks: SubscribeCallback) => {
|
||||
subscribe = (callbacks: SubscribeCallbacks) => {
|
||||
this._callback = callbacks;
|
||||
this.fieldController.subscribeOnFieldsChanged(callbacks.onFieldsChanged);
|
||||
this.fieldController.subscribeOnNumOfFieldsChanged(callbacks.onFieldsChanged);
|
||||
this.databaseViewCache.getRowCache().subscribeOnRowsChanged((reason) => {
|
||||
this._callback?.onRowsChanged?.(this.databaseViewCache.rowInfos, reason);
|
||||
});
|
||||
|
@ -1,49 +1,49 @@
|
||||
import { Log } from '../../../../utils/log';
|
||||
import { DatabaseBackendService } from '../database_bd_svc';
|
||||
import { DatabaseFieldChangesetObserver } from './field_observer';
|
||||
import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend/models/flowy-database/field_entities';
|
||||
import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
|
||||
export class FieldController {
|
||||
private fieldListener: DatabaseFieldChangesetObserver;
|
||||
private backendService: DatabaseBackendService;
|
||||
private fieldNotifier = new FieldNotifier([]);
|
||||
private numOfFieldsObserver: DatabaseFieldChangesetObserver;
|
||||
private numOfFieldsNotifier = new NumOfFieldsNotifier([]);
|
||||
|
||||
constructor(public readonly viewId: string) {
|
||||
this.backendService = new DatabaseBackendService(viewId);
|
||||
this.fieldListener = new DatabaseFieldChangesetObserver(viewId);
|
||||
this.numOfFieldsObserver = new DatabaseFieldChangesetObserver(viewId);
|
||||
}
|
||||
|
||||
dispose = async () => {
|
||||
this.fieldNotifier.unsubscribe();
|
||||
await this.fieldListener.unsubscribe();
|
||||
this.numOfFieldsNotifier.unsubscribe();
|
||||
await this.numOfFieldsObserver.unsubscribe();
|
||||
};
|
||||
|
||||
get fieldInfos(): readonly FieldInfo[] {
|
||||
return this.fieldNotifier.fieldInfos;
|
||||
return this.numOfFieldsNotifier.fieldInfos;
|
||||
}
|
||||
|
||||
getField = (fieldId: string): FieldInfo | undefined => {
|
||||
return this.fieldNotifier.fieldInfos.find((element) => element.field.id === fieldId);
|
||||
return this.numOfFieldsNotifier.fieldInfos.find((element) => element.field.id === fieldId);
|
||||
};
|
||||
|
||||
loadFields = async (fieldIds: FieldIdPB[]) => {
|
||||
const result = await this.backendService.getFields(fieldIds);
|
||||
if (result.ok) {
|
||||
this.fieldNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field));
|
||||
this.numOfFieldsNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field));
|
||||
} else {
|
||||
Log.error(result.val);
|
||||
}
|
||||
};
|
||||
|
||||
subscribeOnFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
|
||||
return this.fieldNotifier.observer.subscribe((fieldInfos) => {
|
||||
subscribeOnNumOfFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
|
||||
return this.numOfFieldsNotifier.observer.subscribe((fieldInfos) => {
|
||||
callback?.(fieldInfos);
|
||||
});
|
||||
};
|
||||
|
||||
listenOnFieldChanges = async () => {
|
||||
await this.fieldListener.subscribe({
|
||||
await this.numOfFieldsObserver.subscribe({
|
||||
onFieldsChanged: (result) => {
|
||||
if (result.ok) {
|
||||
const changeset = result.val;
|
||||
@ -57,7 +57,7 @@ export class FieldController {
|
||||
});
|
||||
};
|
||||
|
||||
_deleteFields = (deletedFields: FieldIdPB[]) => {
|
||||
private _deleteFields = (deletedFields: FieldIdPB[]) => {
|
||||
if (deletedFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
@ -68,10 +68,10 @@ export class FieldController {
|
||||
};
|
||||
const newFieldInfos = [...this.fieldInfos];
|
||||
newFieldInfos.filter(predicate);
|
||||
this.fieldNotifier.fieldInfos = newFieldInfos;
|
||||
this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
|
||||
};
|
||||
|
||||
_insertFields = (insertedFields: IndexFieldPB[]) => {
|
||||
private _insertFields = (insertedFields: IndexFieldPB[]) => {
|
||||
if (insertedFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
@ -84,29 +84,28 @@ export class FieldController {
|
||||
newFieldInfos.push(fieldInfo);
|
||||
}
|
||||
});
|
||||
this.fieldNotifier.fieldInfos = newFieldInfos;
|
||||
this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
|
||||
};
|
||||
|
||||
_updateFields = (updatedFields: FieldPB[]) => {
|
||||
private _updateFields = (updatedFields: FieldPB[]) => {
|
||||
if (updatedFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newFieldInfos = [...this.fieldInfos];
|
||||
updatedFields.forEach((updatedField) => {
|
||||
newFieldInfos.map((element) => {
|
||||
if (element.field.id === updatedField.id) {
|
||||
return updatedField;
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
const index = newFieldInfos.findIndex((fieldInfo) => {
|
||||
return fieldInfo.field.id === updatedField.id;
|
||||
});
|
||||
if (index !== -1) {
|
||||
newFieldInfos.splice(index, 1, new FieldInfo(updatedField));
|
||||
}
|
||||
});
|
||||
this.fieldNotifier.fieldInfos = newFieldInfos;
|
||||
this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
|
||||
};
|
||||
}
|
||||
|
||||
class FieldNotifier extends ChangeNotifier<FieldInfo[]> {
|
||||
class NumOfFieldsNotifier extends ChangeNotifier<FieldInfo[]> {
|
||||
constructor(private _fieldInfos: FieldInfo[]) {
|
||||
super();
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export class DatabaseFieldChangesetObserver {
|
||||
this.notifier?.observer.subscribe(callbacks.onFieldsChanged);
|
||||
|
||||
this.listener = new DatabaseNotificationObserver({
|
||||
viewId: this.viewId,
|
||||
id: this.viewId,
|
||||
parserHandler: (notification, result) => {
|
||||
switch (notification) {
|
||||
case DatabaseNotification.DidUpdateFields:
|
||||
@ -50,12 +50,12 @@ export class DatabaseFieldObserver {
|
||||
|
||||
constructor(public readonly fieldId: string) {}
|
||||
|
||||
subscribe = async (callbacks: { onFieldsChanged: FieldNotificationCallback }) => {
|
||||
subscribe = async (callbacks: { onFieldChanged: FieldNotificationCallback }) => {
|
||||
this._notifier = new ChangeNotifier();
|
||||
this._notifier?.observer.subscribe(callbacks.onFieldsChanged);
|
||||
this._notifier?.observer.subscribe(callbacks.onFieldChanged);
|
||||
|
||||
this._listener = new DatabaseNotificationObserver({
|
||||
viewId: this.fieldId,
|
||||
id: this.fieldId,
|
||||
parserHandler: (notification, result) => {
|
||||
switch (notification) {
|
||||
case DatabaseNotification.DidUpdateField:
|
||||
|
@ -172,7 +172,7 @@ export class TypeOptionContext<T> {
|
||||
this.fieldObserver = new DatabaseFieldObserver(controller.fieldId);
|
||||
|
||||
void this.fieldObserver.subscribe({
|
||||
onFieldsChanged: () => {
|
||||
onFieldChanged: () => {
|
||||
void this.getTypeOption();
|
||||
},
|
||||
});
|
||||
|
@ -13,14 +13,20 @@ export class TypeOptionController {
|
||||
private typeOptionBackendSvc: TypeOptionBackendService;
|
||||
|
||||
// Must call [initialize] if the passed-in fieldInfo is None
|
||||
constructor(public readonly viewId: string, private initialFieldInfo: Option<FieldInfo> = None) {
|
||||
constructor(
|
||||
public readonly viewId: string,
|
||||
private readonly initialFieldInfo: Option<FieldInfo> = None,
|
||||
private readonly defaultFieldType: FieldType = FieldType.RichText
|
||||
) {
|
||||
this.typeOptionData = None;
|
||||
this.typeOptionBackendSvc = new TypeOptionBackendService(viewId);
|
||||
}
|
||||
|
||||
// It will create a new field for the defaultFieldType if the [initialFieldInfo] is None.
|
||||
// Otherwise, it will get the type option of the [initialFieldInfo]
|
||||
initialize = async () => {
|
||||
if (this.initialFieldInfo.none) {
|
||||
await this.createTypeOption();
|
||||
await this.createTypeOption(this.defaultFieldType);
|
||||
} else {
|
||||
await this.getTypeOption();
|
||||
}
|
||||
@ -45,8 +51,16 @@ export class TypeOptionController {
|
||||
return new FieldInfo(this.typeOptionData.val.field);
|
||||
};
|
||||
|
||||
switchToField = (fieldType: FieldType) => {
|
||||
return this.typeOptionBackendSvc.updateTypeOptionType(this.fieldId, fieldType);
|
||||
switchToField = async (fieldType: FieldType) => {
|
||||
const result = await this.typeOptionBackendSvc.updateTypeOptionType(this.fieldId, fieldType);
|
||||
if (result.ok) {
|
||||
const getResult = await this.typeOptionBackendSvc.getTypeOption(this.fieldId, fieldType);
|
||||
if (getResult.ok) {
|
||||
this.updateTypeOptionData(getResult.val);
|
||||
}
|
||||
return getResult;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
setFieldName = async (name: string) => {
|
||||
@ -96,7 +110,7 @@ export class TypeOptionController {
|
||||
});
|
||||
};
|
||||
|
||||
private createTypeOption = (fieldType: FieldType = FieldType.RichText) => {
|
||||
private createTypeOption = (fieldType: FieldType) => {
|
||||
return this.typeOptionBackendSvc.createTypeOption(fieldType).then((result) => {
|
||||
if (result.ok) {
|
||||
this.updateTypeOptionData(result.val);
|
||||
|
@ -6,10 +6,10 @@ import { Result } from 'ts-results';
|
||||
export type ParserHandler = (notification: DatabaseNotification, result: Result<Uint8Array, FlowyError>) => void;
|
||||
|
||||
export class DatabaseNotificationObserver extends AFNotificationObserver<DatabaseNotification> {
|
||||
constructor(params: { viewId?: string; parserHandler: ParserHandler }) {
|
||||
constructor(params: { id?: string; parserHandler: ParserHandler }) {
|
||||
const parser = new DatabaseNotificationParser({
|
||||
callback: params.parserHandler,
|
||||
id: params.viewId,
|
||||
id: params.id,
|
||||
});
|
||||
super(parser);
|
||||
}
|
||||
|
@ -19,26 +19,26 @@ import { Log } from '../../../../utils/log';
|
||||
export type CellByFieldId = Map<string, CellIdentifier>;
|
||||
|
||||
export class RowCache {
|
||||
private readonly _rowList: RowList;
|
||||
private readonly _cellCache: CellCache;
|
||||
private readonly _notifier: RowChangeNotifier;
|
||||
private readonly rowList: RowList;
|
||||
private readonly cellCache: CellCache;
|
||||
private readonly notifier: RowChangeNotifier;
|
||||
|
||||
constructor(public readonly viewId: string, private readonly getFieldInfos: () => readonly FieldInfo[]) {
|
||||
this._rowList = new RowList();
|
||||
this._cellCache = new CellCache(viewId);
|
||||
this._notifier = new RowChangeNotifier();
|
||||
this.rowList = new RowList();
|
||||
this.cellCache = new CellCache(viewId);
|
||||
this.notifier = new RowChangeNotifier();
|
||||
}
|
||||
|
||||
get rows(): readonly RowInfo[] {
|
||||
return this._rowList.rows;
|
||||
return this.rowList.rows;
|
||||
}
|
||||
|
||||
getCellCache = () => {
|
||||
return this._cellCache;
|
||||
return this.cellCache;
|
||||
};
|
||||
|
||||
loadCells = async (rowId: string): Promise<CellByFieldId> => {
|
||||
const opRow = this._rowList.getRow(rowId);
|
||||
const opRow = this.rowList.getRow(rowId);
|
||||
if (opRow.some) {
|
||||
return this._toCellMap(opRow.val.row.id, this.getFieldInfos());
|
||||
} else {
|
||||
@ -54,7 +54,7 @@ export class RowCache {
|
||||
};
|
||||
|
||||
subscribeOnRowsChanged = (callback: (reason: RowChangedReason, cellMap?: Map<string, CellIdentifier>) => void) => {
|
||||
return this._notifier.observer.subscribe((change) => {
|
||||
return this.notifier.observer.subscribe((change) => {
|
||||
if (change.rowId !== undefined) {
|
||||
callback(change.reason, this._toCellMap(change.rowId, this.getFieldInfos()));
|
||||
} else {
|
||||
@ -65,18 +65,19 @@ export class RowCache {
|
||||
|
||||
onFieldUpdated = (fieldInfo: FieldInfo) => {
|
||||
// Remove the cell data if the corresponding field was changed
|
||||
this._cellCache.removeWithFieldId(fieldInfo.field.id);
|
||||
this.cellCache.removeWithFieldId(fieldInfo.field.id);
|
||||
};
|
||||
|
||||
onNumberOfFieldsUpdated = () => {
|
||||
this._notifier.withChange(RowChangedReason.FieldDidChanged);
|
||||
onNumberOfFieldsUpdated = (fieldInfos: readonly FieldInfo[]) => {
|
||||
this.rowList.setFieldInfos(fieldInfos);
|
||||
this.notifier.withChange(RowChangedReason.FieldDidChanged);
|
||||
};
|
||||
|
||||
initializeRows = (rows: RowPB[]) => {
|
||||
rows.forEach((rowPB) => {
|
||||
this._rowList.push(this._toRowInfo(rowPB));
|
||||
this.rowList.push(this._toRowInfo(rowPB));
|
||||
});
|
||||
this._notifier.withChange(RowChangedReason.ReorderRows);
|
||||
this.notifier.withChange(RowChangedReason.ReorderRows);
|
||||
};
|
||||
|
||||
applyRowsChanged = (changeset: RowsChangesetPB) => {
|
||||
@ -91,15 +92,15 @@ export class RowCache {
|
||||
};
|
||||
|
||||
applyReorderRows = (rowIds: string[]) => {
|
||||
this._rowList.reorderByRowIds(rowIds);
|
||||
this._notifier.withChange(RowChangedReason.ReorderRows);
|
||||
this.rowList.reorderByRowIds(rowIds);
|
||||
this.notifier.withChange(RowChangedReason.ReorderRows);
|
||||
};
|
||||
|
||||
applyReorderSingleRow = (reorderRow: ReorderSingleRowPB) => {
|
||||
const rowInfo = this._rowList.getRow(reorderRow.row_id);
|
||||
const rowInfo = this.rowList.getRow(reorderRow.row_id);
|
||||
if (rowInfo !== undefined) {
|
||||
this._rowList.move({ rowId: reorderRow.row_id, fromIndex: reorderRow.old_index, toIndex: reorderRow.new_index });
|
||||
this._notifier.withChange(RowChangedReason.ReorderSingleRow, reorderRow.row_id);
|
||||
this.rowList.move({ rowId: reorderRow.row_id, fromIndex: reorderRow.old_index, toIndex: reorderRow.new_index });
|
||||
this.notifier.withChange(RowChangedReason.ReorderSingleRow, reorderRow.row_id);
|
||||
}
|
||||
};
|
||||
|
||||
@ -108,14 +109,14 @@ export class RowCache {
|
||||
return;
|
||||
}
|
||||
const updatedRow = opRow.row;
|
||||
const option = this._rowList.getRowWithIndex(updatedRow.id);
|
||||
const option = this.rowList.getRowWithIndex(updatedRow.id);
|
||||
if (option.some) {
|
||||
const { rowInfo, index } = option.val;
|
||||
this._rowList.remove(rowInfo.row.id);
|
||||
this._rowList.insert(index, rowInfo.copyWith({ row: updatedRow }));
|
||||
this.rowList.remove(rowInfo.row.id);
|
||||
this.rowList.insert(index, rowInfo.copyWith({ row: updatedRow }));
|
||||
} else {
|
||||
const newRowInfo = new RowInfo(this.viewId, this.getFieldInfos(), updatedRow);
|
||||
this._rowList.push(newRowInfo);
|
||||
this.rowList.push(newRowInfo);
|
||||
}
|
||||
};
|
||||
|
||||
@ -126,9 +127,9 @@ export class RowCache {
|
||||
|
||||
private _deleteRows = (rowIds: string[]) => {
|
||||
rowIds.forEach((rowId) => {
|
||||
const deletedRow = this._rowList.remove(rowId);
|
||||
const deletedRow = this.rowList.remove(rowId);
|
||||
if (deletedRow !== undefined) {
|
||||
this._notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
|
||||
this.notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -136,9 +137,9 @@ export class RowCache {
|
||||
private _insertRows = (rows: InsertedRowPB[]) => {
|
||||
rows.forEach((insertedRow) => {
|
||||
const rowInfo = this._toRowInfo(insertedRow.row);
|
||||
const insertedIndex = this._rowList.insert(insertedRow.index, rowInfo);
|
||||
const insertedIndex = this.rowList.insert(insertedRow.index, rowInfo);
|
||||
if (insertedIndex !== undefined) {
|
||||
this._notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
|
||||
this.notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -152,39 +153,39 @@ export class RowCache {
|
||||
updatedRows.forEach((updatedRow) => {
|
||||
updatedRow.field_ids.forEach((fieldId) => {
|
||||
const key = new CellCacheKey(fieldId, updatedRow.row.id);
|
||||
this._cellCache.remove(key);
|
||||
this.cellCache.remove(key);
|
||||
});
|
||||
|
||||
rowInfos.push(this._toRowInfo(updatedRow.row));
|
||||
});
|
||||
|
||||
const updatedIndexs = this._rowList.insertRows(rowInfos);
|
||||
const updatedIndexs = this.rowList.insertRows(rowInfos);
|
||||
updatedIndexs.forEach((row) => {
|
||||
this._notifier.withChange(RowChangedReason.Update, row.rowId);
|
||||
this.notifier.withChange(RowChangedReason.Update, row.rowId);
|
||||
});
|
||||
};
|
||||
|
||||
private _hideRows = (rowIds: string[]) => {
|
||||
rowIds.forEach((rowId) => {
|
||||
const deletedRow = this._rowList.remove(rowId);
|
||||
const deletedRow = this.rowList.remove(rowId);
|
||||
if (deletedRow !== undefined) {
|
||||
this._notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
|
||||
this.notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private _displayRows = (insertedRows: InsertedRowPB[]) => {
|
||||
insertedRows.forEach((insertedRow) => {
|
||||
const insertedIndex = this._rowList.insert(insertedRow.index, this._toRowInfo(insertedRow.row));
|
||||
const insertedIndex = this.rowList.insert(insertedRow.index, this._toRowInfo(insertedRow.row));
|
||||
|
||||
if (insertedIndex !== undefined) {
|
||||
this._notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
|
||||
this.notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
dispose = async () => {
|
||||
this._notifier.dispose();
|
||||
this.notifier.dispose();
|
||||
};
|
||||
|
||||
private _toRowInfo = (rowPB: RowPB) => {
|
||||
@ -219,7 +220,6 @@ class RowList {
|
||||
return Some(rowInfo);
|
||||
}
|
||||
};
|
||||
|
||||
getRowWithIndex = (rowId: string): Option<{ rowInfo: RowInfo; index: number }> => {
|
||||
const rowInfo = this._rowInfoByRowId.get(rowId);
|
||||
if (rowInfo !== undefined) {
|
||||
@ -322,6 +322,14 @@ class RowList {
|
||||
includes = (rowId: string): boolean => {
|
||||
return this._rowInfoByRowId.has(rowId);
|
||||
};
|
||||
|
||||
setFieldInfos = (fieldInfos: readonly FieldInfo[]) => {
|
||||
const newRowInfos: RowInfo[] = [];
|
||||
this._rowInfos.forEach((rowInfo) => {
|
||||
newRowInfos.push(rowInfo.copyWith({ fieldInfos: fieldInfos }));
|
||||
});
|
||||
this._rowInfos = newRowInfos;
|
||||
};
|
||||
}
|
||||
|
||||
export class RowInfo {
|
||||
@ -331,8 +339,8 @@ export class RowInfo {
|
||||
public readonly row: RowPB
|
||||
) {}
|
||||
|
||||
copyWith = (params: { row?: RowPB }) => {
|
||||
return new RowInfo(this.viewId, this.fieldInfos, params.row || this.row);
|
||||
copyWith = (params: { row?: RowPB; fieldInfos?: readonly FieldInfo[] }) => {
|
||||
return new RowInfo(this.viewId, params.fieldInfos || this.fieldInfos, params.row || this.row);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -12,10 +12,11 @@ export class DatabaseViewCache {
|
||||
constructor(public readonly viewId: string, fieldController: FieldController) {
|
||||
this.rowsObserver = new DatabaseViewRowsObserver(viewId);
|
||||
this.rowCache = new RowCache(viewId, () => fieldController.fieldInfos);
|
||||
this.fieldSubscription = fieldController.subscribeOnFieldsChanged((fieldInfos) => {
|
||||
this.fieldSubscription = fieldController.subscribeOnNumOfFieldsChanged((fieldInfos) => {
|
||||
fieldInfos.forEach((fieldInfo) => {
|
||||
this.rowCache.onFieldUpdated(fieldInfo);
|
||||
});
|
||||
this.rowCache.onNumberOfFieldsUpdated(fieldInfos);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ export class DatabaseViewRowsObserver {
|
||||
this.reorderSingleRowNotifier.observer.subscribe(callbacks.onReorderSingleRow);
|
||||
|
||||
this._listener = new DatabaseNotificationObserver({
|
||||
viewId: this.viewId,
|
||||
id: this.viewId,
|
||||
parserHandler: (notification, result) => {
|
||||
switch (notification) {
|
||||
case DatabaseNotification.DidUpdateViewRowsVisibility:
|
||||
|
@ -27,6 +27,7 @@ export async function {{ event_func_name }}(): Promise<Result<{{ output_deserial
|
||||
console.log({{ event_func_name }}.name, object);
|
||||
return Ok(object);
|
||||
{%- else %}
|
||||
console.log({{ event_func_name }}.name);
|
||||
return Ok.EMPTY;
|
||||
{%- endif %}
|
||||
} else {
|
||||
|
@ -31,7 +31,7 @@ use flowy_task::TaskDispatcher;
|
||||
|
||||
use revision_model::Revision;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::{RwLock, RwLockWriteGuard, TryLockError};
|
||||
|
||||
pub trait DatabaseUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<String, FlowyError>;
|
||||
@ -118,7 +118,6 @@ impl DatabaseManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn create_database_block<T: AsRef<str>>(
|
||||
&self,
|
||||
block_id: T,
|
||||
@ -141,33 +140,32 @@ impl DatabaseManager {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn close_database_view<T: AsRef<str>>(&self, view_id: T) -> FlowyResult<()> {
|
||||
let view_id = view_id.as_ref();
|
||||
let database_info = self.database_ref_indexer.get_database_with_view(view_id)?;
|
||||
tracing::Span::current().record("database_id", &database_info.database_id);
|
||||
|
||||
let mut should_remove_editor = false;
|
||||
if let Some(database_editor) = self
|
||||
.editors_by_database_id
|
||||
.write()
|
||||
.await
|
||||
.get(&database_info.database_id)
|
||||
{
|
||||
database_editor.close_view_editor(view_id).await;
|
||||
should_remove_editor = database_editor.number_of_ref_views().await == 0;
|
||||
if should_remove_editor {
|
||||
database_editor.dispose().await;
|
||||
}
|
||||
match self.editors_by_database_id.try_write() {
|
||||
Ok(mut write_guard) => {
|
||||
if let Some(database_editor) = write_guard.remove(&database_info.database_id) {
|
||||
database_editor.close_view_editor(view_id).await;
|
||||
if database_editor.number_of_ref_views().await == 0 {
|
||||
database_editor.dispose().await;
|
||||
} else {
|
||||
self
|
||||
.editors_by_database_id
|
||||
.write()
|
||||
.await
|
||||
.insert(database_info.database_id, database_editor);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
tracing::error!("Try to get the lock of editors_by_database_id failed");
|
||||
},
|
||||
}
|
||||
|
||||
if should_remove_editor {
|
||||
tracing::debug!("Close database base editor: {}", database_info.database_id);
|
||||
self
|
||||
.editors_by_database_id
|
||||
.write()
|
||||
.await
|
||||
.remove(&database_info.database_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -235,12 +233,10 @@ impl DatabaseManager {
|
||||
pool: Arc<ConnectionPool>,
|
||||
) -> Result<Arc<DatabaseEditor>, FlowyError> {
|
||||
let user = self.database_user.clone();
|
||||
tracing::debug!("Open database view: {}", view_id);
|
||||
let (base_view_pad, base_view_rev_manager) =
|
||||
make_database_view_revision_pad(view_id, user.clone()).await?;
|
||||
let mut database_id = base_view_pad.database_id.clone();
|
||||
|
||||
tracing::debug!("Open database: {}", database_id);
|
||||
tracing::debug!("Open database: {} with view: {}", database_id, view_id);
|
||||
if database_id.is_empty() {
|
||||
// Before the database_id concept comes up, we used the view_id directly. So if
|
||||
// the database_id is empty, which means we can used the view_id. After the version 0.1.1,
|
||||
|
@ -116,13 +116,13 @@ impl DatabaseEditor {
|
||||
self.database_views.open(view_editor).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Close database editor view", level = "debug", skip_all)]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn close_view_editor(&self, view_id: &str) {
|
||||
self.rev_manager.generate_snapshot().await;
|
||||
self.database_views.close(view_id).await;
|
||||
}
|
||||
|
||||
pub async fn dispose(&self) {
|
||||
self.rev_manager.generate_snapshot().await;
|
||||
self.database_blocks.close().await;
|
||||
self.rev_manager.close().await;
|
||||
}
|
||||
|
@ -178,12 +178,12 @@ impl DatabaseViewEditor {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "close grid view editor", level = "trace", skip_all)]
|
||||
#[tracing::instrument(name = "close database view editor", level = "trace", skip_all)]
|
||||
pub async fn close(&self) {
|
||||
self.rev_manager.generate_snapshot().await;
|
||||
self.rev_manager.close().await;
|
||||
self.filter_controller.close().await;
|
||||
self.sort_controller.read().await.close().await;
|
||||
self.sort_controller.write().await.close().await;
|
||||
}
|
||||
|
||||
pub async fn handle_block_event(&self, event: Cow<'_, DatabaseBlockEvent>) {
|
||||
@ -509,8 +509,8 @@ impl DatabaseViewEditor {
|
||||
.did_receive_changes(SortChangeset::from_insert(sort_type))
|
||||
.await
|
||||
};
|
||||
self.notify_did_update_sort(changeset).await;
|
||||
drop(sort_controller);
|
||||
self.notify_did_update_sort(changeset).await;
|
||||
Ok(sort_rev)
|
||||
}
|
||||
|
||||
@ -539,7 +539,7 @@ impl DatabaseViewEditor {
|
||||
|
||||
pub async fn v_delete_all_sorts(&self) -> FlowyResult<()> {
|
||||
let all_sorts = self.v_get_all_sorts().await;
|
||||
self.sort_controller.write().await.delete_all_sorts().await;
|
||||
// self.sort_controller.write().await.delete_all_sorts().await;
|
||||
self
|
||||
.modify(|pad| {
|
||||
let changeset = pad.delete_all_sorts()?;
|
||||
|
@ -60,7 +60,11 @@ impl DatabaseViews {
|
||||
}
|
||||
|
||||
pub async fn close(&self, view_id: &str) {
|
||||
self.view_editors.write().await.remove(view_id).await;
|
||||
if let Ok(mut view_editors) = self.view_editors.try_write() {
|
||||
view_editors.remove(view_id).await;
|
||||
} else {
|
||||
tracing::error!("Try to get the lock of view_editors failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn number_of_views(&self) -> usize {
|
||||
|
@ -289,6 +289,7 @@ where
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<BoxCellData> {
|
||||
// tracing::debug!("get_cell_data: {:?}", std::any::type_name::<Self>());
|
||||
let cell_data = if self.transformable() {
|
||||
match self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev) {
|
||||
None => self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)?,
|
||||
|
@ -22,7 +22,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
|
||||
|
||||
fn create_revision_records(&self, revision_records: Vec<SyncRecord>) -> Result<(), Self::Error> {
|
||||
let conn = self.pool.get().map_err(internal_error)?;
|
||||
GridMetaRevisionSql::create(revision_records, &conn)?;
|
||||
DatabaseBlockMetaRevisionSql::create(revision_records, &conn)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
|
||||
rev_ids: Option<Vec<i64>>,
|
||||
) -> Result<Vec<SyncRecord>, Self::Error> {
|
||||
let conn = self.pool.get().map_err(internal_error)?;
|
||||
let records = GridMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
|
||||
let records = DatabaseBlockMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
|
||||
) -> Result<Vec<SyncRecord>, Self::Error> {
|
||||
let conn = &*self.pool.get().map_err(internal_error)?;
|
||||
let revisions =
|
||||
GridMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
|
||||
DatabaseBlockMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
|
||||
Ok(revisions)
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
|
||||
let conn = &*self.pool.get().map_err(internal_error)?;
|
||||
conn.immediate_transaction::<_, FlowyError, _>(|| {
|
||||
for changeset in changesets {
|
||||
GridMetaRevisionSql::update(changeset, conn)?;
|
||||
DatabaseBlockMetaRevisionSql::update(changeset, conn)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
@ -68,7 +68,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
|
||||
rev_ids: Option<Vec<i64>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let conn = &*self.pool.get().map_err(internal_error)?;
|
||||
GridMetaRevisionSql::delete(object_id, rev_ids, conn)?;
|
||||
DatabaseBlockMetaRevisionSql::delete(object_id, rev_ids, conn)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -80,8 +80,8 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
|
||||
) -> Result<(), Self::Error> {
|
||||
let conn = self.pool.get().map_err(internal_error)?;
|
||||
conn.immediate_transaction::<_, FlowyError, _>(|| {
|
||||
GridMetaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
|
||||
GridMetaRevisionSql::create(inserted_records, &conn)?;
|
||||
DatabaseBlockMetaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
|
||||
DatabaseBlockMetaRevisionSql::create(inserted_records, &conn)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@ -96,8 +96,8 @@ impl SQLiteDatabaseBlockRevisionPersistence {
|
||||
}
|
||||
}
|
||||
|
||||
struct GridMetaRevisionSql();
|
||||
impl GridMetaRevisionSql {
|
||||
struct DatabaseBlockMetaRevisionSql();
|
||||
impl DatabaseBlockMetaRevisionSql {
|
||||
fn create(revision_records: Vec<SyncRecord>, conn: &SqliteConnection) -> Result<(), FlowyError> {
|
||||
// Batch insert: https://diesel.rs/guides/all-about-inserts.html
|
||||
|
||||
@ -105,7 +105,8 @@ impl GridMetaRevisionSql {
|
||||
.into_iter()
|
||||
.map(|record| {
|
||||
tracing::trace!(
|
||||
"[GridMetaRevisionSql] create revision: {}:{:?}",
|
||||
"[{}] create revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
record.revision.object_id,
|
||||
record.revision.rev_id
|
||||
);
|
||||
@ -133,7 +134,8 @@ impl GridMetaRevisionSql {
|
||||
.filter(dsl::object_id.eq(changeset.object_id));
|
||||
let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
|
||||
tracing::debug!(
|
||||
"[GridMetaRevisionSql] update revision:{} state:to {:?}",
|
||||
"[{}] update revision:{} state:to {:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
changeset.rev_id,
|
||||
changeset.state
|
||||
);
|
||||
@ -193,7 +195,8 @@ impl GridMetaRevisionSql {
|
||||
|
||||
if let Some(rev_ids) = rev_ids {
|
||||
tracing::trace!(
|
||||
"[GridMetaRevisionSql] Delete revision: {}:{:?}",
|
||||
"[{}] Delete revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
object_id,
|
||||
rev_ids
|
||||
);
|
||||
@ -201,7 +204,11 @@ impl GridMetaRevisionSql {
|
||||
}
|
||||
|
||||
let affected_row = sql.execute(conn)?;
|
||||
tracing::trace!("[GridMetaRevisionSql] Delete {} rows", affected_row);
|
||||
tracing::trace!(
|
||||
"[{}] Delete {} rows",
|
||||
std::any::type_name::<Self>(),
|
||||
affected_row
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,8 @@ impl DatabaseRevisionSql {
|
||||
.into_iter()
|
||||
.map(|record| {
|
||||
tracing::trace!(
|
||||
"[GridRevisionSql] create revision: {}:{:?}",
|
||||
"[{}] create revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
record.revision.object_id,
|
||||
record.revision.rev_id
|
||||
);
|
||||
@ -132,7 +133,8 @@ impl DatabaseRevisionSql {
|
||||
.filter(dsl::object_id.eq(changeset.object_id));
|
||||
let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
|
||||
tracing::debug!(
|
||||
"[GridRevisionSql] update revision:{} state:to {:?}",
|
||||
"[{}] update revision:{} state:to {:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
changeset.rev_id,
|
||||
changeset.state
|
||||
);
|
||||
@ -192,7 +194,8 @@ impl DatabaseRevisionSql {
|
||||
|
||||
if let Some(rev_ids) = rev_ids {
|
||||
tracing::trace!(
|
||||
"[GridRevisionSql] Delete revision: {}:{:?}",
|
||||
"[{}] Delete revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
object_id,
|
||||
rev_ids
|
||||
);
|
||||
@ -200,7 +203,11 @@ impl DatabaseRevisionSql {
|
||||
}
|
||||
|
||||
let affected_row = sql.execute(conn)?;
|
||||
tracing::trace!("[GridRevisionSql] Delete {} rows", affected_row);
|
||||
tracing::trace!(
|
||||
"[{}] Delete {} rows",
|
||||
std::any::type_name::<Self>(),
|
||||
affected_row
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
|
||||
|
||||
fn create_revision_records(&self, revision_records: Vec<SyncRecord>) -> Result<(), Self::Error> {
|
||||
let conn = self.pool.get().map_err(internal_error)?;
|
||||
GridViewRevisionSql::create(revision_records, &conn)?;
|
||||
DatabaseViewRevisionSql::create(revision_records, &conn)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
|
||||
rev_ids: Option<Vec<i64>>,
|
||||
) -> Result<Vec<SyncRecord>, Self::Error> {
|
||||
let conn = self.pool.get().map_err(internal_error)?;
|
||||
let records = GridViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
|
||||
let records = DatabaseViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
|
||||
) -> Result<Vec<SyncRecord>, Self::Error> {
|
||||
let conn = &*self.pool.get().map_err(internal_error)?;
|
||||
let revisions =
|
||||
GridViewRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
|
||||
DatabaseViewRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
|
||||
Ok(revisions)
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
|
||||
let conn = &*self.pool.get().map_err(internal_error)?;
|
||||
conn.immediate_transaction::<_, FlowyError, _>(|| {
|
||||
for changeset in changesets {
|
||||
GridViewRevisionSql::update(changeset, conn)?;
|
||||
DatabaseViewRevisionSql::update(changeset, conn)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
@ -77,7 +77,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
|
||||
rev_ids: Option<Vec<i64>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let conn = &*self.pool.get().map_err(internal_error)?;
|
||||
GridViewRevisionSql::delete(object_id, rev_ids, conn)?;
|
||||
DatabaseViewRevisionSql::delete(object_id, rev_ids, conn)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -89,22 +89,23 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
|
||||
) -> Result<(), Self::Error> {
|
||||
let conn = self.pool.get().map_err(internal_error)?;
|
||||
conn.immediate_transaction::<_, FlowyError, _>(|| {
|
||||
GridViewRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
|
||||
GridViewRevisionSql::create(inserted_records, &conn)?;
|
||||
DatabaseViewRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
|
||||
DatabaseViewRevisionSql::create(inserted_records, &conn)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct GridViewRevisionSql();
|
||||
impl GridViewRevisionSql {
|
||||
struct DatabaseViewRevisionSql();
|
||||
impl DatabaseViewRevisionSql {
|
||||
fn create(revision_records: Vec<SyncRecord>, conn: &SqliteConnection) -> Result<(), FlowyError> {
|
||||
// Batch insert: https://diesel.rs/guides/all-about-inserts.html
|
||||
let records = revision_records
|
||||
.into_iter()
|
||||
.map(|record| {
|
||||
tracing::trace!(
|
||||
"[GridViewRevisionSql] create revision: {}:{:?}",
|
||||
"[{}] create revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
record.revision.object_id,
|
||||
record.revision.rev_id
|
||||
);
|
||||
@ -132,7 +133,8 @@ impl GridViewRevisionSql {
|
||||
.filter(dsl::object_id.eq(changeset.object_id));
|
||||
let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
|
||||
tracing::debug!(
|
||||
"[GridViewRevisionSql] update revision:{} state:to {:?}",
|
||||
"[{}] update revision:{} state:to {:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
changeset.rev_id,
|
||||
changeset.state
|
||||
);
|
||||
@ -192,7 +194,8 @@ impl GridViewRevisionSql {
|
||||
|
||||
if let Some(rev_ids) = rev_ids {
|
||||
tracing::trace!(
|
||||
"[GridViewRevisionSql] Delete revision: {}:{:?}",
|
||||
"[{}] Delete revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
object_id,
|
||||
rev_ids
|
||||
);
|
||||
@ -200,7 +203,11 @@ impl GridViewRevisionSql {
|
||||
}
|
||||
|
||||
let affected_row = sql.execute(conn)?;
|
||||
tracing::trace!("[GridViewRevisionSql] Delete {} rows", affected_row);
|
||||
tracing::trace!(
|
||||
"[{}] Delete {} rows",
|
||||
std::any::type_name::<Self>(),
|
||||
affected_row
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -63,12 +63,11 @@ impl SortController {
|
||||
}
|
||||
|
||||
pub async fn close(&self) {
|
||||
self
|
||||
.task_scheduler
|
||||
.write()
|
||||
.await
|
||||
.unregister_handler(&self.handler_id)
|
||||
.await;
|
||||
if let Ok(mut task_scheduler) = self.task_scheduler.try_write() {
|
||||
// task_scheduler.unregister_handler(&self.handler_id).await;
|
||||
} else {
|
||||
tracing::error!("Try to get the lock of task_scheduler failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn did_receive_row_changed(&self, row_id: &str) {
|
||||
|
@ -106,7 +106,8 @@ impl FolderRevisionSql {
|
||||
.into_iter()
|
||||
.map(|record| {
|
||||
tracing::trace!(
|
||||
"[TextRevisionSql] create revision: {}:{:?}",
|
||||
"[{}] create revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
record.revision.object_id,
|
||||
record.revision.rev_id
|
||||
);
|
||||
@ -135,7 +136,8 @@ impl FolderRevisionSql {
|
||||
.filter(dsl::doc_id.eq(changeset.object_id));
|
||||
let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
|
||||
tracing::debug!(
|
||||
"[TextRevisionSql] update revision:{} state:to {:?}",
|
||||
"[{}] update revision:{} state:to {:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
changeset.rev_id,
|
||||
changeset.state
|
||||
);
|
||||
@ -193,7 +195,8 @@ impl FolderRevisionSql {
|
||||
|
||||
if let Some(rev_ids) = rev_ids {
|
||||
tracing::trace!(
|
||||
"[TextRevisionSql] Delete revision: {}:{:?}",
|
||||
"[{}] Delete revision: {}:{:?}",
|
||||
std::any::type_name::<Self>(),
|
||||
object_id,
|
||||
rev_ids
|
||||
);
|
||||
@ -201,7 +204,11 @@ impl FolderRevisionSql {
|
||||
}
|
||||
|
||||
let affected_row = sql.execute(conn)?;
|
||||
tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row);
|
||||
tracing::trace!(
|
||||
"[{}] Delete {} rows",
|
||||
std::any::type_name::<Self>(),
|
||||
affected_row
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use anyhow::Error;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_infra::ref_map::{RefCountHashMap, RefCountValue};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -15,7 +16,7 @@ pub struct TaskDispatcher {
|
||||
queue: TaskQueue,
|
||||
store: TaskStore,
|
||||
timeout: Duration,
|
||||
handlers: RefCountHashMap<RefCountTaskHandler>,
|
||||
handlers: HashMap<String, Arc<dyn TaskHandler>>,
|
||||
|
||||
notifier: watch::Sender<bool>,
|
||||
pub(crate) notifier_rx: Option<watch::Receiver<bool>>,
|
||||
@ -28,7 +29,7 @@ impl TaskDispatcher {
|
||||
queue: TaskQueue::new(),
|
||||
store: TaskStore::new(),
|
||||
timeout,
|
||||
handlers: RefCountHashMap::new(),
|
||||
handlers: HashMap::new(),
|
||||
notifier,
|
||||
notifier_rx: Some(notifier_rx),
|
||||
}
|
||||
@ -39,13 +40,17 @@ impl TaskDispatcher {
|
||||
T: TaskHandler,
|
||||
{
|
||||
let handler_id = handler.handler_id().to_owned();
|
||||
self
|
||||
.handlers
|
||||
.insert(handler_id, RefCountTaskHandler(Arc::new(handler)));
|
||||
self.handlers.insert(handler_id, Arc::new(handler));
|
||||
}
|
||||
|
||||
pub async fn unregister_handler<T: AsRef<str>>(&mut self, handler_id: T) {
|
||||
self.handlers.remove(handler_id.as_ref()).await;
|
||||
if let Some(handler) = self.handlers.remove(handler_id.as_ref()) {
|
||||
tracing::trace!(
|
||||
"{}:{} is unregistered",
|
||||
handler.handler_name(),
|
||||
handler.handler_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
@ -54,6 +59,7 @@ impl TaskDispatcher {
|
||||
self.store.clear();
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub(crate) async fn process_next_task(&mut self) -> Option<()> {
|
||||
let pending_task = self.queue.mut_head(|list| list.pop())?;
|
||||
let mut task = self.store.remove_task(&pending_task.id)?;
|
||||
@ -69,25 +75,25 @@ impl TaskDispatcher {
|
||||
let content = task.content.take()?;
|
||||
if let Some(handler) = self.handlers.get(&task.handler_id) {
|
||||
task.set_state(TaskState::Processing);
|
||||
tracing::trace!(
|
||||
"Run {} task with content: {:?}",
|
||||
handler.handler_name(),
|
||||
content
|
||||
);
|
||||
tracing::trace!("{} task is running", handler.handler_name(),);
|
||||
match tokio::time::timeout(self.timeout, handler.run(content)).await {
|
||||
Ok(result) => match result {
|
||||
Ok(_) => task.set_state(TaskState::Done),
|
||||
Ok(_) => {
|
||||
tracing::trace!("{} task is done", handler.handler_name(),);
|
||||
task.set_state(TaskState::Done)
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Process {} task failed: {:?}", handler.handler_name(), e);
|
||||
tracing::error!("{} task is failed: {:?}", handler.handler_name(), e);
|
||||
task.set_state(TaskState::Failure);
|
||||
},
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Process {} task timeout: {:?}", handler.handler_name(), e);
|
||||
tracing::error!("{} task is timeout: {:?}", handler.handler_name(), e);
|
||||
task.set_state(TaskState::Timeout);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tracing::trace!("{} is cancel", task.handler_id);
|
||||
task.set_state(TaskState::Cancel);
|
||||
}
|
||||
let _ = ret.send(task.into());
|
||||
@ -197,18 +203,3 @@ where
|
||||
(**self).run(content)
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct RefCountTaskHandler(Arc<dyn TaskHandler>);
|
||||
|
||||
#[async_trait]
|
||||
impl RefCountValue for RefCountTaskHandler {
|
||||
async fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RefCountTaskHandler {
|
||||
type Target = Arc<dyn TaskHandler>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user