mirror of
https://github.com/ninxsoft/Mist.git
synced 2025-05-16 16:14:49 -04:00
Merge pull request #53 from ninxsoft/feature-bootable-installers
Feature bootable installers
This commit is contained in:
commit
ccb76ca028
35 changed files with 1026 additions and 693 deletions
|
@ -14,7 +14,7 @@
|
|||
390451BF2856E34700E0B563 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451BE2856E34700E0B563 /* String+Extension.swift */; };
|
||||
390451C22856E3F500E0B563 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451C12856E3F500E0B563 /* Hardware.swift */; };
|
||||
390451C62856E80C00E0B563 /* RefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451C52856E80C00E0B563 /* RefreshView.swift */; };
|
||||
390451C82856E94900E0B563 /* FirmwareListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451C72856E94900E0B563 /* FirmwareListRow.swift */; };
|
||||
390451C82856E94900E0B563 /* ListRowFirmware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451C72856E94900E0B563 /* ListRowFirmware.swift */; };
|
||||
390451CA2856F1D300E0B563 /* ScaledImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451C92856F1D300E0B563 /* ScaledImage.swift */; };
|
||||
390451CC2856F23100E0B563 /* ScaledSystemImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451CB2856F23100E0B563 /* ScaledSystemImage.swift */; };
|
||||
390451CE2856F42800E0B563 /* DownloadType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451CD2856F42800E0B563 /* DownloadType.swift */; };
|
||||
|
@ -53,7 +53,7 @@
|
|||
39252AB7285C718C00956C74 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252AB6285C718C00956C74 /* FileManager+Extension.swift */; };
|
||||
39252AB9285C7BC700956C74 /* SettingsInstallersCacheView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252AB8285C7BC700956C74 /* SettingsInstallersCacheView.swift */; };
|
||||
39252ABB285C7D3800956C74 /* SettingsInstallersCatalogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252ABA285C7D3800956C74 /* SettingsInstallersCatalogsView.swift */; };
|
||||
39252ABD285C8FFC00956C74 /* InstallerListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252ABC285C8FFC00956C74 /* InstallerListRow.swift */; };
|
||||
39252ABD285C8FFC00956C74 /* ListRowInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252ABC285C8FFC00956C74 /* ListRowInstaller.swift */; };
|
||||
39252AC3285CA5FE00956C74 /* InstallerExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252AC2285CA5FE00956C74 /* InstallerExportView.swift */; };
|
||||
3935F47428643AB800760AB0 /* UNNotificationCategory+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F47328643AB800760AB0 /* UNNotificationCategory+Extension.swift */; };
|
||||
3935F47628643AF000760AB0 /* UNNotificationAction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F47528643AF000760AB0 /* UNNotificationAction+Extension.swift */; };
|
||||
|
@ -62,22 +62,21 @@
|
|||
3935F47E2864813B00760AB0 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F47D2864813B00760AB0 /* DownloadManager.swift */; };
|
||||
3935F480286551FB00760AB0 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F47F286551FB00760AB0 /* Double+Extension.swift */; };
|
||||
3935F4852866B64900760AB0 /* MistTaskSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4842866B64900760AB0 /* MistTaskSection.swift */; };
|
||||
3935F4892866C68000760AB0 /* DownloadSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4882866C68000760AB0 /* DownloadSectionHeaderView.swift */; };
|
||||
3935F4892866C68000760AB0 /* ActivitySectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4882866C68000760AB0 /* ActivitySectionHeaderView.swift */; };
|
||||
3935F48E2869278200760AB0 /* InstallerExportType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F48D2869278100760AB0 /* InstallerExportType.swift */; };
|
||||
3935F490286976D000760AB0 /* ProgressAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F48F286976D000760AB0 /* ProgressAlertType.swift */; };
|
||||
3935F49D286ABE4D00760AB0 /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F49C286ABE4D00760AB0 /* FooterView.swift */; };
|
||||
3935F49F286AC32C00760AB0 /* ListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F49E286AC32C00760AB0 /* ListRow.swift */; };
|
||||
3935F4A2286ACD4D00760AB0 /* InstallerExportViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A1286ACD4D00760AB0 /* InstallerExportViewItem.swift */; };
|
||||
3935F4A4286AD21000760AB0 /* DownloadProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A3286AD21000760AB0 /* DownloadProgressView.swift */; };
|
||||
3935F4A6286AD3E100760AB0 /* DownloadHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A5286AD3E100760AB0 /* DownloadHeaderView.swift */; };
|
||||
3935F4A8286AD5D000760AB0 /* DownloadRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A7286AD5D000760AB0 /* DownloadRowView.swift */; };
|
||||
3935F4A4286AD21000760AB0 /* ActivityProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A3286AD21000760AB0 /* ActivityProgressView.swift */; };
|
||||
3935F4A6286AD3E100760AB0 /* ActivityHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A5286AD3E100760AB0 /* ActivityHeaderView.swift */; };
|
||||
3935F4A8286AD5D000760AB0 /* ActivityRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A7286AD5D000760AB0 /* ActivityRowView.swift */; };
|
||||
3935F4AB286B04BC00760AB0 /* HelperToolInfoPropertyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A9286B04BC00760AB0 /* HelperToolInfoPropertyList.swift */; };
|
||||
3935F4AC286B04BC00760AB0 /* HelperToolLaunchdPropertyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4AA286B04BC00760AB0 /* HelperToolLaunchdPropertyList.swift */; };
|
||||
3935F4AD286B04BF00760AB0 /* HelperToolInfoPropertyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4A9286B04BC00760AB0 /* HelperToolInfoPropertyList.swift */; };
|
||||
3935F4AE286B04BF00760AB0 /* HelperToolLaunchdPropertyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4AA286B04BC00760AB0 /* HelperToolLaunchdPropertyList.swift */; };
|
||||
3935F4C5286B546A00760AB0 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 3935F4C4286B546A00760AB0 /* Sparkle */; };
|
||||
3935F4C7286B54E200760AB0 /* SparkleUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4C6286B54E200760AB0 /* SparkleUpdater.swift */; };
|
||||
3935F4CB286C1EC500760AB0 /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4CA286C1EC500760AB0 /* DownloadView.swift */; };
|
||||
3935F4CB286C1EC500760AB0 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4CA286C1EC500760AB0 /* ActivityView.swift */; };
|
||||
3935F4CD286C6A5D00760AB0 /* ProcessKiller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3935F4CC286C6A5D00760AB0 /* ProcessKiller.swift */; };
|
||||
393D8029286EB4D6008AA8E3 /* EmptyCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393D8028286EB4D6008AA8E3 /* EmptyCollectionView.swift */; };
|
||||
393F35B928640DF6005B7165 /* ShellExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252A9E285C140D00956C74 /* ShellExecutor.swift */; };
|
||||
|
@ -85,7 +84,7 @@
|
|||
393F35BC28641181005B7165 /* RefreshState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393F35BB28641181005B7165 /* RefreshState.swift */; };
|
||||
393F35BE2864197F005B7165 /* PrivilegedHelperTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393F35BD2864197F005B7165 /* PrivilegedHelperTool.swift */; };
|
||||
393F35C228641E1F005B7165 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393F35C128641E1F005B7165 /* HeaderView.swift */; };
|
||||
395DCD16287FE36E00C411CE /* DownloadAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395DCD15287FE36E00C411CE /* DownloadAlertType.swift */; };
|
||||
395DCD16287FE36E00C411CE /* InstallerAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395DCD15287FE36E00C411CE /* InstallerAlertType.swift */; };
|
||||
398734C428600E6E00B4C357 /* TaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734C328600E6E00B4C357 /* TaskManager.swift */; };
|
||||
398734C6286011C300B4C357 /* Validator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734C5286011C300B4C357 /* Validator.swift */; };
|
||||
398734C828601FFC00B4C357 /* FileMover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734C728601FFC00B4C357 /* FileMover.swift */; };
|
||||
|
@ -132,11 +131,15 @@
|
|||
39FF05FA285985DD00A86670 /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39FF05F9285985DD00A86670 /* SettingsAboutView.swift */; };
|
||||
573A23622A28711C00EC9470 /* Architecture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 573A23612A28711C00EC9470 /* Architecture.swift */; };
|
||||
573A23642A28791F00EC9470 /* Scene+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 573A23632A28791F00EC9470 /* Scene+Extension.swift */; };
|
||||
575812B72A372D7200425BAF /* CapsuleButtonStyleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812B62A372D7200425BAF /* CapsuleButtonStyleType.swift */; };
|
||||
575812BA2A373A4F00425BAF /* FirmwareAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812B92A373A4F00425BAF /* FirmwareAlertType.swift */; };
|
||||
575812BC2A37406300425BAF /* ListRowDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812BB2A37406300425BAF /* ListRowDetail.swift */; };
|
||||
575812BE2A3743E300425BAF /* InstallerSheetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812BD2A3743E300425BAF /* InstallerSheetType.swift */; };
|
||||
575812C02A37493F00425BAF /* InstallerVolumeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812BF2A37493F00425BAF /* InstallerVolumeSelectionView.swift */; };
|
||||
575812C22A380B5E00425BAF /* InstallerVolume.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812C12A380B5E00425BAF /* InstallerVolume.swift */; };
|
||||
575812C42A3821A900425BAF /* InstallerVolumeSelectionInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812C32A3821A900425BAF /* InstallerVolumeSelectionInformationView.swift */; };
|
||||
575812C62A38296A00425BAF /* InstallerVolumeSelectionPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575812C52A38296A00425BAF /* InstallerVolumeSelectionPickerView.swift */; };
|
||||
5795700B2A31B06F004C7051 /* ButtonStyle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5795700A2A31B06F004C7051 /* ButtonStyle+Extension.swift */; };
|
||||
5795700D2A31B081004C7051 /* CapsuleButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5795700C2A31B081004C7051 /* CapsuleButtonStyle.swift */; };
|
||||
57CF961A2A34B65C008D3B1C /* CapsuleLeading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CF96192A34B65C008D3B1C /* CapsuleLeading.swift */; };
|
||||
57CF961C2A34B9E0008D3B1C /* CapsuleTrailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CF961B2A34B9E0008D3B1C /* CapsuleTrailing.swift */; };
|
||||
5795700D2A31B081004C7051 /* MistActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5795700C2A31B081004C7051 /* MistActionButtonStyle.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
|
@ -170,7 +173,7 @@
|
|||
390451BE2856E34700E0B563 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
|
||||
390451C12856E3F500E0B563 /* Hardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hardware.swift; sourceTree = "<group>"; };
|
||||
390451C52856E80C00E0B563 /* RefreshView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshView.swift; sourceTree = "<group>"; };
|
||||
390451C72856E94900E0B563 /* FirmwareListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareListRow.swift; sourceTree = "<group>"; };
|
||||
390451C72856E94900E0B563 /* ListRowFirmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowFirmware.swift; sourceTree = "<group>"; };
|
||||
390451C92856F1D300E0B563 /* ScaledImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledImage.swift; sourceTree = "<group>"; };
|
||||
390451CB2856F23100E0B563 /* ScaledSystemImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledSystemImage.swift; sourceTree = "<group>"; };
|
||||
390451CD2856F42800E0B563 /* DownloadType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadType.swift; sourceTree = "<group>"; };
|
||||
|
@ -208,7 +211,7 @@
|
|||
39252AB6285C718C00956C74 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = "<group>"; };
|
||||
39252AB8285C7BC700956C74 /* SettingsInstallersCacheView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInstallersCacheView.swift; sourceTree = "<group>"; };
|
||||
39252ABA285C7D3800956C74 /* SettingsInstallersCatalogsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInstallersCatalogsView.swift; sourceTree = "<group>"; };
|
||||
39252ABC285C8FFC00956C74 /* InstallerListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerListRow.swift; sourceTree = "<group>"; };
|
||||
39252ABC285C8FFC00956C74 /* ListRowInstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowInstaller.swift; sourceTree = "<group>"; };
|
||||
39252AC2285CA5FE00956C74 /* InstallerExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerExportView.swift; sourceTree = "<group>"; };
|
||||
3935F47328643AB800760AB0 /* UNNotificationCategory+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotificationCategory+Extension.swift"; sourceTree = "<group>"; };
|
||||
3935F47528643AF000760AB0 /* UNNotificationAction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotificationAction+Extension.swift"; sourceTree = "<group>"; };
|
||||
|
@ -217,26 +220,25 @@
|
|||
3935F47D2864813B00760AB0 /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
|
||||
3935F47F286551FB00760AB0 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
|
||||
3935F4842866B64900760AB0 /* MistTaskSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MistTaskSection.swift; sourceTree = "<group>"; };
|
||||
3935F4882866C68000760AB0 /* DownloadSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadSectionHeaderView.swift; sourceTree = "<group>"; };
|
||||
3935F4882866C68000760AB0 /* ActivitySectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivitySectionHeaderView.swift; sourceTree = "<group>"; };
|
||||
3935F48D2869278100760AB0 /* InstallerExportType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstallerExportType.swift; sourceTree = "<group>"; };
|
||||
3935F48F286976D000760AB0 /* ProgressAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressAlertType.swift; sourceTree = "<group>"; };
|
||||
3935F49C286ABE4D00760AB0 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = "<group>"; };
|
||||
3935F49E286AC32C00760AB0 /* ListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRow.swift; sourceTree = "<group>"; };
|
||||
3935F4A1286ACD4D00760AB0 /* InstallerExportViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerExportViewItem.swift; sourceTree = "<group>"; };
|
||||
3935F4A3286AD21000760AB0 /* DownloadProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadProgressView.swift; sourceTree = "<group>"; };
|
||||
3935F4A5286AD3E100760AB0 /* DownloadHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadHeaderView.swift; sourceTree = "<group>"; };
|
||||
3935F4A7286AD5D000760AB0 /* DownloadRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadRowView.swift; sourceTree = "<group>"; };
|
||||
3935F4A3286AD21000760AB0 /* ActivityProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityProgressView.swift; sourceTree = "<group>"; };
|
||||
3935F4A5286AD3E100760AB0 /* ActivityHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityHeaderView.swift; sourceTree = "<group>"; };
|
||||
3935F4A7286AD5D000760AB0 /* ActivityRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityRowView.swift; sourceTree = "<group>"; };
|
||||
3935F4A9286B04BC00760AB0 /* HelperToolInfoPropertyList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelperToolInfoPropertyList.swift; sourceTree = "<group>"; };
|
||||
3935F4AA286B04BC00760AB0 /* HelperToolLaunchdPropertyList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelperToolLaunchdPropertyList.swift; sourceTree = "<group>"; };
|
||||
3935F4AF286B195E00760AB0 /* launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = launchd.plist; sourceTree = "<group>"; };
|
||||
3935F4C6286B54E200760AB0 /* SparkleUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleUpdater.swift; sourceTree = "<group>"; };
|
||||
3935F4CA286C1EC500760AB0 /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = "<group>"; };
|
||||
3935F4CA286C1EC500760AB0 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; };
|
||||
3935F4CC286C6A5D00760AB0 /* ProcessKiller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessKiller.swift; sourceTree = "<group>"; };
|
||||
393D8028286EB4D6008AA8E3 /* EmptyCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyCollectionView.swift; sourceTree = "<group>"; };
|
||||
393F35BB28641181005B7165 /* RefreshState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshState.swift; sourceTree = "<group>"; };
|
||||
393F35BD2864197F005B7165 /* PrivilegedHelperTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivilegedHelperTool.swift; sourceTree = "<group>"; };
|
||||
393F35C128641E1F005B7165 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
|
||||
395DCD15287FE36E00C411CE /* DownloadAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadAlertType.swift; sourceTree = "<group>"; };
|
||||
395DCD15287FE36E00C411CE /* InstallerAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerAlertType.swift; sourceTree = "<group>"; };
|
||||
398734C328600E6E00B4C357 /* TaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskManager.swift; sourceTree = "<group>"; };
|
||||
398734C5286011C300B4C357 /* Validator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validator.swift; sourceTree = "<group>"; };
|
||||
398734C728601FFC00B4C357 /* FileMover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileMover.swift; sourceTree = "<group>"; };
|
||||
|
@ -279,11 +281,15 @@
|
|||
39FF05F9285985DD00A86670 /* SettingsAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = "<group>"; };
|
||||
573A23612A28711C00EC9470 /* Architecture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Architecture.swift; sourceTree = "<group>"; };
|
||||
573A23632A28791F00EC9470 /* Scene+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Scene+Extension.swift"; sourceTree = "<group>"; };
|
||||
575812B62A372D7200425BAF /* CapsuleButtonStyleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleButtonStyleType.swift; sourceTree = "<group>"; };
|
||||
575812B92A373A4F00425BAF /* FirmwareAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareAlertType.swift; sourceTree = "<group>"; };
|
||||
575812BB2A37406300425BAF /* ListRowDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowDetail.swift; sourceTree = "<group>"; };
|
||||
575812BD2A3743E300425BAF /* InstallerSheetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerSheetType.swift; sourceTree = "<group>"; };
|
||||
575812BF2A37493F00425BAF /* InstallerVolumeSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerVolumeSelectionView.swift; sourceTree = "<group>"; };
|
||||
575812C12A380B5E00425BAF /* InstallerVolume.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerVolume.swift; sourceTree = "<group>"; };
|
||||
575812C32A3821A900425BAF /* InstallerVolumeSelectionInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerVolumeSelectionInformationView.swift; sourceTree = "<group>"; };
|
||||
575812C52A38296A00425BAF /* InstallerVolumeSelectionPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerVolumeSelectionPickerView.swift; sourceTree = "<group>"; };
|
||||
5795700A2A31B06F004C7051 /* ButtonStyle+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ButtonStyle+Extension.swift"; sourceTree = "<group>"; };
|
||||
5795700C2A31B081004C7051 /* CapsuleButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapsuleButtonStyle.swift; sourceTree = "<group>"; };
|
||||
57CF96192A34B65C008D3B1C /* CapsuleLeading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleLeading.swift; sourceTree = "<group>"; };
|
||||
57CF961B2A34B9E0008D3B1C /* CapsuleTrailing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleTrailing.swift; sourceTree = "<group>"; };
|
||||
5795700C2A31B081004C7051 /* MistActionButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MistActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -412,13 +418,16 @@
|
|||
39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */,
|
||||
398734CB28603D5F00B4C357 /* Chunklist.swift */,
|
||||
398734CD28603D7F00B4C357 /* Chunk.swift */,
|
||||
395DCD15287FE36E00C411CE /* DownloadAlertType.swift */,
|
||||
390451CD2856F42800E0B563 /* DownloadType.swift */,
|
||||
390451D928573ADC00E0B563 /* ExportListType.swift */,
|
||||
390451B82856E24200E0B563 /* Firmware.swift */,
|
||||
575812B92A373A4F00425BAF /* FirmwareAlertType.swift */,
|
||||
390451C12856E3F500E0B563 /* Hardware.swift */,
|
||||
390451CF2856F63700E0B563 /* Installer.swift */,
|
||||
395DCD15287FE36E00C411CE /* InstallerAlertType.swift */,
|
||||
575812BD2A3743E300425BAF /* InstallerSheetType.swift */,
|
||||
3935F48D2869278100760AB0 /* InstallerExportType.swift */,
|
||||
575812C12A380B5E00425BAF /* InstallerVolume.swift */,
|
||||
39252A9A285C029600956C74 /* MistError.swift */,
|
||||
39252A94285BF83D00956C74 /* MistTask.swift */,
|
||||
3935F4842866B64900760AB0 /* MistTaskSection.swift */,
|
||||
|
@ -440,7 +449,7 @@
|
|||
3935F49C286ABE4D00760AB0 /* FooterView.swift */,
|
||||
390451D728573A2500E0B563 /* ExportListView.swift */,
|
||||
3935F4A0286ACCE100760AB0 /* List */,
|
||||
393F35BF28641D86005B7165 /* Download */,
|
||||
393F35BF28641D86005B7165 /* Activity */,
|
||||
393F35C028641D8F005B7165 /* Refresh */,
|
||||
39FF05F2285984F800A86670 /* Settings */,
|
||||
39FF05F12859849200A86670 /* Components */,
|
||||
|
@ -451,25 +460,28 @@
|
|||
3935F4A0286ACCE100760AB0 /* List */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
390451C72856E94900E0B563 /* FirmwareListRow.swift */,
|
||||
39252ABC285C8FFC00956C74 /* InstallerListRow.swift */,
|
||||
3935F49E286AC32C00760AB0 /* ListRow.swift */,
|
||||
390451C72856E94900E0B563 /* ListRowFirmware.swift */,
|
||||
39252ABC285C8FFC00956C74 /* ListRowInstaller.swift */,
|
||||
575812BB2A37406300425BAF /* ListRowDetail.swift */,
|
||||
39252AC2285CA5FE00956C74 /* InstallerExportView.swift */,
|
||||
3935F4A1286ACD4D00760AB0 /* InstallerExportViewItem.swift */,
|
||||
575812BF2A37493F00425BAF /* InstallerVolumeSelectionView.swift */,
|
||||
575812C52A38296A00425BAF /* InstallerVolumeSelectionPickerView.swift */,
|
||||
575812C32A3821A900425BAF /* InstallerVolumeSelectionInformationView.swift */,
|
||||
);
|
||||
path = List;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
393F35BF28641D86005B7165 /* Download */ = {
|
||||
393F35BF28641D86005B7165 /* Activity */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3935F4CA286C1EC500760AB0 /* DownloadView.swift */,
|
||||
3935F4A5286AD3E100760AB0 /* DownloadHeaderView.swift */,
|
||||
3935F4882866C68000760AB0 /* DownloadSectionHeaderView.swift */,
|
||||
3935F4A7286AD5D000760AB0 /* DownloadRowView.swift */,
|
||||
3935F4A3286AD21000760AB0 /* DownloadProgressView.swift */,
|
||||
3935F4CA286C1EC500760AB0 /* ActivityView.swift */,
|
||||
3935F4A5286AD3E100760AB0 /* ActivityHeaderView.swift */,
|
||||
3935F4882866C68000760AB0 /* ActivitySectionHeaderView.swift */,
|
||||
3935F4A7286AD5D000760AB0 /* ActivityRowView.swift */,
|
||||
3935F4A3286AD21000760AB0 /* ActivityProgressView.swift */,
|
||||
);
|
||||
path = Download;
|
||||
path = Activity;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
393F35C028641D8F005B7165 /* Refresh */ = {
|
||||
|
@ -516,10 +528,10 @@
|
|||
39FF05F12859849200A86670 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
575812B82A37330200425BAF /* Capsule */,
|
||||
39252AA2285C3CC400956C74 /* CodesigningPickerView.swift */,
|
||||
39252AA4285C463A00956C74 /* DynamicTextView.swift */,
|
||||
39252A86285ACE9C00956C74 /* FooterText.swift */,
|
||||
5795700C2A31B081004C7051 /* MistActionButtonStyle.swift */,
|
||||
39252AA0285C2A1600956C74 /* PaddedDivider.swift */,
|
||||
39148CFB28DD55B300011FF5 /* PathControl.swift */,
|
||||
39252A84285ACDC800956C74 /* ResetToDefaultButton.swift */,
|
||||
|
@ -554,17 +566,6 @@
|
|||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
575812B82A37330200425BAF /* Capsule */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
57CF96192A34B65C008D3B1C /* CapsuleLeading.swift */,
|
||||
5795700C2A31B081004C7051 /* CapsuleButtonStyle.swift */,
|
||||
575812B62A372D7200425BAF /* CapsuleButtonStyleType.swift */,
|
||||
57CF961B2A34B9E0008D3B1C /* CapsuleTrailing.swift */,
|
||||
);
|
||||
path = Capsule;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -765,6 +766,7 @@
|
|||
files = (
|
||||
39FF05F02859848500A86670 /* SettingsView.swift in Sources */,
|
||||
39CF562A2861E1CB006FB5D2 /* DirectoryRemover.swift in Sources */,
|
||||
575812C62A38296A00425BAF /* InstallerVolumeSelectionPickerView.swift in Sources */,
|
||||
398734C828601FFC00B4C357 /* FileMover.swift in Sources */,
|
||||
39CF55AF2861582F006FB5D2 /* AuthorizationError+Extension.swift in Sources */,
|
||||
39CF56172861BE66006FB5D2 /* FileCopier.swift in Sources */,
|
||||
|
@ -774,12 +776,13 @@
|
|||
39CF56212861C992006FB5D2 /* DiskImageMounter.swift in Sources */,
|
||||
398734CC28603D5F00B4C357 /* Chunklist.swift in Sources */,
|
||||
39252A83285ACBF200956C74 /* TextFieldStepperView.swift in Sources */,
|
||||
3935F4A8286AD5D000760AB0 /* DownloadRowView.swift in Sources */,
|
||||
3935F4A8286AD5D000760AB0 /* ActivityRowView.swift in Sources */,
|
||||
575812C22A380B5E00425BAF /* InstallerVolume.swift in Sources */,
|
||||
39CF56332862B7A2006FB5D2 /* PackageCreator.swift in Sources */,
|
||||
398734CE28603D7F00B4C357 /* Chunk.swift in Sources */,
|
||||
3935F4A2286ACD4D00760AB0 /* InstallerExportViewItem.swift in Sources */,
|
||||
393F35C228641E1F005B7165 /* HeaderView.swift in Sources */,
|
||||
5795700D2A31B081004C7051 /* CapsuleButtonStyle.swift in Sources */,
|
||||
5795700D2A31B081004C7051 /* MistActionButtonStyle.swift in Sources */,
|
||||
3935F4CD286C6A5D00760AB0 /* ProcessKiller.swift in Sources */,
|
||||
390451BF2856E34700E0B563 /* String+Extension.swift in Sources */,
|
||||
39CF56242861CA85006FB5D2 /* DiskImageUnmounter.swift in Sources */,
|
||||
|
@ -788,29 +791,28 @@
|
|||
398734D228603DE700B4C357 /* Array+Extension.swift in Sources */,
|
||||
39FF05F82859851800A86670 /* SettingsApplicationsView.swift in Sources */,
|
||||
39CF561D2861C3F5006FB5D2 /* DiskImageCreator.swift in Sources */,
|
||||
575812BA2A373A4F00425BAF /* FirmwareAlertType.swift in Sources */,
|
||||
39CF55AD28615530006FB5D2 /* SettingsGeneralHelperView.swift in Sources */,
|
||||
39252A7B285AC50400956C74 /* SettingsDiskImagesView.swift in Sources */,
|
||||
39252A79285A85AF00956C74 /* SettingsInstallersView.swift in Sources */,
|
||||
39252A9F285C140D00956C74 /* ShellExecutor.swift in Sources */,
|
||||
39CF561A2861C2D1006FB5D2 /* DirectoryCreator.swift in Sources */,
|
||||
39252A77285A849F00956C74 /* AppDelegate.swift in Sources */,
|
||||
3935F49F286AC32C00760AB0 /* ListRow.swift in Sources */,
|
||||
3935F47C2864434B00760AB0 /* SettingsGeneralNotificationsView.swift in Sources */,
|
||||
3935F4C7286B54E200760AB0 /* SparkleUpdater.swift in Sources */,
|
||||
393F35BE2864197F005B7165 /* PrivilegedHelperTool.swift in Sources */,
|
||||
573A23622A28711C00EC9470 /* Architecture.swift in Sources */,
|
||||
390451B92856E24200E0B563 /* Firmware.swift in Sources */,
|
||||
390451CE2856F42800E0B563 /* DownloadType.swift in Sources */,
|
||||
3935F4CB286C1EC500760AB0 /* DownloadView.swift in Sources */,
|
||||
3935F4CB286C1EC500760AB0 /* ActivityView.swift in Sources */,
|
||||
398734C6286011C300B4C357 /* Validator.swift in Sources */,
|
||||
39252ABB285C7D3800956C74 /* SettingsInstallersCatalogsView.swift in Sources */,
|
||||
393D8029286EB4D6008AA8E3 /* EmptyCollectionView.swift in Sources */,
|
||||
3935F490286976D000760AB0 /* ProgressAlertType.swift in Sources */,
|
||||
575812B72A372D7200425BAF /* CapsuleButtonStyleType.swift in Sources */,
|
||||
39FF05F62859850F00A86670 /* SettingsFirmwaresView.swift in Sources */,
|
||||
3935F4A6286AD3E100760AB0 /* DownloadHeaderView.swift in Sources */,
|
||||
3935F4A6286AD3E100760AB0 /* ActivityHeaderView.swift in Sources */,
|
||||
3935F480286551FB00760AB0 /* Double+Extension.swift in Sources */,
|
||||
39252ABD285C8FFC00956C74 /* InstallerListRow.swift in Sources */,
|
||||
39252ABD285C8FFC00956C74 /* ListRowInstaller.swift in Sources */,
|
||||
3935F49D286ABE4D00760AB0 /* FooterView.swift in Sources */,
|
||||
390451CC2856F23100E0B563 /* ScaledSystemImage.swift in Sources */,
|
||||
390451DA28573ADC00E0B563 /* ExportListType.swift in Sources */,
|
||||
|
@ -833,27 +835,28 @@
|
|||
3935F47628643AF000760AB0 /* UNNotificationAction+Extension.swift in Sources */,
|
||||
39252AB3285C5D7700956C74 /* SettingsGeneralUpdatesView.swift in Sources */,
|
||||
39CA25E32941D8BB0030711E /* FileAttributesUpdater.swift in Sources */,
|
||||
575812C42A3821A900425BAF /* InstallerVolumeSelectionInformationView.swift in Sources */,
|
||||
3935F4AB286B04BC00760AB0 /* HelperToolInfoPropertyList.swift in Sources */,
|
||||
575812BE2A3743E300425BAF /* InstallerSheetType.swift in Sources */,
|
||||
393F35BC28641181005B7165 /* RefreshState.swift in Sources */,
|
||||
390451CA2856F1D300E0B563 /* ScaledImage.swift in Sources */,
|
||||
39252A95285BF83D00956C74 /* MistTask.swift in Sources */,
|
||||
39CF56272861E10F006FB5D2 /* Codesigner.swift in Sources */,
|
||||
39148CFC28DD55B300011FF5 /* PathControl.swift in Sources */,
|
||||
3935F4892866C68000760AB0 /* DownloadSectionHeaderView.swift in Sources */,
|
||||
3935F4892866C68000760AB0 /* ActivitySectionHeaderView.swift in Sources */,
|
||||
39252AB5285C706000956C74 /* URL+Extension.swift in Sources */,
|
||||
57CF961C2A34B9E0008D3B1C /* CapsuleTrailing.swift in Sources */,
|
||||
390451D828573A2500E0B563 /* ExportListView.swift in Sources */,
|
||||
39FF05EE2859820900A86670 /* AppCommands.swift in Sources */,
|
||||
39252AA3285C3CC400956C74 /* CodesigningPickerView.swift in Sources */,
|
||||
39252AA5285C463A00956C74 /* DynamicTextView.swift in Sources */,
|
||||
575812C02A37493F00425BAF /* InstallerVolumeSelectionView.swift in Sources */,
|
||||
390451C22856E3F500E0B563 /* Hardware.swift in Sources */,
|
||||
39CF56092861AE7F006FB5D2 /* HelperToolCommandRequest.swift in Sources */,
|
||||
57CF961A2A34B65C008D3B1C /* CapsuleLeading.swift in Sources */,
|
||||
390451C82856E94900E0B563 /* FirmwareListRow.swift in Sources */,
|
||||
390451C82856E94900E0B563 /* ListRowFirmware.swift in Sources */,
|
||||
390451E528574F0000E0B563 /* CatalogType.swift in Sources */,
|
||||
3935F4852866B64900760AB0 /* MistTaskSection.swift in Sources */,
|
||||
390451AC2856E1D900E0B563 /* ContentView.swift in Sources */,
|
||||
3935F4A4286AD21000760AB0 /* DownloadProgressView.swift in Sources */,
|
||||
3935F4A4286AD21000760AB0 /* ActivityProgressView.swift in Sources */,
|
||||
39252A89285AD0AB00956C74 /* SettingsHeaderView.swift in Sources */,
|
||||
39252A85285ACDC800956C74 /* ResetToDefaultButton.swift in Sources */,
|
||||
39CF560F2861B857006FB5D2 /* XPCRoute+Extension.swift in Sources */,
|
||||
|
@ -862,6 +865,7 @@
|
|||
39CF562F2862A797006FB5D2 /* ISOConverter.swift in Sources */,
|
||||
39FF05FA285985DD00A86670 /* SettingsAboutView.swift in Sources */,
|
||||
3935F4AC286B04BC00760AB0 /* HelperToolLaunchdPropertyList.swift in Sources */,
|
||||
575812BC2A37406300425BAF /* ListRowDetail.swift in Sources */,
|
||||
39FF05F42859850500A86670 /* SettingsGeneralView.swift in Sources */,
|
||||
390451AA2856E1D900E0B563 /* MistApp.swift in Sources */,
|
||||
39252AA9285C4C9000956C74 /* RefreshRowView.swift in Sources */,
|
||||
|
@ -871,7 +875,7 @@
|
|||
390451E1285740E800E0B563 /* Sequence+Extension.swift in Sources */,
|
||||
398734D4286046B000B4C357 /* UInt32+Extension.swift in Sources */,
|
||||
390451D42856F74B00E0B563 /* Package.swift in Sources */,
|
||||
395DCD16287FE36E00C411CE /* DownloadAlertType.swift in Sources */,
|
||||
395DCD16287FE36E00C411CE /* InstallerAlertType.swift in Sources */,
|
||||
39CF560028619147006FB5D2 /* HelperToolCommandType.swift in Sources */,
|
||||
39252A87285ACE9C00956C74 /* FooterText.swift in Sources */,
|
||||
573A23642A28791F00EC9470 /* Scene+Extension.swift in Sources */,
|
||||
|
|
|
@ -13,7 +13,7 @@ struct AppCommands: Commands {
|
|||
var openURL: OpenURLAction
|
||||
@ObservedObject var sparkleUpdater: SparkleUpdater
|
||||
@Binding var refreshing: Bool
|
||||
@Binding var downloadInProgress: Bool
|
||||
@Binding var tasksInProgress: Bool
|
||||
|
||||
@CommandsBuilder var body: some Commands {
|
||||
CommandGroup(after: .appInfo) {
|
||||
|
@ -27,13 +27,13 @@ struct AppCommands: Commands {
|
|||
refresh()
|
||||
}
|
||||
.keyboardShortcut("r")
|
||||
.disabled(refreshing || downloadInProgress)
|
||||
.disabled(refreshing || tasksInProgress)
|
||||
}
|
||||
CommandGroup(replacing: .systemServices) {
|
||||
Button("Install Privileged Helper Tool...") {
|
||||
install()
|
||||
}
|
||||
.disabled(downloadInProgress)
|
||||
.disabled(tasksInProgress)
|
||||
}
|
||||
CommandGroup(replacing: .help) {
|
||||
Button("Mist Help") {
|
||||
|
|
BIN
Mist/Assets.xcassets/Bootable Installer.imageset/Bootable Installer.png
vendored
Normal file
BIN
Mist/Assets.xcassets/Bootable Installer.imageset/Bootable Installer.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
12
Mist/Assets.xcassets/Bootable Installer.imageset/Contents.json
vendored
Normal file
12
Mist/Assets.xcassets/Bootable Installer.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Bootable Installer.png",
|
||||
"idiom" : "mac"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
extension ButtonStyle where Self == CapsuleButtonStyle {
|
||||
extension ButtonStyle where Self == MistActionButtonStyle {
|
||||
|
||||
static func capsule(_ type: CapsuleButtonStyleType) -> Self {
|
||||
.init(type: type)
|
||||
static var mistAction: Self {
|
||||
.init()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,19 @@ 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).
|
||||
/// - url: The URL of the `createinstallmedia` binary to execute.
|
||||
/// - mountPoint: The URL of the mount point (target volume).
|
||||
/// - sierraOrOlder: `true` if the installer is macOS Sierra or older, otherwise `false`.
|
||||
///
|
||||
/// - 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"]
|
||||
static func create(_ url: URL, mountPoint: URL, sierraOrOlder: Bool) async throws {
|
||||
var arguments: [String] = [url.path, "--volume", mountPoint.path, "--nointeraction"]
|
||||
|
||||
if sierraOrOlder {
|
||||
let applicationPath: String = url.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().path
|
||||
arguments += ["--applicationpath", applicationPath]
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -226,6 +226,44 @@ class TaskManager: ObservableObject {
|
|||
return taskGroups
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
static func taskGroups(
|
||||
for installer: Installer,
|
||||
cacheDownloads: Bool,
|
||||
cacheDirectory: String,
|
||||
retries: Int,
|
||||
delay retryDelay: Int,
|
||||
volume: InstallerVolume
|
||||
) throws -> [(section: MistTaskSection, tasks: [MistTask])] {
|
||||
let cacheDirectoryURL: URL = URL(fileURLWithPath: cacheDirectory).appendingPathComponent(installer.id)
|
||||
let temporaryDirectoryURL: URL = URL(fileURLWithPath: .temporaryDirectory)
|
||||
let taskGroups: [(section: MistTaskSection, tasks: [MistTask])] = [
|
||||
(
|
||||
section: .download,
|
||||
tasks: try downloadTasks(for: installer, cacheDirectory: cacheDirectoryURL, retries: retries, delay: retryDelay)
|
||||
),
|
||||
(
|
||||
section: .setup,
|
||||
tasks: installTasks(for: installer, temporaryDirectory: temporaryDirectoryURL, mountPoint: installer.temporaryDiskImageMountPointURL, cacheDirectory: cacheDirectory)
|
||||
),
|
||||
(
|
||||
section: .bootableInstaller,
|
||||
tasks: bootableInstallerTasks(for: installer, volume: volume)
|
||||
),
|
||||
(
|
||||
section: .cleanup,
|
||||
tasks: cleanupTasks(
|
||||
mountPoint: installer.temporaryDiskImageMountPointURL,
|
||||
temporaryDirectory: temporaryDirectoryURL,
|
||||
cacheDownloads: cacheDownloads,
|
||||
cacheDirectory: cacheDirectoryURL
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
return taskGroups
|
||||
}
|
||||
|
||||
private static func downloadTasks(for installer: Installer, cacheDirectory cacheDirectoryURL: URL, retries: Int, delay retryDelay: Int) throws -> [MistTask] {
|
||||
|
||||
var tasks: [MistTask] = []
|
||||
|
@ -403,7 +441,7 @@ class TaskManager: ObservableObject {
|
|||
try await DiskImageMounter.mount(temporaryImageURL, mountPoint: installer.temporaryISOMountPointURL)
|
||||
},
|
||||
MistTask(type: .create, description: "macOS Installer in temporary Disk Image") {
|
||||
try await InstallMediaCreator.create(createInstallMediaURL, mountPoint: installer.temporaryISOMountPointURL)
|
||||
try await InstallMediaCreator.create(createInstallMediaURL, mountPoint: installer.temporaryISOMountPointURL, sierraOrOlder: installer.sierraOrOlder)
|
||||
},
|
||||
MistTask(type: .unmount, description: "temporary Disk Image") {
|
||||
if FileManager.default.fileExists(atPath: installer.temporaryISOMountPointURL.path) {
|
||||
|
@ -470,6 +508,18 @@ class TaskManager: ObservableObject {
|
|||
return tasks
|
||||
}
|
||||
|
||||
private static func bootableInstallerTasks(for installer: Installer, volume: InstallerVolume) -> [MistTask] {
|
||||
let createInstallMediaURL: URL = installer.temporaryInstallerURL.appendingPathComponent("/Contents/Resources/createinstallmedia")
|
||||
let mountPointURL: URL = URL(fileURLWithPath: volume.path)
|
||||
let tasks: [MistTask] = [
|
||||
MistTask(type: .create, description: "Bootable Installer") {
|
||||
try await InstallMediaCreator.create(createInstallMediaURL, mountPoint: mountPointURL, sierraOrOlder: installer.sierraOrOlder)
|
||||
}
|
||||
]
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
private static func cleanupTasks(mountPoint mountPointURL: URL, temporaryDirectory temporaryDirectoryURL: URL, cacheDownloads: Bool, cacheDirectory cacheDirectoryURL: URL) -> [MistTask] {
|
||||
|
||||
var tasks: [MistTask] = [
|
||||
|
|
|
@ -14,18 +14,18 @@ struct MistApp: App {
|
|||
var appDelegate: AppDelegate
|
||||
@StateObject var sparkleUpdater: SparkleUpdater = SparkleUpdater()
|
||||
@State private var refreshing: Bool = false
|
||||
@State private var downloadInProgress: Bool = false
|
||||
@State private var tasksInProgress: Bool = false
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(refreshing: $refreshing, downloadInProgress: $downloadInProgress)
|
||||
ContentView(refreshing: $refreshing, tasksInProgress: $tasksInProgress)
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.willUpdateNotification)) { _ in
|
||||
hideZoomButton()
|
||||
}
|
||||
}
|
||||
.fixedWindow()
|
||||
.commands {
|
||||
AppCommands(sparkleUpdater: sparkleUpdater, refreshing: $refreshing, downloadInProgress: $downloadInProgress)
|
||||
AppCommands(sparkleUpdater: sparkleUpdater, refreshing: $refreshing, tasksInProgress: $tasksInProgress)
|
||||
}
|
||||
Settings {
|
||||
SettingsView(sparkleUpdater: sparkleUpdater)
|
||||
|
|
|
@ -97,6 +97,14 @@ struct Firmware: Decodable, Hashable, Identifiable {
|
|||
"beta": beta
|
||||
]
|
||||
}
|
||||
var tooltip: String {
|
||||
"""
|
||||
Version: \(version)
|
||||
Build Number: \(build)
|
||||
Release Date: \(formattedDate)
|
||||
Download Size: \(size.bytesString())
|
||||
"""
|
||||
}
|
||||
|
||||
/// Perform a lookup and retrieve a list of supported Firmware builds for this Mac.
|
||||
///
|
||||
|
|
13
Mist/Model/FirmwareAlertType.swift
Normal file
13
Mist/Model/FirmwareAlertType.swift
Normal file
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// FirmwareAlertType.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 12/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum FirmwareAlertType: String {
|
||||
case compatibility = "Compatiblity"
|
||||
case helperTool = "Helper Tool"
|
||||
}
|
|
@ -751,6 +751,9 @@ struct Installer: Decodable, Hashable, Identifiable {
|
|||
"beta": beta
|
||||
]
|
||||
}
|
||||
var mavericksOrNewer: Bool {
|
||||
bigSurOrNewer || version.range(of: "^10\\.(9|1[0-5])\\.", options: .regularExpression) != nil
|
||||
}
|
||||
var sierraOrOlder: Bool {
|
||||
version.range(of: "^10\\.([7-9]|1[0-2])\\.", options: .regularExpression) != nil
|
||||
}
|
||||
|
@ -775,6 +778,14 @@ struct Installer: Decodable, Hashable, Identifiable {
|
|||
var isoSize: Double {
|
||||
ceil(Double(size) / Double(UInt64.gigabyte)) + 1.5
|
||||
}
|
||||
var tooltip: String {
|
||||
"""
|
||||
Version: \(version)
|
||||
Build Number: \(build)
|
||||
Release Date: \(date)
|
||||
Download Size: \(size.bytesString())
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
extension Installer: Equatable {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
//
|
||||
// DownloadAlertType.swift
|
||||
// InstallerAlertType.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 14/7/2022.
|
||||
//
|
||||
|
||||
enum DownloadAlertType: String {
|
||||
enum InstallerAlertType: String {
|
||||
case compatibility = "Compatiblity"
|
||||
case helperTool = "Helper Tool"
|
||||
case fullDiskAccess = "Full Disk Access"
|
14
Mist/Model/InstallerSheetType.swift
Normal file
14
Mist/Model/InstallerSheetType.swift
Normal file
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// InstallerSheetType.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 12/6/2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum InstallerSheetType: String {
|
||||
case download = "Download"
|
||||
case volumeSelection = "Volume Selection"
|
||||
case createBootableInstaller = "Create Bootable Installer"
|
||||
}
|
18
Mist/Model/InstallerVolume.swift
Normal file
18
Mist/Model/InstallerVolume.swift
Normal file
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// InstallerVolume.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 13/6/2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct InstallerVolume: Identifiable, Hashable {
|
||||
static let placeholder: InstallerVolume = InstallerVolume(id: "placeholder", name: "No volume selected", path: "", capacity: 0)
|
||||
static let invalid: InstallerVolume = InstallerVolume(id: "invalid", name: "No available volumes found", path: "", capacity: 0)
|
||||
|
||||
var id: String
|
||||
var name: String
|
||||
var path: String
|
||||
var capacity: UInt64
|
||||
}
|
|
@ -12,6 +12,7 @@ enum MistTaskSection: String, CaseIterable, Identifiable {
|
|||
case diskImage = "Disk Image"
|
||||
case iso = "ISO"
|
||||
case package = "Package"
|
||||
case bootableInstaller = "Bootable Installer"
|
||||
case cleanup = "Cleanup"
|
||||
|
||||
var id: String {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// DownloadHeaderView.swift
|
||||
// ActivityHeaderView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 28/6/2022.
|
||||
|
@ -7,7 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct DownloadHeaderView: View {
|
||||
struct ActivityHeaderView: View {
|
||||
var imageName: String
|
||||
var name: String
|
||||
var version: String
|
||||
|
@ -31,12 +31,12 @@ struct DownloadHeaderView: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct DownloadHeaderView_Previews: PreviewProvider {
|
||||
struct ActivityHeaderView_Previews: PreviewProvider {
|
||||
static let firmware: Firmware = .example
|
||||
static let installer: Installer = .example
|
||||
|
||||
static var previews: some View {
|
||||
DownloadHeaderView(imageName: firmware.imageName, name: firmware.name, version: firmware.version, build: firmware.build, beta: false)
|
||||
DownloadHeaderView(imageName: installer.imageName, name: installer.name, version: installer.version, build: installer.build, beta: false)
|
||||
ActivityHeaderView(imageName: firmware.imageName, name: firmware.name, version: firmware.version, build: firmware.build, beta: false)
|
||||
ActivityHeaderView(imageName: installer.imageName, name: installer.name, version: installer.version, build: installer.build, beta: false)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// DownloadProgressView.swift
|
||||
// ActivityProgressView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 28/6/2022.
|
||||
|
@ -7,7 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct DownloadProgressView: View {
|
||||
struct ActivityProgressView: View {
|
||||
var state: MistTaskState
|
||||
var value: CGFloat
|
||||
var size: UInt64
|
||||
|
@ -36,9 +36,9 @@ struct DownloadProgressView: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct DownloadProgressView_Previews: PreviewProvider {
|
||||
struct ActivityProgressView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DownloadProgressView(state: .inProgress, value: 0.5, size: Firmware.example.size)
|
||||
DownloadProgressView(state: .inProgress, value: 0.5, size: Installer.example.size)
|
||||
ActivityProgressView(state: .inProgress, value: 0.5, size: Firmware.example.size)
|
||||
ActivityProgressView(state: .inProgress, value: 0.5, size: Installer.example.size)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// DownloadRowView.swift
|
||||
// ActivityRowView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 28/6/2022.
|
||||
|
@ -7,7 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct DownloadRowView: View {
|
||||
struct ActivityRowView: View {
|
||||
var state: MistTaskState
|
||||
var description: String
|
||||
var degrees: CGFloat
|
||||
|
@ -30,8 +30,8 @@ struct DownloadRowView: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct DownloadRowView_Previews: PreviewProvider {
|
||||
struct ActivityRowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DownloadRowView(state: .inProgress, description: "Downloading...", degrees: 360)
|
||||
ActivityRowView(state: .inProgress, description: "Downloading...", degrees: 360)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// DownloadSectionHeaderView.swift
|
||||
// ActivitySectionHeaderView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 25/6/2022.
|
||||
|
@ -7,7 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct DownloadSectionHeaderView: View {
|
||||
struct ActivitySectionHeaderView: View {
|
||||
var section: MistTaskSection
|
||||
private let length: CGFloat = 24
|
||||
|
||||
|
@ -23,10 +23,10 @@ struct DownloadSectionHeaderView: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct DownloadSectionHeaderView_Previews: PreviewProvider {
|
||||
struct ActivitySectionHeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ForEach(MistTaskSection.allCases) { section in
|
||||
DownloadSectionHeaderView(section: section)
|
||||
ActivitySectionHeaderView(section: section)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// DownloadView.swift
|
||||
// ActivityView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 29/6/2022.
|
||||
|
@ -8,7 +8,7 @@
|
|||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct DownloadView: View {
|
||||
struct ActivityView: View {
|
||||
// swiftlint:disable:next weak_delegate
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self)
|
||||
var appDelegate: AppDelegate
|
||||
|
@ -35,6 +35,12 @@ struct DownloadView: View {
|
|||
@State private var timer: Publishers.Autoconnect<Timer.TimerPublisher> = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
|
||||
private let width: CGFloat = 420
|
||||
private let height: CGFloat = 640
|
||||
private var bootableInstaller: Bool {
|
||||
taskManager.taskGroups.map { $0.section }.contains(.bootableInstaller)
|
||||
}
|
||||
private var venturaOrOlder: Bool {
|
||||
!ProcessInfo().isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0))
|
||||
}
|
||||
private var buttonText: String {
|
||||
switch taskManager.currentState {
|
||||
case .pending, .inProgress:
|
||||
|
@ -47,20 +53,20 @@ struct DownloadView: View {
|
|||
var body: some View {
|
||||
// swiftlint:disable:next closure_body_length
|
||||
VStack(spacing: 0) {
|
||||
DownloadHeaderView(imageName: imageName, name: name, version: version, build: build, beta: beta)
|
||||
ActivityHeaderView(imageName: imageName, name: name, version: version, build: build, beta: beta)
|
||||
Divider()
|
||||
ScrollViewReader { proxy in
|
||||
List {
|
||||
ForEach(taskManager.taskGroups, id: \.section) { taskGroup in
|
||||
Section(header: DownloadSectionHeaderView(section: taskGroup.section)) {
|
||||
Section(header: ActivitySectionHeaderView(section: taskGroup.section)) {
|
||||
ForEach(taskGroup.tasks.indices, id: \.self) { index in
|
||||
VStack {
|
||||
DownloadRowView(state: taskGroup.tasks[index].state, description: taskGroup.tasks[index].currentDescription, degrees: degrees)
|
||||
ActivityRowView(state: taskGroup.tasks[index].state, description: taskGroup.tasks[index].currentDescription, degrees: degrees)
|
||||
if taskGroup.tasks[index].type == .download && taskGroup.tasks[index].state != .pending,
|
||||
let size: UInt64 = taskGroup.tasks[index].downloadSize {
|
||||
DownloadProgressView(state: taskGroup.tasks[index].state, value: value, size: size)
|
||||
ActivityProgressView(state: taskGroup.tasks[index].state, value: value, size: size)
|
||||
}
|
||||
if index < taskGroup.tasks.count - 1 {
|
||||
if venturaOrOlder && index != taskGroup.tasks.count {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +76,7 @@ struct DownloadView: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: currentTaskId) { id in
|
||||
withAnimation(.easeOut(duration: 1.0)) {
|
||||
withAnimation(.easeOut(duration: 1)) {
|
||||
proxy.scrollTo(id, anchor: .center)
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +163,7 @@ struct DownloadView: View {
|
|||
}
|
||||
|
||||
if showInFinder {
|
||||
|
||||
guard let url: URL = destinationURL else {
|
||||
return
|
||||
}
|
||||
|
@ -192,7 +199,14 @@ struct DownloadView: View {
|
|||
}
|
||||
|
||||
private func sendNotification(for type: DownloadType, name: String, version: String, build: String, success: Bool) {
|
||||
let title: String = " \(type.description) download\(success ? "ed" : " failed")"
|
||||
let title: String
|
||||
|
||||
if bootableInstaller {
|
||||
title = "Bootable Installer \(success ? "created" : "failed")"
|
||||
} else {
|
||||
title = "\(type.description) \(success ? "downloaded" : "failed")"
|
||||
}
|
||||
|
||||
let body: String = "\(name) \(version) (\(build))"
|
||||
appDelegate.sendUpdateNotification(title: title, body: body, success: success, url: destinationURL)
|
||||
}
|
||||
|
@ -218,12 +232,12 @@ struct DownloadView: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct DownloadView_Previews: PreviewProvider {
|
||||
struct ActivityView_Previews: PreviewProvider {
|
||||
static let firmware: Firmware = .example
|
||||
static let installer: Installer = .example
|
||||
|
||||
static var previews: some View {
|
||||
DownloadView(downloadType: .firmware, imageName: firmware.imageName, name: firmware.name, version: firmware.version, build: firmware.build, beta: false, taskManager: .shared)
|
||||
DownloadView(downloadType: .installer, imageName: installer.imageName, name: installer.name, version: installer.version, build: installer.build, beta: false, taskManager: .shared)
|
||||
ActivityView(downloadType: .firmware, imageName: firmware.imageName, name: firmware.name, version: firmware.version, build: firmware.build, beta: false, taskManager: .shared)
|
||||
ActivityView(downloadType: .installer, imageName: installer.imageName, name: installer.name, version: installer.version, build: installer.build, beta: false, taskManager: .shared)
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// CapsuleButtonStyleType.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 12/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// Capsule Button Style Type
|
||||
enum CapsuleButtonStyleType {
|
||||
/// Standard capsule with both leading and trailing edges curved
|
||||
case standard
|
||||
/// Capsule with leading edge curved only
|
||||
case leading
|
||||
/// Capsule with trailing edge curved only
|
||||
case trailing
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// CapsuleLeading.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 10/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CapsuleLeading: Shape {
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path: Path = Path()
|
||||
path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
|
||||
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
|
||||
path.addLine(to: CGPoint(x: rect.height / 2, y: rect.maxY))
|
||||
path.addArc(
|
||||
center: CGPoint(x: rect.height / 2, y: rect.midY),
|
||||
radius: rect.height / 2,
|
||||
startAngle: .degrees(270),
|
||||
endAngle: .degrees(90),
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: rect.height / 2, y: rect.minY))
|
||||
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// CapsuleTrailing.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 11/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CapsuleTrailing: Shape {
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path: Path = Path()
|
||||
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
|
||||
path.addLine(to: CGPoint(x: rect.maxX - rect.height / 2, y: rect.minY))
|
||||
path.addArc(
|
||||
center: CGPoint(x: rect.maxX - rect.height / 2, y: rect.midY),
|
||||
radius: rect.height / 2,
|
||||
startAngle: .degrees(90),
|
||||
endAngle: .degrees(270),
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: rect.maxX - rect.height / 2, y: rect.maxY))
|
||||
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
|
||||
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// CapsuleButtonStyle.swift
|
||||
// MistActionButtonStyle.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 5/6/2023.
|
||||
|
@ -7,28 +7,15 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct CapsuleButtonStyle: ButtonStyle {
|
||||
|
||||
let type: CapsuleButtonStyleType
|
||||
struct MistActionButtonStyle: ButtonStyle {
|
||||
private let padding: CGFloat = 5
|
||||
|
||||
@ViewBuilder
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
|
||||
let view: some View = configuration.label
|
||||
configuration.label
|
||||
.font(.body.bold())
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, padding)
|
||||
.foregroundColor(.white)
|
||||
.background(Color.accentColor.brightness(configuration.isPressed ? -0.5 : 0))
|
||||
|
||||
switch type {
|
||||
case .standard:
|
||||
view.clipShape(Capsule())
|
||||
case .leading:
|
||||
view.clipShape(CapsuleLeading())
|
||||
case .trailing:
|
||||
view.clipShape(CapsuleTrailing())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ struct ContentView: View {
|
|||
@AppStorage("showCompatible")
|
||||
private var showCompatible: Bool = false
|
||||
@Binding var refreshing: Bool
|
||||
@Binding var downloadInProgress: Bool
|
||||
@Binding var tasksInProgress: Bool
|
||||
@State private var firmwares: [Firmware] = []
|
||||
@State private var installers: [Installer] = []
|
||||
@State private var searchString: String = ""
|
||||
|
@ -45,7 +45,6 @@ struct ContentView: View {
|
|||
|
||||
return filteredFirmwares
|
||||
}
|
||||
|
||||
private var filteredInstallers: [Installer] {
|
||||
var filteredInstallers: [Installer] = installers
|
||||
|
||||
|
@ -85,12 +84,12 @@ struct ContentView: View {
|
|||
switch downloadType {
|
||||
case .firmware:
|
||||
ForEach(filteredFirmwares(for: releaseName)) { firmware in
|
||||
FirmwareListRow(firmware: firmware, savePanel: $savePanel, downloadInProgress: $downloadInProgress, taskManager: taskManager)
|
||||
ListRowFirmware(firmware: firmware, savePanel: $savePanel, tasksInProgress: $tasksInProgress, taskManager: taskManager)
|
||||
.tag(firmware)
|
||||
}
|
||||
case .installer:
|
||||
ForEach(filteredInstallers(for: releaseName)) { installer in
|
||||
InstallerListRow(installer: installer, openPanel: $openPanel, downloadInProgress: $downloadInProgress, taskManager: taskManager)
|
||||
ListRowInstaller(installer: installer, openPanel: $openPanel, tasksInProgress: $tasksInProgress, taskManager: taskManager)
|
||||
.tag(installer)
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +164,6 @@ struct ContentView: View {
|
|||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView(refreshing: .constant(false), downloadInProgress: .constant(false))
|
||||
ContentView(refreshing: .constant(false), tasksInProgress: .constant(false))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
//
|
||||
// FirmwareListRow.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 13/6/2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct FirmwareListRow: View {
|
||||
@AppStorage("firmwareFilename")
|
||||
private var firmwareFilename: String = .firmwareFilenameTemplate
|
||||
@AppStorage("retries")
|
||||
private var retries: Int = 10
|
||||
@AppStorage("retryDelay")
|
||||
private var retryDelay: Int = 30
|
||||
var firmware: Firmware
|
||||
@Binding var savePanel: NSSavePanel
|
||||
@Binding var downloadInProgress: Bool
|
||||
@ObservedObject var taskManager: TaskManager
|
||||
@State private var showSavePanel: Bool = false
|
||||
@State private var downloading: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ListRow(
|
||||
type: .firmware,
|
||||
image: firmware.imageName,
|
||||
version: firmware.version,
|
||||
build: firmware.build,
|
||||
beta: firmware.beta,
|
||||
date: firmware.formattedDate,
|
||||
size: firmware.size.bytesString(),
|
||||
compatible: firmware.compatible,
|
||||
showPanel: $showSavePanel,
|
||||
taskManager: taskManager
|
||||
)
|
||||
.onChange(of: showSavePanel) { boolean in
|
||||
|
||||
if boolean {
|
||||
save()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $downloading) {
|
||||
DownloadView(
|
||||
downloadType: .firmware,
|
||||
imageName: firmware.imageName,
|
||||
name: firmware.name,
|
||||
version: firmware.version,
|
||||
build: firmware.build,
|
||||
beta: firmware.beta,
|
||||
destinationURL: savePanel.url,
|
||||
taskManager: taskManager
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func save() {
|
||||
showSavePanel = false
|
||||
savePanel.title = "Download Firmware"
|
||||
savePanel.nameFieldStringValue = firmwareFilename.stringWithSubstitutions(name: firmware.name, version: firmware.version, build: firmware.build)
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.canSelectHiddenExtension = true
|
||||
savePanel.isExtensionHidden = false
|
||||
|
||||
Task {
|
||||
let response: NSApplication.ModalResponse = savePanel.runModal()
|
||||
|
||||
guard response == .OK else {
|
||||
return
|
||||
}
|
||||
|
||||
taskManager.taskGroups = try TaskManager.taskGroups(for: firmware, destination: savePanel.url, retries: retries, delay: retryDelay)
|
||||
downloading = true
|
||||
downloadInProgress = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FirmwareListRow_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
FirmwareListRow(firmware: .example, savePanel: .constant(NSSavePanel()), downloadInProgress: .constant(false), taskManager: .shared)
|
||||
}
|
||||
}
|
|
@ -23,7 +23,20 @@ struct InstallerExportView: View {
|
|||
return false
|
||||
}
|
||||
|
||||
return architecture == .intel || (architecture == .appleSilicon && installer.bigSurOrNewer)
|
||||
return (architecture == .intel && installer.mavericksOrNewer) || (architecture == .appleSilicon && installer.bigSurOrNewer)
|
||||
}
|
||||
private var compatibilityMessage: String {
|
||||
|
||||
guard let architecture: Architecture = Hardware.architecture else {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch architecture {
|
||||
case .appleSilicon:
|
||||
return "**Note:** ISOs are unavailable for building **macOS Catalina 10.15 and older** on [Apple Silicon Macs](https://support.apple.com/en-us/HT211814)."
|
||||
case .intel:
|
||||
return "**Note:** ISOs are unavailable for building **OS X Mountain Lion 10.8 and older** on Intel-based Macs."
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
@ -36,16 +49,15 @@ struct InstallerExportView: View {
|
|||
.disabled(exports.count == 1 && exportApplication)
|
||||
InstallerExportViewItem(exportType: .diskImage, selected: $exportDiskImage)
|
||||
.disabled(exports.count == 1 && exportDiskImage)
|
||||
if isoCompatible {
|
||||
InstallerExportViewItem(exportType: .iso, selected: $exportISO)
|
||||
.disabled(exports.count == 1 && exportISO)
|
||||
}
|
||||
InstallerExportViewItem(exportType: .iso, selected: $exportISO)
|
||||
.disabled(isoCompatible ? exports.count == 1 && exportISO : true)
|
||||
.opacity(isoCompatible ? 1 : 0.5)
|
||||
InstallerExportViewItem(exportType: .package, selected: $exportPackage)
|
||||
.disabled(exports.count == 1 && exportPackage)
|
||||
Spacer()
|
||||
}
|
||||
if !isoCompatible {
|
||||
Text("**Note:** ISOs are unavailable for building **macOS Catalina 10.15 and older** on [Apple Silicon Macs](https://support.apple.com/en-us/HT211814).")
|
||||
Text(.init(compatibilityMessage))
|
||||
.padding(.top)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
//
|
||||
// InstallerListRow.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 17/6/2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InstallerListRow: View {
|
||||
@AppStorage("cacheDownloads")
|
||||
private var cacheDownloads: Bool = false
|
||||
@AppStorage("cacheDirectory")
|
||||
private var cacheDirectory: String = .cacheDirectory
|
||||
@AppStorage("applicationFilename")
|
||||
private var applicationFilename: String = .applicationFilenameTemplate
|
||||
@AppStorage("diskImageFilename")
|
||||
private var diskImageFilename: String = .diskImageFilenameTemplate
|
||||
@AppStorage("diskImageSign")
|
||||
private var diskImageSign: Bool = false
|
||||
@AppStorage("diskImageSigningIdentity")
|
||||
private var diskImageSigningIdentity: String = ""
|
||||
@AppStorage("isoFilename")
|
||||
private var isoFilename: String = .isoFilenameTemplate
|
||||
@AppStorage("packageFilename")
|
||||
private var packageFilename: String = .packageFilenameTemplate
|
||||
@AppStorage("packageIdentifier")
|
||||
private var packageIdentifier: String = .packageIdentifierTemplate
|
||||
@AppStorage("packageSign")
|
||||
private var packageSign: Bool = false
|
||||
@AppStorage("packageSigningIdentity")
|
||||
private var packageSigningIdentity: String = ""
|
||||
@AppStorage("retries")
|
||||
private var retries: Int = 10
|
||||
@AppStorage("retryDelay")
|
||||
private var retryDelay: Int = 30
|
||||
var installer: Installer
|
||||
@Binding var openPanel: NSOpenPanel
|
||||
@Binding var downloadInProgress: Bool
|
||||
@ObservedObject var taskManager: TaskManager
|
||||
@State private var showOpenPanel: Bool = false
|
||||
@State private var downloading: Bool = false
|
||||
@State private var exports: [InstallerExportType] = []
|
||||
|
||||
var body: some View {
|
||||
ListRow(
|
||||
type: .installer,
|
||||
image: installer.imageName,
|
||||
version: installer.version,
|
||||
build: installer.build,
|
||||
beta: installer.beta,
|
||||
date: installer.date,
|
||||
size: installer.size.bytesString(),
|
||||
compatible: installer.compatible,
|
||||
showPanel: $showOpenPanel,
|
||||
taskManager: taskManager
|
||||
)
|
||||
.onChange(of: showOpenPanel) { boolean in
|
||||
|
||||
if boolean {
|
||||
open()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $downloading) {
|
||||
DownloadView(
|
||||
downloadType: .installer,
|
||||
imageName: installer.imageName,
|
||||
name: installer.name.replacingOccurrences(of: " beta", with: ""),
|
||||
version: installer.version,
|
||||
build: installer.build,
|
||||
beta: installer.beta,
|
||||
destinationURL: openPanel.url,
|
||||
taskManager: taskManager
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func open() {
|
||||
showOpenPanel = false
|
||||
openPanel.title = "Download Installer"
|
||||
openPanel.canChooseFiles = false
|
||||
openPanel.canChooseDirectories = true
|
||||
openPanel.allowsMultipleSelection = false
|
||||
openPanel.prompt = "Save"
|
||||
openPanel.accessoryView = NSHostingView(rootView: InstallerExportView(installer: installer, exports: $exports))
|
||||
openPanel.isAccessoryViewDisclosed = true
|
||||
|
||||
Task {
|
||||
let response: NSApplication.ModalResponse = openPanel.runModal()
|
||||
|
||||
guard response == .OK else {
|
||||
return
|
||||
}
|
||||
|
||||
taskManager.taskGroups = try TaskManager.taskGroups(
|
||||
for: installer,
|
||||
destination: openPanel.url,
|
||||
exports: exports,
|
||||
cacheDownloads: cacheDownloads,
|
||||
cacheDirectory: cacheDirectory,
|
||||
retries: retries,
|
||||
delay: retryDelay,
|
||||
applicationFilename: applicationFilename,
|
||||
diskImageFilename: diskImageFilename,
|
||||
diskImageSign: diskImageSign,
|
||||
diskImageSigningIdentity: diskImageSigningIdentity,
|
||||
isoFilename: isoFilename,
|
||||
packageFilename: packageFilename,
|
||||
packageIdentifier: packageIdentifier,
|
||||
packageSign: packageSign,
|
||||
packageSigningIdentity: packageSigningIdentity
|
||||
)
|
||||
|
||||
downloading = true
|
||||
downloadInProgress = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InstallerListRow_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InstallerListRow(installer: .example, openPanel: .constant(NSOpenPanel()), downloadInProgress: .constant(false), taskManager: .shared)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// InstallerVolumeSelectionInformationView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 13/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InstallerVolumeSelectionInformationView: View {
|
||||
private let spacing: CGFloat = 10
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: spacing) {
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "info.circle.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(Color.white, Color.blue)
|
||||
.font(.title)
|
||||
Text("Only removable volumes formatted as **Mac OS Extended (Journaled)** are available for selection. Use **Disk Utility** to format volumes as required.")
|
||||
}
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(Color.black, Color.yellow)
|
||||
.font(.title)
|
||||
Text("The selected volume will be **erased automatically**. Ensure you have backed up any necessary data before proceeding.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InstallerVolumeSelectionInformationView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InstallerVolumeSelectionInformationView()
|
||||
}
|
||||
}
|
49
Mist/Views/List/InstallerVolumeSelectionPickerView.swift
Normal file
49
Mist/Views/List/InstallerVolumeSelectionPickerView.swift
Normal file
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// InstallerVolumeSelectionPickerView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 13/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InstallerVolumeSelectionPickerView: View {
|
||||
@Binding var selectedVolume: InstallerVolume
|
||||
var volumes: [InstallerVolume]
|
||||
var refresh: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Picker("Selected Volume", selection: $selectedVolume) {
|
||||
if volumes.isEmpty {
|
||||
Text(InstallerVolume.invalid.name)
|
||||
.tag(InstallerVolume.invalid)
|
||||
} else {
|
||||
Text(InstallerVolume.placeholder.name)
|
||||
.tag(InstallerVolume.placeholder)
|
||||
Divider()
|
||||
ForEach(volumes) { volume in
|
||||
Text("\(volume.name) - \(volume.capacity.bytesString())")
|
||||
.tag(volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.labelsHidden()
|
||||
Button {
|
||||
refresh()
|
||||
} label: {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
.help("Refresh")
|
||||
}
|
||||
.padding(.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
struct InstallerVolumeSelectionPickerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InstallerVolumeSelectionPickerView(selectedVolume: .constant(.placeholder), volumes: []) { }
|
||||
}
|
||||
}
|
106
Mist/Views/List/InstallerVolumeSelectionView.swift
Normal file
106
Mist/Views/List/InstallerVolumeSelectionView.swift
Normal file
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// InstallerVolumeSelectionView.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 12/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InstallerVolumeSelectionView: View {
|
||||
@Environment(\.presentationMode)
|
||||
var presentationMode: Binding<PresentationMode>
|
||||
@Binding var volume: InstallerVolume?
|
||||
@State private var selectedVolume: InstallerVolume = .placeholder
|
||||
@State private var volumes: [InstallerVolume] = [.placeholder]
|
||||
private let padding: CGFloat = 5
|
||||
private let width: CGFloat = 420
|
||||
private let height: CGFloat = 320
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Text("Create a Bootable Installer for macOS")
|
||||
.font(.title2)
|
||||
.padding(.vertical)
|
||||
Divider()
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Select a volume to create a bootable macOS Installer:")
|
||||
InstallerVolumeSelectionPickerView(selectedVolume: $selectedVolume, volumes: volumes, refresh: refresh)
|
||||
InstallerVolumeSelectionInformationView()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, padding)
|
||||
Spacer()
|
||||
Divider()
|
||||
HStack {
|
||||
Button("Open Disk Utility") {
|
||||
openDiskUtility()
|
||||
}
|
||||
Spacer()
|
||||
Button("Select") {
|
||||
volume = selectedVolume
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled([InstallerVolume.placeholder, InstallerVolume.invalid].contains(selectedVolume))
|
||||
Button("Cancel") {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.frame(width: width, height: height)
|
||||
.onAppear {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private func refresh() {
|
||||
volumes = getAvailableVolumes()
|
||||
selectedVolume = volumes.isEmpty ? InstallerVolume.invalid : InstallerVolume.placeholder
|
||||
}
|
||||
|
||||
private func getAvailableVolumes() -> [InstallerVolume] {
|
||||
|
||||
var volumes: [InstallerVolume] = []
|
||||
let keys: [URLResourceKey] = [.volumeNameKey, .volumeLocalizedFormatDescriptionKey, .volumeIsReadOnlyKey, .volumeTotalCapacityKey]
|
||||
|
||||
guard let urls: [URL] = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: keys, options: [.skipHiddenVolumes]) else {
|
||||
return []
|
||||
}
|
||||
|
||||
for url in urls {
|
||||
do {
|
||||
let resourceValues: URLResourceValues = try url.resourceValues(forKeys: Set(keys))
|
||||
|
||||
guard let volumeName: String = resourceValues.volumeName,
|
||||
let volumeLocalizedFormatDescription: String = resourceValues.volumeLocalizedFormatDescription,
|
||||
let volumeIsReadOnly: Bool = resourceValues.volumeIsReadOnly,
|
||||
let volumeTotalCapacity: Int = resourceValues.volumeTotalCapacity,
|
||||
volumeLocalizedFormatDescription == "Mac OS Extended (Journaled)",
|
||||
!volumeIsReadOnly else {
|
||||
continue
|
||||
}
|
||||
|
||||
let volume: InstallerVolume = InstallerVolume(id: UUID().uuidString, name: volumeName, path: url.path, capacity: UInt64(volumeTotalCapacity))
|
||||
volumes.append(volume)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return volumes
|
||||
}
|
||||
|
||||
private func openDiskUtility() {
|
||||
let url: URL = URL(fileURLWithPath: "/System/Applications/Utilities/Disk Utility.app")
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
struct InstallerVolumeSelectionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InstallerVolumeSelectionView(volume: .constant(.placeholder))
|
||||
}
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
//
|
||||
// ListRow.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 28/6/2022.
|
||||
//
|
||||
|
||||
import Blessed
|
||||
import SwiftUI
|
||||
import System
|
||||
|
||||
struct ListRow: View {
|
||||
var type: DownloadType
|
||||
var image: String
|
||||
var version: String
|
||||
var build: String
|
||||
var beta: Bool
|
||||
var date: String
|
||||
var size: String
|
||||
var compatible: Bool
|
||||
@Binding var showPanel: Bool
|
||||
@ObservedObject var taskManager: TaskManager
|
||||
@State private var alertType: DownloadAlertType = .compatibility
|
||||
@State private var showAlert: Bool = false
|
||||
@AppStorage("cacheDownloads")
|
||||
private var cacheDownloads: Bool = false
|
||||
@AppStorage("cacheDirectory")
|
||||
private var cacheDirectory: String = .cacheDirectory
|
||||
private let length: CGFloat = 48
|
||||
private let spacing: CGFloat = 5
|
||||
private let padding: CGFloat = 3
|
||||
private var helpText: String {
|
||||
"""
|
||||
Version: \(version)
|
||||
Build Number: \(build)
|
||||
Release Date: \(date)
|
||||
Download Size: \(size)
|
||||
"""
|
||||
}
|
||||
private var compatibilityTitle: String {
|
||||
"macOS \(type.description) not compatible!"
|
||||
}
|
||||
private var compatibilityMessage: String {
|
||||
|
||||
guard let architecture: Architecture = Hardware.architecture else {
|
||||
return "Invalid architecture!"
|
||||
}
|
||||
|
||||
let operation: String = type == .firmware ? "restore" : "re-install"
|
||||
let string: String = "This macOS \(type.description) download cannot be used to \(operation) macOS on this \(architecture.description) Mac.\n\nAre you sure you want to continue?"
|
||||
return string
|
||||
}
|
||||
private var privilegedHelperToolTitle: String {
|
||||
"Privileged Helper Tool not installed!"
|
||||
}
|
||||
private var privilegedHelperToolMessage: String {
|
||||
"The Mist Privileged Helper Tool is required to perform Administrator tasks when \(type == .firmware ? "downloading macOS Firmwares" : "creating macOS Installers")."
|
||||
}
|
||||
private var fullDiskAccessTitle: String {
|
||||
"Full Disk Access required!"
|
||||
}
|
||||
private var fullDiskAccessMessage: String {
|
||||
"Mist requires Full Disk Access to perform Administrator tasks when creating macOS Installers."
|
||||
}
|
||||
private var cacheDirectoryTitle: String {
|
||||
"Cache directory settings incorrect!"
|
||||
}
|
||||
private var cacheDirectoryMessage: String {
|
||||
"The cache directory has incorrect ownership and/or permissions, which will cause issues caching macOS Installers.\n\nRepair the cache directory ownership and/or permissions and try again."
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Group {
|
||||
ZStack {
|
||||
ScaledImage(name: image, length: length)
|
||||
if beta {
|
||||
TextRibbon(title: "BETA", length: length * 0.9)
|
||||
}
|
||||
}
|
||||
HStack(spacing: spacing) {
|
||||
Text(version)
|
||||
.font(.title2)
|
||||
Text("(\(build))")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Text(date)
|
||||
.foregroundColor(.secondary)
|
||||
Text(size)
|
||||
}
|
||||
.help(helpText)
|
||||
.textSelection(.enabled)
|
||||
Group {
|
||||
switch type {
|
||||
case .firmware:
|
||||
Button {
|
||||
compatible ? validate() : showCompatibilityWarning()
|
||||
} label: {
|
||||
Image(systemName: "arrow.down.circle")
|
||||
.font(.body.bold())
|
||||
}
|
||||
.help("Download macOS Firmware")
|
||||
.buttonStyle(.capsule(.standard))
|
||||
case .installer:
|
||||
HStack(spacing: 1) {
|
||||
Button {
|
||||
compatible ? validate() : showCompatibilityWarning()
|
||||
} label: {
|
||||
Image(systemName: "arrow.down.circle")
|
||||
.font(.body.bold())
|
||||
}
|
||||
.help("Download and export macOS Installer")
|
||||
.buttonStyle(.capsule(.leading))
|
||||
Button {
|
||||
print("Create bootable installer...")
|
||||
} label: {
|
||||
Image(systemName: "externaldrive")
|
||||
.font(.body.bold())
|
||||
.padding(.vertical, 1)
|
||||
}
|
||||
.help("Create bootable macOS Installer")
|
||||
.buttonStyle(.capsule(.trailing))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.trailing, padding)
|
||||
}
|
||||
.alert(isPresented: $showAlert) {
|
||||
switch alertType {
|
||||
case .compatibility:
|
||||
return Alert(
|
||||
title: Text(compatibilityTitle),
|
||||
message: Text(compatibilityMessage),
|
||||
primaryButton: .default(Text("Cancel")),
|
||||
secondaryButton: .default(Text("Continue")) { Task { validate() } }
|
||||
)
|
||||
case .helperTool:
|
||||
return Alert(
|
||||
title: Text(privilegedHelperToolTitle),
|
||||
message: Text(privilegedHelperToolMessage),
|
||||
primaryButton: .default(Text("Install...")) { installPrivilegedHelperTool() },
|
||||
secondaryButton: .default(Text("Cancel"))
|
||||
)
|
||||
case .fullDiskAccess:
|
||||
return Alert(
|
||||
title: Text(fullDiskAccessTitle),
|
||||
message: Text(fullDiskAccessMessage),
|
||||
primaryButton: .default(Text("Allow...")) { openFullDiskAccessPreferences() },
|
||||
secondaryButton: .default(Text("Cancel"))
|
||||
)
|
||||
case .cacheDirectory:
|
||||
return Alert(
|
||||
title: Text(cacheDirectoryTitle),
|
||||
message: Text(cacheDirectoryMessage),
|
||||
primaryButton: .default(Text("Repair...")) { Task { try await repairCacheDirectoryOwnershipAndPermissions() } },
|
||||
secondaryButton: .default(Text("Cancel"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func showCompatibilityWarning() {
|
||||
alertType = .compatibility
|
||||
showAlert = true
|
||||
}
|
||||
|
||||
private func validate() {
|
||||
|
||||
guard PrivilegedHelperTool.isInstalled() else {
|
||||
alertType = .helperTool
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
if type == .installer {
|
||||
|
||||
guard FileManager.default.isReadableFile(atPath: .tccDatabasePath) else {
|
||||
alertType = .fullDiskAccess
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if cacheDownloads {
|
||||
|
||||
do {
|
||||
var isDirectory: ObjCBool = false
|
||||
|
||||
if !FileManager.default.fileExists(atPath: cacheDirectory, isDirectory: &isDirectory) {
|
||||
try FileManager.default.createDirectory(atPath: cacheDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
let attributes: [FileAttributeKey: Any] = try FileManager.default.attributesOfItem(atPath: cacheDirectory)
|
||||
|
||||
guard let posixPermissions: NSNumber = attributes[.posixPermissions] as? NSNumber else {
|
||||
alertType = .cacheDirectory
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
let filePermissions: FilePermissions = FilePermissions(rawValue: CModeT(posixPermissions.int16Value))
|
||||
|
||||
guard filePermissions == [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute],
|
||||
let ownerAccountName: String = attributes[.ownerAccountName] as? String,
|
||||
ownerAccountName == NSUserName(),
|
||||
let groupOwnerAccountName: String = attributes[.groupOwnerAccountName] as? String,
|
||||
groupOwnerAccountName == "wheel" else {
|
||||
alertType = .cacheDirectory
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
alertType = .cacheDirectory
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
showPanel = true
|
||||
}
|
||||
|
||||
private func installPrivilegedHelperTool() {
|
||||
try? PrivilegedHelperManager.shared.authorizeAndBless()
|
||||
}
|
||||
|
||||
private func openFullDiskAccessPreferences() {
|
||||
|
||||
guard let url: URL = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") else {
|
||||
return
|
||||
}
|
||||
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
|
||||
private func repairCacheDirectoryOwnershipAndPermissions() async throws {
|
||||
let url: URL = URL(fileURLWithPath: cacheDirectory)
|
||||
let ownerAccountName: String = NSUserName()
|
||||
try await FileAttributesUpdater.update(url: url, ownerAccountName: ownerAccountName)
|
||||
}
|
||||
}
|
||||
|
||||
struct ListRow_Previews: PreviewProvider {
|
||||
static let firmware: Firmware = .example
|
||||
static let installer: Installer = .example
|
||||
|
||||
static var previews: some View {
|
||||
ListRow(
|
||||
type: .firmware,
|
||||
image: firmware.imageName,
|
||||
version: firmware.version,
|
||||
build: firmware.build,
|
||||
beta: firmware.beta,
|
||||
date: firmware.formattedDate,
|
||||
size: firmware.size.bytesString(),
|
||||
compatible: firmware.compatible,
|
||||
showPanel: .constant(false),
|
||||
taskManager: .shared
|
||||
)
|
||||
ListRow(
|
||||
type: .installer,
|
||||
image: installer.imageName,
|
||||
version: installer.version,
|
||||
build: installer.build,
|
||||
beta: installer.beta,
|
||||
date: installer.date,
|
||||
size: installer.size.bytesString(),
|
||||
compatible: firmware.compatible,
|
||||
showPanel: .constant(false),
|
||||
taskManager: .shared
|
||||
)
|
||||
}
|
||||
}
|
69
Mist/Views/List/ListRowDetail.swift
Normal file
69
Mist/Views/List/ListRowDetail.swift
Normal file
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// ListRowDetail.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 12/6/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ListRowDetail: View {
|
||||
var imageName: String
|
||||
var beta: Bool
|
||||
var version: String
|
||||
var build: String
|
||||
var date: String
|
||||
var size: String
|
||||
var tooltip: String
|
||||
private let length: CGFloat = 48
|
||||
private let spacing: CGFloat = 5
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
ZStack {
|
||||
ScaledImage(name: imageName, length: length)
|
||||
if beta {
|
||||
TextRibbon(title: "BETA", length: length * 0.9)
|
||||
}
|
||||
}
|
||||
HStack(spacing: spacing) {
|
||||
Text(version)
|
||||
.font(.title2)
|
||||
Text("(\(build))")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Text(date)
|
||||
.foregroundColor(.secondary)
|
||||
Text(size)
|
||||
}
|
||||
.help(tooltip)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
struct ListRowDetail_Previews: PreviewProvider {
|
||||
static let firmware: Firmware = .example
|
||||
static let installer: Installer = .example
|
||||
|
||||
static var previews: some View {
|
||||
ListRowDetail(
|
||||
imageName: firmware.imageName,
|
||||
beta: firmware.beta,
|
||||
version: firmware.version,
|
||||
build: firmware.build,
|
||||
date: firmware.formattedDate,
|
||||
size: firmware.size.bytesString(),
|
||||
tooltip: firmware.tooltip
|
||||
)
|
||||
ListRowDetail(
|
||||
imageName: installer.imageName,
|
||||
beta: installer.beta,
|
||||
version: installer.version,
|
||||
build: installer.build,
|
||||
date: installer.date,
|
||||
size: installer.size.bytesString(),
|
||||
tooltip: installer.tooltip
|
||||
)
|
||||
}
|
||||
}
|
144
Mist/Views/List/ListRowFirmware.swift
Normal file
144
Mist/Views/List/ListRowFirmware.swift
Normal file
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// ListRowFirmware.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 13/6/2022.
|
||||
//
|
||||
|
||||
import Blessed
|
||||
import SwiftUI
|
||||
|
||||
struct ListRowFirmware: View {
|
||||
@AppStorage("firmwareFilename")
|
||||
private var firmwareFilename: String = .firmwareFilenameTemplate
|
||||
@AppStorage("retries")
|
||||
private var retries: Int = 10
|
||||
@AppStorage("retryDelay")
|
||||
private var retryDelay: Int = 30
|
||||
var firmware: Firmware
|
||||
@Binding var savePanel: NSSavePanel
|
||||
@Binding var tasksInProgress: Bool
|
||||
@ObservedObject var taskManager: TaskManager
|
||||
@State private var alertType: FirmwareAlertType = .compatibility
|
||||
@State private var showAlert: Bool = false
|
||||
@State private var showSavePanel: Bool = false
|
||||
@State private var downloading: Bool = false
|
||||
private let length: CGFloat = 48
|
||||
private let spacing: CGFloat = 5
|
||||
private let padding: CGFloat = 3
|
||||
private var compatibilityMessage: String {
|
||||
|
||||
guard let architecture: Architecture = Hardware.architecture else {
|
||||
return "Invalid architecture!"
|
||||
}
|
||||
|
||||
return "This macOS Firmware download cannot be used to restore macOS on this \(architecture.description) Mac.\n\nAre you sure you want to continue?"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
ListRowDetail(
|
||||
imageName: firmware.imageName,
|
||||
beta: firmware.beta,
|
||||
version: firmware.version,
|
||||
build: firmware.build,
|
||||
date: firmware.formattedDate,
|
||||
size: firmware.size.bytesString(),
|
||||
tooltip: firmware.tooltip
|
||||
)
|
||||
Button {
|
||||
firmware.compatible ? validate() : showCompatibilityWarning()
|
||||
} label: {
|
||||
Image(systemName: "arrow.down.circle")
|
||||
.font(.body.bold())
|
||||
}
|
||||
.help("Download macOS Firmware")
|
||||
.buttonStyle(.mistAction)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
.alert(isPresented: $showAlert) {
|
||||
switch alertType {
|
||||
case .compatibility:
|
||||
return Alert(
|
||||
title: Text("macOS Firmware not compatible!"),
|
||||
message: Text(compatibilityMessage),
|
||||
primaryButton: .default(Text("Cancel")),
|
||||
secondaryButton: .default(Text("Continue")) { Task { validate() } }
|
||||
)
|
||||
case .helperTool:
|
||||
return Alert(
|
||||
title: Text("Privileged Helper Tool not installed!"),
|
||||
message: Text("The Mist Privileged Helper Tool is required to perform Administrator tasks when downloading macOS Firmwares"),
|
||||
primaryButton: .default(Text("Install...")) { installPrivilegedHelperTool() },
|
||||
secondaryButton: .default(Text("Cancel"))
|
||||
)
|
||||
}
|
||||
}
|
||||
.onChange(of: showSavePanel) { boolean in
|
||||
|
||||
if boolean {
|
||||
save()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $downloading) {
|
||||
ActivityView(
|
||||
downloadType: .firmware,
|
||||
imageName: firmware.imageName,
|
||||
name: firmware.name,
|
||||
version: firmware.version,
|
||||
build: firmware.build,
|
||||
beta: firmware.beta,
|
||||
destinationURL: savePanel.url,
|
||||
taskManager: taskManager
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func save() {
|
||||
showSavePanel = false
|
||||
savePanel.title = "Download Firmware"
|
||||
savePanel.nameFieldStringValue = firmwareFilename.stringWithSubstitutions(name: firmware.name, version: firmware.version, build: firmware.build)
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.canSelectHiddenExtension = true
|
||||
savePanel.isExtensionHidden = false
|
||||
|
||||
Task {
|
||||
let response: NSApplication.ModalResponse = savePanel.runModal()
|
||||
|
||||
guard response == .OK else {
|
||||
return
|
||||
}
|
||||
|
||||
taskManager.taskGroups = try TaskManager.taskGroups(for: firmware, destination: savePanel.url, retries: retries, delay: retryDelay)
|
||||
downloading = true
|
||||
tasksInProgress = true
|
||||
}
|
||||
}
|
||||
|
||||
private func showCompatibilityWarning() {
|
||||
alertType = .compatibility
|
||||
showAlert = true
|
||||
}
|
||||
|
||||
private func validate() {
|
||||
|
||||
guard PrivilegedHelperTool.isInstalled() else {
|
||||
alertType = .helperTool
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
showSavePanel = true
|
||||
}
|
||||
|
||||
private func installPrivilegedHelperTool() {
|
||||
try? PrivilegedHelperManager.shared.authorizeAndBless()
|
||||
}
|
||||
}
|
||||
|
||||
struct ListRowFirmware_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
ListRowFirmware(firmware: .example, savePanel: .constant(NSSavePanel()), tasksInProgress: .constant(false), taskManager: .shared)
|
||||
}
|
||||
}
|
335
Mist/Views/List/ListRowInstaller.swift
Normal file
335
Mist/Views/List/ListRowInstaller.swift
Normal file
|
@ -0,0 +1,335 @@
|
|||
//
|
||||
// ListRowInstaller.swift
|
||||
// Mist
|
||||
//
|
||||
// Created by Nindi Gill on 17/6/2022.
|
||||
//
|
||||
|
||||
import Blessed
|
||||
import SwiftUI
|
||||
import System
|
||||
|
||||
// swiftlint:disable:next type_body_length
|
||||
struct ListRowInstaller: View {
|
||||
@AppStorage("cacheDownloads")
|
||||
private var cacheDownloads: Bool = false
|
||||
@AppStorage("cacheDirectory")
|
||||
private var cacheDirectory: String = .cacheDirectory
|
||||
@AppStorage("applicationFilename")
|
||||
private var applicationFilename: String = .applicationFilenameTemplate
|
||||
@AppStorage("diskImageFilename")
|
||||
private var diskImageFilename: String = .diskImageFilenameTemplate
|
||||
@AppStorage("diskImageSign")
|
||||
private var diskImageSign: Bool = false
|
||||
@AppStorage("diskImageSigningIdentity")
|
||||
private var diskImageSigningIdentity: String = ""
|
||||
@AppStorage("isoFilename")
|
||||
private var isoFilename: String = .isoFilenameTemplate
|
||||
@AppStorage("packageFilename")
|
||||
private var packageFilename: String = .packageFilenameTemplate
|
||||
@AppStorage("packageIdentifier")
|
||||
private var packageIdentifier: String = .packageIdentifierTemplate
|
||||
@AppStorage("packageSign")
|
||||
private var packageSign: Bool = false
|
||||
@AppStorage("packageSigningIdentity")
|
||||
private var packageSigningIdentity: String = ""
|
||||
@AppStorage("retries")
|
||||
private var retries: Int = 10
|
||||
@AppStorage("retryDelay")
|
||||
private var retryDelay: Int = 30
|
||||
var installer: Installer
|
||||
@Binding var openPanel: NSOpenPanel
|
||||
@Binding var tasksInProgress: Bool
|
||||
@ObservedObject var taskManager: TaskManager
|
||||
@State private var alertType: InstallerAlertType = .compatibility
|
||||
@State private var showAlert: Bool = false
|
||||
@State private var sheetType: InstallerSheetType = .download
|
||||
@State private var showSheet: Bool = false
|
||||
@State private var showOpenPanel: Bool = false
|
||||
@State private var exports: [InstallerExportType] = []
|
||||
@State private var volume: InstallerVolume?
|
||||
private let length: CGFloat = 48
|
||||
private let spacing: CGFloat = 5
|
||||
private let padding: CGFloat = 3
|
||||
private var compatibilityMessage: String {
|
||||
|
||||
guard let architecture: Architecture = Hardware.architecture else {
|
||||
return "Invalid architecture!"
|
||||
}
|
||||
|
||||
return "This macOS Installer download cannot be used to restore macOS on this \(architecture.description) Mac.\n\nAre you sure you want to continue?"
|
||||
}
|
||||
private var cacheDirectoryMessage: String {
|
||||
"The cache directory has incorrect ownership and/or permissions, which will cause issues caching macOS Installers.\n\nRepair the cache directory ownership and/or permissions and try again."
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
ListRowDetail(
|
||||
imageName: installer.imageName,
|
||||
beta: installer.beta,
|
||||
version: installer.version,
|
||||
build: installer.build,
|
||||
date: installer.date,
|
||||
size: installer.size.bytesString(),
|
||||
tooltip: installer.tooltip
|
||||
)
|
||||
HStack(spacing: 1) {
|
||||
Button {
|
||||
pressButton(.download)
|
||||
} label: {
|
||||
Image(systemName: "arrow.down.circle").font(.body.bold())
|
||||
}
|
||||
.help("Download and export macOS Installer")
|
||||
.buttonStyle(.mistAction)
|
||||
if let architecture: Architecture = Hardware.architecture,
|
||||
(architecture == .appleSilicon && installer.bigSurOrNewer) || (architecture == .intel && installer.mavericksOrNewer) {
|
||||
Button {
|
||||
pressButton(.volumeSelection)
|
||||
} label: {
|
||||
Image(systemName: "externaldrive").font(.body.bold())
|
||||
.padding(.vertical, 1)
|
||||
}
|
||||
.help("Create bootable macOS Installer")
|
||||
.buttonStyle(.mistAction)
|
||||
}
|
||||
}
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
.alert(isPresented: $showAlert) {
|
||||
switch alertType {
|
||||
case .compatibility:
|
||||
return Alert(
|
||||
title: Text("macOS Installer not compatible!"),
|
||||
message: Text(compatibilityMessage),
|
||||
primaryButton: .default(Text("Cancel")),
|
||||
secondaryButton: .default(Text("Continue")) { Task { validate() } }
|
||||
)
|
||||
case .helperTool:
|
||||
return Alert(
|
||||
title: Text("Privileged Helper Tool not installed!"),
|
||||
message: Text("The Mist Privileged Helper Tool is required to perform Administrator tasks when creating macOS Installers."),
|
||||
primaryButton: .default(Text("Install...")) { installPrivilegedHelperTool() },
|
||||
secondaryButton: .default(Text("Cancel"))
|
||||
)
|
||||
case .fullDiskAccess:
|
||||
return Alert(
|
||||
title: Text("Full Disk Access required!"),
|
||||
message: Text("Mist requires Full Disk Access to perform Administrator tasks when creating macOS Installers."),
|
||||
primaryButton: .default(Text("Allow...")) { openFullDiskAccessPreferences() },
|
||||
secondaryButton: .default(Text("Cancel"))
|
||||
)
|
||||
case .cacheDirectory:
|
||||
return Alert(
|
||||
title: Text("Cache directory settings incorrect!"),
|
||||
message: Text(cacheDirectoryMessage),
|
||||
primaryButton: .default(Text("Repair...")) { Task { try await repairCacheDirectoryOwnershipAndPermissions() } },
|
||||
secondaryButton: .default(Text("Cancel"))
|
||||
)
|
||||
}
|
||||
}
|
||||
.onChange(of: showOpenPanel) { boolean in
|
||||
|
||||
if boolean {
|
||||
open()
|
||||
}
|
||||
}
|
||||
.onChange(of: volume) { volume in
|
||||
|
||||
if volume != nil {
|
||||
createBootableInstaller()
|
||||
}
|
||||
}
|
||||
.onChange(of: sheetType) { _ in } // hack to make cascading sheets work
|
||||
.sheet(isPresented: $showSheet) {
|
||||
switch sheetType {
|
||||
case .download:
|
||||
ActivityView(
|
||||
downloadType: .installer,
|
||||
imageName: installer.imageName,
|
||||
name: installer.name.replacingOccurrences(of: " beta", with: ""),
|
||||
version: installer.version,
|
||||
build: installer.build,
|
||||
beta: installer.beta,
|
||||
destinationURL: openPanel.url,
|
||||
taskManager: taskManager
|
||||
)
|
||||
case .volumeSelection:
|
||||
InstallerVolumeSelectionView(volume: $volume)
|
||||
case .createBootableInstaller:
|
||||
ActivityView(
|
||||
downloadType: .installer,
|
||||
imageName: installer.imageName,
|
||||
name: installer.name.replacingOccurrences(of: " beta", with: ""),
|
||||
version: installer.version,
|
||||
build: installer.build,
|
||||
beta: installer.beta,
|
||||
destinationURL: URL(fileURLWithPath: "/Volumes/Install \(installer.name)"),
|
||||
taskManager: taskManager
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func pressButton(_ type: InstallerSheetType) {
|
||||
sheetType = type
|
||||
|
||||
if installer.compatible {
|
||||
Task { validate() }
|
||||
} else {
|
||||
showCompatibilityWarning()
|
||||
}
|
||||
}
|
||||
|
||||
private func open() {
|
||||
showOpenPanel = false
|
||||
openPanel.title = "Download Installer"
|
||||
openPanel.canChooseFiles = false
|
||||
openPanel.canChooseDirectories = true
|
||||
openPanel.allowsMultipleSelection = false
|
||||
openPanel.prompt = "Save"
|
||||
openPanel.accessoryView = NSHostingView(rootView: InstallerExportView(installer: installer, exports: $exports))
|
||||
openPanel.isAccessoryViewDisclosed = true
|
||||
|
||||
Task {
|
||||
let response: NSApplication.ModalResponse = openPanel.runModal()
|
||||
|
||||
guard response == .OK else {
|
||||
return
|
||||
}
|
||||
|
||||
taskManager.taskGroups = try TaskManager.taskGroups(
|
||||
for: installer,
|
||||
destination: openPanel.url,
|
||||
exports: exports,
|
||||
cacheDownloads: cacheDownloads,
|
||||
cacheDirectory: cacheDirectory,
|
||||
retries: retries,
|
||||
delay: retryDelay,
|
||||
applicationFilename: applicationFilename,
|
||||
diskImageFilename: diskImageFilename,
|
||||
diskImageSign: diskImageSign,
|
||||
diskImageSigningIdentity: diskImageSigningIdentity,
|
||||
isoFilename: isoFilename,
|
||||
packageFilename: packageFilename,
|
||||
packageIdentifier: packageIdentifier,
|
||||
packageSign: packageSign,
|
||||
packageSigningIdentity: packageSigningIdentity
|
||||
)
|
||||
|
||||
showSheet = true
|
||||
tasksInProgress = true
|
||||
}
|
||||
}
|
||||
|
||||
private func createBootableInstaller() {
|
||||
|
||||
guard let volume: InstallerVolume = volume else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
taskManager.taskGroups = try TaskManager.taskGroups(
|
||||
for: installer,
|
||||
cacheDownloads: cacheDownloads,
|
||||
cacheDirectory: cacheDirectory,
|
||||
retries: retries,
|
||||
delay: retryDelay,
|
||||
volume: volume
|
||||
)
|
||||
|
||||
sheetType = .createBootableInstaller
|
||||
showSheet = true
|
||||
tasksInProgress = true
|
||||
}
|
||||
}
|
||||
|
||||
private func showCompatibilityWarning() {
|
||||
alertType = .compatibility
|
||||
showAlert = true
|
||||
}
|
||||
|
||||
private func validate() {
|
||||
|
||||
guard PrivilegedHelperTool.isInstalled() else {
|
||||
alertType = .helperTool
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
guard FileManager.default.isReadableFile(atPath: .tccDatabasePath) else {
|
||||
alertType = .fullDiskAccess
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
if cacheDownloads {
|
||||
|
||||
do {
|
||||
var isDirectory: ObjCBool = false
|
||||
|
||||
if !FileManager.default.fileExists(atPath: cacheDirectory, isDirectory: &isDirectory) {
|
||||
try FileManager.default.createDirectory(atPath: cacheDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
let attributes: [FileAttributeKey: Any] = try FileManager.default.attributesOfItem(atPath: cacheDirectory)
|
||||
|
||||
guard let posixPermissions: NSNumber = attributes[.posixPermissions] as? NSNumber else {
|
||||
alertType = .cacheDirectory
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
let filePermissions: FilePermissions = FilePermissions(rawValue: CModeT(posixPermissions.int16Value))
|
||||
|
||||
guard filePermissions == [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute],
|
||||
let ownerAccountName: String = attributes[.ownerAccountName] as? String,
|
||||
ownerAccountName == NSUserName(),
|
||||
let groupOwnerAccountName: String = attributes[.groupOwnerAccountName] as? String,
|
||||
groupOwnerAccountName == "wheel" else {
|
||||
alertType = .cacheDirectory
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
alertType = .cacheDirectory
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch sheetType {
|
||||
case .download:
|
||||
showOpenPanel = true
|
||||
case .volumeSelection:
|
||||
showSheet = true
|
||||
case .createBootableInstaller:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func installPrivilegedHelperTool() {
|
||||
try? PrivilegedHelperManager.shared.authorizeAndBless()
|
||||
}
|
||||
|
||||
private func openFullDiskAccessPreferences() {
|
||||
|
||||
guard let url: URL = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") else {
|
||||
return
|
||||
}
|
||||
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
|
||||
private func repairCacheDirectoryOwnershipAndPermissions() async throws {
|
||||
let url: URL = URL(fileURLWithPath: cacheDirectory)
|
||||
let ownerAccountName: String = NSUserName()
|
||||
try await FileAttributesUpdater.update(url: url, ownerAccountName: ownerAccountName)
|
||||
}
|
||||
}
|
||||
|
||||
struct ListRowInstaller_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ListRowInstaller(installer: .example, openPanel: .constant(NSOpenPanel()), tasksInProgress: .constant(false), taskManager: .shared)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue