OpenSlides/client/src/app/core/core-services/autoupdate.service.ts

149 lines
5.3 KiB
TypeScript
Raw Normal View History

import { Injectable } from '@angular/core';
import { WebsocketService } from './websocket.service';
import { CollectionStringMapperService } from './collectionStringMapper.service';
2018-09-13 14:40:04 +02:00
import { DataStoreService } from './data-store.service';
2018-10-26 10:23:14 +02:00
import { BaseModel } from '../../shared/models/base/base-model';
2018-10-14 08:26:51 +02:00
interface AutoupdateFormat {
/**
* All changed (and created) items as their full/restricted data grouped by their collection.
*/
changed: {
[collectionString: string]: object[];
};
/**
* All deleted items (by id) grouped by their collection.
*/
deleted: {
[collectionString: string]: number[];
};
/**
2018-10-26 10:23:14 +02:00
* The lower change id bond for this autoupdate
2018-10-14 08:26:51 +02:00
*/
2018-10-26 10:23:14 +02:00
from_change_id: number;
/**
* The upper change id bound for this autoupdate
*/
to_change_id: number;
/**
* Flag, if this autoupdate contains all data. If so, the DS needs to be resetted.
*/
all_data: boolean;
2018-10-14 08:26:51 +02:00
}
/**
* Handles the initial update and automatic updates using the {@link WebsocketService}
* Incoming objects, usually BaseModels, will be saved in the dataStore (`this.DS`)
* This service usually creates all models
*/
@Injectable({
providedIn: 'root'
})
2019-02-08 17:24:32 +01:00
export class AutoupdateService {
/**
* Constructor to create the AutoupdateService. Calls the constructor of the parent class.
* @param websocketService
2018-10-26 10:23:14 +02:00
* @param DS
* @param modelMapper
*/
public constructor(
2018-10-26 10:23:14 +02:00
private websocketService: WebsocketService,
private DS: DataStoreService,
private modelMapper: CollectionStringMapperService
) {
2018-10-26 10:23:14 +02:00
this.websocketService.getOberservable<AutoupdateFormat>('autoupdate').subscribe(response => {
this.storeResponse(response);
});
}
/**
* Handle the answer of incoming data via {@link WebsocketService}.
*
* Detects the Class of an incomming model, creates a new empty object and assigns
2018-10-26 10:23:14 +02:00
* the data to it using the deserialize function. Also models that are flagged as deleted
* will be removed from the data store.
*
2018-10-26 10:23:14 +02:00
* Handles the change ids of all autoupdates.
*/
2018-10-26 10:23:14 +02:00
private async storeResponse(autoupdate: AutoupdateFormat): Promise<void> {
if (autoupdate.all_data) {
await this.storeAllData(autoupdate);
} else {
await this.storePartialAutoupdate(autoupdate);
}
}
2018-08-24 13:05:03 +02:00
2018-10-26 10:23:14 +02:00
/**
* Stores all data from the autoupdate. This means, that the DS is resettet and filled with just the
* given data from the autoupdate.
* @param autoupdate The autoupdate
*/
private async storeAllData(autoupdate: AutoupdateFormat): Promise<void> {
let elements: BaseModel[] = [];
2018-08-24 13:05:03 +02:00
Object.keys(autoupdate.changed).forEach(collection => {
2018-10-26 10:23:14 +02:00
elements = elements.concat(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
2018-08-24 13:05:03 +02:00
});
2018-10-26 10:23:14 +02:00
await this.DS.set(elements, autoupdate.to_change_id);
}
/**
* handles a normal autoupdate that is not a full update (all_data=false).
* @param autoupdate The autoupdate
*/
private async storePartialAutoupdate(autoupdate: AutoupdateFormat): Promise<void> {
const maxChangeId = this.DS.maxChangeId;
if (autoupdate.from_change_id <= maxChangeId && autoupdate.to_change_id <= maxChangeId) {
console.log('ignore');
return; // Ignore autoupdates, that lay full behind our changeid.
}
// Normal autoupdate
if (autoupdate.from_change_id <= maxChangeId + 1 && autoupdate.to_change_id > maxChangeId) {
// Delete the removed objects from the DataStore
for (const collection of Object.keys(autoupdate.deleted)) {
await this.DS.remove(collection, autoupdate.deleted[collection]);
}
// Add the objects to the DataStore.
for (const collection of Object.keys(autoupdate.changed)) {
if (this.modelMapper.isCollectionRegistered(collection)) {
await this.DS.add(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
} else {
console.error(`Unregistered collection "${collection}". Ignore it.`);
}
2018-10-26 10:23:14 +02:00
}
await this.DS.flushToStorage(autoupdate.to_change_id);
} else {
// autoupdate fully in the future. we are missing something!
this.requestChanges();
}
}
/**
* Creates baseModels for each plain object
* @param collection The collection all models have to be from.
* @param models All models that should be mapped to BaseModels
* @returns A list of basemodels constructed from the given models.
*/
private mapObjectsToBaseModels(collection: string, models: object[]): BaseModel[] {
2019-02-12 09:25:56 +01:00
const targetClass = this.modelMapper.getModelConstructor(collection);
2018-10-26 10:23:14 +02:00
return models.map(model => new targetClass(model));
}
/**
* Sends a WebSocket request to the Server with the maxChangeId of the DataStore.
* The server should return an autoupdate with all new data.
*/
public requestChanges(): void {
2018-10-26 10:23:14 +02:00
console.log('requesting changed objects with DS max change id', this.DS.maxChangeId + 1);
this.websocketService.send('getElements', { change_id: this.DS.maxChangeId + 1 });
}
}