mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add clearChannelMessages to Messages
This commit is contained in:
@@ -818,7 +818,8 @@ class AppProvider with ChangeNotifier {
|
|||||||
'🔔 [AppProvider] onChannelInfoReceived called: idx=$channelIdx, name="$channelName"',
|
'🔔 [AppProvider] onChannelInfoReceived called: idx=$channelIdx, name="$channelName"',
|
||||||
);
|
);
|
||||||
|
|
||||||
final isDeletedChannel = AppProvider.isDeletedChannelInfo(
|
final isDeletedChannel = connectionProvider
|
||||||
|
.shouldTreatChannelInfoAsDeleted(
|
||||||
channelIdx,
|
channelIdx,
|
||||||
channelName,
|
channelName,
|
||||||
secret,
|
secret,
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
bool get isScanning => _isScanning;
|
bool get isScanning => _isScanning;
|
||||||
bool _isSpectrumScanActive = false;
|
bool _isSpectrumScanActive = false;
|
||||||
bool get isSpectrumScanActive => _isSpectrumScanActive;
|
bool get isSpectrumScanActive => _isSpectrumScanActive;
|
||||||
|
final Set<int> _pendingDeletedChannelIndices = <int>{};
|
||||||
|
|
||||||
String? _error;
|
String? _error;
|
||||||
String? get error => _error;
|
String? get error => _error;
|
||||||
@@ -903,6 +904,22 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
return message.contains('0x1f') && message.contains('timed out');
|
return message.contains('0x1f') && message.contains('timed out');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shouldTreatChannelInfoAsDeleted(
|
||||||
|
int channelIdx,
|
||||||
|
String channelName,
|
||||||
|
Uint8List secret,
|
||||||
|
) {
|
||||||
|
final isEmptyInfo =
|
||||||
|
channelIdx != 0 &&
|
||||||
|
channelName.isEmpty &&
|
||||||
|
secret.every((byte) => byte == 0);
|
||||||
|
if (!isEmptyInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _pendingDeletedChannelIndices.remove(channelIdx);
|
||||||
|
}
|
||||||
|
|
||||||
Future<int?> findNextEmptyChannelSlot() async {
|
Future<int?> findNextEmptyChannelSlot() async {
|
||||||
if (!_activeService.isConnected) {
|
if (!_activeService.isConnected) {
|
||||||
throw Exception('Not connected to device');
|
throw Exception('Not connected to device');
|
||||||
@@ -915,58 +932,35 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
final maxChannels = _deviceInfo.maxChannels ?? 40;
|
final maxChannels = _deviceInfo.maxChannels ?? 40;
|
||||||
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
|
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
|
||||||
|
|
||||||
// Match meshcore-open: trust the current synced channel list first and
|
// Match meshcore-open: choose the first missing slot from the synced
|
||||||
// pick the first missing slot rather than probing every slot on-device.
|
// channel set and avoid speculative per-slot probing that can overwrite
|
||||||
if (getChannelInfo != null) {
|
// an existing channel when device state is delayed or transient.
|
||||||
final usedIndices = <int>{};
|
if (getChannelInfo == null) {
|
||||||
for (int i = 1; i < maxChannels; i++) {
|
throw Exception('Channel state is unavailable');
|
||||||
final channel = getChannelInfo!(i);
|
|
||||||
if (channel == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_channelSlotIsOccupied(channel)) {
|
|
||||||
usedIndices.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i < maxChannels; i++) {
|
|
||||||
if (!usedIndices.contains(i)) {
|
|
||||||
debugPrint(' ✅ Found empty slot from synced channels: $i');
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint(
|
|
||||||
' ⚠️ Synced channels report all custom slots occupied ($maxCustomChannels total)',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check each slot starting from 1 (skip 0 = public channel)
|
final usedIndices = <int>{};
|
||||||
for (int i = 1; i < maxChannels; i++) {
|
for (int i = 1; i < maxChannels; i++) {
|
||||||
// First check cache
|
final channel = getChannelInfo!(i);
|
||||||
if (getChannelInfo != null) {
|
if (channel == null) {
|
||||||
final channel = getChannelInfo!(i);
|
continue;
|
||||||
if (channel != null && _channelSlotIsOccupied(channel)) {
|
|
||||||
final channelName = (channel as dynamic).name as String?;
|
|
||||||
final label = (channelName != null && channelName.isNotEmpty)
|
|
||||||
? channelName
|
|
||||||
: 'Channel $i';
|
|
||||||
debugPrint(' ⏭️ Slot $i occupied: "$label"');
|
|
||||||
continue; // Skip occupied slots
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot appears empty in cache, verify by querying device
|
if (_channelSlotIsOccupied(channel)) {
|
||||||
debugPrint(' 🔍 Checking slot $i...');
|
usedIndices.add(i);
|
||||||
final isEmpty = await isChannelSlotEmpty(i);
|
}
|
||||||
if (isEmpty) {
|
}
|
||||||
debugPrint(' ✅ Found empty slot: $i');
|
|
||||||
|
for (int i = 1; i < maxChannels; i++) {
|
||||||
|
if (!usedIndices.contains(i)) {
|
||||||
|
debugPrint(' ✅ Found empty slot from synced channels: $i');
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint(' ❌ All slots (1-${maxChannels - 1}) are in use');
|
debugPrint(
|
||||||
|
' ❌ Synced channels report all custom slots occupied ($maxCustomChannels total)',
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('❌ [Provider] Failed to find empty channel slot: $e');
|
debugPrint('❌ [Provider] Failed to find empty channel slot: $e');
|
||||||
@@ -998,12 +992,16 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
// Determine channel type
|
// Determine channel type
|
||||||
final bool isHashChannel = channelName.startsWith('#');
|
final bool isHashChannel = channelName.startsWith('#');
|
||||||
|
final maxChannels = _deviceInfo.maxChannels ?? 40;
|
||||||
|
|
||||||
|
// Refresh channel state before choosing a slot so we match meshcore-open's
|
||||||
|
// "pick first missing slot from the synced list" behavior.
|
||||||
|
await _activeService.syncAllChannels(maxChannels: maxChannels);
|
||||||
|
|
||||||
// Match meshcore-open behavior:
|
// Match meshcore-open behavior:
|
||||||
// - deterministic hash channels (#name) cannot be duplicated
|
// - deterministic hash channels (#name) cannot be duplicated
|
||||||
// - private channels always use the next empty slot, even if the name matches
|
// - private channels always use the next empty slot, even if the name matches
|
||||||
if (getChannelInfo != null) {
|
if (getChannelInfo != null) {
|
||||||
final maxChannels = _deviceInfo.maxChannels ?? 40;
|
|
||||||
for (int i = 1; i < maxChannels; i++) {
|
for (int i = 1; i < maxChannels; i++) {
|
||||||
final channel = getChannelInfo!(i);
|
final channel = getChannelInfo!(i);
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
@@ -1028,7 +1026,6 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find next empty slot for any new channel.
|
// Find next empty slot for any new channel.
|
||||||
final maxChannels = _deviceInfo.maxChannels ?? 40;
|
|
||||||
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
|
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
|
||||||
final emptySlot = await findNextEmptyChannelSlot();
|
final emptySlot = await findNextEmptyChannelSlot();
|
||||||
if (emptySlot == null) {
|
if (emptySlot == null) {
|
||||||
@@ -1107,6 +1104,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
debugPrint('🗑️ [Provider] Deleting channel in slot $channelIdx...');
|
debugPrint('🗑️ [Provider] Deleting channel in slot $channelIdx...');
|
||||||
|
_pendingDeletedChannelIndices.add(channelIdx);
|
||||||
|
|
||||||
// Delete channel on device (sets empty name and zeroed secret)
|
// Delete channel on device (sets empty name and zeroed secret)
|
||||||
await _activeService.deleteChannel(channelIdx);
|
await _activeService.deleteChannel(channelIdx);
|
||||||
@@ -1126,6 +1124,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// The empty channel will trigger removal via onChannelInfoReceived callback
|
// The empty channel will trigger removal via onChannelInfoReceived callback
|
||||||
await _activeService.getChannel(channelIdx);
|
await _activeService.getChannel(channelIdx);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_pendingDeletedChannelIndices.remove(channelIdx);
|
||||||
_error = 'Failed to delete channel: $e';
|
_error = 'Failed to delete channel: $e';
|
||||||
debugPrint('❌ [Provider] Channel deletion failed: $e');
|
debugPrint('❌ [Provider] Channel deletion failed: $e');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|||||||
@@ -37,5 +37,13 @@ void main() {
|
|||||||
isFalse,
|
isFalse,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('does not treat arbitrary empty channel info as deletion', () {
|
||||||
|
final provider = ConnectionProvider();
|
||||||
|
expect(
|
||||||
|
provider.shouldTreatChannelInfoAsDeleted(3, '', Uint8List(16)),
|
||||||
|
isFalse,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user