import copy import datetime import json import threading import time from timeit import default_timer from asgiref.sync import sync_to_async from channels.generic.websocket import AsyncWebsocketConsumer from .models import OngoingTimer running_timer_threads = [] def tick_timer(timer, iteration: int, total_seconds: int) -> None: second_compensation = 0 start_time = default_timer() timer.refresh_from_db() end_time = default_timer() second_compensation = end_time - start_time while not timer.is_running: time.sleep(0.05) timer.refresh_from_db() if iteration != timer.iteration: # Stop the thread if there is a new timer running_timer_threads.remove(timer.id) return start_time = default_timer() if iteration != timer.iteration: # Stop the thread if there is a new timer running_timer_threads.remove(timer.id) return if total_seconds < 1: return total_seconds -= 1 seconds = total_seconds % 60 minutes = round((total_seconds - seconds) / 60) timer.minutes = minutes timer.seconds = seconds timer.save() end_time = default_timer() second_compensation += end_time - start_time threading.Timer( interval=1 - second_compensation, function=tick_timer, args=( timer, iteration, total_seconds, ), ).start() class TimerConsumer(AsyncWebsocketConsumer): async def connect(self) -> None: timer_id = self.scope["url_route"]["kwargs"]["id"] timer = await sync_to_async(OngoingTimer.objects.filter(id=timer_id).first)() if timer is None: # Not found await self.close() return self.timer = timer await self.channel_layer.group_add(str(self.timer.id), self.channel_name) await self.accept() if self.timer.is_running and self.timer.id not in running_timer_threads: await self.run_timer() async def disconnect(self, close_code) -> None: if hasattr(self, "timer"): await self.channel_layer.group_discard( str(self.timer.id), self.channel_name ) async def run_timer(self, minutes: int = None, seconds: int = None) -> None: self.timer.iteration += 1 await sync_to_async(self.timer.save)() while self.timer.id in running_timer_threads: # Wait until the thread is freed time.sleep(0.05) running_timer_threads.append(self.timer.id) total_seconds = (self.timer.minutes * 60) + self.timer.seconds time.sleep(1) threading.Thread( target=tick_timer, args=( self.timer, self.timer.iteration, total_seconds, ), ).start() async def receive(self, text_data: str) -> None: await sync_to_async(self.timer.refresh_from_db)() json_data = json.loads(text_data) response = {} reset_timer = False if "is_running" in json_data: if json_data["is_running"]: self.timer.is_running = True await sync_to_async(self.timer.save)() else: self.timer.is_running = False await sync_to_async(self.timer.save)() if "time" in json_data: self.timer.minutes = json_data["time"]["minutes"] self.timer.seconds = json_data["time"]["seconds"] reset_timer = True response.update( { "sync_time": { "minutes": self.timer.minutes, "seconds": self.timer.seconds, }, "is_running": self.timer.is_running, } ) if reset_timer: await self.run_timer() await self.channel_layer.group_send( str(self.timer.id), {"type": "timer_message", "text": json.dumps(response)} ) async def timer_message(self, event: dict) -> None: await self.send(text_data=event["text"])