|
| 1 | +import ActiveApplicationMonitor |
| 2 | +import AppKit |
| 3 | +import AXExtension |
1 | 4 | import Foundation |
| 5 | +import Logger |
2 | 6 |
|
3 | 7 | public final class ScheduledCleaner { |
4 | 8 | public init() { |
5 | 9 | // occasionally cleanup workspaces. |
6 | 10 | Task { @ServiceActor in |
7 | 11 | while !Task.isCancelled { |
8 | | - try await Task.sleep(nanoseconds: 2 * 60 * 60 * 1_000_000_000) |
| 12 | + try await Task.sleep(nanoseconds: 4 * 60 * 60 * 1_000_000_000) |
| 13 | + let availableTabs = findAvailableOpenedTabs() |
9 | 14 | for (url, workspace) in workspaces { |
10 | 15 | if workspace.isExpired { |
| 16 | + Logger.service.info("Remove idle workspace") |
| 17 | + for url in workspace.filespaces.keys { |
| 18 | + WidgetDataSource.shared.cleanup(for: url) |
| 19 | + } |
| 20 | + workspace.cleanUp(availableTabs: availableTabs) |
11 | 21 | workspaces[url] = nil |
12 | 22 | } else { |
13 | 23 | // cleanup chats for unused files |
14 | 24 | let filespaces = workspace.filespaces |
15 | | - for (url, filespace) in filespaces { |
16 | | - if filespace.isExpired { |
| 25 | + for (url, _) in filespaces { |
| 26 | + if workspace.isFilespaceExpired( |
| 27 | + fileURL: url, |
| 28 | + availableTabs: availableTabs |
| 29 | + ) { |
| 30 | + Logger.service.info("Remove idle filespace") |
17 | 31 | WidgetDataSource.shared.cleanup(for: url) |
18 | 32 | } |
19 | 33 | } |
20 | 34 | // cleanup workspace |
21 | | - workspace.cleanUp() |
| 35 | + workspace.cleanUp(availableTabs: availableTabs) |
22 | 36 | } |
23 | 37 | } |
24 | 38 | } |
25 | 39 | } |
26 | 40 | } |
| 41 | + |
| 42 | + func findAvailableOpenedTabs() -> Set<String> { |
| 43 | + guard let xcode = ActiveApplicationMonitor.latestXcode else { return [] } |
| 44 | + let app = AXUIElementCreateApplication(xcode.processIdentifier) |
| 45 | + let windows = app.windows.filter { $0.identifier == "Xcode.WorkspaceWindow" } |
| 46 | + guard !windows.isEmpty else { return [] } |
| 47 | + var allTabs = Set<String>() |
| 48 | + for window in windows { |
| 49 | + guard let editArea = window.firstChild(where: { $0.description == "editor area" }) |
| 50 | + else { continue } |
| 51 | + let tabBars = editArea.children { $0.description == "tab bar" } |
| 52 | + for tabBar in tabBars { |
| 53 | + let tabs = tabBar.children { $0.roleDescription == "tab" } |
| 54 | + for tab in tabs { |
| 55 | + allTabs.insert(tab.title) |
| 56 | + } |
| 57 | + } |
| 58 | + } |
| 59 | + return allTabs |
| 60 | + } |
27 | 61 | } |
| 62 | + |
0 commit comments