2
0

UTMConfigurationInfo.swift 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 Foundation
  17. /// Basic information about the VM only used in listing and presenting.
  18. struct UTMConfigurationInfo: Codable {
  19. /// VM name displayed to user.
  20. var name: String = NSLocalizedString("Virtual Machine", comment: "UTMConfigurationInfo")
  21. /// Path to the icon.
  22. var iconURL: URL?
  23. /// If true, the icon is stored in the bundle. Otherwise, the icon is built-in.
  24. var isIconCustom: Bool = false
  25. /// User specified notes to be displayed when the VM is selected.
  26. var notes: String?
  27. /// Random identifier not accessible by the user.
  28. var uuid: UUID = UUID()
  29. enum CodingKeys: String, CodingKey {
  30. case name = "Name"
  31. case icon = "Icon"
  32. case isIconCustom = "IconCustom"
  33. case notes = "Notes"
  34. case uuid = "UUID"
  35. }
  36. init() {
  37. }
  38. init(from decoder: Decoder) throws {
  39. let values = try decoder.container(keyedBy: CodingKeys.self)
  40. name = try values.decode(String.self, forKey: .name)
  41. isIconCustom = try values.decode(Bool.self, forKey: .isIconCustom)
  42. if isIconCustom {
  43. guard let dataURL = decoder.userInfo[.dataURL] as? URL else {
  44. throw UTMConfigurationError.invalidDataURL
  45. }
  46. let iconName = try values.decode(String.self, forKey: .icon)
  47. iconURL = dataURL.appendingPathComponent(iconName)
  48. } else if let iconName = try values.decodeIfPresent(String.self, forKey: .icon) {
  49. iconURL = Self.builtinIcon(named: iconName)
  50. }
  51. notes = try values.decodeIfPresent(String.self, forKey: .notes)
  52. uuid = try values.decode(UUID.self, forKey: .uuid)
  53. }
  54. func encode(to encoder: Encoder) throws {
  55. var container = encoder.container(keyedBy: CodingKeys.self)
  56. try container.encode(name, forKey: .name)
  57. if isIconCustom, let iconURL = iconURL {
  58. try container.encode(true, forKey: .isIconCustom)
  59. try container.encode(iconURL.lastPathComponent, forKey: .icon)
  60. } else if !isIconCustom, let name = iconURL?.deletingPathExtension().lastPathComponent {
  61. try container.encode(false, forKey: .isIconCustom)
  62. try container.encode(name, forKey: .icon)
  63. } else {
  64. try container.encode(false, forKey: .isIconCustom)
  65. }
  66. try container.encodeIfPresent(notes, forKey: .notes)
  67. try container.encode(uuid, forKey: .uuid)
  68. }
  69. static func builtinIcon(named name: String) -> URL? {
  70. Bundle.main.url(forResource: name, withExtension: "png", subdirectory: "Icons")
  71. }
  72. }
  73. // MARK: - Conversion of old config format
  74. extension UTMConfigurationInfo {
  75. init(migrating oldConfig: UTMLegacyQemuConfiguration) {
  76. self.init()
  77. name = oldConfig.name
  78. notes = oldConfig.notes
  79. if let uuidString = oldConfig.systemUUID, let uuid = UUID(uuidString: uuidString) {
  80. self.uuid = uuid
  81. }
  82. isIconCustom = oldConfig.iconCustom
  83. if isIconCustom {
  84. if let name = oldConfig.icon, let dataURL = oldConfig.existingPath {
  85. iconURL = dataURL.appendingPathComponent(name)
  86. } else {
  87. isIconCustom = false
  88. }
  89. }
  90. if !isIconCustom, let name = oldConfig.icon {
  91. iconURL = Self.builtinIcon(named: name)
  92. }
  93. }
  94. #if os(macOS)
  95. init(migrating oldConfig: UTMLegacyAppleConfiguration, dataURL: URL) {
  96. self.init()
  97. name = oldConfig.name
  98. notes = oldConfig.notes
  99. uuid = UUID()
  100. isIconCustom = oldConfig.iconCustom
  101. if isIconCustom {
  102. if let name = oldConfig.icon {
  103. iconURL = dataURL.appendingPathComponent(name)
  104. } else {
  105. isIconCustom = false
  106. }
  107. }
  108. if let name = oldConfig.icon {
  109. iconURL = Self.builtinIcon(named: name)
  110. }
  111. }
  112. #endif
  113. }
  114. // MARK: - Saving data
  115. extension UTMConfigurationInfo {
  116. @MainActor mutating func saveData(to dataURL: URL) async throws -> [URL] {
  117. // save new icon
  118. if isIconCustom, let iconURL = iconURL {
  119. let newIconURL = try await UTMQemuConfiguration.copyItemIfChanged(from: iconURL, to: dataURL)
  120. self.iconURL = newIconURL
  121. return [newIconURL]
  122. }
  123. return []
  124. }
  125. }