mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-12 08:50:30 +00:00
fix: image transfer 10s idle timeout with partial resume
- Replace 1.2s fixed retry with 10s idle debounce timer - Timer resets on every received fragment (debounce) - Timer also starts on IE1 envelope receipt (handles pull-only senders) - IR1 always requests only missing indices (partial resume) - Attempt counter no longer resets per fragment; bounded to 5 retries - Max attempts raised to 5 (was 4)
This commit is contained in:
@@ -48,7 +48,9 @@ class AppProvider with ChangeNotifier {
|
|||||||
bool get isVoiceLimiterEnabled => _isVoiceLimiterEnabled;
|
bool get isVoiceLimiterEnabled => _isVoiceLimiterEnabled;
|
||||||
|
|
||||||
static const Duration _packetRetryDelay = Duration(milliseconds: 1200);
|
static const Duration _packetRetryDelay = Duration(milliseconds: 1200);
|
||||||
|
static const Duration _imageFragmentTimeout = Duration(seconds: 10);
|
||||||
static const int _maxPacketRetryAttempts = 4;
|
static const int _maxPacketRetryAttempts = 4;
|
||||||
|
static const int _maxImageRetryAttempts = 5;
|
||||||
final Map<String, String> _voiceSessionSenderKey6 = {};
|
final Map<String, String> _voiceSessionSenderKey6 = {};
|
||||||
final Map<String, String> _imageSessionSenderKey6 = {};
|
final Map<String, String> _imageSessionSenderKey6 = {};
|
||||||
final Map<String, Timer> _voiceMissingRetryTimers = {};
|
final Map<String, Timer> _voiceMissingRetryTimers = {};
|
||||||
@@ -597,6 +599,13 @@ class AppProvider with ChangeNotifier {
|
|||||||
.senderKey6
|
.senderKey6
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
imageProvider.registerEnvelope(imageEnvelope);
|
imageProvider.registerEnvelope(imageEnvelope);
|
||||||
|
// Start 10s idle timer immediately so we request fragments even if
|
||||||
|
// none arrive at all (sender may need an explicit IR1 pull request).
|
||||||
|
_scheduleImageMissingRetry(
|
||||||
|
imageEnvelope.sessionId,
|
||||||
|
justComplete: false,
|
||||||
|
newSession: true,
|
||||||
|
);
|
||||||
messagesProvider.addMessage(
|
messagesProvider.addMessage(
|
||||||
enrichedMessage,
|
enrichedMessage,
|
||||||
contactLookup: (name) {
|
contactLookup: (name) {
|
||||||
@@ -1094,9 +1103,16 @@ class AppProvider with ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called on every received fragment (and when IE1 envelope arrives).
|
||||||
|
/// Debounces a 10s idle timeout — if no fragment arrives within the window,
|
||||||
|
/// sends IR1 requesting only the still-missing indices (partial resume).
|
||||||
|
/// The attempt counter is NOT reset per-fragment; it only resets when a new
|
||||||
|
/// session starts (IE1 received), so retries are bounded even when fragments
|
||||||
|
/// trickle in slowly.
|
||||||
void _scheduleImageMissingRetry(
|
void _scheduleImageMissingRetry(
|
||||||
String sessionId, {
|
String sessionId, {
|
||||||
required bool justComplete,
|
required bool justComplete,
|
||||||
|
bool newSession = false,
|
||||||
}) {
|
}) {
|
||||||
if (justComplete || imageProvider.isComplete(sessionId)) {
|
if (justComplete || imageProvider.isComplete(sessionId)) {
|
||||||
_imageMissingRetryTimers.remove(sessionId)?.cancel();
|
_imageMissingRetryTimers.remove(sessionId)?.cancel();
|
||||||
@@ -1104,9 +1120,12 @@ class AppProvider with ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageMissingRetryAttempts[sessionId] = 0;
|
// Reset attempt counter only when a brand-new session starts (IE1 arrived).
|
||||||
|
if (newSession) _imageMissingRetryAttempts[sessionId] = 0;
|
||||||
|
|
||||||
|
// Debounce: restart the 10s idle timer on every received fragment.
|
||||||
_imageMissingRetryTimers[sessionId]?.cancel();
|
_imageMissingRetryTimers[sessionId]?.cancel();
|
||||||
_imageMissingRetryTimers[sessionId] = Timer(_packetRetryDelay, () {
|
_imageMissingRetryTimers[sessionId] = Timer(_imageFragmentTimeout, () {
|
||||||
unawaited(_requestMissingImageFragments(sessionId));
|
unawaited(_requestMissingImageFragments(sessionId));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1119,9 +1138,9 @@ class AppProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final attempt = _imageMissingRetryAttempts[sessionId] ?? 0;
|
final attempt = _imageMissingRetryAttempts[sessionId] ?? 0;
|
||||||
if (attempt >= _maxPacketRetryAttempts) {
|
if (attempt >= _maxImageRetryAttempts) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'⚠️ [AppProvider] Image re-request limit reached for $sessionId',
|
'⚠️ [AppProvider] Image re-request limit ($attempt) reached for $sessionId',
|
||||||
);
|
);
|
||||||
_imageMissingRetryTimers.remove(sessionId)?.cancel();
|
_imageMissingRetryTimers.remove(sessionId)?.cancel();
|
||||||
return;
|
return;
|
||||||
@@ -1145,6 +1164,11 @@ class AppProvider with ChangeNotifier {
|
|||||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
|
debugPrint(
|
||||||
|
'📷 [AppProvider] Requesting ${missing.length} missing fragments '
|
||||||
|
'for $sessionId (attempt ${attempt + 1}/$_maxImageRetryAttempts): $missing',
|
||||||
|
);
|
||||||
|
|
||||||
final request = ImageFetchRequest(
|
final request = ImageFetchRequest(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
want: 'missing',
|
want: 'missing',
|
||||||
@@ -1161,8 +1185,9 @@ class AppProvider with ChangeNotifier {
|
|||||||
if (!sent) return;
|
if (!sent) return;
|
||||||
|
|
||||||
_imageMissingRetryAttempts[sessionId] = attempt + 1;
|
_imageMissingRetryAttempts[sessionId] = attempt + 1;
|
||||||
|
// Wait another full timeout window for the sender to retransmit.
|
||||||
_imageMissingRetryTimers[sessionId]?.cancel();
|
_imageMissingRetryTimers[sessionId]?.cancel();
|
||||||
_imageMissingRetryTimers[sessionId] = Timer(_packetRetryDelay, () {
|
_imageMissingRetryTimers[sessionId] = Timer(_imageFragmentTimeout, () {
|
||||||
unawaited(_requestMissingImageFragments(sessionId));
|
unawaited(_requestMissingImageFragments(sessionId));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user