import 'package:nextcloud/nextcloud.dart'; /// True for errors worth a short automatic retry: HTTP 5xx thrown by the /// nextcloud/dynamite clients. Our instance intermittently answers the slow /// root PROPFIND with a quick empty 500 via nginx that succeeds on retry. /// Timeouts are deliberately not transient — the root listing already has a /// multi-minute ceiling, so repeating it would multiply the wait. bool isTransientServerError(Object error) => error is DynamiteApiException && error.statusCode >= 500 && error.statusCode <= 599; Duration _defaultRetryDelay(int retry) => retry == 1 ? const Duration(seconds: 1) : const Duration(seconds: 3); /// Runs [attempt] up to [maxAttempts] times, waiting [delayFor] between /// tries. Errors that don't match [shouldRetry] and the final failure are /// rethrown unchanged. [onRetry] fires before each wait with the 1-based /// number of the upcoming attempt, e.g. `(2, 3)` — used to surface retry /// progress in the UI. [delayFor] receives the 1-based index of the retry /// being scheduled and is injectable so tests run instantly. Future retryOnTransientError( Future Function() attempt, { int maxAttempts = 3, Duration Function(int retry) delayFor = _defaultRetryDelay, bool Function(Object error) shouldRetry = isTransientServerError, void Function(int nextAttempt, int maxAttempts)? onRetry, }) async { for (var attemptNo = 1; ; attemptNo++) { try { return await attempt(); } catch (e) { if (attemptNo >= maxAttempts || !shouldRetry(e)) rethrow; onRetry?.call(attemptNo + 1, maxAttempts); await Future.delayed(delayFor(attemptNo)); } } }