diff --git a/src/Common/model/MsgMessage.swift b/src/Common/model/MsgMessage.swift index 9be50cd0..a4e85664 100644 --- a/src/Common/model/MsgMessage.swift +++ b/src/Common/model/MsgMessage.swift @@ -19,3 +19,10 @@ class MsgMessage: Identifiable { self.metadata = metadata } } + +extension MsgMessage: Equatable { + static func == (lhs: MsgMessage, rhs: MsgMessage) -> Bool { + return lhs.id == rhs.id + } +} + diff --git a/src/Common/model/tree/TopicTree+Deletion.swift b/src/Common/model/tree/TopicTree+Deletion.swift index 9c947e3e..2e8e12b2 100644 --- a/src/Common/model/tree/TopicTree+Deletion.swift +++ b/src/Common/model/tree/TopicTree+Deletion.swift @@ -12,15 +12,31 @@ extension TopicTree { func clear() { children = [:] messageCountDisplay = 0 + totalTopicCounter = 0 topicCountDisplay = 0 childrenDisplay = [] messages = [] timeSeries = TimeSeriesModel() readState = Readstate(read: true) index?.clear(topicStartsWith: nameQualified) + topicLimitExceeded = false } func delete(at offsets: IndexSet) { messages.remove(atOffsets: offsets) } + + func delete(message: MsgMessage) { + if let index = messages.firstIndex(of: message) { + messages.remove(at: index) + } + + var node: TopicTree? = self + while node != nil { + let current = node! + current.messageCountDirty = true + + node = current.parent + } + } } diff --git a/src/Common/model/tree/TopicTree.swift b/src/Common/model/tree/TopicTree.swift index 951d7223..9c5ba23d 100644 --- a/src/Common/model/tree/TopicTree.swift +++ b/src/Common/model/tree/TopicTree.swift @@ -32,7 +32,8 @@ class TopicTree: Identifiable, ObservableObject { private var _messageCountDisplay: Int = 0 private var _topicCountDisplay: Int = 0 - + private var pauseAcceptEmptyUntil: Date? + var messageCountDisplay: Int { get { updateMessageCount() @@ -42,6 +43,7 @@ class TopicTree: Identifiable, ObservableObject { _messageCountDisplay = newValue } } + var topicCountDisplay: Int { get { updateMessageCount() @@ -91,6 +93,17 @@ class TopicTree: Identifiable, ObservableObject { } } + var allRetainedMessages: [MsgMessage] { + var result: [MsgMessage] = [] + for child in children { + result += child.value.allRetainedMessages + } + + result += messages.filter { $0.metadata.retain } + + return result + } + var filterTextCleaned = "" @Published var filterText = "" { didSet { @@ -200,6 +213,10 @@ extension TopicTree { func addMessage(metadata: MsgMetadata, payload: MsgPayload, to topic: String) -> MsgMessage? { if let node = addTopic(topic: topic) { + if !node.canAccept(payload: payload) { + return nil + } + let message = MsgMessage(topic: node, payload: payload, metadata: metadata) node.addMessage(message: message) @@ -211,3 +228,26 @@ extension TopicTree { } } } + +extension TopicTree { + func pauseAcceptEmptyFor(seconds: Int32) { + pauseAcceptEmptyUntil = Date().addingTimeInterval(TimeInterval(seconds)) + } + + func canAccept(payload: MsgPayload) -> Bool { + if !payload.data.isEmpty { + return true + } + var node: TopicTree? = self + while node != nil { + let current = node! + if let pauseUntil = current.pauseAcceptEmptyUntil, Date() < pauseUntil { + return false + } + + node = current.parent + } + + return true + } +} diff --git a/src/MQTTAnalyzer/views/message/MessageView.swift b/src/MQTTAnalyzer/views/message/MessageView.swift index 034bab68..2289dd62 100644 --- a/src/MQTTAnalyzer/views/message/MessageView.swift +++ b/src/MQTTAnalyzer/views/message/MessageView.swift @@ -67,20 +67,34 @@ struct MessageCellView: View { } } .contextMenu { - MenuButton(title: "Copy message", - systemImage: "doc.on.doc", - action: copy) - MenuButton(title: "Publish message again", - systemImage: "paperplane.fill", - action: publish) - MenuButton(title: "Publish new message", - systemImage: "paperplane.fill", - action: publishManually) + MenuButton(title: "Copy topic", systemImage: "doc.on.doc", action: copyTopic) + MenuButton(title: "Copy recent message", systemImage: "doc.on.doc", action: copyMessage) + + Menu { + MenuButton(title: "Message again", systemImage: "paperplane.fill", action: publish) + MenuButton(title: "New message", systemImage: "paperplane.fill", action: publishManually) + .accessibilityLabel("publish new") + } label: { + Label("Publish", systemImage: "paperplane.fill") + } + .accessibilityLabel("publish") + + Menu { + DestructiveMenuButton(title: "Delete retained message from broker", systemImage: "trash.fill", action: deleteRetained) + .accessibilityLabel("confirm-delete-retained") + } label: { + Label("Delete", systemImage: "trash.fill") + } + .accessibilityLabel("delete-retained") } } } - func copy() { + func copyTopic() { + UIPasteboard.general.string = message.topic.nameQualified + } + + func copyMessage() { UIPasteboard.general.string = message.payload.dataString } @@ -91,4 +105,11 @@ struct MessageCellView: View { func publishManually() { selectMessage(message) } + + func deleteRetained() { + model.publish(message: MsgMessage( + topic: message.topic, + payload: MsgPayload(data: []), + metadata: MsgMetadata(qos: message.metadata.qos, retain: true)), on: host) + } } diff --git a/src/MQTTAnalyzer/views/topic/FolderNavigationView.swift b/src/MQTTAnalyzer/views/topic/FolderNavigationView.swift index 37351bb6..2e2948b6 100644 --- a/src/MQTTAnalyzer/views/topic/FolderNavigationView.swift +++ b/src/MQTTAnalyzer/views/topic/FolderNavigationView.swift @@ -29,7 +29,7 @@ struct FolderNavigationView: View { else { ForEach(model.childrenDisplay.sorted { $0.name < $1.name }) { child in NavigationLink(destination: TopicsView(model: child, host: self.host)) { - FolderCellView(model: child) + FolderCellView(model: child, host: host) } .accessibilityLabel("folder: \(child.nameQualified)") } @@ -40,7 +40,9 @@ struct FolderNavigationView: View { struct FolderCellView: View { @ObservedObject var model: TopicTree - + @EnvironmentObject var root: RootModel + let host: Host + var body: some View { HStack { FolderReadMarkerView(read: model.readState) @@ -55,6 +57,14 @@ struct FolderCellView: View { .contextMenu { MenuButton(title: "Copy topic", systemImage: "doc.on.doc", action: copyTopic) MenuButton(title: "Copy name", systemImage: "doc.on.doc", action: copyName) + + Menu { + DestructiveMenuButton(title: "Delete retained messages from broker", systemImage: "trash.fill", action: deleteAllReatined) + .accessibilityLabel("confirm-delete-retained") + } label: { + Label("Delete", systemImage: "trash.fill") + } + .accessibilityLabel("delete-retained") } } @@ -65,6 +75,22 @@ struct FolderCellView: View { func copyName() { UIPasteboard.general.string = model.name } + + func deleteAllReatined() { + model.pauseAcceptEmptyFor(seconds: 5) + + let messages = model.allRetainedMessages + for message in messages { + root.publish(message: MsgMessage( + topic: message.topic, + payload: MsgPayload(data: []), + metadata: MsgMetadata(qos: message.metadata.qos, retain: true)), on: host) + } + + for message in messages { + message.topic.delete(message: message) + } + } } struct CounterCellView: View { diff --git a/src/MQTTAnalyzer/views/topic/TopicCellView.swift b/src/MQTTAnalyzer/views/topic/TopicCellView.swift index 450bcc1b..efab54ed 100644 --- a/src/MQTTAnalyzer/views/topic/TopicCellView.swift +++ b/src/MQTTAnalyzer/views/topic/TopicCellView.swift @@ -37,11 +37,23 @@ struct TopicCellView: View { .contextMenu { MenuButton(title: "Copy topic", systemImage: "doc.on.doc", action: copyTopic) MenuButton(title: "Copy recent message", systemImage: "doc.on.doc", action: copyMessage) - - MenuButton(title: "Publish message again", systemImage: "paperplane.fill", action: publish) - MenuButton(title: "Publish new message", systemImage: "paperplane.fill", action: publishManually) - .accessibilityLabel("publish new") - MenuButton(title: "Delete retained message", systemImage: "paperplane.fill", action: deleteRetained) + + Menu { + MenuButton(title: "Message again", systemImage: "paperplane.fill", action: publish) + MenuButton(title: "New message", systemImage: "paperplane.fill", action: publishManually) + .accessibilityLabel("publish new") + } label: { + Label("Publish", systemImage: "paperplane.fill") + } + .accessibilityLabel("publish") + + Menu { + DestructiveMenuButton(title: "Delete retained message from broker", systemImage: "trash.fill", action: deleteRetained) + .accessibilityLabel("confirm-delete-retained") + } label: { + Label("Delete", systemImage: "trash.fill") + } + .accessibilityLabel("delete-retained") } } .accessibilityLabel("group: \(messages.nameQualified)") diff --git a/src/Podfile b/src/Podfile index 6f36e934..3576c82f 100644 --- a/src/Podfile +++ b/src/Podfile @@ -21,7 +21,7 @@ def shared_pods pod 'Highlightr', :git => 'https://github.com/raspu/Highlightr.git', :tag => '2.1.2' - pod 'CodeEditor', :git => 'https://github.com/ZeeZide/CodeEditor.git' + pod 'CodeEditor', :git => 'https://github.com/ZeeZide/CodeEditor.git', :tag => '1.2.4' pod 'swift-petitparser' @@ -29,7 +29,7 @@ def shared_pods pod 'SwiftLint' - pod 'GRDB.swift', '6.15.0' + pod 'GRDB.swift', :git => 'https://github.com/groue/GRDB.swift', :tag => 'v6.27.0' end target 'MQTTAnalyzer' do diff --git a/src/Podfile.lock b/src/Podfile.lock index 9732d557..b0283429 100644 --- a/src/Podfile.lock +++ b/src/Podfile.lock @@ -6,9 +6,9 @@ PODS: - CocoaMQTT/Core - CodeEditor (1.2.0): - Highlightr - - GRDB.swift (6.15.0): - - GRDB.swift/standard (= 6.15.0) - - GRDB.swift/standard (6.15.0) + - GRDB.swift (6.27.0): + - GRDB.swift/standard (= 6.27.0) + - GRDB.swift/standard (6.27.0) - Highlightr (2.1.0) - swift-petitparser (1.2.1) - SwiftLint (0.49.1) @@ -17,8 +17,8 @@ PODS: DEPENDENCIES: - CocoaMQTT (from `https://github.com/philipparndt/CocoaMQTT.git`, branch `apple-network`) - CocoaMQTT/WebSockets (from `https://github.com/philipparndt/CocoaMQTT.git`, branch `apple-network`) - - CodeEditor (from `https://github.com/ZeeZide/CodeEditor.git`) - - GRDB.swift (= 6.15.0) + - CodeEditor (from `https://github.com/ZeeZide/CodeEditor.git`, tag `1.2.4`) + - GRDB.swift (from `https://github.com/groue/GRDB.swift`, tag `v6.27.0`) - Highlightr (from `https://github.com/raspu/Highlightr.git`, tag `2.1.2`) - swift-petitparser - SwiftLint @@ -26,7 +26,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - - GRDB.swift - swift-petitparser - SwiftLint - SwiftyJSON @@ -37,6 +36,10 @@ EXTERNAL SOURCES: :git: https://github.com/philipparndt/CocoaMQTT.git CodeEditor: :git: https://github.com/ZeeZide/CodeEditor.git + :tag: 1.2.4 + GRDB.swift: + :git: https://github.com/groue/GRDB.swift + :tag: v6.27.0 Highlightr: :git: https://github.com/raspu/Highlightr.git :tag: 2.1.2 @@ -46,8 +49,11 @@ CHECKOUT OPTIONS: :commit: 885e984c186f8b950fb697918a99284e9fb9e6a3 :git: https://github.com/philipparndt/CocoaMQTT.git CodeEditor: - :commit: 180bde07b44dea839b32873bd8586ba146fa9106 :git: https://github.com/ZeeZide/CodeEditor.git + :tag: 1.2.4 + GRDB.swift: + :git: https://github.com/groue/GRDB.swift + :tag: v6.27.0 Highlightr: :git: https://github.com/raspu/Highlightr.git :tag: 2.1.2 @@ -55,12 +61,12 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: CocoaMQTT: 96aa37dde2ed19eb0e413d38fd21fcc7655d6239 CodeEditor: 9fe96645a2af098efc83df3807f41b60bc4fddd1 - GRDB.swift: 70d96b648395ad346a844461c121f005228e7ae1 + GRDB.swift: 384773f598284a05f230fe796dfc646d1925669e Highlightr: 683f05d5223cade533a78528a35c9f06e4caddf8 swift-petitparser: 3a4ef1e19bbf198200d73036459a9ce02a0a3068 SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5 SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e -PODFILE CHECKSUM: 4e0de621ed40310681cd040ae4d4bea351984bbf +PODFILE CHECKSUM: d90353827f8441e72348e600f9b161b860213594 -COCOAPODS: 1.11.3 +COCOAPODS: 1.15.0