import ActiveApplicationMonitor import AppKit import AXExtension import Foundation import Logger public final class ScheduledCleaner { public init() { // occasionally cleanup workspaces. Task { @ServiceActor in while !Task.isCancelled { try await Task.sleep(nanoseconds: 10 * 60 * 1_000_000_000) cleanUp() } } // cleanup when Xcode becomes inactive Task { @ServiceActor in for await app in ActiveApplicationMonitor.createStream() { try Task.checkCancellation() if let app, !app.isXcode { cleanUp() } } } } @ServiceActor func cleanUp() { let availableTabs = findAvailableOpenedTabs() for (url, workspace) in workspaces { if workspace.isExpired { Logger.service.info("Remove idle workspace") for url in workspace.filespaces.keys { WidgetDataSource.shared.cleanup(for: url) } workspace.cleanUp(availableTabs: availableTabs) workspaces[url] = nil } else { // cleanup chats for unused files let filespaces = workspace.filespaces for (url, _) in filespaces { if workspace.isFilespaceExpired( fileURL: url, availableTabs: availableTabs ) { Logger.service.info("Remove idle filespace") WidgetDataSource.shared.cleanup(for: url) } } // cleanup workspace workspace.cleanUp(availableTabs: availableTabs) } } } func findAvailableOpenedTabs() -> Set { guard let xcode = ActiveApplicationMonitor.latestXcode else { return [] } let app = AXUIElementCreateApplication(xcode.processIdentifier) let windows = app.windows.filter { $0.identifier == "Xcode.WorkspaceWindow" } guard !windows.isEmpty else { return [] } var allTabs = Set() for window in windows { guard let editArea = window.firstChild(where: { $0.description == "editor area" }) else { continue } let tabBars = editArea.children { $0.description == "tab bar" } for tabBar in tabBars { let tabs = tabBar.children { $0.roleDescription == "tab" } for tab in tabs { allTabs.insert(tab.title) } } } return allTabs } }