mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
install: support tipa packages and show success alerts (#142)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
enum VPhoneInstallPackage {
|
||||
static let allowedContentTypes: [UTType] = [
|
||||
UTType(filenameExtension: "ipa"),
|
||||
UTType(filenameExtension: "tipa"),
|
||||
].compactMap { $0 }
|
||||
|
||||
static func isSupportedFile(_ url: URL) -> Bool {
|
||||
switch url.pathExtension.lowercased() {
|
||||
case "ipa", "tipa":
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
static func successMessage(for fileName: String, detail: String) -> String {
|
||||
let trimmedDetail = detail.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmedDetail.isEmpty else {
|
||||
return "Installed \(fileName)."
|
||||
}
|
||||
if trimmedDetail.localizedCaseInsensitiveContains(fileName) {
|
||||
return trimmedDetail
|
||||
}
|
||||
return "Installed \(fileName).\n\n\(trimmedDetail)"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import AppKit
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
// MARK: - Install Menu
|
||||
|
||||
@@ -8,14 +7,14 @@ extension VPhoneMenuController {
|
||||
func buildInstallMenu() -> NSMenuItem {
|
||||
let item = NSMenuItem()
|
||||
let menu = NSMenu(title: "Install")
|
||||
menu.addItem(makeItem("Install IPA...", action: #selector(installIPAFromDisk)))
|
||||
menu.addItem(makeItem("Install IPA/TIPA...", action: #selector(installIPAFromDisk)))
|
||||
item.submenu = menu
|
||||
return item
|
||||
}
|
||||
|
||||
@objc func installIPAFromDisk() {
|
||||
guard control.isConnected else {
|
||||
showAlert(title: "Install IPA", message: "Guest is not connected.", style: .warning)
|
||||
showAlert(title: "Install App Package", message: "Guest is not connected.", style: .warning)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,11 +22,9 @@ extension VPhoneMenuController {
|
||||
panel.canChooseFiles = true
|
||||
panel.canChooseDirectories = false
|
||||
panel.allowsMultipleSelection = false
|
||||
panel.allowedContentTypes = [
|
||||
UTType(filenameExtension: "ipa") ?? .data,
|
||||
]
|
||||
panel.allowedContentTypes = VPhoneInstallPackage.allowedContentTypes
|
||||
panel.prompt = "Install"
|
||||
panel.message = "Choose an IPA to install in the guest."
|
||||
panel.message = "Choose an IPA or TIPA package to install in the guest."
|
||||
|
||||
let response = panel.runModal()
|
||||
guard response == .OK, let url = panel.url else { return }
|
||||
@@ -36,8 +33,16 @@ extension VPhoneMenuController {
|
||||
do {
|
||||
let result = try await control.installIPA(localURL: url)
|
||||
print("[install] \(result)")
|
||||
showAlert(
|
||||
title: "Install App Package",
|
||||
message: VPhoneInstallPackage.successMessage(
|
||||
for: url.lastPathComponent,
|
||||
detail: result
|
||||
),
|
||||
style: .informational
|
||||
)
|
||||
} catch {
|
||||
showAlert(title: "Install IPA", message: "\(error)", style: .warning)
|
||||
showAlert(title: "Install App Package", message: "\(error)", style: .warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class VPhoneVirtualMachineView: VZVirtualMachineView {
|
||||
// MARK: - Drag and Drop Install
|
||||
|
||||
override func draggingEntered(_ sender: any NSDraggingInfo) -> NSDragOperation {
|
||||
guard droppedIPAURL(from: sender) != nil else { return [] }
|
||||
guard droppedInstallPackageURL(from: sender) != nil else { return [] }
|
||||
updateDragHighlight(true)
|
||||
return .copy
|
||||
}
|
||||
@@ -99,44 +99,49 @@ class VPhoneVirtualMachineView: VZVirtualMachineView {
|
||||
}
|
||||
|
||||
override func prepareForDragOperation(_ sender: any NSDraggingInfo) -> Bool {
|
||||
droppedIPAURL(from: sender) != nil
|
||||
droppedInstallPackageURL(from: sender) != nil
|
||||
}
|
||||
|
||||
override func performDragOperation(_ sender: any NSDraggingInfo) -> Bool {
|
||||
updateDragHighlight(false)
|
||||
guard let url = droppedIPAURL(from: sender) else { return false }
|
||||
guard let url = droppedInstallPackageURL(from: sender) else { return false }
|
||||
|
||||
Task { @MainActor in
|
||||
guard let control else {
|
||||
showAlert(title: "Install IPA", message: "Guest is not connected.", style: .warning)
|
||||
showAlert(title: "Install App Package", message: "Guest is not connected.", style: .warning)
|
||||
return
|
||||
}
|
||||
guard control.isConnected else {
|
||||
showAlert(title: "Install IPA", message: "Guest is not connected.", style: .warning)
|
||||
showAlert(title: "Install App Package", message: "Guest is not connected.", style: .warning)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let result = try await control.installIPA(localURL: url)
|
||||
print("[install] \(result)")
|
||||
showAlert(
|
||||
title: "Install App Package",
|
||||
message: VPhoneInstallPackage.successMessage(
|
||||
for: url.lastPathComponent,
|
||||
detail: result
|
||||
),
|
||||
style: .informational
|
||||
)
|
||||
} catch {
|
||||
showAlert(title: "Install IPA", message: "\(error)", style: .warning)
|
||||
showAlert(title: "Install App Package", message: "\(error)", style: .warning)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func droppedIPAURL(from sender: any NSDraggingInfo) -> URL? {
|
||||
private func droppedInstallPackageURL(from sender: any NSDraggingInfo) -> URL? {
|
||||
let options: [NSPasteboard.ReadingOptionKey: Any] = [
|
||||
.urlReadingFileURLsOnly: true,
|
||||
]
|
||||
guard let urls = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self], options: options) as? [URL] else {
|
||||
return nil
|
||||
}
|
||||
return urls.first {
|
||||
let ext = $0.pathExtension.lowercased()
|
||||
return ext == "ipa" || ext == "tipa"
|
||||
}
|
||||
return urls.first(where: VPhoneInstallPackage.isSupportedFile)
|
||||
}
|
||||
|
||||
private func updateDragHighlight(_ visible: Bool) {
|
||||
|
||||
Reference in New Issue
Block a user