2
0
Эх сурвалжийг харах

manager: removable drive bookmarks

osy 5 жил өмнө
parent
commit
954d078d29

+ 4 - 0
Configuration/UTMViewState.h

@@ -35,6 +35,10 @@ NS_ASSUME_NONNULL_BEGIN
 - (id)initDefaults NS_DESIGNATED_INITIALIZER;
 - (id)initDefaults NS_DESIGNATED_INITIALIZER;
 - (id)initWithDictionary:(NSMutableDictionary *)dictionary NS_DESIGNATED_INITIALIZER;
 - (id)initWithDictionary:(NSMutableDictionary *)dictionary NS_DESIGNATED_INITIALIZER;
 
 
+- (void)setBookmark:(NSData *)bookmark forRemovableDrive:(NSString *)drive;
+- (void)removeBookmarkForRemovableDrive:(NSString *)drive;
+- (nullable NSData *)bookmarkForRemovableDrive:(NSString *)drive;
+
 @end
 @end
 
 
 NS_ASSUME_NONNULL_END
 NS_ASSUME_NONNULL_END

+ 23 - 0
Configuration/UTMViewState.m

@@ -25,6 +25,7 @@ const NSString *const kUTMViewStateShowToolbarKey = @"ShowToolbar";
 const NSString *const kUTMViewStateShowKeyboardKey = @"ShowKeyboard";
 const NSString *const kUTMViewStateShowKeyboardKey = @"ShowKeyboard";
 const NSString *const kUTMViewStateSuspendedKey = @"Suspended";
 const NSString *const kUTMViewStateSuspendedKey = @"Suspended";
 const NSString *const kUTMViewStateSharedDirectoryKey = @"SharedDirectory";
 const NSString *const kUTMViewStateSharedDirectoryKey = @"SharedDirectory";
+const NSString *const kUTMViewStateRemovableDrivesKey = @"RemovableDrives";
 
 
 @interface UTMViewState ()
 @interface UTMViewState ()
 
 
@@ -34,6 +35,7 @@ const NSString *const kUTMViewStateSharedDirectoryKey = @"SharedDirectory";
 
 
 @implementation UTMViewState {
 @implementation UTMViewState {
     NSMutableDictionary *_rootDict;
     NSMutableDictionary *_rootDict;
+    NSMutableDictionary<NSString *, NSData *> *_removableDrives;
 }
 }
 
 
 #pragma mark - Properties
 #pragma mark - Properties
@@ -118,12 +120,28 @@ const NSString *const kUTMViewStateSharedDirectoryKey = @"SharedDirectory";
     }
     }
 }
 }
 
 
+#pragma mark - Removable drives
+
+- (void)setBookmark:(NSData *)bookmark forRemovableDrive:(NSString *)drive {
+    _removableDrives[drive] = bookmark;
+}
+
+- (void)removeBookmarkForRemovableDrive:(NSString *)drive {
+    [_removableDrives removeObjectForKey:drive];
+}
+
+- (nullable NSData *)bookmarkForRemovableDrive:(NSString *)drive {
+    return _removableDrives[drive];
+}
+
 #pragma mark - Init
 #pragma mark - Init
 
 
 - (id)initDefaults {
 - (id)initDefaults {
     self = [super init];
     self = [super init];
     if (self) {
     if (self) {
         _rootDict = [NSMutableDictionary dictionary];
         _rootDict = [NSMutableDictionary dictionary];
+        _removableDrives = [NSMutableDictionary dictionary];
+        _rootDict[kUTMViewStateRemovableDrivesKey] = _removableDrives;
         self.displayScale = 1.0;
         self.displayScale = 1.0;
         self.displayOriginX = 0;
         self.displayOriginX = 0;
         self.displayOriginY = 0;
         self.displayOriginY = 0;
@@ -139,6 +157,11 @@ const NSString *const kUTMViewStateSharedDirectoryKey = @"SharedDirectory";
     self = [super init];
     self = [super init];
     if (self) {
     if (self) {
         _rootDict = dictionary;
         _rootDict = dictionary;
+        _removableDrives = dictionary[kUTMViewStateRemovableDrivesKey];
+        if (!_removableDrives) {
+            _removableDrives = [NSMutableDictionary dictionary];
+            _rootDict[kUTMViewStateRemovableDrivesKey] = _removableDrives;
+        }
     }
     }
     return self;
     return self;
 }
 }

+ 1 - 0
Managers/UTMQemuManager.h

@@ -27,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, readonly) UTMJSONStream *jsonStream;
 @property (nonatomic, readonly) UTMJSONStream *jsonStream;
 @property (nonatomic, weak) id<UTMQemuManagerDelegate> delegate;
 @property (nonatomic, weak) id<UTMQemuManagerDelegate> delegate;
 @property (nonatomic, assign) int retries;
 @property (nonatomic, assign) int retries;
+@property (nonatomic, readonly) BOOL isConnected;
 
 
 - (void)connect;
 - (void)connect;
 - (void)disconnect;
 - (void)disconnect;

+ 31 - 3
Managers/UTMQemuManager.m

@@ -109,6 +109,12 @@ static void utm_migration_pass_handler(int64_t pass, void *ctx) {
     
     
 }
 }
 
 
+@interface UTMQemuManager ()
+
+@property (nonatomic, readwrite) BOOL isConnected;
+
+@end
+
 @implementation UTMQemuManager {
 @implementation UTMQemuManager {
     UTMJSONStream *_jsonStream;
     UTMJSONStream *_jsonStream;
     void (^_rpc_finish)(NSDictionary *, NSError *);
     void (^_rpc_finish)(NSDictionary *, NSError *);
@@ -169,6 +175,7 @@ void qmp_rpc_call(CFDictionaryRef args, CFDictionaryRef *ret, Error **err, void
 }
 }
 
 
 - (void)disconnect {
 - (void)disconnect {
+    self.isConnected = NO;
     [_jsonStream disconnect];
     [_jsonStream disconnect];
 }
 }
 
 
@@ -214,7 +221,7 @@ void qmp_rpc_call(CFDictionaryRef args, CFDictionaryRef *ret, Error **err, void
             *stop = YES;
             *stop = YES;
         } else if ([key isEqualToString:@"QMP"]) {
         } else if ([key isEqualToString:@"QMP"]) {
             UTMLog(@"Got QMP handshake: %@", dict);
             UTMLog(@"Got QMP handshake: %@", dict);
-            [self qmpEnterCommandMode];
+            [self qmpEnterCommandModeWithError:nil]; // TODO: handle error
             *stop = YES;
             *stop = YES;
         }
         }
     }];
     }];
@@ -224,11 +231,32 @@ void qmp_rpc_call(CFDictionaryRef args, CFDictionaryRef *ret, Error **err, void
     return [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithCString:error_get_pretty(qerr) encoding:NSASCIIStringEncoding]}];
     return [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithCString:error_get_pretty(qerr) encoding:NSASCIIStringEncoding]}];
 }
 }
 
 
-- (void)qmpEnterCommandMode {
+- (BOOL)qmpEnterCommandModeWithError:(NSError * _Nullable __autoreleasing *)error {
     NSDictionary *cmd = @{
     NSDictionary *cmd = @{
         @"execute": @"qmp_capabilities"
         @"execute": @"qmp_capabilities"
     };
     };
-    qmp_rpc_call((__bridge CFDictionaryRef)cmd, NULL, NULL, (__bridge void *)self);
+    Error *qerr = NULL;
+    qmp_rpc_call((__bridge CFDictionaryRef)cmd, NULL, &qerr, (__bridge void *)self);
+    if (qerr != NULL) {
+        if (error) {
+            *error = [self errorForQerror:qerr];
+            error_free(qerr);
+        }
+        return NO;
+    } else {
+        self.isConnected = YES;
+        [self.delegate qemuQmpDidConnect:self];
+        qmp_cont(&qerr, (__bridge void *)self);
+        if (qerr != NULL) {
+            if (error) {
+                *error = [self errorForQerror:qerr];
+                error_free(qerr);
+            }
+            self.isConnected = NO;
+            return NO;
+        }
+        return YES;
+    }
 }
 }
 
 
 - (void)vmPowerCommand:(NSString *)command completion:(void (^ _Nullable)(NSError * _Nullable))completion {
 - (void)vmPowerCommand:(NSString *)command completion:(void (^ _Nullable)(NSError * _Nullable))completion {

+ 1 - 0
Managers/UTMQemuManagerDelegate.h

@@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
 - (void)qemuHasWakeup:(UTMQemuManager *)manager;
 - (void)qemuHasWakeup:(UTMQemuManager *)manager;
 - (void)qemuWillQuit:(UTMQemuManager *)manager guest:(BOOL)guest reason:(ShutdownCause)reason;
 - (void)qemuWillQuit:(UTMQemuManager *)manager guest:(BOOL)guest reason:(ShutdownCause)reason;
 - (void)qemuError:(UTMQemuManager *)manager error:(NSString *)error;
 - (void)qemuError:(UTMQemuManager *)manager error:(NSString *)error;
+- (void)qemuQmpDidConnect:(UTMQemuManager *)manager;
 
 
 @end
 @end
 
 

+ 1 - 0
Managers/UTMQemuSystemConfiguration.m

@@ -258,6 +258,7 @@
                                                         relativeToURL:nil
                                                         relativeToURL:nil
                                                                 error:nil]];
                                                                 error:nil]];
     [self pushArgv:resourceURL.path];
     [self pushArgv:resourceURL.path];
+    [self pushArgv:@"-S"]; // startup stopped
     [self pushArgv:@"-qmp"];
     [self pushArgv:@"-qmp"];
     [self pushArgv:@"tcp:localhost:4444,server,nowait"];
     [self pushArgv:@"tcp:localhost:4444,server,nowait"];
     [self pushArgv:@"-smp"];
     [self pushArgv:@"-smp"];

+ 1 - 0
Managers/UTMVirtualMachine+Drives.h

@@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 
 - (BOOL)ejectDrive:(UTMDrive *)drive force:(BOOL)force error:(NSError * _Nullable *)error;
 - (BOOL)ejectDrive:(UTMDrive *)drive force:(BOOL)force error:(NSError * _Nullable *)error;
 - (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable *)error;
 - (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable *)error;
+- (void)restoreRemovableDrivesFromBookmarks;
 
 
 @end
 @end
 
 

+ 64 - 1
Managers/UTMVirtualMachine+Drives.m

@@ -15,6 +15,8 @@
 //
 //
 
 
 #import "UTMVirtualMachine+Drives.h"
 #import "UTMVirtualMachine+Drives.h"
+#import "UTMLogging.h"
+#import "UTMViewState.h"
 #import "UTMDrive.h"
 #import "UTMDrive.h"
 #import "UTMQemu.h"
 #import "UTMQemu.h"
 #import "UTMQemuManager+BlockDevices.h"
 #import "UTMQemuManager+BlockDevices.h"
@@ -23,6 +25,9 @@
 
 
 @property (nonatomic, readonly) UTMQemuManager *qemu;
 @property (nonatomic, readonly) UTMQemuManager *qemu;
 @property (nonatomic, readonly) UTMQemu *system;
 @property (nonatomic, readonly) UTMQemu *system;
+@property (nonatomic) UTMViewState *viewState;
+
+- (void)saveViewState;
 
 
 @end
 @end
 
 
@@ -60,6 +65,13 @@
 }
 }
 
 
 - (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
 - (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
+    if (![self saveBookmarkForDrive:drive url:url error:error]) {
+        return NO;
+    }
+    if (!self.qemu.isConnected) {
+        return YES; // not ready yet
+    }
+    
     NSData *bookmark = [url bookmarkDataWithOptions:0
     NSData *bookmark = [url bookmarkDataWithOptions:0
                      includingResourceValuesForKeys:nil
                      includingResourceValuesForKeys:nil
                                       relativeToURL:nil
                                       relativeToURL:nil
@@ -68,7 +80,58 @@
         return NO;
         return NO;
     }
     }
     [self.system accessDataWithBookmark:bookmark];
     [self.system accessDataWithBookmark:bookmark];
-    return [self.qemu changeMediumForDrive:drive.name path:url.path error:error];
+    if (![self.qemu changeMediumForDrive:drive.name path:url.path error:error]) {
+        return NO;
+    }
+    return [self saveBookmarkForDrive:drive url:url error:error];
+}
+
+- (BOOL)saveBookmarkForDrive:(UTMDrive *)drive url:(nullable NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
+    NSData *bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
+                     includingResourceValuesForKeys:nil
+                                      relativeToURL:nil
+                                              error:error];
+    if (bookmark) {
+        [self.viewState setBookmark:bookmark forRemovableDrive:drive.name];
+    } else {
+        [self.viewState removeBookmarkForRemovableDrive:drive.name];
+    }
+    [self saveViewState];
+    return (bookmark != nil);
+}
+
+- (void)restoreRemovableDrivesFromBookmarks {
+    NSArray<UTMDrive *> *drives = self.drives;
+    for (UTMDrive *drive in drives) {
+        NSData *bookmark = [self.viewState bookmarkForRemovableDrive:drive.name];
+        if (bookmark) {
+            UTMLog(@"found bookmark for %@", drive.name);
+            if (drive.status == UTMDriveStatusFixed) {
+                UTMLog(@"%@ is no longer removable, removing bookmark", drive.name);
+                [self saveBookmarkForDrive:drive url:nil error:nil];
+                continue;
+            }
+            BOOL stale;
+            NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
+                                                   options:NSURLBookmarkResolutionWithSecurityScope
+                                             relativeToURL:nil
+                                       bookmarkDataIsStale:&stale
+                                                     error:nil];
+            if (!url) {
+                UTMLog(@"failed to resolve bookmark for %@", drive.name);
+                continue;
+            }
+            if (stale) {
+                UTMLog(@"bookmark is stale, attempting to re-create");
+                if (![self saveBookmarkForDrive:drive url:url error:nil]) {
+                    UTMLog(@"bookmark re-creation failed");
+                }
+            }
+            if (![self changeMediumForDrive:drive url:url error:nil]) {
+                UTMLog(@"failed to change %@ image to %@", drive.name, url);
+            }
+        }
+    }
 }
 }
 
 
 @end
 @end

+ 7 - 0
Managers/UTMVirtualMachine.m

@@ -16,6 +16,7 @@
 
 
 #import <TargetConditionals.h>
 #import <TargetConditionals.h>
 #import "UTMVirtualMachine.h"
 #import "UTMVirtualMachine.h"
+#import "UTMVirtualMachine+Drives.h"
 #import "UTMVirtualMachine+Sharing.h"
 #import "UTMVirtualMachine+Sharing.h"
 #import "UTMConfiguration.h"
 #import "UTMConfiguration.h"
 #import "UTMConfiguration+Constants.h"
 #import "UTMConfiguration+Constants.h"
@@ -574,6 +575,12 @@ error:
     });
     });
 }
 }
 
 
+// this is called right before we execute qmp_cont so we can setup additional option
+- (void)qemuQmpDidConnect:(UTMQemuManager *)manager {
+    UTMLog(@"qemuQmpDidConnect");
+    [self restoreRemovableDrivesFromBookmarks];
+}
+
 #pragma mark - Plist Handling
 #pragma mark - Plist Handling
 
 
 - (NSMutableDictionary *)loadPlist:(NSURL *)path withError:(NSError **)err {
 - (NSMutableDictionary *)loadPlist:(NSURL *)path withError:(NSError **)err {