Source code for ucsschool.lib.schoollessons

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# UCS@school python lib
#
# Copyright 2012-2021 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.

import datetime
import re
import shutil

from six import string_types
from six.moves.configparser import ConfigParser

from univention.lib import locking
from univention.lib.i18n import Translation
from univention.management.console.log import MODULE

LESSONS_FILE = "/var/lib/ucs-school-lib/lessons.ini"
LESSONS_BACKUP = "/var/lib/ucs-school-lib/lessons.bak"

_ = Translation("python-ucs-school").translate


[docs]class Lesson(object): TIME_REGEX = re.compile(r"^([01][0-9]|2[0-3]|[0-9]):([0-5][0-9])") def __init__(self, name, begin, end): self._name = self._check_name(name) self._begin = self._parse_time(begin) self._end = self._parse_time(end) if self._end <= self._begin: raise AttributeError(_("Overlapping lessons are not allowed")) def _check_name(self, string): if not isinstance(string, string_types): raise TypeError("string expected") for char in ( "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14" "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f[]\x7f" ): string = string.replace(char, "") return string def _parse_time(self, string): if not isinstance(string, string_types): raise TypeError("string expected") m = Lesson.TIME_REGEX.match(string) if not m: raise AttributeError("invalid time format: %s" % string) return datetime.time(*map(int, m.groups())) @property def name(self): return self._name @property def begin(self): return self._begin @property def end(self): return self._end def __cmp__(self, other): if other.end < self.begin: return 1 if other.begin > self.end: return -1 return 0 def __gt__(self, other): return self.begin > other.end def __ge__(self, other): return self.begin >= other.end def __lt__(self, other): return self.end < other.begin def __le__(self, other): return self.end <= other.begin def __eq__(self, other): return self.begin == other.begin and self.end == other.end def __ne__(self, other): return not self == other
[docs] def intersect(self, lesson): return not (self.begin > lesson.end or self.end < lesson.begin)
def __str__(self): return "%s: %s - %s" % (self._name, self._begin, self._end)
[docs]class SchoolLessons(ConfigParser): def __init__(self, filename=LESSONS_FILE): ConfigParser.__init__(self) self._lessons = [] self.read(filename) self.init()
[docs] def init(self): for sec in self.sections(): try: lesson = Lesson(sec, self.get(sec, "begin"), self.get(sec, "end")) self.add(lesson) except (AttributeError, TypeError) as exc: MODULE.warn("Lesson %s could not be added: %s" % (sec, str(exc)))
[docs] def remove(self, lesson): if isinstance(lesson, Lesson): lesson = lesson.name self._lessons[:] = [les for les in self._lessons if les.name != lesson]
[docs] def add(self, lesson, begin=None, end=None): if isinstance(lesson, string_types): lesson = Lesson(lesson, begin, end) # ensure there is no intersection between the lessons for item in self._lessons: if lesson.intersect(item) or lesson.name == item.name: raise AttributeError(_("Overlapping lessons are not allowed")) self._lessons.append(lesson)
[docs] def save(self): # remove all sections for sec in self.sections(): self.remove_section(sec) for lesson in self.lessons: self.add_section(lesson.name) self.set(lesson.name, "begin", str(lesson.begin)) self.set(lesson.name, "end", str(lesson.end)) lock = locking.get_lock("ucs-school-lib-schoollessons") with open(LESSONS_FILE, "w") as fd: shutil.copyfile(LESSONS_FILE, LESSONS_BACKUP) self.write(fd) locking.release_lock(lock)
@property def lessons(self): self._lessons.sort() return list(self._lessons) @property def current(self): now = datetime.datetime.now().time() # currently active lesson for lesson in self.lessons: if now >= lesson.begin and now <= lesson.end: return lesson return None @property def next(self): now = datetime.datetime.now().time() # currently active lesson for lesson in self.lessons: if now < lesson.begin: return lesson return None @property def previous(self): now = datetime.datetime.now().time() self._lessons.sort(reverse=True) # currently active lesson for lesson in self._lessons: if now > lesson.end: return lesson return None