Esempio n. 1
0
def generate(name: str, cards: Dict[Tuple[int, int], List[Dict]],
             semester: Semester, filename: str) -> None:
    """
    生成 ics 文件并保存到目录

    :param name: 姓名
    :param cards: 参与的课程
    :param semester: 当前导出的学期
    :param filename: 输出的文件名称,带后缀
    :return: None
    """
    from everyclass.server import statsd

    with tracer.trace("calendar_init"):
        semester_string = semester.to_str(simplify=True)
        semester = semester.to_tuple()

        # 创建 calender 对象
        cal = Calendar()
        cal.add('prodid', '-//Admirable//EveryClass//EN')
        cal.add('version', '2.0')
        cal.add('calscale', 'GREGORIAN')
        cal.add('method', 'PUBLISH')
        cal.add('X-WR-CALNAME', name + '的' + semester_string + '课表')
        cal.add('X-WR-TIMEZONE', 'Asia/Shanghai')

        # 时区
        tzc.add_component(tzs)
        cal.add_component(tzc)

    with tracer.trace("add_events"):
        # 创建 events
        for time in range(1, 7):
            for day in range(1, 8):
                if (day, time) in cards:
                    for card in cards[(day, time)]:
                        for week in card['week']:
                            dtstart = _get_datetime(week, day,
                                                    get_time(time)[0],
                                                    semester)
                            dtend = _get_datetime(week, day,
                                                  get_time(time)[1], semester)

                            if dtstart.year == 1984:
                                continue

                            cal.add_component(
                                _build_event(card_name=card['name'],
                                             times=(dtstart, dtend),
                                             classroom=card['classroom'],
                                             teacher=card['teacher'],
                                             week_string=card['week_string'],
                                             current_week=week,
                                             cid=card['cid']))

    with tracer.trace("write_file"):
        with open(os.path.join(calendar_dir(), filename), 'wb') as f:
            data = cal.to_ical()
            statsd.histogram('calendar.ics.generate.size', len(data))
            f.write(data)
Esempio n. 2
0
def legacy_get_ics(student_id, semester_str):
    """
    早期 iCalendar 订阅端点,出于兼容性考虑保留,仅支持未设定隐私等级的学生,其他情况使用新的日历订阅令牌获得 ics 文件。
    """
    # fix parameters
    place = student_id.find('-')
    semester_str = student_id[place + 1:len(student_id)] + '-' + semester_str
    student_id = student_id[:place]

    semester = Semester(semester_str)

    search_result = Entity.search(student_id)

    if len(search_result.students) != 1:
        # bad request
        return abort(400)

    if semester.to_str() not in search_result.students[0].semesters:
        return abort(400)

    with tracer.trace('get_privacy_settings'):
        privacy_settings = PrivacySettings.get_level(
            search_result.students[0].student_id)

    if privacy_settings != 0:
        # force user to get a calendar token when the user is privacy-protected but accessed through legacy interface
        return "Visit {} to get your calendar".format(
            url_for("main.main", _external=True)), 401
    else:
        token = CalendarToken.get_or_set_calendar_token(
            resource_type="student",
            identifier=search_result.students[0].student_id,
            semester=semester.to_str())
        return redirect(url_for('calendar.ics_download', calendar_token=token))
Esempio n. 3
0
def generate(name: str, cards: Dict[Tuple[int, int], List[Dict]],
             semester: Semester, ics_token: str):
    """
    生成 ics 文件并保存到目录

    :param name: 姓名
    :param cards: 参与的课程
    :param semester: 当前导出的学期
    :param ics_token: ics 令牌
    :return: None
    """
    semester_string = semester.to_str(simplify=True)
    semester = semester.to_tuple()

    # 创建 calender 对象
    cal = Calendar()
    cal.add('prodid', '-//Admirable//EveryClass//EN')
    cal.add('version', '2.0')
    cal.add('calscale', 'GREGORIAN')
    cal.add('method', 'PUBLISH')
    cal.add('X-WR-CALNAME', name + '的' + semester_string + '课表')
    cal.add('X-WR-TIMEZONE', 'Asia/Shanghai')

    # 时区
    tzc.add_component(tzs)
    cal.add_component(tzc)

    # 创建 events
    for time in range(1, 7):
        for day in range(1, 8):
            if (day, time) in cards:
                for card in cards[(day, time)]:
                    for week in card['week']:
                        dtstart = _get_datetime(week, day,
                                                get_time(time)[0], semester)
                        dtend = _get_datetime(week, day,
                                              get_time(time)[1], semester)

                        if dtstart.year == 1984:
                            continue

                        cal.add_component(
                            _build_event(card_name=card['name'],
                                         times=(dtstart, dtend),
                                         classroom=card['classroom'],
                                         teacher=card['teacher'],
                                         week_string=card['week_string'],
                                         current_week=week,
                                         cid=card['cid']))

    # 写入文件
    import os

    with open(
            os.path.join(os.path.dirname(__file__),
                         '../../../calendar_files/{}.ics'.format(ics_token)),
            'w') as f:
        f.write(cal.to_ical().decode(encoding='utf-8'))
Esempio n. 4
0
def ics_download(calendar_token: str):
    """
    iCalendar ics file download

    因为课表会更新,所以 ics 文件只能在这里动态生成,不能在日历订阅页面就生成
    """
    from collections import defaultdict

    from flask import send_from_directory
    from everyclass.server.db.dao import CalendarToken
    from everyclass.server.models import Semester
    from everyclass.server.calendar import ics_generator
    from everyclass.server.rpc.api_server import APIServer, teacher_list_to_name_str
    from everyclass.server.utils import lesson_string_to_tuple

    result = CalendarToken.find_calendar_token(token=calendar_token)
    if not result:
        return 'invalid calendar token', 404

    CalendarToken.update_last_used_time(calendar_token)

    # 获得原始学号或教工号
    if result['type'] == 'student':
        rpc_result = APIServer.get_student_timetable(result['identifier'],
                                                     result['semester'])
    else:
        # teacher
        rpc_result = APIServer.get_teacher_timetable(result['identifier'],
                                                     result['semester'])

    with elasticapm.capture_span('process_rpc_result'):
        semester = Semester(result['semester'])

        cards: Dict[Tuple[int, int], List[Dict]] = defaultdict(list)
        for card in rpc_result.cards:
            day, time = lesson_string_to_tuple(card.lesson)
            cards[(day, time)].append(
                dict(name=card.name,
                     teacher=teacher_list_to_name_str(card.teachers),
                     week=card.weeks,
                     week_string=card.week_string,
                     classroom=card.room,
                     cid=card.card_id_encoded))

    ics_generator.generate(name=rpc_result.name,
                           cards=cards,
                           semester=semester,
                           ics_token=calendar_token)

    return send_from_directory("../../calendar_files",
                               calendar_token + ".ics",
                               as_attachment=True,
                               mimetype='text/calendar')
Esempio n. 5
0
def ics_download(calendar_token: str):
    """
    iCalendar ics 文件下载

    2019-8-25 改为预先缓存文件而非每次动态生成,降低 CPU 压力。如果一小时内两次访问则强刷缓存。
    """
    import time
    from everyclass.server import statsd

    if not is_valid_uuid(calendar_token):
        return 'invalid calendar token', 404

    result = CalendarToken.find_calendar_token(token=calendar_token)
    if not result:
        return 'invalid calendar token', 404
    CalendarToken.update_last_used_time(calendar_token)

    cal_dir = calendar_dir()
    cal_filename = f"{result['type']}_{result['identifier']}_{result['semester']}.ics"
    cal_full_path = os.path.join(cal_dir, cal_filename)
    # 有缓存、且缓存时间小于一天,且不用强刷缓存
    if os.path.exists(cal_full_path) \
            and time.time() - os.path.getmtime(cal_full_path) < SECONDS_IN_ONE_DAY \
            and Redis.calendar_token_use_cache(calendar_token):
        logger.info("ics cache hit")
        statsd.increment("calendar.ics.cache.hit")
        return send_from_directory(cal_dir,
                                   cal_filename,
                                   as_attachment=True,
                                   mimetype='text/calendar')
    statsd.increment("calendar.ics.cache.miss")

    # 无缓存、或需要强刷缓存
    with tracer.trace('rpc'):
        # 获得原始学号或教工号
        if result['type'] == 'student':
            rpc_result = Entity.get_student_timetable(result['identifier'],
                                                      result['semester'])
        else:
            # teacher
            rpc_result = Entity.get_teacher_timetable(result['identifier'],
                                                      result['semester'])

        semester = Semester(result['semester'])

        cards: Dict[Tuple[int, int], List[Dict]] = defaultdict(list)
        for card in rpc_result.cards:
            cards[lesson_string_to_tuple(card.lesson)].append(
                dict(name=card.name,
                     teacher=teacher_list_to_name_str(card.teachers),
                     week=card.weeks,
                     week_string=card.week_string,
                     classroom=card.room,
                     cid=card.card_id_encoded))

    ics_generator.generate(name=rpc_result.name,
                           cards=cards,
                           semester=semester,
                           filename=cal_filename)

    return send_from_directory(cal_dir,
                               cal_filename,
                               as_attachment=True,
                               mimetype='text/calendar')
Esempio n. 6
0
 def test_semester_code(self):
     from everyclass.server.models import Semester
     self.assertTrue(Semester((2016, 2017, 2)).to_db_code() == "16_17_2")
Esempio n. 7
0
 def test_tuple_semester(self):
     from everyclass.server.models import Semester
     self.assertTrue(Semester('2016-2017-2').to_tuple() == (2016, 2017, 2))
Esempio n. 8
0
 def test_string_semester(self):
     from everyclass.server.models import Semester
     self.assertTrue(Semester((2016, 2017, 2)).to_str(simplify=False) == '2016-2017-2')
     self.assertTrue(Semester((2016, 2017, 2)).to_str(simplify=True) == '16-17-2')