stateStream function

Stream<MapEntry<RequestState, dynamic>> stateStream(
  1. String uuid
)

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;
    }
  }
}