Preserve courses with pending rooms and report precise graduate import errors

This commit is contained in:
david1465833828
2026-10-28 19:56:02 +08:00
parent 8713bce0ee
commit 21b0df9e33
17 changed files with 340 additions and 53 deletions
+33 -7
View File
@@ -174,6 +174,8 @@ class SchoolSession extends ChangeNotifier {
bool _routeClosed = false;
int _pollsLeft = 0;
int? _probeGeneration;
UnsupportedSchoolLayout? _lastLayoutFailure;
int? _layoutFailureGeneration;
SchoolConnectionState connectionState = SchoolConnectionState.signedOut;
bool _loading = false,
hasOpened = false,
@@ -334,9 +336,16 @@ class SchoolSession extends ChangeNotifier {
String get _findingMessage => requestingExams ? '正在查找考试安排,请稍候' : '正在查找课表,请稍候';
void _armTimeout() {
_timeout?.cancel();
_timeout = Timer(
const Duration(seconds: 45),
() => _failure('查找超时,请检查学校页面后重试。已有数据保持不变。', code: 'read_timeout'),
_timeout = Timer(const Duration(seconds: 45), _finishReadFailure);
}
void _finishReadFailure() {
final error = _layoutFailureGeneration == _pageGeneration
? _lastLayoutFailure
: null;
_failure(
error == null ? '查找超时,请检查学校页面后重试。已有数据保持不变。' : '课表读取未完成:$error',
code: error?.code ?? 'read_timeout',
);
}
@@ -396,7 +405,7 @@ class SchoolSession extends ChangeNotifier {
}
_poll?.cancel();
if (_pollsLeft-- <= 0) {
if (finding) _failure('查找超时,请检查学校页面后重试。已有数据保持不变。', code: 'read_timeout');
if (finding) _finishReadFailure();
return;
}
_poll = Timer(const Duration(milliseconds: 750), () {
@@ -500,6 +509,8 @@ class SchoolSession extends ChangeNotifier {
// gets only its recognized fragment with the canonical table path.
final draft = await _reader.read(_target, () async => page.html);
if (!hasOpened || generation != _pageGeneration) return;
_lastLayoutFailure = null;
_layoutFailureGeneration = null;
if (draft.schoolTerm != selectedTerm) {
DiagnosticLog.instance.add(
'term_mismatch',
@@ -528,11 +539,24 @@ class SchoolSession extends ChangeNotifier {
notifyListeners();
return;
} catch (error) {
if (generation != _pageGeneration) return;
if (error is UnsupportedSchoolLayout) {
_lastLayoutFailure = error;
_layoutFailureGeneration = generation;
message =
'课表暂未识别:${error.description}。正在等待页面加载完整;若仍失败,请在反馈中附带学校HTML和日志。';
}
DiagnosticLog.instance.add(
'school_parse_rejected',
fields: {
'school': 'graduate',
'code': error.runtimeType.toString(),
'code': error is UnsupportedSchoolLayout
? error.code
: error.runtimeType.toString(),
if (error is UnsupportedSchoolLayout && error.row != null)
'row': error.row,
if (error is UnsupportedSchoolLayout && error.column != null)
'column': error.column,
},
);
if (generation != _pageGeneration) return;
@@ -595,6 +619,7 @@ class SchoolSession extends ChangeNotifier {
liveUrl != uri.toString()) {
throw UnsupportedSchoolLayout();
}
UnsupportedSchoolLayout? rejected;
for (final page in evidence.pages) {
if (page.kind != 'timetable') continue;
SchoolPageDiagnostic.instance.remember(page.html, page.url);
@@ -608,11 +633,12 @@ class SchoolSession extends ChangeNotifier {
}
if (draft.schoolTerm != selectedTerm) continue;
return draft;
} on UnsupportedSchoolLayout {
} on UnsupportedSchoolLayout catch (error) {
if (generation != _pageGeneration) rethrow;
rejected = error;
}
}
throw UnsupportedSchoolLayout();
throw rejected ?? const UnsupportedSchoolLayout(code: 'table_missing');
}
Future<void> readCurrentPage() async {