Files
UTM/Scripting/UTMScriptingSnapshotImpl.swift
T
osy b1e1b12a05 scripting: model snapshots as scriptable objects
The snapshot commands could only name a snapshot and could not say anything
about it. A script could not tell when a snapshot was taken, how much it
holds, whether restoring it resumes the guest or starts it up, or whether
its data has gone missing, and there was no way to rename one or to throw
away a suspended state, both of which the window offers.

Make a snapshot an object instead. A virtual machine now has snapshots as
elements, each carrying those details, so a script can pick one out and
read it:

    tell application "UTM"
      set vm to virtual machine named "Debian"
      set candidates to (snapshots of vm whose includes running state)
      restore (item 1 of candidates)
    end tell

Renaming is setting the name, and the commands that act on a snapshot take
the snapshot itself. Creating one returns it. Two more commands cover what
the window can do and scripting could not: scanning the images for
snapshots made by other tools, and discarding a saved state so the guest
starts up instead of resuming.

This replaces the snapshot commands of the 5.0.5 beta, which addressed a
snapshot by name through its virtual machine, so scripts and utmctl calls
written against them have to be updated.

Snapshots are addressed by name, so names are now kept apart the way
virtual machine names are, by appending a number.

Deleting a snapshot is its own command rather than the standard delete,
which coerces a specifier for anything inside a virtual machine into the
virtual machine itself; deleting that would throw away far more than was
asked for. The delete command refuses such a specifier for the same reason.

UTMScripting.swift is regenerated from the sdef; only the hunks that
changed are taken.

Assisted-by: Claude:claude-opus-5
2026-09-22 10:57:36 -05:00

115 lines
3.5 KiB
Swift

//
// Copyright © 2026 Turing Software, LLC. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
@MainActor
@objc(UTMScriptingSnapshotImpl)
class UTMScriptingSnapshotImpl: NSObject, UTMScriptable {
/// Read when the object is resolved, which happens anew for every event
private var snapshot: UTMSnapshot
/// Held strongly because a command runs after the accessor that made this object has returned
@objc private(set) var parent: UTMScriptingVirtualMachineImpl
private var vm: (any UTMVirtualMachine)! {
parent.vm
}
init(snapshot: UTMSnapshot, parent: UTMScriptingVirtualMachineImpl) {
self.snapshot = snapshot
self.parent = parent
}
@objc var id: String {
snapshot.id.uuidString
}
@objc var name: String {
get {
snapshot.title
}
set {
do {
snapshot = try UTMSnapshotService.renameSnapshot(snapshot.id, to: newValue, on: vm)
} catch {
let command = NSScriptCommand.current()
command?.scriptErrorNumber = errOSAGeneralError
command?.scriptErrorString = error.localizedDescription
}
}
}
@objc var creationDate: Date {
snapshot.dateCreated
}
@objc var modificationDate: Date {
snapshot.dateModified
}
@objc var size: Int {
Int(snapshot.size)
}
@objc var includesRunningState: Bool {
snapshot.hasState
}
@objc var isDataMissing: Bool {
snapshot.isOrphaned
}
/// Identifier of the snapshot this one was based on, empty if it was not based on any
@objc var basedOn: String {
snapshot.parentID?.uuidString ?? ""
}
override var objectSpecifier: NSScriptObjectSpecifier? {
guard let parentDescription = parent.classDescription as? NSScriptClassDescription else {
return nil
}
return NSUniqueIDSpecifier(containerClassDescription: parentDescription,
containerSpecifier: parent.objectSpecifier,
key: "snapshots",
uniqueID: id)
}
// MARK: - Commands
@objc func restore(_ command: NSScriptCommand) {
withScriptCommand(command) { [self] in
try await UTMSnapshotService.restoreSnapshot(snapshot.id, on: vm)
}
}
@objc func overwrite(_ command: NSScriptCommand) {
withScriptCommand(command) { [self] in
try await UTMSnapshotService.overwriteSnapshot(snapshot.id, on: vm)
}
}
@objc func deleteSnapshot(_ command: NSScriptCommand) {
withScriptCommand(command) { [self] in
try await UTMSnapshotService.deleteSnapshot(snapshot.id, on: vm)
}
}
static func == (lhs: UTMScriptingSnapshotImpl, rhs: UTMScriptingSnapshotImpl) -> Bool {
lhs.snapshot.id == rhs.snapshot.id && lhs.parent == rhs.parent
}
}