Initial commit

This commit is contained in:
Nindi Gill 2022-07-01 16:44:06 +10:00
parent 9197011230
commit 2ed311a818
184 changed files with 8052 additions and 2 deletions

5
.drstring.toml Normal file
View file

@ -0,0 +1,5 @@
include = ["Mist/**/*.swift"]
align-after-colon = ["parameters", "throws", "returns"]
parameter-style = "grouped"
needs-separation = ["description", "parameters", "throws"]
vertical-align = true

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.DS_Store
.build/
.swiftpm/
build/
xcuserdata/
Package.resolved

78
.swiftlint.yml Normal file
View file

@ -0,0 +1,78 @@
excluded:
- .build
closure_body_length:
warning: 20
error: 40
line_length:
warning: 200
error: 220
missing_docs:
warning:
- private
- fileprivate
- internal
- public
- open
opt_in_rules:
- attributes
- balanced_xctest_lifecycle
- closure_body_length
- closure_end_indentation
- closure_spacing
- collection_alignment
- conditional_returns_on_newline
- discarded_notification_center_observer
- discouraged_optional_boolean
- discouraged_optional_collection
- empty_collection_literal
- empty_count
- empty_string
- empty_xctest_method
- enum_case_associated_values_count
- explicit_enum_raw_value
- explicit_init
- explicit_type_interface
- file_header
- file_name
- file_name_no_space
- file_types_order
- first_where
- flatmap_over_map_reduce
- function_default_parameter_at_end
- force_unwrapping
- identical_operands
- implicit_return
- implicitly_unwrapped_optional
- indentation_width
- joined_default_parameter
- last_where
- literal_expression_end_indentation
- missing_docs
- modifier_order
- multiline_arguments
- multiline_arguments_brackets
- multiline_function_chains
- multiline_literal_brackets
- multiline_parameters
- multiline_parameters_brackets
- nimble_operator
- number_separator
- operator_usage_whitespace
- prefer_zero_over_explicit_init
- redundant_nil_coalescing
- sorted_first_last
- sorted_imports
- switch_case_on_newline
- toggle_bool
- trailing_closure
- type_contents_order
- unneeded_parentheses_in_closure_argument
- unused_declaration
- unused_import
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- yoda_condition

34
Mist.pkg.recipe.yaml Normal file
View file

@ -0,0 +1,34 @@
---
Identifier: com.ninxsoft.pkg.mist
Input:
NAME: Mist
Process:
- Processor: URLDownloader
Arguments:
PKG: /Applications/Mist.app
url: ""
- Processor: EndOfCheckPhase
- Processor: CodeSignatureVerifier
Arguments:
input_path: "%pathname%"
requirement: 'anchor apple generic and identifier "com.ninxsoft.mist" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "7K3HVCLV7Z")'
- Processor: Versioner
Arguments:
input_plist_path: "%pathname%/Contents/Info.plist"
- Processor: AppPkgCreator
Arguments:
app_path: "%pathname%"
pkg_path: "%RECIPE_CACHE_DIR%/%NAME%-%version%.pkg"
version: "%version%"
- Processor: PkgSigner
Arguments:
pkg_path: "%RECIPE_CACHE_DIR%/%NAME%-%version%.pkg"
signing_cert: "%DEVELOPER_ID_INSTALLER%"
- Processor: FileMover
Arguments:
source: "%RECIPE_CACHE_DIR%/%NAME%-%version%.pkg"
target: "%DESTINATION_DIR%/%NAME% %version%.pkg"
- Processor: PathDeleter
Arguments:
path_list:
- "%RECIPE_CACHE_DIR%/%NAME%-%version%-unsigned.pkg"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "390451A52856E1D900E0B563"
BuildableName = "Mist.app"
BlueprintName = "Mist"
ReferencedContainer = "container:Mist.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "390451A52856E1D900E0B563"
BuildableName = "Mist.app"
BlueprintName = "Mist"
ReferencedContainer = "container:Mist.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "390451A52856E1D900E0B563"
BuildableName = "Mist.app"
BlueprintName = "Mist"
ReferencedContainer = "container:Mist.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "39CF559C28614DD8006FB5D2"
BuildableName = "com.ninxsoft.mist.helper"
BlueprintName = "MistHelperTool"
ReferencedContainer = "container:Mist.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "39CF559C28614DD8006FB5D2"
BuildableName = "com.ninxsoft.mist.helper"
BlueprintName = "MistHelperTool"
ReferencedContainer = "container:Mist.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "39CF559C28614DD8006FB5D2"
BuildableName = "com.ninxsoft.mist.helper"
BlueprintName = "MistHelperTool"
ReferencedContainer = "container:Mist.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

61
Mist/AppCommands.swift Normal file
View file

@ -0,0 +1,61 @@
//
// AppCommands.swift
// Mist
//
// Created by Nindi Gill on 15/6/2022.
//
import Blessed
import SwiftUI
struct AppCommands: Commands {
@Environment(\.openURL) var openURL: OpenURLAction
@ObservedObject var sparkleUpdater: SparkleUpdater
@Binding var refreshing: Bool
@Binding var downloadInProgress: Bool
@CommandsBuilder var body: some Commands {
CommandGroup(after: .appInfo) {
Button("Check for Updates...") {
sparkleUpdater.checkForUpdates()
}
.disabled(!sparkleUpdater.canCheckForUpdates)
}
CommandGroup(replacing: .newItem) {
Button("Refresh") {
refresh()
}
.keyboardShortcut("r")
.disabled(refreshing || downloadInProgress)
}
CommandGroup(replacing: .saveItem) {}
CommandGroup(replacing: .systemServices) {
Button("Install Privileged Helper Tool...") {
install()
}
.disabled(downloadInProgress)
}
CommandGroup(replacing: .help) {
Button("Mist Help") {
help()
}
}
}
private func refresh() {
refreshing = true
}
private func install() {
try? PrivilegedHelperManager.shared.authorizeAndBless()
}
private func help() {
guard let url: URL = URL(string: .repositoryURL) else {
return
}
openURL(url)
}
}

60
Mist/AppDelegate.swift Normal file
View file

@ -0,0 +1,60 @@
//
// AppDelegate.swift
// Mist
//
// Created by Nindi Gill on 16/6/2022.
//
import Cocoa
import UserNotifications
class AppDelegate: NSObject, NSApplicationDelegate {
// swiftlint:disable:next weak_delegate
private let userNotificationCenterDelegate: UserNotificationCenterDelegate = UserNotificationCenterDelegate()
func applicationDidFinishLaunching(_ notification: Notification) {
UNUserNotificationCenter.current().delegate = userNotificationCenterDelegate
let show: UNNotificationAction = UNNotificationAction(identifier: UNNotificationAction.Identifier.show, title: "Show", options: .foreground)
let success: UNNotificationCategory = UNNotificationCategory(identifier: UNNotificationCategory.Identifier.success, actions: [show], intentIdentifiers: [], options: [])
let failure: UNNotificationCategory = UNNotificationCategory(identifier: UNNotificationCategory.Identifier.failure, actions: [], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([success, failure])
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
true
}
func sendUpdateNotification(title: String, body: String, success: Bool, url: URL?) {
let notificationCenter: UNUserNotificationCenter = .current()
notificationCenter.getNotificationSettings { settings in
guard [.authorized, .provisional].contains(settings.authorizationStatus) else {
return
}
let identifier: String = UUID().uuidString
let content: UNMutableNotificationContent = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
content.categoryIdentifier = success ? UNNotificationCategory.Identifier.success : UNNotificationCategory.Identifier.failure
if success,
let url: URL = url {
content.userInfo = ["URL": url.path]
}
let trigger: UNTimeIntervalNotificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request: UNNotificationRequest = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
notificationCenter.add(request) { error in
if let error: Error = error {
print(error.localizedDescription)
}
}
}
}
}

View file

@ -0,0 +1,56 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.269",
"green" : "0.647",
"red" : "0.946"
}
},
"idiom" : "mac"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.269",
"green" : "0.647",
"red" : "0.946"
}
},
"idiom" : "mac"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.269",
"green" : "0.647",
"red" : "0.946"
}
},
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,68 @@
{
"images" : [
{
"filename" : "icon_16x16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon_16x16@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon_32x32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon_32x32@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon_128x128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "icon_128x128@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "icon_256x256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "icon_256x256@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "icon_512x512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon_512x512@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App Installers - macOS Big Sur.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App Installer - macOS Catalina.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App Installer - macOS High Sierra.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App Installers - macOS Mojave.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App Installer - macOS Monterey.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App Installer - macOS Ventura.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "App Installer - macOS.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Certificate.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View file

@ -0,0 +1,12 @@
{
"images": [
{
"filename": "Cleanup.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Disk Image.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Download.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Firmware.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ISO.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Installer.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Package.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Setup.png",
"idiom" : "mac"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -0,0 +1,12 @@
{
"images": [
{
"filename": "macOS Big Sur.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View file

@ -0,0 +1,22 @@
{
"images": [
{
"filename": "macOS Catalina Light.png",
"idiom": "mac"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"filename": "macOS Catalina Dark.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -0,0 +1,12 @@
{
"images": [
{
"filename": "macOS High Sierra.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View file

@ -0,0 +1,22 @@
{
"images": [
{
"filename": "macOS Mojave Light.png",
"idiom": "mac"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"filename": "macOS Mojave Dark.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View file

@ -0,0 +1,12 @@
{
"images": [
{
"filename": "macOS Monterey.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,12 @@
{
"images": [
{
"filename": "macOS Ventura.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,12 @@
{
"images": [
{
"filename": "macOS.png",
"idiom": "mac"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,27 @@
//
// Array+Extension.swift
// Mist
//
// Created by Nindi Gill on 20/6/2022.
//
import Foundation
extension Array where Element == UInt8 {
func uInt8(at offset: Int) -> UInt8 {
self[offset]
}
func uInt32(at offset: Int) -> UInt32 {
self[offset...offset + 0x03].reversed().reduce(0) {
$0 << 0x08 + UInt32($1)
}
}
func uInt64(at offset: Int) -> UInt64 {
self[offset...offset + 0x07].reversed().reduce(0) {
$0 << 0x08 + UInt64($1)
}
}
}

View file

@ -0,0 +1,15 @@
//
// AuthorizationError+Extension.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Authorized
extension AuthorizationError: Equatable {
public static func == (lhs: AuthorizationError, rhs: AuthorizationError) -> Bool {
lhs.localizedDescription == rhs.localizedDescription
}
}

View file

@ -0,0 +1,58 @@
//
// Dictionary+Extension.swift
// Mist
//
// Created by Nindi Gill on 13/6/2022.
//
import Foundation
import Yams
extension Dictionary where Key == String {
func firmwareCSVString() -> String {
guard let signed: Bool = self["signed"] as? Bool,
let name: String = self["name"] as? String,
let version: String = self["version"] as? String,
let build: String = self["build"] as? String,
let size: Int64 = self["size"] as? Int64,
let date: String = self["date"] as? String,
let compatible: Bool = self["compatible"] as? Bool else {
return ""
}
let string: String = "\(signed ? "YES" : "NO"),\"\(name)\",\"=\"\"\(version)\"\"\",\"=\"\"\(build)\"\"\",\(size),\(date),\(compatible ? "YES" : "NO")\n"
return string
}
func installerCSVString() -> String {
guard let identifier: String = self["identifier"] as? String,
let name: String = self["name"] as? String,
let version: String = self["version"] as? String,
let build: String = self["build"] as? String,
let size: Int64 = self["size"] as? Int64,
let date: String = self["date"] as? String,
let compatible: Bool = self["compatible"] as? Bool else {
return ""
}
let string: String = "\"\(identifier)\",\"\(name)\",\"=\"\"\(version)\"\"\",\"=\"\"\(build)\"\"\",\(size),\(date),\(compatible ? "YES" : "NO")\n"
return string
}
func jsonString() throws -> String {
let data: Data = try JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted, .sortedKeys])
return String(data: data, encoding: .utf8) ?? ""
}
func propertyListString() throws -> String {
let data: Data = try PropertyListSerialization.data(fromPropertyList: self, format: .xml, options: .bitWidth)
return String(data: data, encoding: .utf8) ?? ""
}
func yamlString() throws -> String {
try Yams.dump(object: self)
}
}

View file

@ -0,0 +1,31 @@
//
// Double+Extension.swift
// Mist
//
// Created by Nindi Gill on 24/6/2022.
//
import Foundation
extension Double {
/// kilobytes constant
static let kilobyte: Double = 1_000
/// megabytes constant
static let megabyte: Double = .kilobyte * 1_000
/// gigabytes constant
static let gigabyte: Double = .megabyte * 1_000
func bytesString() -> String {
if self < .kilobyte {
return "\(Int(self)) bytes"
} else if self < .megabyte {
return String(format: "%5.2f KB", self / .kilobyte)
} else if self < .gigabyte {
return String(format: "%5.2f MB", self / .megabyte)
} else {
return String(format: "%5.2f GB", self / .gigabyte)
}
}
}

View file

@ -0,0 +1,50 @@
//
// FileManager+Extension.swift
// Mist
//
// Created by Nindi Gill on 17/6/2022.
//
import Foundation
extension FileManager {
func sizeOfDirectory(at url: URL) throws -> UInt64 {
var enumeratorError: Error?
let urlResourceKeys: Set<URLResourceKey> = [
.isRegularFileKey,
.fileAllocatedSizeKey,
.totalFileAllocatedSizeKey
]
guard let enumerator: FileManager.DirectoryEnumerator = self.enumerator(at: url, includingPropertiesForKeys: Array(urlResourceKeys), options: [], errorHandler: { _, error -> Bool in
enumeratorError = error
return false
}) else {
return 0
}
var size: UInt64 = 0
for item in enumerator {
if enumeratorError != nil {
break
}
guard let url: URL = item as? URL else {
continue
}
size += try url.fileSize()
}
if let error: Error = enumeratorError {
throw error
}
return size
}
}

View file

@ -0,0 +1,34 @@
//
// Sequence+Extension.swift
// Mist
//
// Created by Nindi Gill on 13/6/2022.
//
import Foundation
import Yams
extension Sequence where Iterator.Element == [String: Any] {
func firmwaresCSVString() -> String {
"Signed,Name,Version,Build,Size,Date,Compatible\n" + self.map { $0.firmwareCSVString() }.joined()
}
func installersCSVString() -> String {
"Identifier,Name,Version,Build,Size,Date,Compatible\n" + self.map { $0.installerCSVString() }.joined()
}
func jsonString() throws -> String {
let data: Data = try JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted, .sortedKeys])
return String(data: data, encoding: .utf8) ?? ""
}
func propertyListString() throws -> String {
let data: Data = try PropertyListSerialization.data(fromPropertyList: self, format: .xml, options: .bitWidth)
return String(data: data, encoding: .utf8) ?? ""
}
func yamlString() throws -> String {
try Yams.dump(object: self)
}
}

View file

@ -0,0 +1,34 @@
//
// String+Extension.swift
// Mist
//
// Created by Nindi Gill on 13/6/2022.
//
import Foundation
extension String {
static let appName: String = "mist"
static let appIdentifier: String = "com.ninxsoft.\(appName)"
static let helperIdentifier: String = "\(appIdentifier).helper"
static let helperLaunchDaemonURL: String = "/Library/LaunchDaemons/\(helperIdentifier).plist"
static let helperURL: String = "/Library/PrivilegedHelperTools/\(helperIdentifier)"
static let repositoryURL: String = "https://github.com/ninxsoft/\(appName)"
static let filenameTemplate: String = "Install %NAME% %VERSION%_%BUILD%"
static let firmwareFilenameTemplate: String = "\(filenameTemplate).ipsw"
static let applicationFilenameTemplate: String = "\(filenameTemplate).app"
static let diskImageFilenameTemplate: String = "\(filenameTemplate).dmg"
static let isoFilenameTemplate: String = "\(filenameTemplate).iso"
static let packageFilenameTemplate: String = "\(filenameTemplate).pkg"
static let packageIdentifierTemplate: String = "com.company.pkg.%NAME%.%VERSION%.%BUILD%"
static let temporaryDirectory: String = "/private/tmp/\(appIdentifier)"
static let cacheDirectory: String = "/Users/Shared/Mist/Cache"
func stringWithSubstitutions(name: String, version: String, build: String) -> String {
self.replacingOccurrences(of: "%NAME%", with: name)
.replacingOccurrences(of: "%VERSION%", with: version)
.replacingOccurrences(of: "%BUILD%", with: build)
.replacingOccurrences(of: "//", with: "/")
}
}

View file

@ -0,0 +1,15 @@
//
// UInt32+Extension.swift
// Mist
//
// Created by Nindi Gill on 20/6/2022.
//
import Foundation
extension UInt32 {
func hexString() -> String {
String(format: "0x%08X", self)
}
}

View file

@ -0,0 +1,35 @@
//
// Int64+Extension.swift
// Mist
//
// Created by Nindi Gill on 13/6/2022.
//
import Foundation
extension UInt64 {
/// kilobytes constant
static let kilobyte: UInt64 = 1_000
/// megabytes constant
static let megabyte: UInt64 = .kilobyte * 1_000
/// gigabytes constant
static let gigabyte: UInt64 = .megabyte * 1_000
func bytesString() -> String {
if self < .kilobyte {
return "\(self) bytes"
} else if self < .megabyte {
return String(format: "%5.2f KB", Double(self) / Double(.kilobyte))
} else if self < .gigabyte {
return String(format: "%5.2f MB", Double(self) / Double(.megabyte))
} else {
return String(format: "%5.2f GB", Double(self) / Double(.gigabyte))
}
}
func hexString() -> String {
String(format: "0x%016X", self)
}
}

View file

@ -0,0 +1,15 @@
//
// UInt8+Extension.swift
// Mist
//
// Created by Nindi Gill on 20/6/2022.
//
import Foundation
extension UInt8 {
func hexString() -> String {
String(format: "0x%02X", self)
}
}

View file

@ -0,0 +1,16 @@
//
// UNNotificationAction+Extension.swift
// Mist
//
// Created by Nindi Gill on 23/6/2022.
//
import UserNotifications
extension UNNotificationAction {
struct Identifier {
/// Show Identifier
static let show: String = "Show"
}
}

View file

@ -0,0 +1,18 @@
//
// UNNotificationCategory+Extension.swift
// Mist
//
// Created by Nindi Gill on 23/6/2022.
//
import UserNotifications
extension UNNotificationCategory {
struct Identifier {
/// Success Identifier
static let success: String = "Success"
/// Failure Identifier
static let failure: String = "Failure"
}
}

View file

@ -0,0 +1,62 @@
//
// URL+Extension.swift
// Mist
//
// Created by Nindi Gill on 17/6/2022.
//
import CryptoKit
import Foundation
extension URL {
func fileSize() throws -> UInt64 {
let urlResourceKeys: Set<URLResourceKey> = [
.isRegularFileKey,
.fileAllocatedSizeKey,
.totalFileAllocatedSizeKey
]
let resourceValues: URLResourceValues = try self.resourceValues(forKeys: urlResourceKeys)
guard let isRegularFile: Bool = resourceValues.isRegularFile,
isRegularFile else {
return 0
}
return UInt64(resourceValues.totalFileAllocatedSize ?? resourceValues.fileAllocatedSize ?? 0)
}
func shasum() -> String? {
let length: Int = 1_024 * 1_024 * 50 // 50 MB
do {
let fileHandle: FileHandle = try FileHandle(forReadingFrom: self)
defer {
fileHandle.closeFile()
}
var shasum: Insecure.SHA1 = Insecure.SHA1()
while try autoreleasepool(invoking: {
try Task.checkCancellation()
let data: Data = fileHandle.readData(ofLength: length)
if !data.isEmpty {
shasum.update(data: data)
}
return !data.isEmpty
}) { }
let data: Data = Data(shasum.finalize())
return data.map { String(format: "%02hhx", $0) }.joined()
} catch {
print(error.localizedDescription)
return nil
}
}
}

View file

@ -0,0 +1,28 @@
//
// Codesigner.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
/// Helper struct to codesign a file (ie. Disk Image).
struct Codesigner {
/// Sign a file with the provided signing identity.
///
/// - Parameters:
/// - url: The URL of the file to sign.
/// - identity: The codesigning identity.
///
/// - Throws: A `MistError` if the command failed to execute.
static func sign(_ url: URL, identity: String) async throws {
let arguments: [String] = ["codesign", "--sign", identity, url.path]
let result: (terminationStatus: Int32, standardOutput: String?, standardError: String?) = try ShellExecutor.shared.execute(arguments)
guard result.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: result.terminationStatus, string: result.standardError)
}
}
}

View file

@ -0,0 +1,23 @@
//
// DirectoryCreator.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
/// Helper struct to create directories.
struct DirectoryCreator {
/// Create a directory at the provided URL.
///
/// - Parameters:
/// - url: The URL of the directory to create.
///
/// - Throws: An `Error` if the command failed to execute.
static func create(_ url: URL) async throws {
try await DirectoryRemover.remove(url)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
}
}

View file

@ -0,0 +1,35 @@
//
// DirectoryRemover.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
import SecureXPC
/// Helper struct to remove directories
struct DirectoryRemover {
/// Remove directory at the provided URL.
///
/// - Parameters:
/// - url: The URL of the directory to remove.
///
/// - Throws: An `Error` if the command failed to execute.
static func remove(_ url: URL) async throws {
guard FileManager.default.fileExists(atPath: url.path) else {
return
}
let arguments: [String] = [url.path]
let client: XPCClient = XPCClient.forMachService(named: .helperIdentifier)
let request: HelperToolCommandRequest = HelperToolCommandRequest(type: .remove, arguments: arguments, environment: [:])
let response: HelperToolCommandResponse = try await client.sendMessage(request, to: XPCRoute.commandRoute)
guard response.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: response.terminationStatus, string: response.standardError)
}
}
}

View file

@ -0,0 +1,68 @@
//
// DiskImageCreator.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
/// Helper struct to create disk images.
struct DiskImageCreator {
/// Create an empty Disk Image of fixed size.
///
/// - Parameters:
/// - url: The URL of the disk image to create.
/// - size: The fixed size of the disk image.
///
/// - Throws: An `MistError` if the command failed to execute.
static func create(_ url: URL, size: UInt64) async throws {
let arguments: [String] = [
"hdiutil", "create",
"-fs", "JHFS+",
"-layout", "SPUD",
"-size", "\(size)g",
"-volname", url.lastPathComponent.replacingOccurrences(of: ".dmg", with: ""),
url.path
]
try await create(url, with: arguments)
}
/// Create a Disk Image (with dynamic size) containing the contents of a source directory.
///
/// - Parameters:
/// - url: The URL of the disk image to create.
/// - source: The URL of the source directory.
///
/// - Throws: An `MistError` if the command failed to execute.
static func create(_ url: URL, from source: URL) async throws {
let arguments: [String] = [
"hdiutil", "create",
"-fs", "HFS+",
"-srcFolder", source.path,
"-volname", url.lastPathComponent.replacingOccurrences(of: ".dmg", with: ""),
url.path
]
try await create(url, with: arguments)
}
/// Create a Disk Image based in supplied commandline arguments.
///
/// - Parameters:
/// - url: The URL of the disk image to create.
/// - arguments: A list of commandline arguments passed to the shell operation used to create the disk image.
///
/// - Throws: An `MistError` if the command failed to execute.
private static func create(_ url: URL, with arguments: [String]) async throws {
try await DirectoryRemover.remove(url)
let result: (terminationStatus: Int32, standardOutput: String?, standardError: String?) = try ShellExecutor.shared.execute(arguments)
guard result.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: result.terminationStatus, string: result.standardError)
}
}
}

View file

@ -0,0 +1,35 @@
//
// DiskImageMounter.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
/// Helper struct to mount Disk Images.
struct DiskImageMounter {
/// Mount a Disk Image at the provided mount point.
///
/// - Parameters:
/// - url: The URL of the disk image to mount.
/// - mountPoint: The URL of the directory mount point.
///
/// - Throws: A `MistError` if the command failed to execute.
static func mount(_ url: URL, mountPoint: URL) async throws {
do {
try await DiskImageUnmounter.unmount(mountPoint)
} catch {
// do nothing
}
let arguments: [String] = ["hdiutil", "attach", url.path, "-noverify", "-mountpoint", mountPoint.path]
let result: (terminationStatus: Int32, standardOutput: String?, standardError: String?) = try ShellExecutor.shared.execute(arguments)
guard result.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: result.terminationStatus, string: result.standardError)
}
}
}

View file

@ -0,0 +1,27 @@
//
// DiskImageUnmounter.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
/// Helper struct to unmount Disk Images.
struct DiskImageUnmounter {
/// Unmount a Disk Image at the provided mount point.
///
/// - Parameters:
/// - url: The URL of the directory mount point.
///
/// - Throws: A `MistError` if the command failed to execute.
static func unmount(_ url: URL) async throws {
let arguments: [String] = ["hdiutil", "detach", url.path, "-force"]
let result: (terminationStatus: Int32, standardOutput: String?, standardError: String?) = try ShellExecutor.shared.execute(arguments)
guard result.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: result.terminationStatus, string: result.standardError)
}
}
}

View file

@ -0,0 +1,105 @@
//
// DownloadManager.swift
// Mist
//
// Created by Nindi Gill on 23/6/2022.
//
import Foundation
class DownloadManager: NSObject, ObservableObject {
static let shared: DownloadManager = DownloadManager()
private var task: URLSessionDownloadTask?
private var progress: Progress = Progress()
var currentValue: Double {
progress.fractionCompleted
}
func download(_ url: URL, to destination: URL, retries retriesMaximum: Int, delay retryDelay: Int) async throws {
guard !FileManager.default.fileExists(atPath: destination.path) else {
return
}
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
var mistError: MistError?
var urlError: URLError?
var retries: Int = 0
let completionHandler: (URL?, URLResponse?, Error?) -> Void = { url, _, error in
if let error: URLError = error as? URLError {
guard error.code != .cancelled else {
mistError = .userCancelled
semaphore.signal()
return
}
urlError = error
semaphore.signal()
return
}
if let error: Error = error {
mistError = MistError.generalError(error.localizedDescription)
semaphore.signal()
return
}
guard let url: URL = url else {
mistError = MistError.invalidDestinationURL
semaphore.signal()
return
}
do {
if FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
try FileManager.default.moveItem(at: url, to: destination)
} catch {
mistError = MistError.generalError(error.localizedDescription)
}
semaphore.signal()
}
while mistError == nil {
guard retries < retriesMaximum else {
throw MistError.maximumRetriesReached
}
if let error: URLError = urlError {
guard let data: Data = error.downloadTaskResumeData else {
throw MistError.invalidDownloadResumeData
}
sleep(UInt32(retryDelay))
retries += 1
task = URLSession.shared.downloadTask(withResumeData: data, completionHandler: completionHandler)
} else {
task = URLSession.shared.downloadTask(with: url, completionHandler: completionHandler)
}
if let task: URLSessionDownloadTask = task {
progress = task.progress
}
urlError = nil
task?.resume()
semaphore.wait()
}
if let mistError: MistError = mistError {
throw mistError
}
}
func cancelTask() {
task?.cancel()
}
}

View file

@ -0,0 +1,30 @@
//
// FileCompressor.swift
// Mist
//
// Created by Nindi Gill on 22/6/2022.
//
import Foundation
/// Helper struct to create Zip archives.
struct FileCompressor {
/// Compress a file or the contents of a directory.
///
/// - Parameters:
/// - url: The URL of the file or directory to be compressed.
/// - destination: The URL of the Zip file to be created.
///
/// - Throws: An `Error` if the command failed to execute.
static func compress(_ url: URL, to destination: URL) async throws {
try await DirectoryRemover.remove(destination)
let arguments: [String] = ["ditto", "-c", "-k", "--keepParent", "--sequesterRsrc", "--zlibCompressionLevel", "0", url.path, destination.path]
let result: (terminationStatus: Int32, standardOutput: String?, standardError: String?) = try ShellExecutor.shared.execute(arguments)
guard result.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: result.terminationStatus, string: result.standardError)
}
}
}

View file

@ -0,0 +1,24 @@
//
// FileCopier.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
/// Helper struct to copy files.
struct FileCopier {
/// Copy a file from one location to another.
///
/// - Parameters:
/// - source: The URL of the file to copy.
/// - destination: The destination URL.
///
/// - Throws: An `Error` if the command failed to execute.
static func copy(_ source: URL, to destination: URL) async throws {
try await DirectoryRemover.remove(destination)
try FileManager.default.copyItem(at: source, to: destination)
}
}

View file

@ -0,0 +1,26 @@
//
// FileCreator.swift
// Mist
//
// Created by Nindi Gill on 22/6/2022.
//
import Foundation
/// Helper struct to create files with textual content.
struct FileCreator {
/// Create a file with the provided string and POSIX permissions.
///
/// - Parameters:
/// - url: The URL of the file to create.
/// - contents: The string to write to disk.
/// - permissions: The POSIX permissions to apply to the file being created.
///
/// - Throws: An `Error` if the command failed to execute.
static func create(_ url: URL, contents: String, permissions: Int) async throws {
try await DirectoryRemover.remove(url)
try contents.write(to: url, atomically: true, encoding: .utf8)
try FileManager.default.setAttributes([.posixPermissions: permissions], ofItemAtPath: url.path)
}
}

View file

@ -0,0 +1,24 @@
//
// FileMover.swift
// Mist
//
// Created by Nindi Gill on 20/6/2022.
//
import Foundation
/// Helper struct to move files.
struct FileMover {
/// Move a file from one location to another.
///
/// - Parameters:
/// - source: The URL of the file to move.
/// - destination: The destination URL.
///
/// - Throws: An `Error` if the command failed to execute.
static func move(_ source: URL, to destination: URL) async throws {
try await DirectoryRemover.remove(destination)
try FileManager.default.moveItem(at: source, to: destination)
}
}

View file

@ -0,0 +1,57 @@
//
// FileSplitter.swift
// Mist
//
// Created by Nindi Gill on 22/6/2022.
//
import Foundation
/// Helper struct to split files into chunks.
struct FileSplitter {
/// Split a file into chunks with the provided filename prefix.
///
/// - Parameters:
/// - url: The URL of the file to be split.
/// - currentDirectory: The directory in which to create the split files.
/// - prefix: The filename prefix for the split files.
///
/// - Throws: An `Error` if the command failed to execute.
static func split(_ url: URL, from currentDirectory: URL, prefix: String) async throws {
let fileSize: UInt64 = try url.fileSize()
let factor: UInt64 = 100
let chunkSize: UInt64 = 1_024 * 1_024 * factor // 100 MB
let totalFileChunks: UInt64 = UInt64(ceil(Double(fileSize) / Double(chunkSize)))
let standardFileChunks: UInt64 = 8_000 / factor // 8000 MB
let lastFileChunks: UInt64 = totalFileChunks % standardFileChunks
let numberOfFiles: Int = Int(ceil((Double(totalFileChunks) / Double(standardFileChunks))))
let readHandle: FileHandle = try FileHandle(forReadingFrom: url)
for file in 0..<numberOfFiles {
let isLastFile: Bool = file == numberOfFiles - 1
let numberOfChunks: UInt64 = isLastFile ? lastFileChunks : standardFileChunks
let destination: URL = currentDirectory.appendingPathComponent("\(prefix)\(file)")
try await DirectoryRemover.remove(destination)
try Data().write(to: destination)
let writeHandle: FileHandle = try FileHandle(forWritingTo: destination)
var writeOffset: UInt64 = 0
for chunk in 0..<numberOfChunks {
try Task.checkCancellation()
try autoreleasepool {
let readOffset: UInt64 = UInt64(file) * standardFileChunks * chunkSize + UInt64(chunk) * chunkSize
try readHandle.seek(toOffset: readOffset)
try writeHandle.seek(toOffset: writeOffset)
let data: Data = readHandle.readData(ofLength: Int(chunkSize))
try writeHandle.write(contentsOf: data)
writeOffset += chunkSize
}
}
try writeHandle.close()
}
readHandle.closeFile()
}
}

View file

@ -0,0 +1,28 @@
//
// ISOConverter.swift
// Mist
//
// Created by Nindi Gill on 22/6/2022.
//
import Foundation
/// Helper struct to convert Disk Images to ISOs.
struct ISOConverter {
/// Convert a Disk Image to an ISO.
///
/// - Parameters:
/// - source: The URL of the Disk Image to be converted.
/// - destination: The URL of the ISO to be created.
///
/// - Throws: An `Error` if the command failed to execute.
static func convert(_ source: URL, destination: URL) async throws {
let arguments: [String] = ["hdiutil", "convert", source.path, "-format", "UDTO", "-o", destination.path]
let result: (terminationStatus: Int32, standardOutput: String?, standardError: String?) = try ShellExecutor.shared.execute(arguments)
guard result.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: result.terminationStatus, string: result.standardError)
}
}
}

View file

@ -0,0 +1,31 @@
//
// InstallMediaCreator.swift
// Mist
//
// Created by Nindi Gill on 22/6/2022.
//
import Foundation
import SecureXPC
/// Helper struct to execute the `createinstallmedia` command found in macOS Install app bundles.
struct InstallMediaCreator {
/// Create the macOS Install Media at the specified mount point.
///
/// - Parameters:
/// - url: The URL of the `createinstallmedia` binary to execute.
/// - mountPoint: The URL of the mount point (target volume).
///
/// - Throws: An `Error` if the command failed to execute.
static func create(_ url: URL, mountPoint: URL) async throws {
let arguments: [String] = [url.path, "--volume", mountPoint.path, "--nointeraction"]
let client: XPCClient = XPCClient.forMachService(named: .helperIdentifier)
let request: HelperToolCommandRequest = HelperToolCommandRequest(type: .createinstallmedia, arguments: arguments, environment: [:])
let response: HelperToolCommandResponse = try await client.sendMessage(request, to: XPCRoute.commandRoute)
guard response.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: response.terminationStatus, string: response.standardError)
}
}
}

View file

@ -0,0 +1,54 @@
//
// InstallerCreator.swift
// Mist
//
// Created by Nindi Gill on 21/6/2022.
//
import Foundation
import SecureXPC
/// Helper Struct used to create macOS Installers.
struct InstallerCreator {
/// Creates a recently downloaded macOS Installer.
///
/// - Parameters:
/// - installer: The selected macOS Installer that was downloaded.
/// - mountPoint: The URL of the directory mount point.
///
/// - Throws: A `MistError` if the downloaded macOS Installer fails to generate.
static func create(_ installer: Installer, mountPoint: URL) async throws {
guard let url: URL = URL(string: installer.distributionURL) else {
throw MistError.invalidURL(installer.distributionURL)
}
try await DirectoryRemover.remove(installer.temporaryInstallerURL)
let cacheDirectoryURL: URL = URL(fileURLWithPath: .cacheDirectory)
let distributionURL: URL = cacheDirectoryURL.appendingPathComponent(installer.id).appendingPathComponent(url.lastPathComponent)
var argumentsArrays: [[String]] = [
["installer", "-pkg", distributionURL.path, "-target", mountPoint.path]
]
if installer.catalinaOrNewer {
argumentsArrays += [
["ditto", "\(mountPoint.path)Applications", "\(mountPoint.path)/Applications"],
["rm", "-r", "\(mountPoint.path)Applications"]
]
}
let variables: [String: String] = ["CM_BUILD": "CM_BUILD"]
let client: XPCClient = XPCClient.forMachService(named: .helperIdentifier)
for arguments in argumentsArrays {
let request: HelperToolCommandRequest = HelperToolCommandRequest(type: .installer, arguments: arguments, environment: variables)
let response: HelperToolCommandResponse = try await client.sendMessage(request, to: XPCRoute.commandRoute)
guard response.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: response.terminationStatus, string: response.standardError)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more