UniSquat_Python/rooms_get.py

359 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 24 08:51:58 2022
@author: antoine
"""
################
### UniSquat ###
################
# Une application pour afficher les salles libres dans les différents
# départements de l'Université de Strasbourg.
### Fichier du backend (récupération des salles libres et des départements ###
# Modules :
import requests
import icalendar
# import ics as icalendar
import pytz
import os
import shutil
import time
# Fichiers locaux :
from objects import Room
from objects import Dept
# Constantes :
CACHE_DIR = "cache"
# Délai de réinitialisation du cache, en minutes :
CACHE_TTL = 5
# Nombres maximum de fichier dans le cache :
CACHE_SIZE = 10
# Globales :
last_cache_init = -999
# Fonctions :
def reinit_cache() :
"""
Vide le dossier CACHE_DIR et l'initialise.
Modifie la variable globale 'last_cache_init'.
Parameters
----------
None
Returns
-------
None
"""
global last_cache_init
if os.path.isdir(CACHE_DIR) :
shutil.rmtree(CACHE_DIR)
os.mkdir(CACHE_DIR)
last_cache_init = time.time()
def trim(link) :
"""
Retourne la chaîne de caractères 'link' en minuscule, sans les caractères
spéciaux (utilisé pour le cache des requêtes).
Parameters
----------
link: str
Le lien à simplifier.
Returns
-------
str
La chaine de caractères correspondante.
"""
result = ""
for i in link.lower() :
if i not in "/:;\\'\" *?":
result += i
return result
def sched_get(date, link, enddate = None, nocache = False) :
"""
Récupère l'emploi du temps de toutes les salles sur ADE
depuis le site de l'Unistra.
Parameters
----------
date : datetime.datetime()
Date du calendrier à télécharger (date de début si une date de fin
'enddate' est indiquée).
link:
Un lien vers lequel effectuer la recherche :
$YEAR$ : l'année
$MONTH$ : le mois
$DAY$ : le jour
Optional :
enddate : datetime.datetime()
Date de fin du calendrier à télécharger (par défaut, il s'agit de
la date de début).
nocache : booléen
Si mis à 'True', ne lit et ne modifie pas le dossier CACHE_DIR.
Returns
-------
bytes
Le texte du résultat de la requête.
"""
link += "&firstDate=$YEAR1$-$MONTH1$-$DAY1$&lastDate=$YEAR2$-$MONTH2$-$DAY2$"
day1 = str(date.day)
month1 = str(date.month)
year1 = str(date.year)
finallink = link.replace("$DAY1$", day1)
finallink = finallink.replace("$MONTH1$", month1)
finallink = finallink.replace("$YEAR1$", year1)
if enddate != None :
day2 = str(enddate.day)
month2 = str(enddate.month)
year2 = str(enddate.year)
finallink = finallink.replace("$DAY2$", day2)
finallink = finallink.replace("$MONTH2$", month2)
finallink = finallink.replace("$YEAR2$", year2)
else :
finallink = finallink.replace("$DAY2$", day1)
finallink = finallink.replace("$MONTH2$", month1)
finallink = finallink.replace("$YEAR2$", year1)
if nocache :
return requests.get(finallink).content
else :
# Vérifie la TTL :
elapsed = time.time() - last_cache_init
if elapsed>CACHE_TTL*60:
reinit_cache()
# Vérifie que le nombre total de fichiers dans le cache
# n'est pas dépassé :
if len(os.listdir(CACHE_DIR)) > CACHE_SIZE:
reinit_cache()
# Vérifie que le lien est dans le cache :
cachepath = os.path.join(CACHE_DIR, trim(finallink))
if os.path.isfile(cachepath) :
result = ""
with open(cachepath,'rb') as f :
result = f.read()
return result
else:
result = requests.get(finallink).content
with open(cachepath,'wb') as f :
f.write(result)
return result
def get_depts(filename) :
"""
Crée une liste de tous les départements disponibles.
Parameters
----------
filename : str
Nom du fichier contenant les départements, et les IDs ADE de ceux-ci.
Returns
-------
dept_list : list
Liste des départements.
"""
dept_list = list()
dept_file = open(filename, "r")
i = 0
# dept = Dept("", "", [])
dfile_content = dept_file.readlines()
ident = 0 # Compteur pour les identifiants des départements
for i in range(0, len(dfile_content) - 1, 2) :
dept_list.append(Dept(ident, dfile_content[i], dfile_content[i + 1], []))
ident += 1
dept_file.close()
return dept_list
def getrooms(datet, depts, ignore_list) :
"""
Génération de la liste des salles avec heures de début et fin de dispo,
et indicateur de dispo.
Parameters
----------
datet : datetime.datetime()
Date pour la recherche de salles.
depts : list
Liste des départements dans lesquel chercher des salles.
ignore_list : list
Liste des noms de salles à ignorer.
Returns
-------
total_rooms : list
Liste des salles.
"""
total_rooms = list()
# Marge de temps (en mois) pour le début du calendrier.
# Certaines salles ne sont pas utilisées tous les jours,
# donc on télécharge l'EDT d'un mois complet pour être tranquille.
margintime = 1
# Récupération des calendriers correspondants aux liens des départements,
# sur une période de 'margintime' mois :
cals = list() # Liste des EDT des départements choisis
for d in depts :
if datet.month < 12 :
result = sched_get(datet, d.link,
datet.replace(month = datet.month + margintime))
else :
result = sched_get(datet, d.link,
datet.replace(month = 1, year = datet.year + 1))
cals.append(icalendar.Calendar.from_ical(result))
# cals.append(icalendar.Calendar(result.decode("utf-8")))
# Parcours de ces calendriers :
dept_index = 0
for cal in cals :
# Première boucle pour la liste des salles + déterminer les occupées :
for comp in cal.walk() : # Événements
if comp.name == "VEVENT" :
# Récupération des infos :
roomname = str(comp.get("location"))
datestart = comp.decoded("dtstart")
dateend = comp.decoded("dtend")
roomname = str(comp.get("location"))
# Contient le nom de toutes les salles indiquées
# dans la section "LOCATION" :
rnamelist = list()
# Séparation des salles multiples, le cas échéant :
if "," in roomname :
rnamelist = roomname.split(",")
else :
rnamelist.append(roomname)
for rname in rnamelist :
rname = rname.strip()
if rname not in ignore_list :
# Création d'une nouvelle salle
# si elle n'existe pas déjà :
exists = False
for room in total_rooms :
if room.name == rname :
exists = True
r = room
if not exists :
# Par défaut :
# - L'heure de début de disponibilité
# est aujourd'hui à 00:00.
# - L'heure de fin de la prochaine période
# de disponibilité est aujourd'hui à 23:59.
# - La salle est libre.
r = Room(rname,
datet.replace(hour = 0, minute = 0, second = 0),
datet.replace(hour = 23, minute = 59, second = 59),
True,
depts[dept_index].name
)
# Si l'événement se passe aujourd'hui :
if datestart.day == datet.day and \
datestart.month == datet.month and \
datestart.year == datet.year :
if rname == "Grand Amphi MAI Frenkel" :
print(r.start.hour, r.end.hour, datestart.hour+2, dateend.hour+2)
# Si l'événement se passe maintenant
# (salle occupée maintenant) :
if datestart.timestamp() <= datet.timestamp() and \
dateend.timestamp() > datet.timestamp() :
r.is_free = False
# L'heure de début de la prochaine dispo est
# la fin de l'événement,
# si différente de la fin de dispo :
r.start = dateend
# Réglage du fuseau horaire :
r.start = r.start.astimezone(pytz.timezone('Europe/Paris'))
r.end = r.end.astimezone(pytz.timezone('Europe/Paris'))
if not exists :
total_rooms.append(r)
# Deuxième boucle, pour déterminer les périodes de dispo :
for comp in cal.walk() :
if comp.name == "VEVENT" :
# Récupération des infos :
roomname = str(comp.get("location"))
datestart = comp.decoded("dtstart")
dateend = comp.decoded("dtend")
roomname = str(comp.get("location"))
# Contient le nom de toutes les salles indiquées
# dans la section "LOCATION" :
rnamelist = list()
# Séparation des salles multiples, le cas échéant :
if "," in roomname :
rnamelist = roomname.split(",")
else :
rnamelist.append(roomname)
for rname in rnamelist :
rname = rname.strip()
if rname not in ignore_list :
# Recherche de la salle :
for room in total_rooms :
if room.name == rname :
r = room
# Si l'événement se passe aujourd'hui :
if datestart.day == datet.day and \
datestart.month == datet.month and \
datestart.year == datet.year :
if rname == "Grand Amphi MAI Frenkel" :
print(r.start.hour, r.end.hour, datestart.hour+2, dateend.hour+2)
# Si l'événement se passe prochainement
# (salle occupée à l'occasion de cet événement) :
if datestart.timestamp() > datet.timestamp() :
if datestart.timestamp() == r.start.timestamp() :
r.start = dateend
elif datestart.timestamp() < r.end.timestamp() :
r.end = datestart
# Réglage du fuseau horaire :
r.start = r.start.astimezone(pytz.timezone('Europe/Paris'))
r.end = r.end.astimezone(pytz.timezone('Europe/Paris'))
dept_index += 1
return total_rooms