import logging import datetime import pytz from scheduler.asyncio import Scheduler from db.constants import TIMELINE_ORDER_ISSUED, TIMELINE_ORDER_NOT_ISSUED from settings import TIMEZONE, SCHEDULE_SYNC_INTERVAL from orders import order_issue, order_check from db.queries import orders_pool_by_id, orders_pool_scheduled, orders_pool_since, skip_day_get, order_status_outstanding, timeline_event_put from util import order_time, sqlite_time logger = logging.getLogger(__name__) WEEKDAYS = [0, 1, 2, 3, 4] WEEKENDS = [5, 6] GRACE_PERIOD = datetime.timedelta(seconds=10) class OrderScheduler(): def __init__(self, loop): self.tz = pytz.timezone(TIMEZONE) self.scheduler = Scheduler(loop=loop, tzinfo=self.tz) self.scheduled_pools = {} for orders_pool in orders_pool_scheduled(): self.schedule_pool(orders_pool) for order_status in order_status_outstanding(): check_time = order_status.verify_at if order_status.verify_at is not None else order_status.due_at self.scheduler.once( check_time + GRACE_PERIOD, self.scheduled_check, args=(order_status.id,) ) self.last_update = datetime.datetime.now(datetime.UTC) self.scheduler.cyclic( datetime.timedelta(seconds=SCHEDULE_SYNC_INTERVAL), self.update_schedule ) logger.info(self.scheduler) def schedule_pool(self, orders_pool): if orders_pool.id in self.scheduled_pools: self.scheduler.delete_job(self.scheduled_pools[orders_pool.id]) del self.scheduled_pools[orders_pool.id] if orders_pool.scheduled: self.scheduled_pools[orders_pool.id] = self.scheduler.daily( [order_time(t) for t in orders_pool.time.split(",")], self.scheduled_order, args=(orders_pool.id,) ) async def scheduled_order(self, orders_pool_id): orders_pool = orders_pool_by_id(orders_pool_id) # Skip weekends or weekdays day_of_week = datetime.datetime.now(tz=self.tz).weekday() if ( (not orders_pool.weekends and day_of_week in WEEKENDS) or (not orders_pool.weekdays and day_of_week in WEEKDAYS) ): logger.info(f'{orders_pool} Not scheduled for today') timeline_event_put( TIMELINE_ORDER_NOT_ISSUED, f"{orders_pool.name} is not scheduled for today", user=orders_pool.user, orders_pool=orders_pool ) return # Skip stored dates today = datetime.datetime.now(tz=self.tz).strftime("%Y-%m-%d") skip_day = skip_day_get(orders_pool.user, today) if (skip_day is not None and ( skip_day.pool is None or skip_day.pool.id == orders_pool.id )): logger.info(f'{orders_pool} Today is a skip day') timeline_event_put( TIMELINE_ORDER_NOT_ISSUED, f"Today is a skip day", user=orders_pool.user, orders_pool=orders_pool ) return logger.info(f'Issuing order for {orders_pool.name}[{orders_pool.user.telegram_username}]') issue_result = await order_issue(orders_pool) if 'order_status' in issue_result: order_status = issue_result['order_status'] if order_status.due_at is not None or order_status.verify_at is not None: # Schedule check check_time = order_status.verify_at if order_status.verify_at is not None else order_status.due_at self.scheduler.once( check_time + GRACE_PERIOD, self.scheduled_check, args=(order_status.id,) ) timeline_event_put( TIMELINE_ORDER_ISSUED, order_status.text.split("\n")[0], user=orders_pool.user, orders_pool=orders_pool, order_status=order_status, extra={ "mastodon_status_url": issue_result['mastodon_status']['url'] } ) elif 'reason' in issue_result: timeline_event_put( TIMELINE_ORDER_NOT_ISSUED, issue_result['reason'], user=orders_pool.user, orders_pool=orders_pool ) async def scheduled_check(self, outstanding_order_id): await order_check(outstanding_order_id) async def update_schedule(self): last_update_sqlite = sqlite_time(self.last_update) updated = False for orders_pool_updated in orders_pool_since(last_update_sqlite): logger.info(f'Updating schedule for {orders_pool_updated}') self.schedule_pool(orders_pool_updated) updated = True if updated: logger.info(self.scheduler) self.last_update = datetime.datetime.now(datetime.UTC)