Repository: utmapp/UTM
File: Managers/UTMVirtualMachine+Drives.m (Objective-C)

Code before:
    [self.viewState removeBookmarkForRemovableDrive:drive.name];
    [self saveViewState];
    [self.system stopAccessingPath:oldPath];
    if (!self.qemu.isConnected) {
        return YES; // not running
    }
    return [self.qemu ejectDrive:drive.name force:force error:error];
}

- (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
    [url startAccessingSecurityScopedResource];
    NSData *bookmark = [url bookmarkDataWithOptions:0
                     includingResourceValuesForKeys:nil
                                      relativeToURL:nil
                                              error:error];
    [url stopAccessingSecurityScopedResource];
    if (!bookmark) {
        if (error) {
            *error = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed create bookmark.", "UTMVirtualMachine+Drives")}];
        }
        return NO;
    }
    if (!self.qemu.isConnected) {
        [self.viewState setBookmark:bookmark path:url.path forRemovableDrive:drive.name persistent:YES];
        [self saveViewState];
        return YES; // not ready yet
    } else {
        [self.viewState setBookmark:bookmark path:url.path forRemovableDrive:drive.name persistent:NO];
        NSString *oldPath = [self.viewState pathForRemovableDrive:drive.name];
        if (oldPath) {
            [self.system stopAccessingPath:oldPath];
        }
        return [self changeMediumForDriveInternal:drive bookmark:bookmark securityScoped:NO error:error];
    }
}

- (BOOL)changeMediumForDriveInternal:(UTMDrive *)drive bookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped error:(NSError * _Nullable __autoreleasing *)error {
    __block BOOL ret = NO;
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    [self.system accessDataWithBookmark:bookmark
                         securityScoped:securityScoped
                             completion:^(BOOL success, NSData *newBookmark, NSString *path) {
        if (success) {
            [self.viewState setBookmark:newBookmark path:path forRemovableDrive:drive.name persistent:YES];
            [self saveViewState];
            [self.qemu changeMediumForDrive:drive.name path:path error:error];
        } else {
            if (error) {
                *error = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to access drive image path.", "UTMVirtualMachine+Drives")}];
            }
        }

Review comment (refactor):
This still won't work for macOS. To get a persistent bookmark, you need to get it from the helper process, which at this point isn't launched yet.

We can make it so it saves the settings only for iOS (which doesn't have this requirement) but it would be inconsistent with macOS. Or we can refactor the code so it will always call `[self.system accessDataWithBookmark]`.

Code after:
    [self.viewState removeBookmarkForRemovableDrive:drive.name];
    [self saveViewState];
    [self.system stopAccessingPath:oldPath];
    if (!self.qemu.isConnected) {
        return YES; // not running
    }
    return [self.qemu ejectDrive:drive.name force:force error:error];
}

- (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
    [url startAccessingSecurityScopedResource];
    NSData *bookmark = [url bookmarkDataWithOptions:0
                     includingResourceValuesForKeys:nil
                                      relativeToURL:nil
                                              error:error];
    [url stopAccessingSecurityScopedResource];
    if (!bookmark) {
        if (error) {
            *error = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed create bookmark.", "UTMVirtualMachine+Drives")}];
        }
        return NO;
    }
    if (!self.qemu.isConnected) {
        // On macOS, the `bookmark` will not work for the helper process.
        // This is caught and addressed in `restoreRemovableDrivesFromBookmarksWithError`.
        [self.viewState setBookmark:bookmark path:url.path forRemovableDrive:drive.name persistent:YES];
        [self saveViewState];
        return YES; // not ready yet
    } else {
        [self.viewState setBookmark:bookmark path:url.path forRemovableDrive:drive.name persistent:NO];
        NSString *oldPath = [self.viewState pathForRemovableDrive:drive.name];
        if (oldPath) {
            [self.system stopAccessingPath:oldPath];
        }
        return [self changeMediumForDriveInternal:drive bookmark:bookmark securityScoped:NO error:error];
    }
}

- (BOOL)changeMediumForDriveInternal:(UTMDrive *)drive bookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped error:(NSError * _Nullable __autoreleasing *)error {
    __block BOOL ret = NO;
    __block NSError *qemuError = nil;
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    [self.system accessDataWithBookmark:bookmark
                         securityScoped:securityScoped
                             completion:^(BOOL success, NSData *newBookmark, NSString *path) {
        if (success) {
            [self.viewState setBookmark:newBookmark path:path forRemovableDrive:drive.name persistent:YES];
            [self saveViewState];
            success = [self.qemu changeMediumForDrive:drive.name path:path error:&qemuError];
        } else {
            qemuError = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to access drive image path.", "UTMVirtualMachine+Drives")}];