mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
CoreData Writes wrapped in an Actor (#1569)
* MeshPackets.swift into an actor and make async * Move upserts in UpdateCoreData to the MeshPackets actor * Update Meshtastic/Views/Settings/AppSettings.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/Persistence/UpdateCoreData.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Jake-B <jake-b@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
09dc74ceee
commit
4c370e5bee
17 changed files with 2975 additions and 2688 deletions
|
|
@ -61,7 +61,7 @@ extension AccessoryManager {
|
|||
self.updateState(.communicating)
|
||||
self.connectionEventTask = Task {
|
||||
for await event in eventStream {
|
||||
self.didReceive(event)
|
||||
await self.didReceive(event)
|
||||
}
|
||||
Logger.transport.info("[Accessory] Event stream closed")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ extension AccessoryManager {
|
|||
Logger.services.error("⚠️ Client Notification: \(clientNotification.message, privacy: .public)")
|
||||
}
|
||||
|
||||
func handleMyInfo(_ myNodeInfo: MyNodeInfo) {
|
||||
func handleMyInfo(_ myNodeInfo: MyNodeInfo) async {
|
||||
// TODO: this works for connections like BLE that have a uniqueId, but what about ones like serial?
|
||||
guard let connectedDeviceId = activeConnection?.device.id.uuidString else {
|
||||
Logger.services.error("⚠️ Failed to decode MyInfo, no connected device ID")
|
||||
|
|
@ -75,7 +75,8 @@ extension AccessoryManager {
|
|||
|
||||
updateDevice(key: \.num, value: Int64(myNodeInfo.myNodeNum))
|
||||
|
||||
if let myInfo = myInfoPacket(myInfo: myNodeInfo, peripheralId: connectedDeviceId, context: context) {
|
||||
if let myInfoId = await MeshPackets.shared.myInfoPacket(myInfo: myNodeInfo, peripheralId: connectedDeviceId),
|
||||
let myInfo = try? context.existingObject(with: myInfoId) as? MyInfoEntity {
|
||||
if let bleName = myInfo.bleName {
|
||||
updateDevice(key: \.name, value: bleName)
|
||||
updateDevice(key: \.longName, value: bleName)
|
||||
|
|
@ -97,7 +98,7 @@ extension AccessoryManager {
|
|||
initializeTAKBridge()
|
||||
}
|
||||
|
||||
func handleNodeInfo(_ nodeInfo: NodeInfo) {
|
||||
func handleNodeInfo(_ nodeInfo: NodeInfo) async {
|
||||
if let continuation = self.firstDatabaseNodeInfoContinuation {
|
||||
continuation.resume()
|
||||
self.firstDatabaseNodeInfoContinuation = nil
|
||||
|
|
@ -109,10 +110,13 @@ extension AccessoryManager {
|
|||
}
|
||||
|
||||
// Check if we're in database retrieval mode to defer saves for performance
|
||||
let isRetrievingDatabase = if case .retrievingDatabase = self.state { true } else { false }
|
||||
// Commented out: No need to defer save when nodeInfoPacket is now happening off the main thread
|
||||
// let isRetrievingDatabase = if case .retrievingDatabase = self.state { true } else { false }
|
||||
|
||||
// TODO: nodeInfoPacket's channel: parameter is not used
|
||||
if let nodeInfo = nodeInfoPacket(nodeInfo: nodeInfo, channel: 0, context: context, deferSave: isRetrievingDatabase) {
|
||||
// deferSave hard coded: No need to defer save when nodeInfoPacket is now happening off the main thread
|
||||
if let nodeInfoId = await MeshPackets.shared.nodeInfoPacket(nodeInfo: nodeInfo, channel: 0, deferSave: false),
|
||||
let nodeInfo = try? context.existingObject(with: nodeInfoId) as? NodeInfoEntity {
|
||||
if let activeDevice = activeConnection?.device, activeDevice.num == nodeInfo.num {
|
||||
if let user = nodeInfo.user {
|
||||
updateDevice(deviceId: activeDevice.id, key: \.shortName, value: user.shortName ?? "?")
|
||||
|
|
@ -138,24 +142,24 @@ extension AccessoryManager {
|
|||
|
||||
}
|
||||
|
||||
func handleChannel(_ channel: Channel) {
|
||||
func handleChannel(_ channel: Channel) async {
|
||||
guard let deviceNum = activeConnection?.device.num else {
|
||||
Logger.data.error("Attempt to process channel information when no connected device.")
|
||||
return
|
||||
}
|
||||
|
||||
channelPacket(channel: channel, fromNum: Int64(truncatingIfNeeded: deviceNum), context: context)
|
||||
await MeshPackets.shared.channelPacket(channel: channel, fromNum: Int64(truncatingIfNeeded: deviceNum))
|
||||
|
||||
}
|
||||
|
||||
func handleConfig(_ config: Config) {
|
||||
func handleConfig(_ config: Config) async {
|
||||
guard let device = activeConnection?.device, let deviceNum = device.num, let longName = device.longName else {
|
||||
Logger.data.error("Attempt to process channel information when no connected device.")
|
||||
return
|
||||
}
|
||||
|
||||
// Local config parses out the variants. Should we do that here maybe?
|
||||
localConfig(config: config, context: context, nodeNum: Int64(truncatingIfNeeded: deviceNum), nodeLongName: longName)
|
||||
await MeshPackets.shared.localConfig(config: config, nodeNum: Int64(truncatingIfNeeded: deviceNum), nodeLongName: longName)
|
||||
|
||||
// Handle Timezone
|
||||
if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) {
|
||||
|
|
@ -169,12 +173,12 @@ extension AccessoryManager {
|
|||
}
|
||||
}
|
||||
|
||||
func handleModuleConfig(_ moduleConfigPacket: ModuleConfig) {
|
||||
func handleModuleConfig(_ moduleConfigPacket: ModuleConfig) async {
|
||||
guard let device = activeConnection?.device, let deviceNum = device.num, let longName = device.longName else {
|
||||
Logger.services.error("Attempt to process channel information when no connected device.")
|
||||
return
|
||||
}
|
||||
moduleConfig(config: moduleConfigPacket, context: context, nodeNum: Int64(truncatingIfNeeded: deviceNum), nodeLongName: longName)
|
||||
await MeshPackets.shared.moduleConfig(config: moduleConfigPacket, nodeNum: Int64(truncatingIfNeeded: deviceNum), nodeLongName: longName)
|
||||
// Get Canned Message Message List if the Module is Canned Messages
|
||||
if moduleConfigPacket.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(moduleConfigPacket.cannedMessage) {
|
||||
try? getCannedMessageModuleMessages(destNum: deviceNum, wantResponse: true)
|
||||
|
|
@ -185,7 +189,7 @@ extension AccessoryManager {
|
|||
}
|
||||
}
|
||||
|
||||
func handleDeviceMetadata(_ metadata: DeviceMetadata) {
|
||||
func handleDeviceMetadata(_ metadata: DeviceMetadata) async {
|
||||
// Note: moved firmware version check to be inline with connection process
|
||||
guard let device = activeConnection?.device, let deviceNum = device.num else {
|
||||
Logger.services.error("Attempt to process device metadata information when no connected device.")
|
||||
|
|
@ -196,7 +200,7 @@ extension AccessoryManager {
|
|||
|
||||
updateDevice(key: \.firmwareVersion, value: metadata.firmwareVersion)
|
||||
|
||||
deviceMetadataPacket(metadata: metadata, fromNum: deviceNum, context: context)
|
||||
await MeshPackets.shared.deviceMetadataPacket(metadata: metadata, fromNum: deviceNum)
|
||||
}
|
||||
|
||||
internal func tryClearExistingChannels() {
|
||||
|
|
@ -227,17 +231,16 @@ extension AccessoryManager {
|
|||
|
||||
}
|
||||
|
||||
func handleTextMessageAppPacket(_ packet: MeshPacket) {
|
||||
func handleTextMessageAppPacket(_ packet: MeshPacket) async {
|
||||
guard let device = activeConnection?.device, let deviceNum = device.num else {
|
||||
Logger.services.error("Attempt to handle text message when no connected device.")
|
||||
return
|
||||
}
|
||||
|
||||
textMessageAppPacket(
|
||||
await MeshPackets.shared.textMessageAppPacket(
|
||||
packet: packet,
|
||||
wantRangeTestPackets: wantRangeTestPackets,
|
||||
connectedNode: deviceNum,
|
||||
context: context,
|
||||
appState: appState
|
||||
)
|
||||
|
||||
|
|
@ -322,25 +325,27 @@ extension AccessoryManager {
|
|||
case .UNRECOGNIZED:
|
||||
Logger.mesh.info("\("📮 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())")")
|
||||
case .routerTextDirect:
|
||||
Logger.mesh.info("\("💬 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())")")
|
||||
textMessageAppPacket(
|
||||
packet: packet,
|
||||
wantRangeTestPackets: false,
|
||||
connectedNode: connectedNodeNum,
|
||||
storeForward: true,
|
||||
context: context,
|
||||
appState: appState
|
||||
)
|
||||
Task {
|
||||
Logger.mesh.info("\("💬 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())")")
|
||||
await MeshPackets.shared.textMessageAppPacket(
|
||||
packet: packet,
|
||||
wantRangeTestPackets: false,
|
||||
connectedNode: connectedNodeNum,
|
||||
storeForward: true,
|
||||
appState: appState
|
||||
)
|
||||
}
|
||||
case .routerTextBroadcast:
|
||||
Logger.mesh.info("\("✉️ Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())")")
|
||||
textMessageAppPacket(
|
||||
packet: packet,
|
||||
wantRangeTestPackets: false,
|
||||
connectedNode: connectedNodeNum,
|
||||
storeForward: true,
|
||||
context: context,
|
||||
appState: appState
|
||||
)
|
||||
Task {
|
||||
Logger.mesh.info("\("✉️ Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())")")
|
||||
await MeshPackets.shared.textMessageAppPacket(
|
||||
packet: packet,
|
||||
wantRangeTestPackets: false,
|
||||
connectedNode: connectedNodeNum,
|
||||
storeForward: true,
|
||||
appState: appState
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ extension AccessoryManager {
|
|||
|
||||
// Update local database with the new node info
|
||||
// FUTURE: after https://github.com/meshtastic/firmware/pull/8495 is merged, `favorite: true` becomes `favorite: (connectedDeviceRole != DeviceRoles.clientBase)`
|
||||
upsertNodeInfoPacket(packet: nodeMeshPacket, favorite: true, context: context)
|
||||
await MeshPackets.shared.upsertNodeInfoPacket(packet: nodeMeshPacket, favorite: true)
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("Failed to decode contact data: \(error.localizedDescription, privacy: .public)")
|
||||
|
|
@ -864,7 +864,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertAmbientLightingModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertAmbientLightingModuleConfigPacket(config: config, nodeNum: toUser.num)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
|
||||
|
|
@ -920,7 +920,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertCannedMessagesModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertCannedMessagesModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1003,7 +1003,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved Detection Sensor Module Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertDetectionSensorModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertDetectionSensorModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1057,7 +1057,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved External Notification Module Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertExternalNotificationModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertExternalNotificationModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1087,7 +1087,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved PAX Counter Module Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertPaxCounterModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertPaxCounterModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1117,7 +1117,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1148,7 +1148,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved MQTT Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertMqttModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertMqttModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1178,7 +1178,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertRangeTestModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertRangeTestModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1208,7 +1208,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved Serial Module Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertSerialModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertSerialModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1387,7 +1387,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved Store & Forward Module Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertStoreForwardModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertStoreForwardModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1623,7 +1623,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertPositionConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
try await MeshPackets.shared.upsertPositionConfigPacket(config: config, nodeNum: toUser.num)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1677,7 +1677,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertPowerConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertPowerConfigPacket(config: config, nodeNum: toUser.num)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1733,7 +1733,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertNetworkConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertNetworkConfigPacket(config: config, nodeNum: toUser.num)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1764,7 +1764,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertSecurityConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertSecurityConfigPacket(config: config, nodeNum: toUser.num)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1898,7 +1898,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context)
|
||||
await MeshPackets.shared.upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1928,7 +1928,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertTelemetryModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
await MeshPackets.shared.upsertTelemetryModuleConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -1981,7 +1981,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved Display Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context)
|
||||
await MeshPackets.shared.upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -2060,7 +2060,7 @@ extension AccessoryManager {
|
|||
let messageDescription = "🛟 Saved Device Config for \(toUser.longName ?? "Unknown".localized)"
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context)
|
||||
await MeshPackets.shared.upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
@ -2089,7 +2089,7 @@ extension AccessoryManager {
|
|||
|
||||
try await sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription)
|
||||
|
||||
upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context)
|
||||
await MeshPackets.shared.upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey)
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
return
|
||||
}
|
||||
|
||||
_ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context)
|
||||
_ = await MeshPackets.shared.clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays))
|
||||
|
||||
try await withTaskCancellationHandler {
|
||||
var toRadio: ToRadio = ToRadio()
|
||||
|
|
@ -370,13 +370,13 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func didReceive(_ event: ConnectionEvent) {
|
||||
func didReceive(_ event: ConnectionEvent) async {
|
||||
packetsReceived += 1
|
||||
|
||||
switch event {
|
||||
case .data(let fromRadio):
|
||||
// Logger.transport.info("✅ [Accessory] didReceive: \(fromRadio.payloadVariant.debugDescription)")
|
||||
self.processFromRadio(fromRadio)
|
||||
await self.processFromRadio(fromRadio)
|
||||
Task {
|
||||
await self.heartbeatResponseTimer?.cancel(withReason: "Data packet received")
|
||||
await self.heartbeatTimer?.reset(delay: .seconds(15.0))
|
||||
|
|
@ -484,7 +484,7 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private func processFromRadio(_ decodedInfo: FromRadio) {
|
||||
private func processFromRadio(_ decodedInfo: FromRadio) async {
|
||||
switch decodedInfo.payloadVariant {
|
||||
case .mqttClientProxyMessage(let mqttClientProxyMessage):
|
||||
handleMqttClientProxyMessage(mqttClientProxyMessage)
|
||||
|
|
@ -493,12 +493,12 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
handleClientNotification(clientNotification)
|
||||
|
||||
case .myInfo(let myNodeInfo):
|
||||
handleMyInfo(myNodeInfo)
|
||||
await handleMyInfo(myNodeInfo)
|
||||
|
||||
case .packet(let packet):
|
||||
// All received packets get passed through updateAnyPacketFrom to update lastHeard, rxSnr, etc. (like firmware's NodeDB::updateFrom).
|
||||
if let connectedNodeNum = self.activeDeviceNum {
|
||||
updateAnyPacketFrom(packet: packet, activeDeviceNum: connectedNodeNum, context: context)
|
||||
await MeshPackets.shared.updateAnyPacketFrom(packet: packet, activeDeviceNum: connectedNodeNum)
|
||||
} else {
|
||||
Logger.mesh.error("🕸️ Unable to determine connectedNodeNum for updateAnyPacketFrom. Skipping.")
|
||||
}
|
||||
|
|
@ -507,20 +507,20 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
if case let .decoded(data) = packet.payloadVariant {
|
||||
switch data.portnum {
|
||||
case .textMessageApp, .detectionSensorApp, .alertApp:
|
||||
handleTextMessageAppPacket(packet)
|
||||
await handleTextMessageAppPacket(packet)
|
||||
case .remoteHardwareApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .positionApp:
|
||||
upsertPositionPacket(packet: packet, context: context)
|
||||
await MeshPackets.shared.upsertPositionPacket(packet: packet)
|
||||
case .waypointApp:
|
||||
waypointPacket(packet: packet, context: context)
|
||||
await MeshPackets.shared.waypointPacket(packet: packet)
|
||||
case .nodeinfoApp:
|
||||
guard let connectedNodeNum = self.activeDeviceNum else {
|
||||
Logger.mesh.error("🕸️ Unable to determine connectedNodeNum for node info upsert.")
|
||||
return
|
||||
}
|
||||
if packet.from != connectedNodeNum {
|
||||
upsertNodeInfoPacket(packet: packet, context: context)
|
||||
await MeshPackets.shared.upsertNodeInfoPacket(packet: packet)
|
||||
} else {
|
||||
Logger.mesh.error("🕸️ Received a node info packet from ourselves over the mesh. Dropping.")
|
||||
}
|
||||
|
|
@ -529,16 +529,16 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
Logger.mesh.error("🕸️ No active connection. Unable to determine connectedNodeNum for routingPacket.")
|
||||
return
|
||||
}
|
||||
routingPacket(packet: packet, connectedNodeNum: deviceNum, context: context)
|
||||
await MeshPackets.shared.routingPacket(packet: packet, connectedNodeNum: deviceNum)
|
||||
case .adminApp:
|
||||
adminAppPacket(packet: packet, context: context)
|
||||
await MeshPackets.shared.adminAppPacket(packet: packet)
|
||||
case .replyApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Reply App handling as a text message")
|
||||
guard let deviceNum = activeConnection?.device.num else {
|
||||
Logger.mesh.error("🕸️ No active connection. Unable to determine connectedNodeNum for replyApp.")
|
||||
return
|
||||
}
|
||||
textMessageAppPacket(packet: packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: deviceNum, context: context, appState: appState)
|
||||
await MeshPackets.shared.textMessageAppPacket(packet: packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: deviceNum, appState: appState)
|
||||
case .ipTunnelApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED")
|
||||
case .serialApp:
|
||||
|
|
@ -555,11 +555,10 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
return
|
||||
}
|
||||
if wantRangeTestPackets {
|
||||
textMessageAppPacket(
|
||||
await MeshPackets.shared.textMessageAppPacket(
|
||||
packet: packet,
|
||||
wantRangeTestPackets: true,
|
||||
connectedNode: deviceNum,
|
||||
context: context,
|
||||
appState: appState
|
||||
)
|
||||
} else {
|
||||
|
|
@ -570,7 +569,7 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
Logger.mesh.error("🕸️ No active connection. Unable to determine connectedNodeNum for telemetryApp.")
|
||||
return
|
||||
}
|
||||
telemetryPacket(packet: packet, connectedNode: deviceNum, context: context)
|
||||
await MeshPackets.shared.telemetryPacket(packet: packet, connectedNode: deviceNum)
|
||||
case .textMessageCompressedApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED")
|
||||
case .zpsApp:
|
||||
|
|
@ -592,7 +591,7 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
Logger.mesh.info("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED \((try? neighborInfo.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
}
|
||||
case .paxcounterApp:
|
||||
paxCounterPacket(packet: decodedInfo.packet, context: context)
|
||||
await MeshPackets.shared.paxCounterPacket(packet: decodedInfo.packet)
|
||||
case .mapReportApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received Map Report App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .UNRECOGNIZED:
|
||||
|
|
@ -615,19 +614,19 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
}
|
||||
|
||||
case .nodeInfo(let nodeInfo):
|
||||
handleNodeInfo(nodeInfo)
|
||||
await handleNodeInfo(nodeInfo)
|
||||
|
||||
case .channel(let channel):
|
||||
handleChannel(channel)
|
||||
await handleChannel(channel)
|
||||
|
||||
case .config(let config):
|
||||
handleConfig(config)
|
||||
await handleConfig(config)
|
||||
|
||||
case .moduleConfig(let moduleConfig):
|
||||
handleModuleConfig(moduleConfig)
|
||||
await handleModuleConfig(moduleConfig)
|
||||
|
||||
case .metadata(let metadata):
|
||||
handleDeviceMetadata(metadata)
|
||||
await handleDeviceMetadata(metadata)
|
||||
|
||||
case .deviceuiConfig:
|
||||
#if DEBUG
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Foundation
|
|||
import SwiftUI
|
||||
import OSLog
|
||||
|
||||
@MainActor
|
||||
class LocalNotificationManager {
|
||||
|
||||
var notifications = [Notification]()
|
||||
|
|
@ -10,20 +11,23 @@ class LocalNotificationManager {
|
|||
let replyInputAction = UNTextInputNotificationAction(identifier: "messageNotification.replyInputAction", title: "Reply".localized, options: [])
|
||||
|
||||
// Step 1 Request Permissions for notifications
|
||||
private func requestAuthorization() {
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
|
||||
|
||||
if granted == true && error == nil {
|
||||
self.scheduleNotifications()
|
||||
private func requestAuthorization() async {
|
||||
do {
|
||||
let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound])
|
||||
if granted {
|
||||
self.scheduleNotifications()
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("Error requesting notification authorization: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
Task { @MainActor in
|
||||
let settings = await UNUserNotificationCenter.current().notificationSettings()
|
||||
switch settings.authorizationStatus {
|
||||
case .notDetermined:
|
||||
self.requestAuthorization()
|
||||
await self.requestAuthorization()
|
||||
case .authorized, .provisional:
|
||||
self.scheduleNotifications()
|
||||
default:
|
||||
|
|
@ -97,7 +101,7 @@ class LocalNotificationManager {
|
|||
for notification in notifications {
|
||||
if let userInfo = notification.content.userInfo["messageId"] as? Int64, userInfo == messageId {
|
||||
Logger.services.debug("Cancelling notification with id: \(notification.identifier)")
|
||||
center.removePendingNotificationRequests(withIdentifiers: [notification.identifier])
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notification.identifier])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -511,23 +511,23 @@ struct ManualConnectionMenu: View {
|
|||
})
|
||||
}.confirmationDialog("Connecting to a new radio will clear all app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
|
||||
Button("Connect to new radio?", role: .destructive) {
|
||||
if let device = deviceForManualConnection {
|
||||
UserDefaults.preferredPeripheralId = device.id.uuidString
|
||||
UserDefaults.preferredPeripheralNum = 0
|
||||
if accessoryManager.allowDisconnect {
|
||||
Task { try await accessoryManager.disconnect() }
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
clearNotifications()
|
||||
Task {
|
||||
try await selectedTransport?.transport.manuallyConnect(toDevice: device)
|
||||
}
|
||||
|
||||
// Clean up just in case
|
||||
deviceForManualConnection = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Task {
|
||||
if let device = deviceForManualConnection {
|
||||
UserDefaults.preferredPeripheralId = device.id.uuidString
|
||||
UserDefaults.preferredPeripheralNum = 0
|
||||
if accessoryManager.allowDisconnect {
|
||||
try await accessoryManager.disconnect()
|
||||
}
|
||||
await MeshPackets.shared.clearCoreDataDatabase(includeRoutes: false)
|
||||
clearNotifications()
|
||||
try await selectedTransport?.transport.manuallyConnect(toDevice: device)
|
||||
|
||||
// Clean up just in case
|
||||
deviceForManualConnection = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -593,15 +593,17 @@ struct DeviceConnectRow: View {
|
|||
}.padding([.bottom, .top])
|
||||
.confirmationDialog("Connecting to a new radio will clear all app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
|
||||
Button("Connect to new radio?", role: .destructive) {
|
||||
UserDefaults.preferredPeripheralId = device.id.uuidString
|
||||
UserDefaults.preferredPeripheralNum = 0
|
||||
if accessoryManager.allowDisconnect {
|
||||
Task { try await accessoryManager.disconnect() }
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
clearNotifications()
|
||||
Task {
|
||||
UserDefaults.preferredPeripheralId = device.id.uuidString
|
||||
UserDefaults.preferredPeripheralNum = 0
|
||||
if accessoryManager.allowDisconnect {
|
||||
try await accessoryManager.disconnect()
|
||||
}
|
||||
await MeshPackets.shared.clearCoreDataDatabase(includeRoutes: false)
|
||||
clearNotifications()
|
||||
|
||||
try await accessoryManager.connect(to: device)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,9 +160,11 @@ struct ChannelList: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteChannelMessages(channel: channelToDeleteMessages!, context: context)
|
||||
context.refresh(myInfo, mergeChanges: true)
|
||||
channelToDeleteMessages = nil
|
||||
Task {
|
||||
await MeshPackets.shared.deleteChannelMessages(channel: channelToDeleteMessages!)
|
||||
context.refresh(myInfo, mergeChanges: true)
|
||||
channelToDeleteMessages = nil
|
||||
}
|
||||
} label: {
|
||||
Text("Delete")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,8 +224,10 @@ fileprivate struct FilteredUserList: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteUserMessages(user: userToDeleteMessages!, context: context)
|
||||
context.refresh(node!.user!, mergeChanges: true)
|
||||
Task {
|
||||
await MeshPackets.shared.deleteUserMessages(user: userToDeleteMessages!)
|
||||
context.refresh(node!.user!, mergeChanges: true)
|
||||
}
|
||||
} label: {
|
||||
Text("Delete")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,10 +199,12 @@ struct DeviceMetricsLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all device metrics?", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 0, context: context) {
|
||||
Logger.data.notice("Cleared Device Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Device Metrics Log Failed")
|
||||
Task {
|
||||
if await MeshPackets.shared.clearTelemetry(destNum: node.num, metricsType: 0) {
|
||||
Logger.data.notice("Cleared Device Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Device Metrics Log Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,8 +128,10 @@ struct EnvironmentMetricsLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all environment metrics?", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 1, context: context) {
|
||||
Logger.services.error("Clear Environment Metrics Log Failed")
|
||||
Task {
|
||||
if await MeshPackets.shared.clearTelemetry(destNum: node.num, metricsType: 1) {
|
||||
Logger.services.error("Clear Environment Metrics Log Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,10 +175,12 @@ struct PaxCounterLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all pax data?", role: .destructive) {
|
||||
if clearPax(destNum: node.num, context: context) {
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.services.error("Clear Pax Counter Log Failed")
|
||||
Task {
|
||||
if await MeshPackets.shared.clearPax(destNum: node.num) {
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.services.error("Clear Pax Counter Log Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,10 +131,12 @@ struct PositionLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all positions?", role: .destructive) {
|
||||
if clearPositions(destNum: node.num, context: context) {
|
||||
Logger.services.info("Successfully Cleared Position Log")
|
||||
} else {
|
||||
Logger.services.error("Clear Position Log Failed")
|
||||
Task {
|
||||
if await MeshPackets.shared.clearPositions(destNum: node.num) {
|
||||
Logger.services.info("Successfully Cleared Position Log")
|
||||
} else {
|
||||
Logger.services.error("Clear Position Log Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,10 +242,12 @@ struct PowerMetricsLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete Power metrics?", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 2, context: context) {
|
||||
Logger.data.notice("Cleared Power Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Power Metrics Log Failed")
|
||||
Task {
|
||||
if await MeshPackets.shared.clearTelemetry(destNum: node.num, metricsType: 2) {
|
||||
Logger.data.notice("Cleared Power Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Power Metrics Log Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,30 +138,31 @@ struct AppSettings: View {
|
|||
Button("Erase all app data?", role: .destructive) {
|
||||
Task {
|
||||
try await accessoryManager.disconnect()
|
||||
}
|
||||
/// Delete any database backups too
|
||||
if var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
url = url.appendingPathComponent("backup").appendingPathComponent(String(UserDefaults.preferredPeripheralNum))
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url.appendingPathComponent("Meshtastic.sqlite"))
|
||||
/// Delete -shm file
|
||||
|
||||
/// Delete any database backups too
|
||||
if var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
url = url.appendingPathComponent("backup").appendingPathComponent(String(UserDefaults.preferredPeripheralNum))
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url.appendingPathComponent("Meshtastic.sqlite-wal"))
|
||||
try FileManager.default.removeItem(at: url.appendingPathComponent("Meshtastic.sqlite"))
|
||||
/// Delete -shm file
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url.appendingPathComponent("Meshtastic.sqlite-shm"))
|
||||
try FileManager.default.removeItem(at: url.appendingPathComponent("Meshtastic.sqlite-wal"))
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url.appendingPathComponent("Meshtastic.sqlite-shm"))
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-shm file \(error, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-shm file \(error, privacy: .public)")
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-wal file \(error, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-wal file \(error, privacy: .public)")
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite file \(error, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite file \(error, privacy: .public)")
|
||||
}
|
||||
await MeshPackets.shared.clearCoreDataDatabase(includeRoutes: true, context: context)
|
||||
clearNotifications()
|
||||
context.refreshAllObjects()
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: true)
|
||||
clearNotifications()
|
||||
context.refreshAllObjects()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ struct DeviceConfig: View {
|
|||
try await accessoryManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!)
|
||||
try await Task.sleep(for: .seconds(1))
|
||||
try await accessoryManager.disconnect()
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
await MeshPackets.shared.clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
clearNotifications()
|
||||
} catch {
|
||||
Logger.mesh.error("NodeDB Reset Failed")
|
||||
|
|
@ -200,7 +200,7 @@ struct DeviceConfig: View {
|
|||
try await accessoryManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!)
|
||||
try await Task.sleep(for: .seconds(1))
|
||||
try await accessoryManager.disconnect()
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
await MeshPackets.shared.clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
clearNotifications()
|
||||
} catch {
|
||||
Logger.mesh.error("Factory Reset Failed")
|
||||
|
|
@ -213,7 +213,7 @@ struct DeviceConfig: View {
|
|||
try await accessoryManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!, resetDevice: true)
|
||||
try? await Task.sleep(for: .seconds(1))
|
||||
try await accessoryManager.disconnect()
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
await MeshPackets.shared.clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
clearNotifications()
|
||||
} catch {
|
||||
Logger.mesh.error("Factory Reset Failed")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue