From c8d02788dc6f48255c8f0f754b071d222c388c77 Mon Sep 17 00:00:00 2001 From: GabrielMeyer Date: Tue, 1 Oct 2019 15:36:59 +0200 Subject: [PATCH] Enhances the autoupdate of projector by change-id --- .../core-services/projector-data.service.ts | 30 +++++++++++++++++-- openslides/core/websocket.py | 4 +-- openslides/utils/autoupdate.py | 7 ++++- openslides/utils/consumers.py | 19 +++++++++++- tests/integration/utils/test_consumers.py | 30 +++++++++++-------- 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/client/src/app/core/core-services/projector-data.service.ts b/client/src/app/core/core-services/projector-data.service.ts index 237bdc8e1..0374110f2 100644 --- a/client/src/app/core/core-services/projector-data.service.ts +++ b/client/src/app/core/core-services/projector-data.service.ts @@ -18,6 +18,21 @@ interface AllProjectorData { [id: number]: ProjectorData | { error: string }; } +/** + * Received data from server. + */ +interface ProjectorWebsocketMessage { + /** + * The `change_id` of the current update. + */ + change_id: number; + + /** + * The necessary new projector-data. + */ + data: AllProjectorData; +} + /** * This service handles the websocket connection for the projector data. * Each projector instance registers itself by calling `getProjectorObservable`. @@ -43,6 +58,11 @@ export class ProjectorDataService { */ private readonly updateProjectorDataDebounceSubject = new Subject(); + /** + * Holds the current change id to check, if the update contains new content or a deprecated one. + */ + private currentChangeId = 0; + /** * Constructor. * @@ -50,13 +70,17 @@ export class ProjectorDataService { */ public constructor(private websocketService: WebsocketService) { // Dispatch projector data. - this.websocketService.getOberservable('projector').subscribe((update: AllProjectorData) => { - Object.keys(update).forEach(_id => { + this.websocketService.getOberservable('projector').subscribe((update: ProjectorWebsocketMessage) => { + if (this.currentChangeId > update.change_id) { + return; + } + Object.keys(update.data).forEach(_id => { const id = parseInt(_id, 10); if (this.currentProjectorData[id]) { - this.currentProjectorData[id].next(update[id] as ProjectorData); + this.currentProjectorData[id].next(update.data[id] as ProjectorData); } }); + this.currentChangeId = update.change_id; }); // The service need to re-register, if the websocket connection was lost. diff --git a/openslides/core/websocket.py b/openslides/core/websocket.py index 5739d04af..e5fa3dde9 100644 --- a/openslides/core/websocket.py +++ b/openslides/core/websocket.py @@ -179,9 +179,7 @@ class ListenToProjectors(BaseWebsocketClientMessage): for projector_id, data in projector_data.items(): consumer.projector_hash[projector_id] = hash(str(data)) - await consumer.send_json( - type="projector", content=projector_data, in_response=id - ) + await consumer.send_projector_data(projector_data, in_response=id) class PingPong(BaseWebsocketClientMessage): diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py index 9a3272078..0529c7fc5 100644 --- a/openslides/utils/autoupdate.py +++ b/openslides/utils/autoupdate.py @@ -217,7 +217,12 @@ def handle_changed_elements(elements: Iterable[Element]) -> None: # Send projector channel_layer = get_channel_layer() await channel_layer.group_send( - "projector", {"type": "projector_changed", "data": projector_data} + "projector", + { + "type": "projector_changed", + "data": projector_data, + "change_id": change_id, + }, ) if elements: diff --git a/openslides/utils/consumers.py b/openslides/utils/consumers.py index 2ad665054..198e9ac97 100644 --- a/openslides/utils/consumers.py +++ b/openslides/utils/consumers.py @@ -190,6 +190,8 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer): The projector has changed. """ all_projector_data = event["data"] + change_id = event["change_id"] + projector_data: Dict[int, Dict[str, Any]] = {} for projector_id in self.listen_projector_ids: data = all_projector_data.get(projector_id, []) @@ -199,4 +201,19 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer): self.projector_hash[projector_id] = new_hash if projector_data: - await self.send_json(type="projector", content=projector_data) + await self.send_projector_data(projector_data, change_id=change_id) + + async def send_projector_data( + self, + data: Dict[int, Dict[str, Any]], + change_id: Optional[int] = None, + in_response: Optional[str] = None, + ) -> None: + """ + Sends projector data to the consumer. + """ + if change_id is None: + change_id = await element_cache.get_current_change_id() + + content = {"change_id": change_id, "data": data} + await self.send_json(type="projector", content=content, in_response=in_response) diff --git a/tests/integration/utils/test_consumers.py b/tests/integration/utils/test_consumers.py index 3303a9ca6..3b4cf65b2 100644 --- a/tests/integration/utils/test_consumers.py +++ b/tests/integration/utils/test_consumers.py @@ -565,12 +565,15 @@ async def test_listen_to_projector(communicator, set_config): content = response.get("content") assert type == "projector" assert content == { - "1": [ - { - "data": {"name": "slide1", "event_name": "OpenSlides"}, - "element": {"id": 1, "name": "test/slide1"}, - } - ] + "change_id": 3, + "data": { + "1": [ + { + "data": {"name": "slide1", "event_name": "OpenSlides"}, + "element": {"id": 1, "name": "test/slide1"}, + } + ] + }, } @@ -595,12 +598,15 @@ async def test_update_projector(communicator, set_config): content = response.get("content") assert type == "projector" assert content == { - "1": [ - { - "data": {"name": "slide1", "event_name": "Test Event"}, - "element": {"id": 1, "name": "test/slide1"}, - } - ] + "change_id": 4, + "data": { + "1": [ + { + "data": {"name": "slide1", "event_name": "Test Event"}, + "element": {"id": 1, "name": "test/slide1"}, + } + ] + }, }