refactored timetable

This commit is contained in:
2026-05-05 13:49:45 +02:00
parent 551c1bf1fa
commit e8faa77e70
29 changed files with 1574 additions and 300 deletions
@@ -8,7 +8,17 @@ import 'getCustomTimetableEventResponse.dart';
class GetCustomTimetableEventCache extends RequestCache<GetCustomTimetableEventResponse> {
GetCustomTimetableEventParams params;
GetCustomTimetableEventCache(this.params, {onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) {
GetCustomTimetableEventCache(
this.params, {
void Function(GetCustomTimetableEventResponse)? onUpdate,
void Function(Exception)? onError,
bool? renew,
}) : super(
RequestCache.cacheMinute,
onUpdate,
onError: onError ?? RequestCache.ignore,
renew: renew,
) {
start('customTimetableEvents');
}
+30 -17
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:localstore/localstore.dart';
@@ -17,29 +18,41 @@ abstract class RequestCache<T extends ApiResponse?> {
void Function(Exception) onError;
bool? renew;
final Completer<void> _ready = Completer<void>();
/// Resolves when [start] has finished, regardless of whether the network
/// call succeeded, failed, or was skipped due to a fresh cache. Callers
/// can await this to know when both the cache lookup and the network
/// attempt have settled.
Future<void> get ready => _ready.future;
RequestCache(this.maxCacheTime, this.onUpdate, {this.onError = ignore, this.renew = false});
static void ignore(Exception e) {}
Future<void> start(String document) async {
final tableData = await Localstore.instance.collection(collection).doc(document).get();
if (tableData != null) {
onUpdate?.call(onLocalData(tableData['json']));
}
if (DateTime.now().millisecondsSinceEpoch - (maxCacheTime * 1000) < (tableData?['lastupdate'] ?? 0)) {
if (renew == null || !renew!) return;
}
try {
final newValue = await onLoad();
onUpdate?.call(newValue);
Localstore.instance.collection(collection).doc(document).set({
'json': jsonEncode(newValue),
'lastupdate': DateTime.now().millisecondsSinceEpoch,
});
} on Exception catch (e) {
onError(e);
final tableData = await Localstore.instance.collection(collection).doc(document).get();
if (tableData != null) {
onUpdate?.call(onLocalData(tableData['json']));
}
if (DateTime.now().millisecondsSinceEpoch - (maxCacheTime * 1000) < (tableData?['lastupdate'] ?? 0)) {
if (renew == null || !renew!) return;
}
try {
final newValue = await onLoad();
onUpdate?.call(newValue);
Localstore.instance.collection(collection).doc(document).set({
'json': jsonEncode(newValue),
'lastupdate': DateTime.now().millisecondsSinceEpoch,
});
} on Exception catch (e) {
onError(e);
}
} finally {
if (!_ready.isCompleted) _ready.complete();
}
}
@@ -14,11 +14,23 @@ class Authenticate extends WebuntisApi {
@override
Future<AuthenticateResponse> run() async {
awaitingResponse = true;
var rawAnswer = await query(this);
AuthenticateResponse response = finalize(AuthenticateResponse.fromJson(jsonDecode(rawAnswer)['result']));
_lastResponse = response;
if(!awaitedResponse.isCompleted) awaitedResponse.complete();
return response;
try {
var rawAnswer = await query(this);
AuthenticateResponse response = finalize(AuthenticateResponse.fromJson(jsonDecode(rawAnswer)['result']));
_lastResponse = response;
if(!awaitedResponse.isCompleted) awaitedResponse.complete();
return response;
} catch (e) {
// Surface the error to anyone waiting on the current completer, then
// install a fresh one so a future attempt can succeed. Without this,
// any later call to getSession() would hang forever on a completer
// that is already settled with no listeners (or never settles at all).
if(!awaitedResponse.isCompleted) awaitedResponse.completeError(e);
awaitedResponse = Completer();
rethrow;
} finally {
awaitingResponse = false;
}
}
static bool awaitingResponse = false;
@@ -5,7 +5,16 @@ import 'getHolidays.dart';
import 'getHolidaysResponse.dart';
class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
GetHolidaysCache({void Function(GetHolidaysResponse)? onUpdate}) : super(RequestCache.cacheDay, onUpdate) {
GetHolidaysCache({
void Function(GetHolidaysResponse)? onUpdate,
void Function(Exception)? onError,
bool? renew,
}) : super(
RequestCache.cacheDay,
onUpdate,
onError: onError ?? RequestCache.ignore,
renew: renew,
) {
start('wu-holidays');
}
@@ -5,7 +5,16 @@ import 'getRooms.dart';
import 'getRoomsResponse.dart';
class GetRoomsCache extends RequestCache<GetRoomsResponse> {
GetRoomsCache({void Function(GetRoomsResponse)? onUpdate}) : super(RequestCache.cacheHour, onUpdate) {
GetRoomsCache({
void Function(GetRoomsResponse)? onUpdate,
void Function(Exception)? onError,
bool? renew,
}) : super(
RequestCache.cacheHour,
onUpdate,
onError: onError ?? RequestCache.ignore,
renew: renew,
) {
start('wu-rooms');
}
@@ -5,7 +5,16 @@ import 'getSubjects.dart';
import 'getSubjectsResponse.dart';
class GetSubjectsCache extends RequestCache<GetSubjectsResponse> {
GetSubjectsCache({void Function(GetSubjectsResponse)? onUpdate}) : super(RequestCache.cacheHour, onUpdate) {
GetSubjectsCache({
void Function(GetSubjectsResponse)? onUpdate,
void Function(Exception)? onError,
bool? renew,
}) : super(
RequestCache.cacheHour,
onUpdate,
onError: onError ?? RequestCache.ignore,
renew: renew,
) {
start('wu-subjects');
}
@@ -0,0 +1,22 @@
import 'dart:convert';
import 'dart:developer';
import '../../webuntisApi.dart';
import 'getTimegridUnitsResponse.dart';
class GetTimegridUnits extends WebuntisApi {
GetTimegridUnits() : super('getTimegridUnits', null);
@override
Future<GetTimegridUnitsResponse> run() async {
var rawAnswer = await query(this);
try {
return finalize(GetTimegridUnitsResponse.fromJson(jsonDecode(rawAnswer)));
} catch (e, trace) {
log(trace.toString());
log('Failed to parse getTimegridUnits data with server response: $rawAnswer');
}
throw Exception('Failed to parse getTimegridUnits server response: $rawAnswer');
}
}
@@ -0,0 +1,20 @@
import 'dart:convert';
import '../../../requestCache.dart';
import 'getTimegridUnits.dart';
import 'getTimegridUnitsResponse.dart';
class GetTimegridUnitsCache extends RequestCache<GetTimegridUnitsResponse> {
GetTimegridUnitsCache({
void Function(GetTimegridUnitsResponse)? onUpdate,
bool? renew,
}) : super(RequestCache.cacheDay, onUpdate, renew: renew) {
start('wu-timegrid');
}
@override
Future<GetTimegridUnitsResponse> onLoad() => GetTimegridUnits().run();
@override
GetTimegridUnitsResponse onLocalData(String json) => GetTimegridUnitsResponse.fromJson(jsonDecode(json));
}
@@ -0,0 +1,38 @@
import 'package:json_annotation/json_annotation.dart';
import '../../../apiResponse.dart';
part 'getTimegridUnitsResponse.g.dart';
@JsonSerializable(explicitToJson: true)
class GetTimegridUnitsResponse extends ApiResponse {
List<GetTimegridUnitsResponseDay> result;
GetTimegridUnitsResponse(this.result);
factory GetTimegridUnitsResponse.fromJson(Map<String, dynamic> json) => _$GetTimegridUnitsResponseFromJson(json);
Map<String, dynamic> toJson() => _$GetTimegridUnitsResponseToJson(this);
}
@JsonSerializable(explicitToJson: true)
class GetTimegridUnitsResponseDay {
int day;
List<GetTimegridUnitsResponseUnit> timeUnits;
GetTimegridUnitsResponseDay(this.day, this.timeUnits);
factory GetTimegridUnitsResponseDay.fromJson(Map<String, dynamic> json) => _$GetTimegridUnitsResponseDayFromJson(json);
Map<String, dynamic> toJson() => _$GetTimegridUnitsResponseDayToJson(this);
}
@JsonSerializable(explicitToJson: true)
class GetTimegridUnitsResponseUnit {
String name;
int startTime;
int endTime;
GetTimegridUnitsResponseUnit(this.name, this.startTime, this.endTime);
factory GetTimegridUnitsResponseUnit.fromJson(Map<String, dynamic> json) => _$GetTimegridUnitsResponseUnitFromJson(json);
Map<String, dynamic> toJson() => _$GetTimegridUnitsResponseUnitToJson(this);
}
@@ -0,0 +1,64 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getTimegridUnitsResponse.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GetTimegridUnitsResponse _$GetTimegridUnitsResponseFromJson(
Map<String, dynamic> json,
) =>
GetTimegridUnitsResponse(
(json['result'] as List<dynamic>)
.map(
(e) => GetTimegridUnitsResponseDay.fromJson(
e as Map<String, dynamic>,
),
)
.toList(),
)
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
);
Map<String, dynamic> _$GetTimegridUnitsResponseToJson(
GetTimegridUnitsResponse instance,
) => <String, dynamic>{
'headers': ?instance.headers,
'result': instance.result.map((e) => e.toJson()).toList(),
};
GetTimegridUnitsResponseDay _$GetTimegridUnitsResponseDayFromJson(
Map<String, dynamic> json,
) => GetTimegridUnitsResponseDay(
(json['day'] as num).toInt(),
(json['timeUnits'] as List<dynamic>)
.map(
(e) => GetTimegridUnitsResponseUnit.fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$GetTimegridUnitsResponseDayToJson(
GetTimegridUnitsResponseDay instance,
) => <String, dynamic>{
'day': instance.day,
'timeUnits': instance.timeUnits.map((e) => e.toJson()).toList(),
};
GetTimegridUnitsResponseUnit _$GetTimegridUnitsResponseUnitFromJson(
Map<String, dynamic> json,
) => GetTimegridUnitsResponseUnit(
json['name'] as String,
(json['startTime'] as num).toInt(),
(json['endTime'] as num).toInt(),
);
Map<String, dynamic> _$GetTimegridUnitsResponseUnitToJson(
GetTimegridUnitsResponseUnit instance,
) => <String, dynamic>{
'name': instance.name,
'startTime': instance.startTime,
'endTime': instance.endTime,
};
@@ -15,7 +15,13 @@ class GetTimetableCache extends RequestCache<GetTimetableResponse> {
void Function(Exception)? onError,
required this.startdate,
required this.enddate,
}) : super(RequestCache.cacheMinute, onUpdate, onError: onError ?? RequestCache.ignore) {
bool? renew,
}) : super(
RequestCache.cacheMinute,
onUpdate,
onError: onError ?? RequestCache.ignore,
renew: renew,
) {
start('wu-timetable-$startdate-$enddate');
}