stateStream function
Stream of states for given uuid
request. Can be useful for example for
re-checking state after network loss. Otherwise just use makeRequest
Possible values:
-
RequestState.queued, DateTime? eta
-
RequestState.running, DateTime? eta
-
RequestState.finished, int result
-
RequestState.expired and RequestState.failed, Exception e - can be:
-
ApiException - generic, something just went wrong, try again
-
ApiUnavailableException - api unavailable, probably maintenance
-
This is used by makeRequest, and is preferred implementation of Request.stateStream
Implementation
Stream<MapEntry<RequestState, dynamic?>> stateStream(String uuid) async* {
yield MapEntry(RequestState.queued, null);
final infoUrl = Uri.parse('${API_BASE_URL}info/$uuid/');
DateTime etaDateTime(num epoch) =>
DateTime.fromMillisecondsSinceEpoch((epoch * 1000).toInt());
var errorCount = 0;
const maxTries = 6;
var json = <String, dynamic>{};
while (true) {
final epoch =
((json['eta'] as num?) ?? DateTime.now().millisecondsSinceEpoch / 1000)
.toInt();
final num delay =
etaDateTime(epoch).difference(DateTime.now()).inSeconds.clamp(0, 10);
await Future.delayed(Duration(seconds: delay.toInt()));
final infoRes = await http.get(infoUrl, headers: HEADERS);
if (infoRes.statusCode != 200) {
if (errorCount < maxTries) {
errorCount++;
continue;
} else {
yield MapEntry(
RequestState.failed,
infoRes.statusCode == 502
? ApiUnavailableException(infoRes.body)
: ApiException(
'$infoUrl : ${infoRes.statusCode} : ${infoRes.body}'),
);
return;
}
}
// At this point we know it was 200, so we can safely parse the body:
json = jsonDecode(infoRes.body);
// If any of the fields are null then there's something definitely wrong
// with the API - thus, crush the whole thing
// "Albo zadziaĆa, albo *totalnie* sie zesra"
final state = requestStateFromName(json['status']!);
if (requestWaitingStates.contains(state)) {
// Normal flow - waiting for result
// Update the eta - it may change during the waiting
yield MapEntry(state, etaDateTime(json['eta']! as num));
} else if (state == RequestState.finished) {
yield MapEntry(RequestState.finished, json['result']! as int);
return;
} else if (requestErrorStates.contains(state)) {
// If it's already expired then something is not right :/
yield MapEntry(
state,
ApiException('$infoUrl : ${infoRes.statusCode} : ${infoRes.body}'),
);
return;
} else {
yield MapEntry(
RequestState.failed,
UnimplementedError(
'Unimplemented request state. This should never happen. '
'Tell @TheLastGimbus that he broke something',
),
);
return;
}
}
}