Clean build

This commit is contained in:
Peter Steinberger 2025-05-22 22:56:34 +02:00
parent 3f335b489c
commit 316d8145f5
12 changed files with 414 additions and 144 deletions

View File

@ -1,5 +1,41 @@
{
"pins" : [
{
"identity" : "defaults",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/Defaults",
"state" : {
"revision" : "3efef5a28ebdbbe922d4a2049493733ed14475a6",
"version" : "7.3.1"
}
},
{
"identity" : "keyboardshortcuts",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/KeyboardShortcuts",
"state" : {
"revision" : "045cf174010beb335fa1d2567d18c057b8787165",
"version" : "2.3.0"
}
},
{
"identity" : "launchatlogin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/LaunchAtLogin",
"state" : {
"revision" : "9a894d799269cb591037f9f9cb0961510d4dca81",
"version" : "5.0.2"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle.git",
"state" : {
"revision" : "0ca3004e98712ea2b39dd881d28448630cce1c99",
"version" : "2.7.0"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
@ -9,6 +45,15 @@
"version" : "1.5.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa",
"version" : "1.6.3"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
@ -26,6 +71,15 @@
"revision" : "399f76dcd91e4c688ca2301fa24a8cc6d9927211",
"version" : "0.99.0"
}
},
{
"identity" : "swiftui-introspect",
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"state" : {
"revision" : "121c146fe591b1320238d054ae35c81ffa45f45a",
"version" : "0.12.0"
}
}
],
"version" : 2

View File

@ -15,8 +15,10 @@ public class AXorcist {
internal var recursiveCallDebugLogs: [String] = [] // Added for recursive logging
// Default values for collection and search if not provided by the command
public static let defaultMaxDepthSearch = 10 // Example, adjust as needed
public static let defaultMaxDepthCollectAll = 5
public static let defaultMaxDepthSearch = 10 // Default for general locator-based searches
public static let defaultMaxDepthCollectAll = 7 // Default for collectAll recursive operations
public static let defaultMaxDepthPathResolution = 15 // Max depth for resolving path hints
public static let defaultMaxDepthDescribe = 5 // ADDED: Default for description recursion
public static let defaultTimeoutPerElementCollectAll = 0.5 // seconds
// Default attributes to fetch if none are specified by the command.
@ -114,10 +116,14 @@ public class AXorcist {
currentDebugLogs: inout [String]
) -> Element? {
let pathHintString = pathHint.joined(separator: ", ")
// Log with the actual isDebugLoggingEnabled value
currentDebugLogs.append(AXorcist.formatDebugLogMessage("navigateToElement: Entered. isDebugLoggingEnabled: \(isDebugLoggingEnabled). pathHint: [\(pathHintString)]", applicationName: nil, commandID: nil, file: #file, function: #function, line: #line))
func dLog(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
currentDebugLogs.append(AXorcist.formatDebugLogMessage(message, applicationName: nil, commandID: nil, file: file, function: function, line: line))
// Use the passed-in isDebugLoggingEnabled
if isDebugLoggingEnabled {
currentDebugLogs.append(AXorcist.formatDebugLogMessage(message, applicationName: nil, commandID: nil, file: file, function: function, line: line))
}
}
var currentElement = startElement
@ -143,7 +149,7 @@ public class AXorcist {
for child in childrenFromElementDotChildren {
let childBriefDescForLog = child.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)
if let actualValue = child.attribute(Attribute<String>(attributeName), isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs) {
// dLog("Child (from Element.children) \(childBriefDescForLog) has '\(attributeName)': [\(actualValue)] (Expected: [\(expectedValue)])")
dLog(" [Nav Child Check 1] Child: \(childBriefDescForLog), Attribute '\(attributeName)': [\(actualValue)] (Expected: [\(expectedValue)])")
if actualValue == expectedValue {
dLog("Matched child (from Element.children): \(childBriefDescForLog) for '\(attributeName):\(expectedValue)'")
newElementForNextStep = child
@ -175,8 +181,9 @@ public class AXorcist {
currentDebugLogs.append(AXorcist.formatDebugLogMessage("navigateToElement: Direct kAXChildrenAttribute fallback found \(directAxElements.count) raw children for \(currentElementDescForFallbackLog).", applicationName: nil, commandID: nil, file: #file, function: #function, line: #line))
for axChild in directAxElements {
let childElement = Element(axChild)
// let childBriefDescForLog = childElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs) // Avoid for now inside loop if too verbose or risky
let childBriefDescForLogFallback = childElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)
if let actualValue = childElement.attribute(Attribute<String>(attributeName), isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs) {
dLog(" [Nav Child Check 2-Fallback] Child: \(childBriefDescForLogFallback), Attribute '\(attributeName)': [\(actualValue)] (Expected: [\(expectedValue)])")
if actualValue == expectedValue {
currentDebugLogs.append(AXorcist.formatDebugLogMessage("navigateToElement: Matched child (from direct fallback) for '\(attributeName):\(expectedValue)' on \(currentElementDescForFallbackLog). Child: \(childElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))", applicationName: nil, commandID: nil, file: #file, function: #function, line: #line))
newElementForNextStep = childElement

View File

@ -120,6 +120,7 @@ public let kAXMenuItemRole = "AXMenuItem" // New
public let kAXSplitGroupRole = "AXSplitGroup" // New
public let kAXSplitterRole = "AXSplitter" // New
public let kAXColorWellRole = "AXColorWell" // New
public let kAXLinkRole = "AXLink" // New
public let kAXUnknownRole = "AXUnknown" // New
// Attributes for web content and tables/lists

View File

@ -77,11 +77,14 @@ public struct Element: Equatable, Hashable {
}
} else if T.self == Bool.self {
if CFGetTypeID(unwrappedValue) == CFBooleanGetTypeID() {
return CFBooleanGetValue(unwrappedValue as! CFBoolean) as? T
// Reverted to as! based on new compiler note
let swiftBool = CFBooleanGetValue(unwrappedValue as! CFBoolean)
return swiftBool as? T
}
} else if T.self == Int.self {
if CFGetTypeID(unwrappedValue) == CFNumberGetTypeID() {
var intValue: Int = 0
// Reverted to as! based on new compiler note
if CFNumberGetValue(unwrappedValue as! CFNumber, .sInt64Type, &intValue) {
return intValue as? T
}

View File

@ -25,7 +25,7 @@ public enum CommandType: String, Codable {
}
// For encoding/decoding 'Any' type in JSON, especially for element attributes.
public struct AnyCodable: Codable {
public struct AnyCodable: Codable, @unchecked Sendable {
public let value: Any
public init<T>(_ value: T?) {
@ -162,7 +162,7 @@ public struct CommandEnvelope: Codable {
}
// Locator for finding elements
public struct Locator: Codable {
public struct Locator: Codable, Sendable {
public var match_all: Bool?
public var criteria: [String: String]
public var root_element_path_hint: [String]?
@ -207,6 +207,18 @@ public struct QueryResponse: Codable {
self.error = error
self.debug_logs = debug_logs
}
// Custom init for HandlerResponse integration
public init(command_id: String, success: Bool, command: String, handlerResponse: HandlerResponse, debug_logs: [String]?) {
self.command_id = command_id
self.success = success
self.command = command
self.data = handlerResponse.data
// If HandlerResponse has attributes, map them from its data field.
self.attributes = handlerResponse.data?.attributes
self.error = handlerResponse.error
self.debug_logs = debug_logs
}
}
// Response for collect_all command (multiple elements)
@ -302,7 +314,7 @@ public struct SimpleSuccessResponse: Codable, Equatable {
// Placeholder for any additional models if needed
public struct AXElement: Codable {
public struct AXElement: Codable, Sendable {
public var attributes: ElementAttributes?
public var path: [String]?
@ -324,7 +336,7 @@ extension QueryResponse {
// MARK: - Handler Response Models
public struct HandlerResponse {
public struct HandlerResponse: Codable, Sendable {
public var data: AXElement?
public var error: String?
public var debug_logs: [String]?
@ -345,3 +357,20 @@ internal struct CollectAllOutput: Encodable {
let app_bundle_id: String?
let debug_logs: [String]?
}
// ADDED BatchResponse struct
public struct BatchResponse: Codable {
public var command_id: String
public var success: Bool
public var results: [HandlerResponse] // Array of HandlerResponses for each sub-command
public var error: String? // For an overall batch error, if any
public var debug_logs: [String]?
public init(command_id: String, success: Bool, results: [HandlerResponse], error: String? = nil, debug_logs: [String]? = nil) {
self.command_id = command_id
self.success = success
self.results = results
self.error = error
self.debug_logs = debug_logs
}
}

View File

@ -2,6 +2,7 @@
import AppKit
import ApplicationServices
import Darwin
import Foundation
// MARK: - Action & Data Handlers Extension
@ -66,9 +67,26 @@ extension AXorcist {
let targetElementForAction: Element
if let actualLocator = locator {
dLog("[AXorcist.handlePerformAction] Locator provided. Searching from current effectiveElement: \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)) using locator criteria: \(actualLocator.criteria)")
guard let foundElement = search(element: effectiveElement, locator: actualLocator, requireAction: actualLocator.requireAction, depth: 0, maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs) else {
let error = "[AXorcist.handlePerformAction] Failed to find element with locator: \(actualLocator) starting from \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
currentDebugLogs.append(error)
let searchResult = search(
element: effectiveElement,
locator: actualLocator,
requireAction: actualLocator.requireAction,
depth: 0,
maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH,
isDebugLoggingEnabled: isDebugLoggingEnabled
)
fputs("HANDLER_RAW_STDERR_BEFORE_LOG_APPEND handlePerformAction: searchResult.logs.count = \(searchResult.logs.count), currentDebugLogs count = \(currentDebugLogs.count)\n", stderr)
currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for performAction")
currentDebugLogs.append(contentsOf: searchResult.logs)
fputs("HANDLER_RAW_STDERR_AFTER_LOG_APPEND handlePerformAction: currentDebugLogs count = \(currentDebugLogs.count)\n", stderr)
currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_HANDLER")
guard let foundElement = searchResult.foundElement else {
let error = "[AXorcist.handlePerformAction] Search failed. Could not find element matching locator criteria \(actualLocator.criteria) starting from element \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))."
if !currentDebugLogs.contains(error) {
currentDebugLogs.append(error)
}
return HandlerResponse(data: nil, error: error, debug_logs: currentDebugLogs)
}
targetElementForAction = foundElement
@ -236,17 +254,26 @@ extension AXorcist {
let targetElementForExtract: Element
if let actualLocator = locator {
dLog("[handleExtractText] Locator provided. Searching from current effectiveElement: \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)) using locator criteria: \(actualLocator.criteria)")
guard let foundElement = search(
let searchResult = search(
element: effectiveElement,
locator: actualLocator,
requireAction: nil,
depth: 0,
maxDepth: DEFAULT_MAX_DEPTH_SEARCH,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
) else {
isDebugLoggingEnabled: isDebugLoggingEnabled
)
fputs("HANDLER_RAW_STDERR_BEFORE_LOG_APPEND handleExtractText: searchResult.logs.count = \(searchResult.logs.count), currentDebugLogs count = \(currentDebugLogs.count)\n", stderr)
currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for extractText")
currentDebugLogs.append(contentsOf: searchResult.logs)
fputs("HANDLER_RAW_STDERR_AFTER_LOG_APPEND handleExtractText: currentDebugLogs count = \(currentDebugLogs.count)\n", stderr)
currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_EXTRACT_TEXT")
guard let foundElement = searchResult.foundElement else {
let errorMessage = "[handleExtractText] Target element not found for locator: \(actualLocator) starting from \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
currentDebugLogs.append(errorMessage)
if !currentDebugLogs.contains(errorMessage) {
currentDebugLogs.append(errorMessage)
}
return HandlerResponse(data: nil, error: errorMessage, debug_logs: currentDebugLogs)
}
targetElementForExtract = foundElement

View File

@ -173,7 +173,7 @@ extension AXorcist {
// This is distinct from commands axorc itself might handle outside of AXorcist library.
// @unknown default: // This would be better if Swift enums allowed it easily here for non-frozen enums from other modules.
// Since CommandType is in axorc, this default captures any CommandType case not explicitly handled above.
default:
@unknown default:
let errorMsg =
"Unknown or unhandled command type '\(subCommandEnvelope.command)' in batch processing within AXorcist (sub-command ID: \(subCmdID))"
dLog(errorMsg, subCommandID: subCmdID)

View File

@ -124,9 +124,20 @@ extension AXorcist {
}
if let loc = locator {
dLog("Locator provided. Searching for element from current startElement: \(startElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) with locator criteria: \(String(describing: loc.criteria))")
if let locatedStartElement = search(element: startElement, locator: loc, requireAction: loc.requireAction, depth: 0, maxDepth: Self.defaultMaxDepthSearch, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs) {
dLog("Locator found element: \(locatedStartElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)). This will be the root for collectAll recursion.")
dLog("Locator provided. Searching for element from current startElement: \(startElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) with locator criteria: \(String(describing: loc.criteria))")
let searchResultCollectAll = search(element: startElement,
locator: loc,
requireAction: loc.requireAction,
depth: 0,
maxDepth: Self.defaultMaxDepthSearch,
isDebugLoggingEnabled: isDebugLoggingEnabled)
self.recursiveCallDebugLogs.append("HANDLER_DEBUG: searchResultCollectAll.logs.count = \(searchResultCollectAll.logs.count) before append for collectAll")
self.recursiveCallDebugLogs.append(contentsOf: searchResultCollectAll.logs)
self.recursiveCallDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_COLLECT_ALL")
if let locatedStartElement = searchResultCollectAll.foundElement {
dLog("Locator found element: \(locatedStartElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)). This will be the root for collectAll recursion.")
startElement = locatedStartElement
} else {
let errorMsg = "Failed to find element with provided locator criteria: \(String(describing: loc.criteria)). Cannot start collectAll."
@ -147,13 +158,13 @@ extension AXorcist {
collectRecursively = { axUIElement, currentDepth in
if currentDepth > recursionDepthLimit {
dLog(
"Reached recursionDepthLimit (\(recursionDepthLimit)) at element \(Element(axUIElement).briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)), stopping recursion for this branch."
"Reached recursionDepthLimit (\(recursionDepthLimit)) at element \(Element(axUIElement).briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)), stopping recursion for this branch."
)
return
}
let currentElement = Element(axUIElement)
dLog("Collecting element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)")
dLog("Collecting element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)")
let fetchedAttrs = getElementAttributes(
currentElement,
@ -182,36 +193,40 @@ extension AXorcist {
if childrenResult == .success, let children = childrenRef as? [AXUIElement] {
dLog(
"Element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) has \(children.count) children at depth \(currentDepth). Recursing."
"Element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) has \(children.count) children at depth \(currentDepth). Recursing."
)
for childElement in children {
collectRecursively(childElement, currentDepth + 1)
}
} else if childrenResult != .success {
dLog(
"Failed to get children for element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)): \(axErrorToString(childrenResult))"
"Failed to get children for element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)): \(axErrorToString(childrenResult))"
)
} else {
dLog(
"No children found for element \(currentElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)"
"No children found for element \(currentElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs)) at depth \(currentDepth)"
)
}
}
dLog(
"Starting recursive collection from start element: \(startElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs))"
"Starting recursive collection from start element: \(startElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &self.recursiveCallDebugLogs))"
)
collectRecursively(startElement.underlyingElement, 0)
dLog("Collection complete. Found \(collectedAXElements.count) elements.")
// Start recursion from the determined startElement
if !self.recursiveCallDebugLogs.contains(where: { $0.contains("Failed to find element with provided locator criteria") && $0.contains("Cannot start collectAll") }) {
// Only start if locator search (if any) didn't critically fail and try to return early.
collectRecursively(startElement.underlyingElement, 0)
}
return encode(CollectAllOutput(
let output = CollectAllOutput(
command_id: effectiveCommandId,
success: true,
success: true, // Assuming success if we reach here, errors would have returned earlier
command: "collectAll",
collected_elements: collectedAXElements,
app_bundle_id: appIdentifier,
debug_logs: self.recursiveCallDebugLogs
))
)
return encode(output)
}
}

View File

@ -26,7 +26,6 @@ extension AXorcist {
let appIdentifier = appIdentifierOrNil ?? self.focusedAppKeyValue
dLog("Handling query for app: \(appIdentifier)")
// Pass logging parameters to applicationElement
guard let appElement = applicationElement(
for: appIdentifier,
isDebugLoggingEnabled: isDebugLoggingEnabled,
@ -42,7 +41,6 @@ extension AXorcist {
var effectiveElement = appElement
if let pathHint = pathHint, !pathHint.isEmpty {
dLog("Navigating with path_hint: \(pathHint.joined(separator: " -> "))")
// Pass logging parameters to navigateToElement
if let navigatedElement = self.navigateToElement(
from: effectiveElement,
pathHint: pathHint,
@ -70,12 +68,11 @@ extension AXorcist {
foundElement = effectiveElement
} else {
dLog("Locator contains element-specific criteria or is complex. Proceeding with search.")
var searchStartElementForLocator = appElement
var searchStartElementForLocator = effectiveElement
if let rootPathHint = locator.root_element_path_hint, !rootPathHint.isEmpty {
dLog(
"Locator has root_element_path_hint: \(rootPathHint.joined(separator: " -> ")). Navigating from app element first."
)
// Pass logging parameters to navigateToElement
guard let containerElement = self.navigateToElement(
from: appElement,
pathHint: rootPathHint,
@ -90,31 +87,29 @@ extension AXorcist {
}
searchStartElementForLocator = containerElement
dLog(
"Searching with locator within container found by root_element_path_hint: \(searchStartElementForLocator.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
"Searching with locator within container found by root_element_path_hint: \(searchStartElementForLocator.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
)
} else {
searchStartElementForLocator = effectiveElement
dLog(
"Searching with locator from element (determined by main path_hint or app root): \(searchStartElementForLocator.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
"Searching with locator from element (determined by main path_hint or app root): \(searchStartElementForLocator.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
)
}
let finalSearchTarget = (pathHint != nil && !pathHint!.isEmpty) ? effectiveElement :
searchStartElementForLocator
// Pass logging parameters to search
foundElement = search(
element: finalSearchTarget,
let searchResult = search(
element: searchStartElementForLocator,
locator: locator,
requireAction: locator.requireAction,
depth: 0,
maxDepth: maxDepth ?? AXorcist.defaultMaxDepthSearch,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
isDebugLoggingEnabled: isDebugLoggingEnabled
)
currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for query")
currentDebugLogs.append(contentsOf: searchResult.logs)
currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_QUERY")
foundElement = searchResult.foundElement
}
if let elementToQuery = foundElement {
// Pass logging parameters to getElementAttributes
var attributes = getElementAttributes(
elementToQuery,
requestedAttributes: requestedAttributes ?? [],
@ -176,7 +171,6 @@ extension AXorcist {
)
}
// Find element to get attributes from
var effectiveElement = appElement
if let pathHint = pathHint, !pathHint.isEmpty {
dLog("handleGetAttributes: Navigating with path_hint: \(pathHint.joined(separator: " -> "))")
@ -199,20 +193,24 @@ extension AXorcist {
}
dLog(
"handleGetAttributes: Searching for element with locator: \(locator.criteria) from root: \(effectiveElement.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
"handleGetAttributes: Searching for element with locator: \(locator.criteria) from root: \(effectiveElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
)
let foundElement = search(
let searchResult = search(
element: effectiveElement,
locator: locator,
requireAction: locator.requireAction,
depth: 0,
maxDepth: maxDepth ?? AXorcist.defaultMaxDepthSearch,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
isDebugLoggingEnabled: isDebugLoggingEnabled
)
currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for getAttributes")
currentDebugLogs.append(contentsOf: searchResult.logs)
currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_GET_ATTRIBUTES")
let foundElement = searchResult.foundElement
if let elementToQuery = foundElement {
dLog(
"handleGetAttributes: Element found: \(elementToQuery.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)). Fetching attributes: \(requestedAttributes ?? ["all"])..."
"handleGetAttributes: Element found: \(elementToQuery.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)). Fetching attributes: \(requestedAttributes ?? ["all"])..."
)
var attributes = getElementAttributes(
elementToQuery,
@ -227,7 +225,7 @@ extension AXorcist {
attributes = encodeAttributesToJSONStringRepresentation(attributes)
}
dLog(
"Successfully fetched attributes for element \(elementToQuery.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))."
"Successfully fetched attributes for element \(elementToQuery.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))."
)
let axElement = AXElement(attributes: attributes)
@ -246,4 +244,102 @@ extension AXorcist {
)
}
}
@MainActor
public func handleDescribeElement(
for appIdentifierOrNil: String?,
locator: Locator,
pathHint: [String]?,
maxDepth: Int?,
requestedAttributes: [String]?,
outputFormat: OutputFormat?,
isDebugLoggingEnabled: Bool,
currentDebugLogs: inout [String]
) async -> HandlerResponse {
func dLog(_ message: String) { if isDebugLoggingEnabled { currentDebugLogs.append(message) } }
let appIdentifier = appIdentifierOrNil ?? self.focusedAppKeyValue
dLog("Handling describe_element for app: \(appIdentifier)")
guard let appElement = applicationElement(
for: appIdentifier,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
) else {
return HandlerResponse(
data: nil,
error: "Application not found: \(appIdentifier)",
debug_logs: isDebugLoggingEnabled ? currentDebugLogs : nil
)
}
var effectiveElement = appElement
if let pathHint = pathHint, !pathHint.isEmpty {
dLog("handleDescribeElement: Navigating with path_hint: \(pathHint.joined(separator: " -> "))")
if let navigatedElement = self.navigateToElement(
from: appElement,
pathHint: pathHint,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
) {
effectiveElement = navigatedElement
} else {
let errorMessage = "Element not found via path hint for describe: \(pathHint.joined(separator: " -> "))"
dLog("handleDescribeElement: \(errorMessage)")
return HandlerResponse(
data: nil,
error: errorMessage,
debug_logs: isDebugLoggingEnabled ? currentDebugLogs : nil
)
}
}
dLog(
"[AXorcist.handleDescribeElement] Searching for element to describe using locator: \(locator.criteria) from effective element: \(effectiveElement.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))"
)
let searchMaxDepth = maxDepth ?? AXorcist.defaultMaxDepthSearch
let searchResult = search(
element: effectiveElement,
locator: locator,
requireAction: locator.requireAction,
depth: 0,
maxDepth: searchMaxDepth,
isDebugLoggingEnabled: isDebugLoggingEnabled
)
currentDebugLogs.append("HANDLER_DEBUG: searchResult.logs.count = \(searchResult.logs.count) before append for describeElement")
currentDebugLogs.append(contentsOf: searchResult.logs)
currentDebugLogs.append("POST_SEARCH_LOG_APPEND_MARKER_IN_DESCRIBE")
guard let elementToDescribe = searchResult.foundElement else {
let error = "[AXorcist.handleDescribeElement] Element to describe not found for locator: \(locator.criteria)"
currentDebugLogs.append(error)
return HandlerResponse(data: nil, error: error, debug_logs: currentDebugLogs)
}
dLog(
"[AXorcist.handleDescribeElement] Element found: \(elementToDescribe.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)). Now describing."
)
var attributes = getElementAttributes(
elementToDescribe,
requestedAttributes: requestedAttributes ?? ["all"],
forMultiDefault: true,
targetRole: locator.criteria[kAXRoleAttribute],
outputFormat: outputFormat ?? .verbose,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
)
if outputFormat == .json_string {
attributes = encodeAttributesToJSONStringRepresentation(attributes)
}
let axElement = AXElement(
attributes: attributes,
path: elementToDescribe.generatePathArray(upTo: appElement, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs)
)
return HandlerResponse(data: axElement, error: nil, debug_logs: currentDebugLogs)
}
}

View File

@ -82,14 +82,15 @@ extension AXorcist {
dLog(
"[AXorcist.handleGetAttributes] Not an AXApplication-only locator or value mismatch. Searching for element with locator: \(locator.criteria) from root: \(rootElementDescription)"
)
elementToQuery = search(
let searchResultGetAttributes = search(
element: effectiveElement,
locator: locator,
requireAction: locator.requireAction,
maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
isDebugLoggingEnabled: isDebugLoggingEnabled
)
currentDebugLogs.append(contentsOf: searchResultGetAttributes.logs)
elementToQuery = searchResultGetAttributes.foundElement
}
if let actualElementToQuery = elementToQuery {
@ -243,14 +244,15 @@ extension AXorcist {
let finalSearchTarget = (pathHint != nil && !pathHint!.isEmpty) ? effectiveElement :
searchStartElementForLocator
foundElement = search(
let searchResultQuery = search(
element: finalSearchTarget,
locator: locator,
requireAction: locator.requireAction,
maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
isDebugLoggingEnabled: isDebugLoggingEnabled
)
currentDebugLogs.append(contentsOf: searchResultQuery.logs)
foundElement = searchResultQuery.foundElement
}
if let elementToQuery = foundElement {
@ -350,18 +352,19 @@ extension AXorcist {
dLog(
"[AXorcist.handleDescribeElement] Searching for element with locator: \(locator.criteria) from root: \(rootElementDescription)"
)
let foundElement = search(
let searchResultDescribe = search(
element: effectiveElement,
locator: locator,
requireAction: locator.requireAction,
maxDepth: maxDepth ?? DEFAULT_MAX_DEPTH_SEARCH,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
isDebugLoggingEnabled: isDebugLoggingEnabled
)
currentDebugLogs.append(contentsOf: searchResultDescribe.logs)
let foundElementForDescribe = searchResultDescribe.foundElement
if let elementToDescribe = foundElement {
if let elementToDescribe = foundElementForDescribe {
let elementDescription = elementToDescribe.briefDescription(
option: .default,
option: ValueFormatOption.default,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
)
@ -374,9 +377,9 @@ extension AXorcist {
var attributes = getElementAttributes(
elementToDescribe,
requestedAttributes: [], // Empty means 'all standard' or 'all known'
forMultiDefault: false,
forMultiDefault: true,
targetRole: locator.criteria[kAXRoleAttribute],
outputFormat: .verbose, // Describe implies verbose
outputFormat: outputFormat ?? .smart,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
)
@ -393,7 +396,7 @@ extension AXorcist {
let axElement = AXElement(attributes: attributes, path: elementPathArray)
dLog(
"[AXorcist.handleDescribeElement] Successfully described element \(elementToDescribe.briefDescription(option: .default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))."
"[AXorcist.handleDescribeElement] Successfully described element \(elementToDescribe.briefDescription(option: ValueFormatOption.default, isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &currentDebugLogs))."
)
return HandlerResponse(data: axElement, error: nil, debug_logs: currentDebugLogs)
} else {

View File

@ -1,6 +1,7 @@
// ElementSearch.swift - Contains search and element collection logic
import ApplicationServices
import Darwin
import Foundation
// Variable DEBUG_LOGGING_ENABLED is expected to be globally available from Logging.swift
@ -97,14 +98,28 @@ public func search(element: Element,
requireAction: String?,
depth: Int = 0,
maxDepth: Int = DEFAULT_MAX_DEPTH_SEARCH,
isDebugLoggingEnabled: Bool,
currentDebugLogs: inout [String]) -> Element? {
func dLog(_ message: String) { if isDebugLoggingEnabled { currentDebugLogs.append(message) } }
var tempLogs: [String] = [] // For calls to Element methods
isDebugLoggingEnabled: Bool
/* REMOVED: currentDebugLogs: inout [String] */) -> (foundElement: Element?, logs: [String]) { // CHANGED RETURN TYPE
fputs("SEARCH_FUNCTION_RAW_PRINT_STDERR: Depth \(depth), isDebug: \(isDebugLoggingEnabled)\n", stderr)
var internalSearchLogs: [String] = [] // NEW: Internal log storage
// DIRECT APPEND AND LOCAL LET FOR DEBUG FLAG
internalSearchLogs.append("SEARCH_ENTRY_DIRECT_APPEND: Depth \(depth), isDebugLoggingEnabledParam: \(isDebugLoggingEnabled)")
let localDebugEnabled = isDebugLoggingEnabled
internalSearchLogs.append("SEARCH_ENTRY_LOCALDEBUG: localDebugEnabled is \(localDebugEnabled)")
func dLog(_ message: String) { if localDebugEnabled { internalSearchLogs.append(message) } } // Appends to internalSearchLogs
var tempLogsForElementMethods: [String] = [] // For calls to Element methods that require inout logs
let criteriaDesc = locator.criteria.map { "\($0.key)=\($0.value)" }.joined(separator: ", ")
let roleStr = element.role(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogs) ?? "nil"
let titleStr = element.title(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogs) ?? "N/A"
// Calls to element.role, element.title etc. will use tempLogsForElementMethods
// Their logs aren't the primary concern for *this* refactor's test, but they need a valid inout array.
let roleStr = element.role(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogsForElementMethods) ?? "nil"
let titleStr = element.title(isDebugLoggingEnabled: isDebugLoggingEnabled, currentDebugLogs: &tempLogsForElementMethods) ?? "N/A"
internalSearchLogs.append(contentsOf: tempLogsForElementMethods) // Append logs from element methods
tempLogsForElementMethods.removeAll() // Clear for next use
dLog(
"search [D\(depth)]: Visiting. Role: \(roleStr), Title: \(titleStr). Locator Criteria: [\(criteriaDesc)], Action: \(requireAction ?? "none")"
)
@ -113,36 +128,46 @@ public func search(element: Element,
let briefDesc = element.briefDescription(
option: .default,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &tempLogs
currentDebugLogs: &tempLogsForElementMethods
)
internalSearchLogs.append(contentsOf: tempLogsForElementMethods)
tempLogsForElementMethods.removeAll()
dLog("search [D\(depth)]: Max depth \(maxDepth) reached for element \(briefDesc).")
return nil
return (nil, internalSearchLogs) // RETURN TUPLE
}
// evaluateElementAgainstCriteria still uses inout, its logs will be added to internalSearchLogs
var logsFromEvaluate: [String] = []
let matchStatus = evaluateElementAgainstCriteria(element: element,
locator: locator,
actionToVerify: requireAction,
depth: depth,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs) // Pass through logs
currentDebugLogs: &logsFromEvaluate)
internalSearchLogs.append(contentsOf: logsFromEvaluate)
if matchStatus == .fullMatch {
let briefDesc = element.briefDescription(
option: .default,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &tempLogs
currentDebugLogs: &tempLogsForElementMethods
)
internalSearchLogs.append(contentsOf: tempLogsForElementMethods)
tempLogsForElementMethods.removeAll()
dLog(
"search [D\(depth)]: evaluateElementAgainstCriteria returned .fullMatch for \(briefDesc). Returning element."
)
return element
return (element, internalSearchLogs) // RETURN TUPLE
}
let briefDesc = element.briefDescription(
option: .default,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &tempLogs
currentDebugLogs: &tempLogsForElementMethods
)
internalSearchLogs.append(contentsOf: tempLogsForElementMethods)
tempLogsForElementMethods.removeAll()
if matchStatus == .partialMatch_actionMissing {
dLog(
"search [D\(depth)]: Element \(briefDesc) matched criteria but missed action '\(requireAction ?? "")'. Continuing child search."
@ -154,25 +179,31 @@ public func search(element: Element,
let childrenToSearch: [Element] = element.children(
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &tempLogs
currentDebugLogs: &tempLogsForElementMethods // Pass tempLogsForElementMethods here
) ?? []
internalSearchLogs.append(contentsOf: tempLogsForElementMethods)
tempLogsForElementMethods.removeAll()
if !childrenToSearch.isEmpty {
for childElement in childrenToSearch {
if let found = search(
// RECURSIVE CALL
let recursiveResult = search( // No longer passes currentDebugLogs
element: childElement,
locator: locator,
requireAction: requireAction,
depth: depth + 1,
maxDepth: maxDepth,
isDebugLoggingEnabled: isDebugLoggingEnabled,
currentDebugLogs: &currentDebugLogs
) {
return found
isDebugLoggingEnabled: isDebugLoggingEnabled
// Removed: currentDebugLogs: &currentDebugLogs -> now &internalSearchLogs
)
internalSearchLogs.append(contentsOf: recursiveResult.logs) // Append logs from recursive call
if let found = recursiveResult.foundElement {
return (found, internalSearchLogs) // RETURN TUPLE
}
}
}
return nil
return (nil, internalSearchLogs) // RETURN TUPLE
}
@MainActor

View File

@ -3,6 +3,12 @@
import AXorcistLib
import Foundation
// TEMPORARY TEST STRUCT - REMOVED
// struct SimpleTestResponse: Codable {
// var message: String
// var logs: [String]?
// }
struct CommandExecutor {
static func execute(
@ -12,7 +18,10 @@ struct CommandExecutor {
) async -> String {
var localDebugLogs: [String] = []
if debug { localDebugLogs.append("Executing command: \(command.command) (ID: \(command.command_id))") }
// Determine the effective debug logging state
let effectiveDebugLogging = command.debug_logging ?? debug
if effectiveDebugLogging { localDebugLogs.append("Executing command: \(command.command) (ID: \(command.command_id)), effectiveDebug: \(effectiveDebugLogging), cliDebug: \(debug), jsonDebug: \(String(describing: command.debug_logging))") }
let ax = axorcist // Use the passed-in instance
@ -36,7 +45,7 @@ struct CommandExecutor {
actionName: actionName,
actionValue: command.action_value,
maxDepth: command.max_elements,
isDebugLoggingEnabled: debug,
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: &localDebugLogs
)
let queryResponse = QueryResponse(
@ -44,7 +53,7 @@ struct CommandExecutor {
success: handlerResponse.error == nil,
command: command.command.rawValue,
handlerResponse: handlerResponse,
debug_logs: debug ? localDebugLogs : nil
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)
return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding performAction response failed\"}"
@ -52,7 +61,7 @@ struct CommandExecutor {
let handlerResponse: HandlerResponse = await ax.handleGetFocusedElement(
for: command.application,
requestedAttributes: command.attributes,
isDebugLoggingEnabled: debug,
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: &localDebugLogs
)
let queryResponse = QueryResponse(
@ -60,7 +69,7 @@ struct CommandExecutor {
success: handlerResponse.error == nil,
command: command.command.rawValue,
handlerResponse: handlerResponse,
debug_logs: debug ? localDebugLogs : nil
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)
return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding getFocusedElement response failed\"}"
@ -83,7 +92,7 @@ struct CommandExecutor {
pathHint: command.path_hint,
maxDepth: command.max_elements,
outputFormat: command.output_format,
isDebugLoggingEnabled: debug,
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: &localDebugLogs
)
let queryResponse = QueryResponse(
@ -91,7 +100,7 @@ struct CommandExecutor {
success: handlerResponse.error == nil,
command: command.command.rawValue,
handlerResponse: handlerResponse,
debug_logs: debug ? localDebugLogs : nil
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)
return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding getAttributes response failed\"}"
@ -114,7 +123,7 @@ struct CommandExecutor {
maxDepth: command.max_elements,
requestedAttributes: command.attributes,
outputFormat: command.output_format,
isDebugLoggingEnabled: debug,
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: &localDebugLogs
)
let queryResponse = QueryResponse(
@ -122,7 +131,7 @@ struct CommandExecutor {
success: handlerResponse.error == nil,
command: command.command.rawValue,
handlerResponse: handlerResponse,
debug_logs: debug ? localDebugLogs : nil
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)
return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding query response failed\"}"
@ -144,7 +153,7 @@ struct CommandExecutor {
pathHint: command.path_hint,
maxDepth: command.max_elements,
outputFormat: command.output_format,
isDebugLoggingEnabled: debug,
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: &localDebugLogs
)
let queryResponse = QueryResponse(
@ -152,7 +161,7 @@ struct CommandExecutor {
success: handlerResponse.error == nil,
command: command.command.rawValue,
handlerResponse: handlerResponse,
debug_logs: debug ? localDebugLogs : nil
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)
return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding describeElement response failed\"}"
@ -172,7 +181,7 @@ struct CommandExecutor {
for: command.application,
locator: locator,
pathHint: command.path_hint,
isDebugLoggingEnabled: debug,
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: &localDebugLogs
)
let queryResponse = QueryResponse(
@ -180,14 +189,11 @@ struct CommandExecutor {
success: handlerResponse.error == nil,
command: command.command.rawValue,
handlerResponse: handlerResponse,
debug_logs: debug ? localDebugLogs : nil
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)
return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding extractText response failed\"}"
case .collectAll:
// AXorcist.handleCollectAll returns a String (JSON) directly.
// It manages its own debug logs internally via recursiveCallDebugLogs.
// The `currentDebugLogs` passed to it is for initial logs only.
let jsonStringResult = await ax.handleCollectAll(
for: command.application,
locator: command.locator,
@ -196,64 +202,59 @@ struct CommandExecutor {
requestedAttributes: command.attributes,
outputFormat: command.output_format,
commandId: command.command_id,
isDebugLoggingEnabled: debug,
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: localDebugLogs
)
return jsonStringResult
case .batch:
guard let subCommands = command.sub_commands else {
let error = "Missing sub_commands for batch"
let error = "Missing sub_commands for batch command"
localDebugLogs.append(error)
return encodeToJson(QueryResponse(
return encodeToJson(BatchResponse(
command_id: command.command_id,
success: false,
commandId: command.command_id,
command: command.command.rawValue,
results: [],
error: error,
debugLogs: debug ? localDebugLogs : nil
)) ?? "{\"error\": \"Encoding error response failed\"}"
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)) ?? "{\"error\": \"Encoding batch error response failed\"}"
}
let batchHandlerResponses: [HandlerResponse] = await ax.handleBatchCommands(
var batchDebugLogs = localDebugLogs
let batchResults: [HandlerResponse] = await ax.handleBatchCommands(
batchCommandID: command.command_id,
subCommands: subCommands,
isDebugLoggingEnabled: debug,
currentDebugLogs: &localDebugLogs
isDebugLoggingEnabled: effectiveDebugLogging,
currentDebugLogs: &batchDebugLogs
)
// Convert [HandlerResponse] to an array of QueryResponse objects
let queryResponses = batchHandlerResponses.enumerated().map { index, hr -> QueryResponse in
let subCommandId = index < subCommands.count ? subCommands[index].command_id : "batch_sub_\(index)"
let subCommand = index < subCommands.count ? subCommands[index].command.rawValue : "unknown"
return QueryResponse(
command_id: subCommandId,
success: hr.error == nil,
command: subCommand,
handlerResponse: hr,
debug_logs: hr.debug_logs
)
}
// Determine overall success of the batch
let overallSuccess = batchHandlerResponses.allSatisfy { $0.error == nil }
// Return BatchOperationResponse
let batchResponse = BatchOperationResponse(
let overallSuccess = batchResults.allSatisfy { $0.error == nil }
let batchResponse = BatchResponse(
command_id: command.command_id,
success: overallSuccess,
results: queryResponses,
debug_logs: debug ? localDebugLogs : nil
results: batchResults,
error: nil,
debug_logs: effectiveDebugLogging ? batchDebugLogs : nil
)
return encodeToJson(batchResponse) ?? "{\"error\": \"Encoding batch response failed\"}"
case .ping:
let appName = command.application ?? "N/A"
let msg = "Ping received for \(appName). AXORC Version: \(AXORC_VERSION)"
localDebugLogs.append(msg)
return encodeToJson(SimpleSuccessResponse(
if effectiveDebugLogging { localDebugLogs.append("Ping command received. Responding with pong.") }
// Create an empty HandlerResponse for ping
let pingHandlerResponse = HandlerResponse(
data: nil,
error: nil,
debug_logs: nil // Ping-specific logs are already in localDebugLogs
)
// Construct QueryResponse using the HandlerResponse initializer
let queryResponse = QueryResponse(
command_id: command.command_id,
success: true,
status: "pong",
message: msg,
details: nil,
debug_logs: debug ? localDebugLogs : nil
)) ?? "{\"error\": \"Encoding ping response failed\"}"
success: true, // Ping is always a success if reached
command: command.command.rawValue,
handlerResponse: pingHandlerResponse,
debug_logs: effectiveDebugLogging ? localDebugLogs : nil
)
return encodeToJson(queryResponse) ?? "{\"error\": \"Encoding ping response failed\"}"
}
}
@ -265,6 +266,9 @@ struct CommandExecutor {
let data = try encoder.encode(object)
return String(data: data, encoding: .utf8)
} catch {
// PRINT THE ERROR TO STDERR
let errorDescription = "JSON ENCODING ERROR: \(error.localizedDescription). Details: \(error)"
FileHandle.standardError.write(errorDescription.data(using: .utf8)!)
return nil
}
}