Skip to content

Commit 53d7766

Browse files
authored
Merge pull request #540 from insidegui/new-vm-thumbnail
Revamp virtual machine thumbnails
2 parents 55ae916 + 33392e1 commit 53d7766

File tree

63 files changed

+748
-480
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+748
-480
lines changed

VirtualBuddy.xcodeproj/project.pbxproj

Lines changed: 49 additions & 29 deletions
Large diffs are not rendered by default.

VirtualBuddyGuest/GuestAppDelegate.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import Cocoa
22
import SwiftUI
33
import VirtualUI
44
import VirtualWormhole
5+
import OSLog
56

67
@NSApplicationMain
78
final class GuestAppDelegate: NSObject, NSApplicationDelegate {
89

10+
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "Guest", category: "GuestAppDelegate")
11+
912
private lazy var launchAtLoginManager = GuestLaunchAtLoginManager()
1013

1114
private lazy var sharedFolders = GuestSharedFoldersManager()
@@ -40,6 +43,8 @@ final class GuestAppDelegate: NSObject, NSApplicationDelegate {
4043
}
4144
}
4245

46+
private var isPoweringOff = false
47+
4348
func applicationDidFinishLaunching(_ notification: Notification) {
4449
/// Skip regular app activation if installation is needed (i.e. running from disk image).
4550
guard !installer.needsInstall else { return }
@@ -55,6 +60,12 @@ final class GuestAppDelegate: NSObject, NSApplicationDelegate {
5560
dashboardItem.install()
5661

5762
perform(#selector(showPanelForFirstLaunchIfNeeded), with: nil, afterDelay: 0.5)
63+
64+
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willPowerOffNotification, object: nil, queue: nil) { _ in
65+
self.logger.notice("Received power off notification.")
66+
67+
self.isPoweringOff = true
68+
}
5869
}
5970

6071
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
@@ -76,4 +87,19 @@ final class GuestAppDelegate: NSObject, NSApplicationDelegate {
7687
dashboardItem.showPanel()
7788
}
7889

90+
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
91+
logger.debug(#function)
92+
93+
guard isPoweringOff else { return .terminateNow }
94+
95+
logger.notice("Guest is powering off, delaying slightly to allow for final messages to be sent to host.")
96+
97+
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
98+
logger.notice("Allowing guest to terminate now.")
99+
NSApp.reply(toApplicationShouldTerminate: true)
100+
}
101+
102+
return .terminateLater
103+
}
104+
79105
}

VirtualCore/Source/Definitions/PreviewSupport.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ public extension VBVirtualMachine {
99
static func previewMachine(named name: String) -> VBVirtualMachine {
1010
try! VBVirtualMachine(bundleURL: Bundle.virtualCore.url(forResource: name, withExtension: VBVirtualMachine.bundleExtension, subdirectory: previewLibraryDirName)!)
1111
}
12-
static let preview = VBVirtualMachine.previewMachine(named: "Preview")
13-
static let previewLinux = VBVirtualMachine.previewMachine(named: "Preview-Linux")
12+
static let preview = VBVirtualMachine.previewMachine(named: "PreviewMac")
13+
static let previewBlurHash = VBVirtualMachine.previewMachine(named: "PreviewMacBlurHash")
14+
static let previewNoArtwork = VBVirtualMachine.previewMachine(named: "PreviewMacNoArtwork")
15+
16+
static let previewLinux = VBVirtualMachine.previewMachine(named: "PreviewLinux")
17+
static let previewLinuxBlurHash = VBVirtualMachine.previewMachine(named: "PreviewLinuxBlurHash")
18+
static let previewLinuxNoArtwork = VBVirtualMachine.previewMachine(named: "PreviewLinuxNoArtwork")
1419
}
1520

1621
extension Bundle {

VirtualCore/Source/GuestSupport/GuestAdditionsDiskImage.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ public final class GuestAdditionsDiskImage {
8585
}
8686
}
8787

88-
private var imagesRootURL: URL { URL.defaultVirtualBuddyLibraryURL.appendingPathComponent("_GuestImage") }
88+
static let imagesRootURL: URL = URL.defaultVirtualBuddyLibraryURL.appendingPathComponent("_GuestImage")
89+
90+
private var imagesRootURL: URL { Self.imagesRootURL }
8991

9092
private var installedImageDigestURL: URL {
9193
imagesRootURL

VirtualCore/Source/Models/BlurHashToken.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,10 @@ public extension BlurHashToken {
2828
value: "U4H09BEfIY$%U7ocVcM$8%R*M}f~zwIXcArd",
2929
size: 4
3030
)
31+
32+
/// Hardcoded VirtualBuddy background blur hash for Linux VMs.
33+
static let virtualBuddyBackgroundLinux = BlurHashToken(
34+
value: "UsLn]CG1I;t19uxAR$jJOXjEj=ayn%fkjubI",
35+
size: 4
36+
)
3137
}

VirtualCore/Source/Models/VBVirtualMachine+Metadata.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,21 @@ public extension VBVirtualMachine {
2222
}
2323

2424
func deleteMetadataFile(named name: String) throws {
25-
let baseURL = try metadataDirectoryCreatingIfNeeded()
26-
27-
let fileURL = baseURL.appendingPathComponent(name)
25+
let fileURL = metadataDirectoryURL.appendingPathComponent(name)
2826

2927
guard FileManager.default.fileExists(atPath: fileURL.path) else { return }
3028

3129
try FileManager.default.removeItem(at: fileURL)
3230
}
3331

34-
func metadataFileURL(_ fileName: String) throws -> URL {
35-
let baseURL = try metadataDirectoryCreatingIfNeeded()
36-
37-
let fileURL = baseURL.appendingPathComponent(fileName)
32+
func metadataFileURL(_ fileName: String) -> URL {
33+
let fileURL = metadataDirectoryURL.appendingPathComponent(fileName)
3834

3935
return fileURL
4036
}
4137

4238
func metadataContents(_ fileName: String) -> Data? {
43-
guard let fileURL = try? metadataFileURL(fileName) else { return nil }
39+
let fileURL = metadataFileURL(fileName)
4440

4541
guard FileManager.default.fileExists(atPath: fileURL.path) else { return nil }
4642

@@ -57,3 +53,19 @@ extension URL {
5753
return self
5854
}
5955
}
56+
57+
extension URL {
58+
/// `true` if URL points to a file contained within a VirtualBuddy VM bundle metadata directory.
59+
var isVirtualBuddyDataDirectoryFile: Bool {
60+
deletingLastPathComponent().lastPathComponent == VBVirtualMachine.metadataDirectoryName
61+
}
62+
63+
var virtualMachineBundleParent: URL? {
64+
var current = self
65+
while current.pathExtension != VBVirtualMachine.bundleExtension {
66+
current = current.deletingLastPathComponent()
67+
guard current.path != "/" else { return nil }
68+
}
69+
return current
70+
}
71+
}

VirtualCore/Source/Models/VBVirtualMachine+Screenshot.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ public extension VBVirtualMachine {
77
return NSImage(data: imageData)
88
}
99

10+
var thumbnail: NSImage? {
11+
guard let imageData = metadataContents(VBVirtualMachine.thumbnailFileName) ?? metadataContents(VBVirtualMachine._legacyThumbnailFileName) else { return nil }
12+
return NSImage(data: imageData)
13+
}
14+
1015
func thumbnailImage() -> NSImage? {
11-
guard let thumbnailURL = try? metadataFileURL(Self.thumbnailFileName) else { return nil }
16+
let thumbnailURL = metadataFileURL(Self.thumbnailFileName)
1217

1318
if let existingImage = NSImage(contentsOf: thumbnailURL) {
1419
return existingImage
@@ -19,10 +24,6 @@ public extension VBVirtualMachine {
1924

2025
func invalidateThumbnail() throws {
2126
try deleteMetadataFile(named: Self.thumbnailFileName)
22-
23-
DispatchQueue.main.async {
24-
self.didInvalidateThumbnail.send()
25-
}
2627
}
2728

2829
}

VirtualCore/Source/Models/VBVirtualMachine.swift

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public typealias BoolSubject = PassthroughSubject<Bool, Never>
77

88
public struct VBVirtualMachine: Identifiable, VBStorageDeviceContainer {
99

10-
public struct Metadata: Codable {
10+
public struct Metadata: Hashable, Codable {
1111
public static let currentVersion = 1
1212
@DecodableDefault.EmptyPlaceholder
1313
public var uuid = UUID()
@@ -40,6 +40,12 @@ public struct VBVirtualMachine: Identifiable, VBStorageDeviceContainer {
4040
remoteInstallImageURL = url
4141
}
4242
}
43+
44+
/// If Linux VM is using fallback VirtualBuddy orange background hash, updates it to use the Linux-specific one.
45+
fileprivate mutating func setLinuxBackgroundHashIfNeeded() {
46+
guard backgroundHash == .virtualBuddyBackground else { return }
47+
backgroundHash = .virtualBuddyBackgroundLinux
48+
}
4349
}
4450

4551
public var id: String { bundleURL.absoluteString }
@@ -72,8 +78,6 @@ public struct VBVirtualMachine: Identifiable, VBStorageDeviceContainer {
7278
get { _installRestoreData }
7379
set { _installRestoreData = newValue }
7480
}
75-
76-
public private(set) var didInvalidateThumbnail = VoidSubject()
7781

7882
}
7983

@@ -110,8 +114,10 @@ extension VBVirtualMachine {
110114

111115
public var metadataDirectoryURL: URL { Self.metadataDirectoryURL(for: bundleURL) }
112116

117+
static let metadataDirectoryName = ".vbdata"
118+
113119
static func metadataDirectoryURL(for bundleURL: URL) -> URL {
114-
bundleURL.appendingPathComponent(".vbdata")
120+
bundleURL.appendingPathComponent(metadataDirectoryName)
115121
}
116122

117123
public var needsInstall: Bool {
@@ -131,8 +137,19 @@ public extension UTType {
131137
}
132138

133139
public extension VBVirtualMachine {
134-
135-
init(bundleURL: URL, isNewInstall: Bool = false) throws {
140+
141+
struct BundleDirectoryMissingError: Error { }
142+
143+
init(bundleURL: URL, isNewInstall: Bool = false, createIfNeeded: Bool = true) throws {
144+
/// If we're not allowed to create the bundle and its metadata directory doesn't exist, throw a specific error type that's caught in ``VMLibraryController``.
145+
/// This is to prevent the app from creating a dummy VM bundle after a VM is deleted from the library.
146+
if !createIfNeeded {
147+
let metaDirectory = bundleURL.appending(path: Self.metadataDirectoryName, directoryHint: .isDirectory)
148+
guard metaDirectory.isReadableDirectory else {
149+
throw BundleDirectoryMissingError()
150+
}
151+
}
152+
136153
if !FileManager.default.fileExists(atPath: bundleURL.path) {
137154
#if DEBUG
138155
guard !ProcessInfo.isSwiftUIPreview else {
@@ -142,14 +159,19 @@ public extension VBVirtualMachine {
142159

143160
try FileManager.default.createDirectory(at: bundleURL, withIntermediateDirectories: true)
144161
}
145-
162+
146163
self.bundleURL = bundleURL
147164
var (metadata, config, installRestore) = try loadMetadata()
148165

149166
/// Migration from previous versions that didn't have a configuration file
150167
/// describing the storage devices.
151168
config.hardware.addMissingBootDeviceIfNeeded()
152-
169+
170+
/// Migration from previous versions that didn't have dedicated fallback artwork for Linux.
171+
if config.systemType == .linux {
172+
metadata?.setLinuxBackgroundHashIfNeeded()
173+
}
174+
153175
self.configuration = config
154176

155177
if let metadata {
@@ -170,6 +192,7 @@ public extension VBVirtualMachine {
170192
self.configuration = .init(systemType: .linux)
171193

172194
self.metadata = Metadata(installFinished: false, firstBootDate: .now, lastBootDate: .now)
195+
metadata.setLinuxBackgroundHashIfNeeded()
173196

174197
try saveMetadata()
175198
}
@@ -197,10 +220,6 @@ public extension VBVirtualMachine {
197220
}
198221

199222
func loadMetadata() throws -> (Metadata?, VBMacConfiguration, Data?) {
200-
#if DEBUG
201-
guard !ProcessInfo.isSwiftUIPreview else { return (nil, .default, nil) }
202-
#endif
203-
204223
let metadata: Metadata?
205224
let config: VBMacConfiguration
206225
let installRestore: Data?
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)