import json from flask import Flask, render_template, request, g import psycopg2 import psycopg2.extras import time from datetime import datetime, timedelta import sys import dateutil import dateutil.tz ### Flask app object ### app = Flask(__name__) app.config.from_pyfile('config.py') ### Database connection ### db_connection = None db = None # XXX: This is safe only because we never write to the database. Otherwise, we would # have to handle transactions and roll them back if an exception occurs during # processing of the request. def db_connect(): global db_connection, db db_connection = psycopg2.connect( host = 'localhost', user = app.config['DB_USER'], password = app.config['DB_PASSWD'], dbname = app.config['DB_NAME'], ) db = db_connection.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor) def db_query(query, args=()): if db is None: db_connect() try: db.execute(query, args) except psycopg2.DatabaseError: # Reconnect if the connection died (timeout, server restart etc.) db_connect() db.execute(query, args) ### Schedule ### def get_date(): """Return a datetime object corresponding to the start of the given date.""" try: d = request.args.get('date', "") dt = datetime.strptime(d, "%Y-%m-%d") except ValueError: dt = datetime.today() tz = dateutil.tz.tzlocal() return dt.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=tz) room_list = { 'i': [ ('Z1', 'zoom-1@d3s.mff.cuni.cz'), ('Z2', 'zoom-2@d3s.mff.cuni.cz'), ('Z3', 'zoom-3@d3s.mff.cuni.cz'), ('Z4', 'zoom-4@d3s.mff.cuni.cz'), ('Z5', 'zoom-5@d3s.mff.cuni.cz'), ('Z6', 'zoom-6@d3s.mff.cuni.cz'), ('Z7', 'zoom-7@d3s.mff.cuni.cz'), ('Z8', 'zoom-8@d3s.mff.cuni.cz'), ('Z9', 'zoom-9@d3s.mff.cuni.cz'), ('Z10', 'zoom-10@d3s.mff.cuni.cz'), ], 'm': [ ('ZM1', 'zoom-m-1@d3s.mff.cuni.cz'), ('ZM2', 'zoom-m-2@d3s.mff.cuni.cz'), ('ZM3', 'zoom-m-3@d3s.mff.cuni.cz'), ('ZM4', 'zoom-m-4@d3s.mff.cuni.cz'), ('ZM7', 'zoom-m-7@d3s.mff.cuni.cz'), ('ZM8', 'zoom-m-8@d3s.mff.cuni.cz'), ], } @app.route('/') def main_page(): dt = get_date() g.date = dt.strftime("%Y-%m-%d") g.dow = dt.strftime("%A") hours_arg = request.args.get("hours", "") if hours_arg in ["0", "1", "2"]: g.hours = int(hours_arg) else: g.hours = 0 if g.hours == 0: slot_size = 100*60 first_slot = 9*3600 num_slots = 10 elif g.hours == 1: slot_size = 3600 first_slot = 8*3600 num_slots = 16 else: slot_size = 3600 first_slot = 0 num_slots = 24 g.rooms = request.args.get("rooms", "") if g.rooms not in room_list: g.rooms = "i" rooms = room_list[g.rooms] num_rooms = len(rooms) email_to_room_index = { rooms[i][1]: i for i in range(num_rooms) } room_box_width = 100 room_hour_height = 50 room_slot_height = room_hour_height * slot_size // 3600 row_offset = 70 g.total_width = room_box_width * num_rooms g.total_height = num_slots * room_slot_height g.room_boxes = [{ "x": row_offset + i * room_box_width + 1, "w": room_box_width - 1, "h": g.total_height - 1, "name": rooms[i][0], } for i in range(num_rooms)] g.slot_boxes = [{ "x": row_offset + 1, "y": i * room_slot_height + 1, "w": num_rooms * room_box_width, "h": room_slot_height - 1, } for i in range(num_slots)] g.slot_labels = [{ "x": 0, "y": i * room_slot_height, "w": row_offset - 2, "h": room_slot_height - 1, "label": (dt + timedelta(seconds = first_slot + i*slot_size)).strftime("%H:%M"), } for i in range(num_slots)] # XXX: No meeting is ever longer than 24 hours db_query(""" SELECT m.meeting_id, m.topic, s.start_time, s.duration, u.email, u.full_name FROM zoom_meetings m JOIN zoom_users u ON u.uid = m.host_uid JOIN zoom_schedule s ON s.mid = m.mid WHERE s.start_time >= DATE %s - INTERVAL '1 day' AND s.start_time < DATE %s + INTERVAL '1 day' ORDER BY u.email, s.start_time """, (dt, dt)) g.meetings = [] prev_room_i = -1 prev_end_t = None for r in db.fetchall(): i = email_to_room_index.get(r.email, -1) if i < 0: continue # Datetime in the DB is in UTC, but psycopg2 interprets it as local time start_dt = r.start_time.replace(tzinfo=dateutil.tz.tz.tzutc()) start_t = int(start_dt.timestamp()) end_t = start_t + r.duration*60 rel_start = start_t - dt.timestamp() - first_slot rel_end = rel_start + r.duration*60 start = max(0, int(rel_start)) end = min(num_slots * slot_size, int(rel_end)) app.logger.debug("Meeting: %s start_t=%s start=%s end=%s room_i=%s", r, start_t, start, end, i) if start < end: coll = (i == prev_room_i and start_t < prev_end_t) g.meetings.append({ "x": row_offset + i * room_box_width + 4 + 10*int(coll), "y": int(start / 3600. * room_hour_height), "w": room_box_width - 7 - 10*int(coll), "h": int((end - start) / 3600 * room_hour_height), "start": time.strftime("%H:%M", time.localtime(start_t)), "end": time.strftime("%H:%M", time.localtime(end_t)), "topic": r.topic, "coll": coll, }) prev_room_i = i prev_end_t = end_t return render_template('main.html')