UTMRegistry.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. //
  2. // Copyright © 2022 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import Combine
  17. import Foundation
  18. class UTMRegistry: NSObject {
  19. @objc static let shared = UTMRegistry()
  20. private var serializedEntries: [String: Any] {
  21. get {
  22. UserDefaults.standard.dictionary(forKey: "Registry") ?? [:]
  23. }
  24. set {
  25. UserDefaults.standard.setValue(newValue, forKey: "Registry")
  26. }
  27. }
  28. private var registryListener: AnyCancellable?
  29. private var changeListeners: [String: AnyCancellable] = [:]
  30. @Published private var entries: [String: UTMRegistryEntry] {
  31. didSet {
  32. let toAdd = entries.keys.filter({ !changeListeners.keys.contains($0) })
  33. for key in toAdd {
  34. let entry = entries[key]!
  35. changeListeners[key] = entry.objectWillChange
  36. .debounce(for: .seconds(1), scheduler: DispatchQueue.global(qos: .utility))
  37. .sink { [weak self, weak entry] in
  38. if let self = self, let entry = entry {
  39. self.commit(entry: entry, to: &self.serializedEntries)
  40. }
  41. }
  42. }
  43. let toRemove = changeListeners.keys.filter({ !entries.keys.contains($0) })
  44. for key in toRemove {
  45. changeListeners.removeValue(forKey: key)
  46. }
  47. }
  48. }
  49. private override init() {
  50. entries = [:]
  51. super.init()
  52. if let newEntries = try? serializedEntries.mapValues({ value in
  53. let dict = value as! [String: Any]
  54. return try UTMRegistryEntry(fromPropertyList: dict)
  55. }) {
  56. entries = newEntries
  57. }
  58. registryListener = $entries
  59. .debounce(for: .seconds(1), scheduler: DispatchQueue.global(qos: .utility))
  60. .sink { [weak self] newEntries in
  61. self?.commitAll(entries: newEntries)
  62. }
  63. }
  64. /// Gets an existing registry entry or create a new entry
  65. /// - Parameter vm: UTM virtual machine to locate in the registry
  66. /// - Returns: Either an existing registry entry or a new entry
  67. func entry(for vm: any UTMVirtualMachine) -> UTMRegistryEntry {
  68. if let entry = entries[vm.id.uuidString] {
  69. return entry
  70. }
  71. let newEntry = UTMRegistryEntry(newFrom: vm)
  72. entries[newEntry.uuid.uuidString] = newEntry
  73. return newEntry
  74. }
  75. /// Gets an existing registry entry or create a new entry for a legacy bookmark
  76. /// - Parameters:
  77. /// - uuid: UUID
  78. /// - name: VM name
  79. /// - path: VM path string
  80. /// - bookmark: VM bookmark
  81. /// - Returns: Either an existing registry entry or a new entry
  82. func entry(uuid: UUID, name: String, path: String, bookmark: Data? = nil) -> UTMRegistryEntry {
  83. if let entry = entries[uuid.uuidString] {
  84. return entry
  85. }
  86. let newEntry = UTMRegistryEntry(uuid: uuid, name: name, path: path, bookmark: bookmark)
  87. entries[uuid.uuidString] = newEntry
  88. return newEntry
  89. }
  90. /// Get an existing registry entry for a UUID
  91. /// - Parameter uuidString: UUID
  92. /// - Returns: An existing registry entry or nil if it does not exist
  93. func entry(for uuidString: String) -> UTMRegistryEntry? {
  94. return entries[uuidString]
  95. }
  96. /// Commit the entry to persistent storage
  97. /// This runs in a background queue.
  98. /// - Parameter entry: Entry to commit
  99. private func commit(entry: UTMRegistryEntry, to entries: inout [String: Any]) {
  100. let uuid = entry.uuid
  101. if let dict = try? entry.asDictionary() {
  102. entries[uuid.uuidString] = dict
  103. } else {
  104. logger.error("Failed to commit entry for \(uuid)")
  105. }
  106. }
  107. /// Commit all entries to persistent storage
  108. /// This runs in a background queue.
  109. /// - Parameter entries: All entries to commit
  110. private func commitAll(entries: [String: UTMRegistryEntry]) {
  111. var newSerializedEntries: [String: Any] = [:]
  112. for key in entries.keys {
  113. let entry = entries[key]!
  114. commit(entry: entry, to: &newSerializedEntries)
  115. }
  116. serializedEntries = newSerializedEntries
  117. }
  118. /// Remove an entry from the registry
  119. /// - Parameter entry: Entry to remove
  120. func remove(entry: UTMRegistryEntry) {
  121. entries.removeValue(forKey: entry.uuid.uuidString)
  122. }
  123. /// Remove all entries from the registry except for the specified set
  124. /// - Parameter uuidStrings: Keys to NOT remove
  125. func prune(exceptFor uuidStrings: Set<String>) {
  126. for key in entries.keys {
  127. if !uuidStrings.contains(key) {
  128. entries.removeValue(forKey: key)
  129. }
  130. }
  131. }
  132. /// Make sure the registry is synchronized when UTM terminates
  133. func sync() {
  134. commitAll(entries: entries)
  135. }
  136. }