Initial commit
5
.drstring.toml
Normal 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
|
@ -0,0 +1,6 @@
|
||||||
|
.DS_Store
|
||||||
|
.build/
|
||||||
|
.swiftpm/
|
||||||
|
build/
|
||||||
|
xcuserdata/
|
||||||
|
Package.resolved
|
78
.swiftlint.yml
Normal 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
|
@ -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"
|
1171
Mist.xcodeproj/project.pbxproj
Normal file
7
Mist.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
|
@ -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>
|
78
Mist.xcodeproj/xcshareddata/xcschemes/Mist.xcscheme
Normal 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>
|
|
@ -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
|
@ -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
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
Mist/Assets.xcassets/AccentColor.colorset/Contents.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
68
Mist/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
Mist/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
Normal file
After Width: | Height: | Size: 203 KiB |
BIN
Mist/Assets.xcassets/Application - macOS Big Sur.imageset/App Installers - macOS Big Sur.png
vendored
Normal file
After Width: | Height: | Size: 86 KiB |
12
Mist/Assets.xcassets/Application - macOS Big Sur.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "App Installers - macOS Big Sur.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Application - macOS Catalina.imageset/App Installer - macOS Catalina.png
vendored
Normal file
After Width: | Height: | Size: 67 KiB |
12
Mist/Assets.xcassets/Application - macOS Catalina.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "App Installer - macOS Catalina.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Application - macOS High Sierra.imageset/App Installer - macOS High Sierra.png
vendored
Normal file
After Width: | Height: | Size: 82 KiB |
12
Mist/Assets.xcassets/Application - macOS High Sierra.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "App Installer - macOS High Sierra.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Application - macOS Mojave.imageset/App Installers - macOS Mojave.png
vendored
Normal file
After Width: | Height: | Size: 58 KiB |
12
Mist/Assets.xcassets/Application - macOS Mojave.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "App Installers - macOS Mojave.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Application - macOS Monterey.imageset/App Installer - macOS Monterey.png
vendored
Normal file
After Width: | Height: | Size: 50 KiB |
12
Mist/Assets.xcassets/Application - macOS Monterey.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "App Installer - macOS Monterey.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Application - macOS Ventura.imageset/App Installer - macOS Ventura.png
vendored
Normal file
After Width: | Height: | Size: 28 KiB |
12
Mist/Assets.xcassets/Application - macOS Ventura.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "App Installer - macOS Ventura.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Application - macOS.imageset/App Installer - macOS.png
vendored
Normal file
After Width: | Height: | Size: 28 KiB |
12
Mist/Assets.xcassets/Application - macOS.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "App Installer - macOS.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Certificate.imageset/Certificate.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
12
Mist/Assets.xcassets/Certificate.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Certificate.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Cleanup.imageset/Cleanup.png
vendored
Normal file
After Width: | Height: | Size: 61 KiB |
12
Mist/Assets.xcassets/Cleanup.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "Cleanup.png",
|
||||||
|
"idiom": "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
6
Mist/Assets.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
12
Mist/Assets.xcassets/Disk Image.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Disk Image.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Disk Image.imageset/Disk Image.png
vendored
Normal file
After Width: | Height: | Size: 32 KiB |
12
Mist/Assets.xcassets/Download.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Download.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Download.imageset/Download.png
vendored
Normal file
After Width: | Height: | Size: 89 KiB |
12
Mist/Assets.xcassets/Firmware.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Firmware.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Firmware.imageset/Firmware.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
12
Mist/Assets.xcassets/ISO.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ISO.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/ISO.imageset/ISO.png
vendored
Normal file
After Width: | Height: | Size: 79 KiB |
12
Mist/Assets.xcassets/Installer.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Installer.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Installer.imageset/Installer.png
vendored
Normal file
After Width: | Height: | Size: 50 KiB |
12
Mist/Assets.xcassets/Package.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Package.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Package.imageset/Package.png
vendored
Normal file
After Width: | Height: | Size: 62 KiB |
12
Mist/Assets.xcassets/Setup.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Setup.png",
|
||||||
|
"idiom" : "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/Setup.imageset/Setup.png
vendored
Normal file
After Width: | Height: | Size: 50 KiB |
12
Mist/Assets.xcassets/macOS Big Sur.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "macOS Big Sur.png",
|
||||||
|
"idiom": "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/macOS Big Sur.imageset/macOS Big Sur.png
vendored
Normal file
After Width: | Height: | Size: 114 KiB |
22
Mist/Assets.xcassets/macOS Catalina.imageset/Contents.json
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/macOS Catalina.imageset/macOS Catalina Dark.png
vendored
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
Mist/Assets.xcassets/macOS Catalina.imageset/macOS Catalina Light.png
vendored
Normal file
After Width: | Height: | Size: 86 KiB |
12
Mist/Assets.xcassets/macOS High Sierra.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "macOS High Sierra.png",
|
||||||
|
"idiom": "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/macOS High Sierra.imageset/macOS High Sierra.png
vendored
Normal file
After Width: | Height: | Size: 105 KiB |
22
Mist/Assets.xcassets/macOS Mojave.imageset/Contents.json
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/macOS Mojave.imageset/macOS Mojave Dark.png
vendored
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
Mist/Assets.xcassets/macOS Mojave.imageset/macOS Mojave Light.png
vendored
Normal file
After Width: | Height: | Size: 74 KiB |
12
Mist/Assets.xcassets/macOS Monterey.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "macOS Monterey.png",
|
||||||
|
"idiom": "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/macOS Monterey.imageset/macOS Monterey.png
vendored
Normal file
After Width: | Height: | Size: 66 KiB |
12
Mist/Assets.xcassets/macOS Ventura.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "macOS Ventura.png",
|
||||||
|
"idiom": "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/macOS Ventura.imageset/macOS Ventura.png
vendored
Normal file
After Width: | Height: | Size: 27 KiB |
12
Mist/Assets.xcassets/macOS.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "macOS.png",
|
||||||
|
"idiom": "mac"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Mist/Assets.xcassets/macOS.imageset/macOS.png
vendored
Normal file
After Width: | Height: | Size: 27 KiB |
27
Mist/Extensions/Array+Extension.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
Mist/Extensions/AuthorizationError+Extension.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
58
Mist/Extensions/Dictionary+Extension.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
31
Mist/Extensions/Double+Extension.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Mist/Extensions/FileManager+Extension.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
34
Mist/Extensions/Sequence+Extension.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
34
Mist/Extensions/String+Extension.swift
Normal 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: "/")
|
||||||
|
}
|
||||||
|
}
|
15
Mist/Extensions/UInt32+Extension.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
35
Mist/Extensions/UInt64+Extension.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
15
Mist/Extensions/UInt8+Extension.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
16
Mist/Extensions/UNNotificationAction+Extension.swift
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
18
Mist/Extensions/UNNotificationCategory+Extension.swift
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
62
Mist/Extensions/URL+Extension.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Mist/Helpers/Codesigner.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Mist/Helpers/DirectoryCreator.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
35
Mist/Helpers/DirectoryRemover.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Mist/Helpers/DiskImageCreator.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
Mist/Helpers/DiskImageMounter.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
Mist/Helpers/DiskImageUnmounter.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
Mist/Helpers/DownloadManager.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
30
Mist/Helpers/FileCompressor.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
Mist/Helpers/FileCopier.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
26
Mist/Helpers/FileCreator.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
24
Mist/Helpers/FileMover.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
57
Mist/Helpers/FileSplitter.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
28
Mist/Helpers/ISOConverter.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
Mist/Helpers/InstallMediaCreator.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
Mist/Helpers/InstallerCreator.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|