Skip to content
Snippets Groups Projects
Select Git revision
  • b37d50f30d4f8bf7a07af2bec760fb85e773d77d
  • main default protected
  • improvement_risk_prediction_of_heart_disease
  • improvement_increase_customer_satisfaction
  • improvement_predicting_mental_illness_for_health_insurance
  • improvement_predicting_Hotelcancellation
  • improvement_free_ranging2
  • improvement_Forecast_Of_Required_Vehicles_In_the_City
  • improvement_Predicting_mental_illness
  • bugfix_SuccessPrediction_Startups
  • improvement_make_einheitlich
  • bugfix_Maintenance_IOTsystems
  • improvement_Tags_nach_oben_Hannes
  • improvement_onlineretail2
  • improvement_free_ranging_cattle
  • improvement_onlineretail
  • improvement_sentiment_fluggeselschaft
  • improvement_Forecast_Of_Required_Vehicles_City_Center
  • improvement_mark_spot
  • improvement_Predicting_mental_illness_for_health_insurance
  • improvement_Analysis_of_the_movement_and_activity_of_free-ranging_cattle
21 results

notebook.ipynb

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    fetch.dart 4.91 KiB
    import 'dart:convert';
    
    import 'package:flutter/foundation.dart';
    import 'package:http/http.dart' as http;
    
    import '../fetching/pageconfig.dart';
    import '../fetching/timetable.dart';
    import '../util/date_extension.dart';
    import '../util/serializable.dart';
    import 'class.dart';
    import 'element_type.dart';
    import 'lecture.dart';
    
    typedef Cookie = (String key, String value);
    
    typedef RequestOptions = (String path, String method, Json params);
    typedef RequestResult = (Json json, List<Cookie> cookies, FetchStats stats);
    
    class UntisFetch {
      static const String apiBase = '/WebUntis/api/public';
      static const String idHsReutlingen = '_aHMgcmV1dGxpbmdlbg==';
      static const _sessionCookieNames = <String>{
        'JSESSIONID',
        'traceId',
      };
    
      final http.Client client = http.Client();
      final Uri serverHost = Uri.https('poly.webuntis.com');
    
      final List<Cookie> _sessionCookies = [];
    
      late PageConfig<Class> allClasses = PageConfig(
        this,
        elementType: ElementType.classGroup,
        convert: Class.new,
      );
    
      late PageConfig<Lecture> allLectures = PageConfig(
        this,
        elementType: ElementType.lecture,
        convert: Lecture.new,
      );
    
      final Map<TimetableOptions, CacheableTimetable> _timetables = {};
    
      CacheableTimetable timetableOf({
        required ElementType type,
        required int elementId,
        required DateTime date,
      }) {
        final startOfWeek = date.atStartOfWeek();
        final TimetableOptions options = (type, elementId, startOfWeek);
        return _timetables.putIfAbsent(
            options,
            () => CacheableTimetable(this,
                date: startOfWeek, type: type, elementId: elementId));
      }
    
      Uri _makeUri(RequestOptions options) {
        final (String path, _, Json params) = options;
    
        return serverHost.replace(
          path: '$apiBase/$path',
          queryParameters: params,
        );
      }
    
      Future<http.StreamedResponse> _request(String method, Uri uri) async {
        final request = http.Request(method, uri);
    
        final cookies = <Cookie>[];
        cookies.add(('schoolname', idHsReutlingen));
        cookies.addAll(_sessionCookies);
    
        request.headers['Set-Cookie'] =
            cookies.map((cookie) => '${cookie.$1}=${cookie.$2}').join('; ');
    
        final response = await client.send(request);
        return response;
      }
    
      Future<RequestResult> _requestJson(RequestOptions options) async {
        final (_, String method, _) = options;
    
        final onStart = DateTime.now();
        final response = await _request(method, _makeUri(options));
    
        final cookieStrings =
            response.headersSplitValues['Set-Cookie'] ?? <String>[];
        final cookies = cookieStrings
            .map((cookieString) => cookieString.split('='))
            .map<Cookie>((parts) => (parts[0], parts[1]));
    
        final resultCookies = <Cookie>[];
    
        for (final cookie in cookies) {
          if (_sessionCookieNames.contains(cookie.$1)) {
            resultCookies.add(cookie);
          }
        }
    
        final onDownloadStart = DateTime.now();
    
        final responseString = await utf8.decodeStream(response.stream);
        final onDownloadEnd = DateTime.now();
    
        if (response.statusCode != 200) {
          throw FetchError(response.statusCode, responseString);
        }
    
        try {
          final bodyJson = jsonDecode(responseString);
          final onDecodeEnd = DateTime.now();
    
          final stats =
              FetchStats(onStart, onDownloadStart, onDownloadEnd, onDecodeEnd);
    
          return (bodyJson as Json, resultCookies, stats);
        } on FormatException catch (_) {
          throw FetchError(response.statusCode, responseString);
        }
      }
    
      Future<Json> requestOptions(RequestOptions options) async {
        final (_, String method, _) = options;
        final uri = _makeUri(options);
    
        final methodPadded = method.padRight(8);
        print('$methodPadded $uri');
    
        final (result, cookies, stats) = await compute(_requestJson, options);
        _sessionCookies.addAll(cookies);
    
        print('$stats');
        return result;
      }
    
      Future<Json> request(
        String path,
        Json params, {
        String method = 'GET',
      }) async {
        final RequestOptions options = (path, method, params);
        return await requestOptions(options);
      }
    }
    
    class FetchStats {
      final DateTime onStart;
      final DateTime onDownloadStart;
      final DateTime onDownloadEnd;
      final DateTime onDecodeEnd;
    
      FetchStats(
        this.onStart,
        this.onDownloadStart,
        this.onDownloadEnd,
        this.onDecodeEnd,
      );
    
      @override
      String toString() {
        final responseTime = onDownloadStart.difference(onStart);
        final downloadTime = onDownloadEnd.difference(onDownloadStart);
        final decodeTime = onDecodeEnd.difference(onDownloadEnd);
    
        final timeStats = {
          'Ping': responseTime,
          'Download': downloadTime,
          'JSON': decodeTime,
        };
    
        return timeStats.entries
            .map((e) => '${e.key}: ${e.value.inMilliseconds}ms')
            .join(', ');
      }
    }
    
    class FetchError extends Error {
      final int statusCode;
      final String body;
    
      FetchError(this.statusCode, this.body);
    
      @override
      String toString() => 'Server responded with status code $statusCode\n$body';
    }