2020-05-15 18:24:21 +02:00
|
|
|
import asyncio
|
|
|
|
from asyncio import Task
|
|
|
|
from typing import Optional, cast
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
|
|
|
from .autoupdate import get_autoupdate_data
|
|
|
|
from .cache import element_cache
|
|
|
|
from .websocket import ChangeIdTooHighException, ProtocollAsyncJsonWebsocketConsumer
|
|
|
|
|
|
|
|
|
|
|
|
AUTOUPDATE_DELAY = getattr(settings, "AUTOUPDATE_DELAY", None)
|
|
|
|
|
|
|
|
|
|
|
|
class ConsumerAutoupdateStrategy:
|
|
|
|
def __init__(self, consumer: ProtocollAsyncJsonWebsocketConsumer) -> None:
|
|
|
|
self.consumer = consumer
|
|
|
|
# client_change_id = None: unknown -> set on first autoupdate or request_change_id
|
2020-05-28 13:53:01 +02:00
|
|
|
# client_change_id is int: the change_id, the client knows about, so the next
|
|
|
|
# update must be from client_change_id+1 .. <next clange_id>
|
2020-05-15 18:24:21 +02:00
|
|
|
self.client_change_id: Optional[int] = None
|
|
|
|
self.next_send_time = None
|
|
|
|
self.timer_task_handle: Optional[Task[None]] = None
|
|
|
|
self.lock = asyncio.Lock()
|
|
|
|
|
|
|
|
async def request_change_id(
|
|
|
|
self, change_id: int, in_response: Optional[str] = None
|
|
|
|
) -> None:
|
2020-05-28 13:53:01 +02:00
|
|
|
"""
|
|
|
|
The change id is not inclusive, so the client is on change_id and wants
|
|
|
|
data from change_id+1 .. now
|
|
|
|
"""
|
2020-05-15 18:24:21 +02:00
|
|
|
# This resets the server side tracking of the client's change id.
|
|
|
|
async with self.lock:
|
|
|
|
await self.stop_timer()
|
|
|
|
|
2020-06-03 14:11:25 +02:00
|
|
|
max_change_id = await element_cache.get_current_change_id()
|
2020-05-15 18:24:21 +02:00
|
|
|
self.client_change_id = change_id
|
|
|
|
|
2020-06-03 14:11:25 +02:00
|
|
|
if self.client_change_id == max_change_id:
|
2020-05-15 18:24:21 +02:00
|
|
|
# The client is up-to-date, so nothing will be done
|
|
|
|
return None
|
|
|
|
|
2020-06-03 14:11:25 +02:00
|
|
|
if self.client_change_id > max_change_id:
|
2020-05-15 18:24:21 +02:00
|
|
|
message = (
|
|
|
|
f"Requested change_id {self.client_change_id} is higher than the "
|
2020-06-03 14:11:25 +02:00
|
|
|
+ f"highest change_id {max_change_id}."
|
2020-05-15 18:24:21 +02:00
|
|
|
)
|
|
|
|
raise ChangeIdTooHighException(message, in_response=in_response)
|
|
|
|
|
|
|
|
await self.send_autoupdate(in_response=in_response)
|
|
|
|
|
|
|
|
async def new_change_id(self, change_id: int) -> None:
|
|
|
|
async with self.lock:
|
|
|
|
if self.client_change_id is None:
|
2020-05-28 13:53:01 +02:00
|
|
|
# The -1 is to send this autoupdate as the first one to he client.
|
|
|
|
# Remember: the client_change_id is the change_id the client knows about
|
|
|
|
self.client_change_id = change_id - 1
|
2020-05-15 18:24:21 +02:00
|
|
|
|
|
|
|
if AUTOUPDATE_DELAY is None: # feature deactivated, send directly
|
|
|
|
await self.send_autoupdate()
|
|
|
|
elif self.timer_task_handle is None:
|
|
|
|
await self.start_timer()
|
|
|
|
|
|
|
|
async def get_running_loop(self) -> asyncio.AbstractEventLoop:
|
|
|
|
if hasattr(asyncio, "get_running_loop"):
|
|
|
|
return asyncio.get_running_loop() # type: ignore
|
|
|
|
else:
|
|
|
|
return asyncio.get_event_loop()
|
|
|
|
|
|
|
|
async def start_timer(self) -> None:
|
|
|
|
loop = await self.get_running_loop()
|
|
|
|
self.timer_task_handle = loop.create_task(self.timer_task())
|
|
|
|
|
|
|
|
async def stop_timer(self) -> None:
|
|
|
|
if self.timer_task_handle is not None:
|
|
|
|
self.timer_task_handle.cancel()
|
|
|
|
self.timer_task_handle = None
|
|
|
|
|
|
|
|
async def timer_task(self) -> None:
|
|
|
|
try:
|
|
|
|
await asyncio.sleep(AUTOUPDATE_DELAY)
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
return
|
|
|
|
|
|
|
|
async with self.lock:
|
|
|
|
await self.send_autoupdate()
|
|
|
|
self.timer_task_handle = None
|
|
|
|
|
|
|
|
async def send_autoupdate(self, in_response: Optional[str] = None) -> None:
|
2020-05-28 13:53:01 +02:00
|
|
|
# here, 1 is added to the change_id, because the client_change_id is the id the client
|
|
|
|
# *knows* about -> the client needs client_change_id+1 since get_autoupdate_data is
|
|
|
|
# inclusive [change_id .. max_change_id].
|
2020-06-03 14:11:25 +02:00
|
|
|
max_change_id, autoupdate = await get_autoupdate_data(
|
|
|
|
cast(int, self.client_change_id) + 1, self.consumer.user_id
|
2020-05-15 18:24:21 +02:00
|
|
|
)
|
|
|
|
if autoupdate is not None:
|
|
|
|
self.client_change_id = max_change_id
|
2020-06-03 14:11:25 +02:00
|
|
|
# It will be send, so we can set the client_change_id
|
2020-05-15 18:24:21 +02:00
|
|
|
await self.consumer.send_json(
|
|
|
|
type="autoupdate", content=autoupdate, in_response=in_response,
|
|
|
|
)
|