From b06be4e3fa5e8fd432546c7f1155987b1353cb55 Mon Sep 17 00:00:00 2001 From: Matt Kiazyk Date: Sat, 13 Feb 2021 11:20:26 -0600 Subject: [PATCH 1/8] Download stats for Aria2 --- Xcodes.xcodeproj/project.pbxproj | 32 ++++++-- Xcodes/Backend/Environment.swift | 15 +--- Xcodes/Backend/Progress+.swift | 78 +++++++++++++++++++ .../Common/ObservingProgressIndicator.swift | 2 +- .../{XcodeList => InfoPane}/InfoPane.swift | 51 +++++++++--- .../InfoPane/InstallationStepDetailView.swift | 35 +++++++++ .../InfoPane/ObservingDownloadStatsView.swift | 77 ++++++++++++++++++ ...ew.swift => InstallationStepRowView.swift} | 16 ++-- .../Frontend/XcodeList/XcodeListViewRow.swift | 2 +- 9 files changed, 270 insertions(+), 38 deletions(-) create mode 100644 Xcodes/Backend/Progress+.swift rename Xcodes/Frontend/{XcodeList => InfoPane}/InfoPane.swift (88%) create mode 100644 Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift create mode 100644 Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift rename Xcodes/Frontend/XcodeList/{InstallationStepView.swift => InstallationStepRowView.swift} (91%) diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 88c61ec..1c21fa9 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -75,7 +75,6 @@ CABFAA432593104F00380FEE /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA422593104F00380FEE /* AboutView.swift */; }; CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */; }; CAC28188259EE27200B8AB0B /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = CAC28187259EE27200B8AB0B /* CombineExpectations */; }; - CAC281C8259F97E100B8AB0B /* InstallationStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */; }; CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */; }; CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281D9259F985100B8AB0B /* InstallationStep.swift */; }; CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */; }; @@ -99,7 +98,11 @@ CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4AB325B7D3AF0064FE51 /* AdvancedPreferencePane.swift */; }; CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */; }; CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; }; + E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; }; E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; }; + E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; }; + E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; }; + E8F0838625D873A400A4C470 /* ObservingDownloadStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -227,7 +230,6 @@ CABFAA2B2592FBFC00380FEE /* Configure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Configure.swift; path = Xcodes/Backend/Configure.swift; sourceTree = SOURCE_ROOT; }; CABFAA422593104F00380FEE /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+InfoPlistValues.swift"; sourceTree = ""; }; - CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepView.swift; sourceTree = ""; }; CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingProgressIndicator.swift; sourceTree = ""; }; CAC281D9259F985100B8AB0B /* InstallationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStep.swift; sourceTree = ""; }; CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+XcodesTests.swift"; sourceTree = ""; }; @@ -247,7 +249,7 @@ CAE42486259A68A300B8B246 /* XcodeListCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListCategory.swift; sourceTree = ""; }; CAE4248B259A68B800B8B246 /* Optional+IsNotNil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+IsNotNil.swift"; sourceTree = ""; }; CAE424B3259A764700B8B246 /* AppState+Install.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppState+Install.swift"; sourceTree = ""; }; - CAFBC3FF259AC17F00E2A3D8 /* InstallationStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepView.swift; sourceTree = ""; }; + CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepRowView.swift; sourceTree = ""; }; CAFBC421259ACF8000E2A3D8 /* ObservingProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingProgressView.swift; sourceTree = ""; }; CAFBDB902598FE80003DCC5A /* SelectedXcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedXcode.swift; sourceTree = ""; }; CAFBDB942598FE96003DCC5A /* FocusedValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedValues.swift; sourceTree = ""; }; @@ -260,7 +262,10 @@ CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesPreferencePane.swift; sourceTree = ""; }; CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListViewRow.swift; sourceTree = ""; }; CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = ""; }; + E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = ""; }; E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; + E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = ""; }; + E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingDownloadStatsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -387,14 +392,12 @@ isa = PBXGroup; children = ( CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */, - CAFBDC67259A308B003DCC5A /* InfoPane.swift */, - CAFBC3FF259AC17F00E2A3D8 /* InstallationStepView.swift */, + CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */, CAFBDC4D2599B33D003DCC5A /* MainToolbar.swift */, CA44901E2463AD34003D8213 /* Tag.swift */, CAE42486259A68A300B8B246 /* XcodeListCategory.swift */, CAD2E7A32449574E00113D76 /* XcodeListView.swift */, CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */, - CAC281C7259F97E100B8AB0B /* InstallationStepView.swift */, ); path = XcodeList; sourceTree = ""; @@ -437,6 +440,7 @@ CA61A6DF259835580008926E /* Xcode.swift */, CA25192925A9644800F08414 /* XcodeInstallState.swift */, CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */, + E87DD6EA25D053FA00D86808 /* Progress+.swift */, ); path = Backend; sourceTree = ""; @@ -444,6 +448,7 @@ CABFAA1A2592F7D900380FEE /* Frontend */ = { isa = PBXGroup; children = ( + E8E98A9425D863B100EC89A0 /* InfoPane */, 63EAA4E9259944340046AB8F /* Common */, CA9FF8552595082000E47BAF /* About */, CAFE4AAA25B7D29B0064FE51 /* Preferences */, @@ -547,6 +552,16 @@ path = Preferences; sourceTree = ""; }; + E8E98A9425D863B100EC89A0 /* InfoPane */ = { + isa = PBXGroup; + children = ( + CAFBDC67259A308B003DCC5A /* InfoPane.swift */, + E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, + E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */, + ); + path = InfoPane; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -757,6 +772,7 @@ CABFA9BD2592EEEA00380FEE /* Environment.swift in Sources */, CABFA9C32592EEEA00380FEE /* Downloads.swift in Sources */, CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */, + E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */, CA378F992466567600A58CE0 /* AppState.swift in Sources */, CAD2E7A42449574E00113D76 /* XcodeListView.swift in Sources */, CAA1CB45255A5B60003FD669 /* SignIn2FAView.swift in Sources */, @@ -768,7 +784,6 @@ CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */, CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */, CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */, - CAC281C8259F97E100B8AB0B /* InstallationStepView.swift in Sources */, CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */, CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */, CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */, @@ -782,6 +797,8 @@ CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */, CAFBDB952598FE96003DCC5A /* FocusedValues.swift in Sources */, CAC9F92D25BCDA4400B4965F /* HelperInstallState.swift in Sources */, + E8F0838625D873A400A4C470 /* ObservingDownloadStatsView.swift in Sources */, + E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */, CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */, CABFA9C22592EEEA00380FEE /* Publisher+Resumable.swift in Sources */, CAFBDC68259A308B003DCC5A /* InfoPane.swift in Sources */, @@ -797,6 +814,7 @@ CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */, CABFA9C92592EEEA00380FEE /* URLRequest+Apple.swift in Sources */, CABFAA432593104F00380FEE /* AboutView.swift in Sources */, + E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */, CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */, CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */, 63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */, diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index dea687b..71a753d 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -45,6 +45,7 @@ public struct Shell { "--stop-with-process=\(ProcessInfo.processInfo.processIdentifier)", "--dir=\(destination.parent.string)", "--out=\(destination.basename())", + "--human-readable=false", // sets the output to use bytes instead of formatting url.absoluteString, ] let stdOutPipe = Pipe() @@ -53,7 +54,7 @@ public struct Shell { process.standardError = stdErrPipe var progress = Progress(totalUnitCount: 100) - + let observer = NotificationCenter.default.addObserver( forName: .NSFileHandleDataAvailable, object: nil, @@ -68,16 +69,8 @@ public struct Shell { defer { handle.waitForDataInBackgroundAndNotify() } let string = String(decoding: handle.availableData, as: UTF8.self) - let regex = try! NSRegularExpression(pattern: #"((?\d+)%\))"#) - let range = NSRange(location: 0, length: string.utf16.count) - - guard - let match = regex.firstMatch(in: string, options: [], range: range), - let matchRange = Range(match.range(withName: "percent"), in: string), - let percentCompleted = Int64(string[matchRange]) - else { return } - - progress.completedUnitCount = percentCompleted + + progress.updateFromAria2(string: string) } stdOutPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() diff --git a/Xcodes/Backend/Progress+.swift b/Xcodes/Backend/Progress+.swift new file mode 100644 index 0000000..0998369 --- /dev/null +++ b/Xcodes/Backend/Progress+.swift @@ -0,0 +1,78 @@ + +import Foundation + +extension Progress { + func updateFromAria2(string: String) { + + let range = NSRange(location: 0, length: string.utf16.count) + + // MARK: Total Downloaded + let regexTotalDownloaded = try! NSRegularExpression(pattern: #"(?<= )(.*)(?=\/)"#) + + if let match = regexTotalDownloaded.firstMatch(in: string, options: [], range: range), + let matchRange = Range(match.range(at: 0), in: string), + let totalDownloaded = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) { + self.fileCompletedCount = totalDownloaded + self.completedUnitCount = Int64(totalDownloaded) + } + + // MARK: Filesize + let regexTotalFileSize = try! NSRegularExpression(pattern: #"(?<=/)(.*)(?=\()"#) + + if let match = regexTotalFileSize.firstMatch(in: string, options: [], range: range), + let matchRange = Range(match.range(at: 0), in: string), + let totalFileSize = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) { + + if totalFileSize > 0 { + self.fileTotalCount = totalFileSize + self.totalUnitCount = Int64(totalFileSize) + } + } + + // MARK: PERCENT DOWNLOADED + // Since we get fractionCompleted from completedUnitCount + totalUnitCount, no need to process + // let regexPercent = try! NSRegularExpression(pattern: #"((?\d+)%\))"#) + + // MARK: Speed + let regexSpeed = try! NSRegularExpression(pattern: #"(?<=DL:)(.*)(?= )"#) + + if let match = regexSpeed.firstMatch(in: string, options: [], range: range), + let matchRange = Range(match.range(at: 0), in: string), + let speed = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) { + self.throughput = speed + } else { + print("Could not find speed") + } + + // MARK: Estimated Time Remaining + let regexETA = try! NSRegularExpression(pattern: #"(?<=ETA:)(?\d*d)?(?\d*h)?(?\d*m)?(?\d*s)?"#) + + if let match = regexETA.firstMatch(in: string, options: [], range: range) { + var seconds: Int = 0 + + if let matchRange = Range(match.range(withName: "days"), in: string), + let days = Int(string[matchRange].replacingOccurrences(of: "d", with: "")) { + seconds += (days * 60 * 60 * 24) + } + + if let matchRange = Range(match.range(withName: "hours"), in: string), + let hours = Int(string[matchRange].replacingOccurrences(of: "h", with: "")) { + seconds += (hours * 60 * 60) + } + + if let matchRange = Range(match.range(withName: "minutes"), in: string), + let minutes = Int(string[matchRange].replacingOccurrences(of: "m", with: "")) { + seconds += (minutes * 60) + } + + if let matchRange = Range(match.range(withName: "seconds"), in: string), + let second = Int(string[matchRange].replacingOccurrences(of: "s", with: "")) { + seconds += (second) + } + + self.estimatedTimeRemaining = TimeInterval(seconds) + } + + } +} + diff --git a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift index 88f0b05..c63ea21 100644 --- a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift +++ b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift @@ -27,7 +27,7 @@ public struct ObservingProgressIndicator: View { init(progress: Progress) { self.progress = progress cancellable = progress - .publisher(for: \.fractionCompleted) + .publisher(for: \.completedUnitCount) .receive(on: RunLoop.main) .sink { [weak self] _ in self?.objectWillChange.send() } } diff --git a/Xcodes/Frontend/XcodeList/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift similarity index 88% rename from Xcodes/Frontend/XcodeList/InfoPane.swift rename to Xcodes/Frontend/InfoPane/InfoPane.swift index 53d5088..0a8cf03 100644 --- a/Xcodes/Frontend/XcodeList/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -51,13 +51,17 @@ struct InfoPane: View { } Divider() - - releaseNotes(for: xcode) - identicalBuilds(for: xcode) - compatibility(for: xcode) - sdks(for: xcode) - compilers(for: xcode) - downloadFileSize(for: xcode) + Group{ + releaseNotes(for: xcode) + identicalBuilds(for: xcode) + compatibility(for: xcode) + sdks(for: xcode) + compilers(for: xcode) + } + Group { + downloadFileSize(for: xcode) + downloadStats(for: xcode) + } Spacer() } @@ -210,6 +214,19 @@ struct InfoPane: View { } } + @ViewBuilder + private func downloadStats(for xcode: Xcode) -> some View { + switch xcode.installState { + case let .installing(installationStep): + Divider() + InstallationStepDetailView( + installationStep: installationStep + ) + default: + EmptyView() + } + + } @ViewBuilder private var empty: some View { @@ -252,7 +269,7 @@ struct InfoPane_Previews: PreviewProvider { ] }) .previewDisplayName("Populated, Installed, Selected") - + InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 0)) .environmentObject(configure(AppState()) { $0.allXcodes = [ @@ -278,7 +295,7 @@ struct InfoPane_Previews: PreviewProvider { ] }) .previewDisplayName("Populated, Installed, Unselected") - + InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 0)) .environmentObject(configure(AppState()) { $0.allXcodes = [ @@ -304,7 +321,7 @@ struct InfoPane_Previews: PreviewProvider { ] }) .previewDisplayName("Populated, Uninstalled") - + InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"])) .environmentObject(configure(AppState()) { $0.allXcodes = [ @@ -318,6 +335,20 @@ struct InfoPane_Previews: PreviewProvider { ] }) .previewDisplayName("Basic, installed") + + InfoPane(selectedXcodeID: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"])) + .environmentObject(configure(AppState()) { + $0.allXcodes = [ + .init( + version: Version(major: 12, minor: 3, patch: 1, buildMetadataIdentifiers: ["1234A"]), + installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 232323232; $0.fileCompletedCount = 2323004; $0.fileTotalCount = 1193939393 })), + selected: false, + icon: nil, + sdks: nil, + compilers: nil) + ] + }) + .previewDisplayName("Basic, installing") InfoPane(selectedXcodeID: nil) .environmentObject(configure(AppState()) { diff --git a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift new file mode 100644 index 0000000..825fe08 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift @@ -0,0 +1,35 @@ +import SwiftUI + +struct InstallationStepDetailView: View { + let installationStep: InstallationStep + + var body: some View { + VStack { + switch installationStep { + case let .downloading(progress): + Text("Step \(installationStep.stepNumber) of \(installationStep.stepCount): \(installationStep.message)") + .font(.title2) + ObservingDownloadStatsView( + progress, + controlSize: .regular, + style: .bar + ) + + case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing: + ProgressView() + .scaleEffect(0.5) + } + } + .frame(minWidth: 80) + } +} + +struct InstallDetailView_Previews: PreviewProvider { + static var previews: some View { + InstallationStepDetailView( + installationStep: .downloading( + progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 9211681; $0.fileCompletedCount = 84844492; $0.fileTotalCount = 11944848484 } + ) + ) + } +} diff --git a/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift b/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift new file mode 100644 index 0000000..d14e525 --- /dev/null +++ b/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift @@ -0,0 +1,77 @@ + +import Combine +import SwiftUI + +/// A ProgressIndicator that reflects the state of a Progress object. +/// This functionality is already built in to ProgressView, +/// but this implementation ensures that changes are received on the main thread. +@available(iOS 14.0, macOS 11.0, *) +public struct ObservingDownloadStatsView: View { + let controlSize: NSControl.ControlSize + let style: NSProgressIndicator.Style + @StateObject private var progress: ProgressWrapper + + public init( + _ progress: Progress, + controlSize: NSControl.ControlSize, + style: NSProgressIndicator.Style + ) { + _progress = StateObject(wrappedValue: ProgressWrapper(progress: progress)) + self.controlSize = controlSize + self.style = style + } + + class ProgressWrapper: ObservableObject { + var progress: Progress + var cancellable: AnyCancellable! + + init(progress: Progress) { + self.progress = progress + cancellable = progress + .publisher(for: \.completedUnitCount) + .receive(on: RunLoop.main) + .sink { [weak self] _ in self?.objectWillChange.send() } + } + } + + public var body: some View { + + VStack{ + ProgressIndicator( + minValue: 0.0, + maxValue: 1.0, + doubleValue: progress.progress.fractionCompleted, + controlSize: controlSize, + isIndeterminate: progress.progress.isIndeterminate, + style: style + ) + .help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete") + HStack { + if let fileCompletedCount = progress.progress.fileCompletedCount, let fileTotalCount = progress.progress.fileTotalCount { + Text("\(ByteCountFormatter.string(fromByteCount: Int64(fileCompletedCount), countStyle: .file)) of \(ByteCountFormatter.string(fromByteCount: Int64(fileTotalCount), countStyle: .file))") + } + if let throughput = progress.progress.throughput { + Text(" at \(ByteCountFormatter.string(fromByteCount: Int64(throughput), countStyle: .binary))/sec") + } + } + } + + + } +} + +@available(iOS 14.0, macOS 11.0, *) +struct ObservingDownloadStats_Previews: PreviewProvider { + static var previews: some View { + Group { + ObservingDownloadStatsView( + configure(Progress(totalUnitCount: 100)) { + $0.completedUnitCount = 40 + }, + controlSize: .small, + style: .spinning + ) + } + .previewLayout(.sizeThatFits) + } +} diff --git a/Xcodes/Frontend/XcodeList/InstallationStepView.swift b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift similarity index 91% rename from Xcodes/Frontend/XcodeList/InstallationStepView.swift rename to Xcodes/Frontend/XcodeList/InstallationStepRowView.swift index 42d3f25..92d3c2d 100644 --- a/Xcodes/Frontend/XcodeList/InstallationStepView.swift +++ b/Xcodes/Frontend/XcodeList/InstallationStepRowView.swift @@ -1,6 +1,6 @@ import SwiftUI -struct InstallationStepView: View { +struct InstallationStepRowView: View { let installationStep: InstallationStep let highlighted: Bool let cancel: () -> Void @@ -42,7 +42,7 @@ struct InstallView_Previews: PreviewProvider { Group { ForEach(ColorScheme.allCases, id: \.self) { colorScheme in Group { - InstallationStepView( + InstallationStepRowView( installationStep: .downloading( progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 } ), @@ -50,31 +50,31 @@ struct InstallView_Previews: PreviewProvider { cancel: {} ) - InstallationStepView( + InstallationStepRowView( installationStep: .unarchiving, highlighted: false, cancel: {} ) - InstallationStepView( + InstallationStepRowView( installationStep: .moving(destination: "/Applications"), highlighted: false, cancel: {} ) - InstallationStepView( + InstallationStepRowView( installationStep: .trashingArchive, highlighted: false, cancel: {} ) - InstallationStepView( + InstallationStepRowView( installationStep: .checkingSecurity, highlighted: false, cancel: {} ) - InstallationStepView( + InstallationStepRowView( installationStep: .finishing, highlighted: false, cancel: {} @@ -87,7 +87,7 @@ struct InstallView_Previews: PreviewProvider { ForEach(ColorScheme.allCases, id: \.self) { colorScheme in Group { - InstallationStepView( + InstallationStepRowView( installationStep: .downloading( progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 } ), diff --git a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift index 723d690..9b0c645 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListViewRow.swift @@ -109,7 +109,7 @@ struct XcodeListViewRow: View { .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: selected)) .help("Install this version") case let .installing(installationStep): - InstallationStepView( + InstallationStepRowView( installationStep: installationStep, highlighted: selected, cancel: { appState.xcodeBeingConfirmedForInstallCancellation = xcode } From e638547900a06357eaa9c2800d7b710a900ed63c Mon Sep 17 00:00:00 2001 From: Matt Kiazyk Date: Mon, 15 Feb 2021 13:14:52 -0600 Subject: [PATCH 2/8] add Percent text to bar --- .../InfoPane/ObservingDownloadStatsView.swift | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift b/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift index d14e525..f165fb0 100644 --- a/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift +++ b/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift @@ -37,15 +37,18 @@ public struct ObservingDownloadStatsView: View { public var body: some View { VStack{ - ProgressIndicator( - minValue: 0.0, - maxValue: 1.0, - doubleValue: progress.progress.fractionCompleted, - controlSize: controlSize, - isIndeterminate: progress.progress.isIndeterminate, - style: style - ) - .help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete") + HStack { + ProgressIndicator( + minValue: 0.0, + maxValue: 1.0, + doubleValue: progress.progress.fractionCompleted, + controlSize: controlSize, + isIndeterminate: progress.progress.isIndeterminate, + style: style + ) + .help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete") + Text("\(Int((progress.progress.fractionCompleted * 100)))%") + } HStack { if let fileCompletedCount = progress.progress.fileCompletedCount, let fileTotalCount = progress.progress.fileTotalCount { Text("\(ByteCountFormatter.string(fromByteCount: Int64(fileCompletedCount), countStyle: .file)) of \(ByteCountFormatter.string(fromByteCount: Int64(fileTotalCount), countStyle: .file))") @@ -66,10 +69,10 @@ struct ObservingDownloadStats_Previews: PreviewProvider { Group { ObservingDownloadStatsView( configure(Progress(totalUnitCount: 100)) { - $0.completedUnitCount = 40 + $0.completedUnitCount = 40; $0.throughput = 9211681; $0.fileCompletedCount = 84844492; $0.fileTotalCount = 11944848484 }, controlSize: .small, - style: .spinning + style: .bar ) } .previewLayout(.sizeThatFits) From 1469dfa56b574394008ff572e11ce6e873077b59 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 18 Feb 2021 19:10:12 -0700 Subject: [PATCH 3/8] Replace ObservingDownloadStatsView with ObservingProgressIndicator This more closely replicates the default look and feel of SwiftUI.ProgressView, but with explicit control over whether localizedAdditionalDescription is shown and without the label above the progress view that displays a fileOperationKind string. --- Xcodes.xcodeproj/project.pbxproj | 4 - .../Common/ObservingProgressIndicator.swift | 51 +++++++++--- .../InfoPane/InstallationStepDetailView.swift | 5 +- .../InfoPane/ObservingDownloadStatsView.swift | 80 ------------------- 4 files changed, 41 insertions(+), 99 deletions(-) delete mode 100644 Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 1c21fa9..c7d1b70 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -102,7 +102,6 @@ E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; }; E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; }; E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; }; - E8F0838625D873A400A4C470 /* ObservingDownloadStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -265,7 +264,6 @@ E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = ""; }; E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = ""; }; - E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingDownloadStatsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -557,7 +555,6 @@ children = ( CAFBDC67259A308B003DCC5A /* InfoPane.swift */, E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */, - E8F0838525D873A400A4C470 /* ObservingDownloadStatsView.swift */, ); path = InfoPane; sourceTree = ""; @@ -797,7 +794,6 @@ CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */, CAFBDB952598FE96003DCC5A /* FocusedValues.swift in Sources */, CAC9F92D25BCDA4400B4965F /* HelperInstallState.swift in Sources */, - E8F0838625D873A400A4C470 /* ObservingDownloadStatsView.swift in Sources */, E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */, CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */, CABFA9C22592EEEA00380FEE /* Publisher+Resumable.swift in Sources */, diff --git a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift index c63ea21..d4dc428 100644 --- a/Xcodes/Frontend/Common/ObservingProgressIndicator.swift +++ b/Xcodes/Frontend/Common/ObservingProgressIndicator.swift @@ -8,16 +8,19 @@ import SwiftUI public struct ObservingProgressIndicator: View { let controlSize: NSControl.ControlSize let style: NSProgressIndicator.Style + let showsAdditionalDescription: Bool @StateObject private var progress: ProgressWrapper public init( _ progress: Progress, controlSize: NSControl.ControlSize, - style: NSProgressIndicator.Style + style: NSProgressIndicator.Style, + showsAdditionalDescription: Bool = false ) { _progress = StateObject(wrappedValue: ProgressWrapper(progress: progress)) self.controlSize = controlSize self.style = style + self.showsAdditionalDescription = showsAdditionalDescription } class ProgressWrapper: ObservableObject { @@ -26,23 +29,31 @@ public struct ObservingProgressIndicator: View { init(progress: Progress) { self.progress = progress - cancellable = progress - .publisher(for: \.completedUnitCount) - .receive(on: RunLoop.main) + cancellable = progress.publisher(for: \.fractionCompleted) + .combineLatest(progress.publisher(for: \.localizedAdditionalDescription)) + .throttle(for: 1.0, scheduler: DispatchQueue.main, latest: true) .sink { [weak self] _ in self?.objectWillChange.send() } } } public var body: some View { - ProgressIndicator( - minValue: 0.0, - maxValue: 1.0, - doubleValue: progress.progress.fractionCompleted, - controlSize: controlSize, - isIndeterminate: progress.progress.isIndeterminate, - style: style - ) - .help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete") + VStack(alignment: .leading, spacing: 0) { + ProgressIndicator( + minValue: 0.0, + maxValue: 1.0, + doubleValue: progress.progress.fractionCompleted, + controlSize: controlSize, + isIndeterminate: progress.progress.isIndeterminate, + style: style + ) + .help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete") + + if showsAdditionalDescription, progress.progress.localizedAdditionalDescription.isEmpty == false { + Text(progress.progress.localizedAdditionalDescription) + .font(.subheadline) + .foregroundColor(.secondary) + } + } } } @@ -57,6 +68,20 @@ struct ObservingProgressBar_Previews: PreviewProvider { controlSize: .small, style: .spinning ) + + ObservingProgressIndicator( + configure(Progress()) { + $0.kind = .file + $0.fileOperationKind = .downloading + $0.estimatedTimeRemaining = 123 + $0.totalUnitCount = 11944848484 + $0.completedUnitCount = 848444920 + $0.throughput = 9211681 + }, + controlSize: .regular, + style: .bar, + showsAdditionalDescription: true + ) } .previewLayout(.sizeThatFits) } diff --git a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift index 825fe08..e7fd41a 100644 --- a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift +++ b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift @@ -9,10 +9,11 @@ struct InstallationStepDetailView: View { case let .downloading(progress): Text("Step \(installationStep.stepNumber) of \(installationStep.stepCount): \(installationStep.message)") .font(.title2) - ObservingDownloadStatsView( + ObservingProgressIndicator( progress, controlSize: .regular, - style: .bar + style: .bar, + showsAdditionalDescription: true ) case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing: diff --git a/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift b/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift deleted file mode 100644 index f165fb0..0000000 --- a/Xcodes/Frontend/InfoPane/ObservingDownloadStatsView.swift +++ /dev/null @@ -1,80 +0,0 @@ - -import Combine -import SwiftUI - -/// A ProgressIndicator that reflects the state of a Progress object. -/// This functionality is already built in to ProgressView, -/// but this implementation ensures that changes are received on the main thread. -@available(iOS 14.0, macOS 11.0, *) -public struct ObservingDownloadStatsView: View { - let controlSize: NSControl.ControlSize - let style: NSProgressIndicator.Style - @StateObject private var progress: ProgressWrapper - - public init( - _ progress: Progress, - controlSize: NSControl.ControlSize, - style: NSProgressIndicator.Style - ) { - _progress = StateObject(wrappedValue: ProgressWrapper(progress: progress)) - self.controlSize = controlSize - self.style = style - } - - class ProgressWrapper: ObservableObject { - var progress: Progress - var cancellable: AnyCancellable! - - init(progress: Progress) { - self.progress = progress - cancellable = progress - .publisher(for: \.completedUnitCount) - .receive(on: RunLoop.main) - .sink { [weak self] _ in self?.objectWillChange.send() } - } - } - - public var body: some View { - - VStack{ - HStack { - ProgressIndicator( - minValue: 0.0, - maxValue: 1.0, - doubleValue: progress.progress.fractionCompleted, - controlSize: controlSize, - isIndeterminate: progress.progress.isIndeterminate, - style: style - ) - .help("Downloading: \(Int((progress.progress.fractionCompleted * 100)))% complete") - Text("\(Int((progress.progress.fractionCompleted * 100)))%") - } - HStack { - if let fileCompletedCount = progress.progress.fileCompletedCount, let fileTotalCount = progress.progress.fileTotalCount { - Text("\(ByteCountFormatter.string(fromByteCount: Int64(fileCompletedCount), countStyle: .file)) of \(ByteCountFormatter.string(fromByteCount: Int64(fileTotalCount), countStyle: .file))") - } - if let throughput = progress.progress.throughput { - Text(" at \(ByteCountFormatter.string(fromByteCount: Int64(throughput), countStyle: .binary))/sec") - } - } - } - - - } -} - -@available(iOS 14.0, macOS 11.0, *) -struct ObservingDownloadStats_Previews: PreviewProvider { - static var previews: some View { - Group { - ObservingDownloadStatsView( - configure(Progress(totalUnitCount: 100)) { - $0.completedUnitCount = 40; $0.throughput = 9211681; $0.fileCompletedCount = 84844492; $0.fileTotalCount = 11944848484 - }, - controlSize: .small, - style: .bar - ) - } - .previewLayout(.sizeThatFits) - } -} From d00a356c295f9ae1264c10d4f33d022e5d8035f9 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 18 Feb 2021 19:14:06 -0700 Subject: [PATCH 4/8] Show installation step description for all steps in InstallationStepDetailView --- .../InfoPane/InstallationStepDetailView.swift | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift index e7fd41a..a1992d5 100644 --- a/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift +++ b/Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift @@ -4,11 +4,11 @@ struct InstallationStepDetailView: View { let installationStep: InstallationStep var body: some View { - VStack { + VStack(alignment: .leading, spacing: 0) { + Text("Step \(installationStep.stepNumber) of \(installationStep.stepCount): \(installationStep.message)") + switch installationStep { case let .downloading(progress): - Text("Step \(installationStep.stepNumber) of \(installationStep.stepCount): \(installationStep.message)") - .font(.title2) ObservingProgressIndicator( progress, controlSize: .regular, @@ -21,16 +21,28 @@ struct InstallationStepDetailView: View { .scaleEffect(0.5) } } - .frame(minWidth: 80) } } struct InstallDetailView_Previews: PreviewProvider { static var previews: some View { - InstallationStepDetailView( - installationStep: .downloading( - progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40; $0.throughput = 9211681; $0.fileCompletedCount = 84844492; $0.fileTotalCount = 11944848484 } + Group { + InstallationStepDetailView( + installationStep: .downloading( + progress: configure(Progress()) { + $0.kind = .file + $0.fileOperationKind = .downloading + $0.estimatedTimeRemaining = 123 + $0.totalUnitCount = 11944848484 + $0.completedUnitCount = 848444920 + $0.throughput = 9211681 + } + ) ) - ) + + InstallationStepDetailView( + installationStep: .unarchiving + ) + } } } From e301ad881861135a9fa45bfefe3aabaf458b0850 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 18 Feb 2021 19:14:44 -0700 Subject: [PATCH 5/8] Configure aria2c Progress better Using .file kind and .downloading file operation kind will produce a more appropriate localizedAdditionalDescription. The fileTotalCount and fileCompletedCount aren't appropriate for this case, because there's only one file being downloaded. --- Xcodes/Backend/Environment.swift | 4 +++- Xcodes/Backend/Progress+.swift | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index 71a753d..1093aa4 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -53,7 +53,9 @@ public struct Shell { let stdErrPipe = Pipe() process.standardError = stdErrPipe - var progress = Progress(totalUnitCount: 100) + var progress = Progress() + progress.kind = .file + progress.fileOperationKind = .downloading let observer = NotificationCenter.default.addObserver( forName: .NSFileHandleDataAvailable, diff --git a/Xcodes/Backend/Progress+.swift b/Xcodes/Backend/Progress+.swift index 0998369..61d26e2 100644 --- a/Xcodes/Backend/Progress+.swift +++ b/Xcodes/Backend/Progress+.swift @@ -12,7 +12,6 @@ extension Progress { if let match = regexTotalDownloaded.firstMatch(in: string, options: [], range: range), let matchRange = Range(match.range(at: 0), in: string), let totalDownloaded = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) { - self.fileCompletedCount = totalDownloaded self.completedUnitCount = Int64(totalDownloaded) } @@ -24,7 +23,6 @@ extension Progress { let totalFileSize = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) { if totalFileSize > 0 { - self.fileTotalCount = totalFileSize self.totalUnitCount = Int64(totalFileSize) } } From 899eec7dae0146dc4af145d7dd8fe64998703d4d Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 18 Feb 2021 19:16:08 -0700 Subject: [PATCH 6/8] Don't attempt to parse days from aria2c ETA I had initially suggested this but then went and checked in the source and the largest unit in the format is hours. https://github.com/aria2/aria2/blob/52da4e40ea22d1db13c1eadeac5baed4e8fd5dd8/src/util.cc#L518-L534 --- Xcodes/Backend/Progress+.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Xcodes/Backend/Progress+.swift b/Xcodes/Backend/Progress+.swift index 61d26e2..ad15f1c 100644 --- a/Xcodes/Backend/Progress+.swift +++ b/Xcodes/Backend/Progress+.swift @@ -43,16 +43,11 @@ extension Progress { } // MARK: Estimated Time Remaining - let regexETA = try! NSRegularExpression(pattern: #"(?<=ETA:)(?\d*d)?(?\d*h)?(?\d*m)?(?\d*s)?"#) + let regexETA = try! NSRegularExpression(pattern: #"(?<=ETA:)(?\d*h)?(?\d*m)?(?\d*s)?"#) if let match = regexETA.firstMatch(in: string, options: [], range: range) { var seconds: Int = 0 - if let matchRange = Range(match.range(withName: "days"), in: string), - let days = Int(string[matchRange].replacingOccurrences(of: "d", with: "")) { - seconds += (days * 60 * 60 * 24) - } - if let matchRange = Range(match.range(withName: "hours"), in: string), let hours = Int(string[matchRange].replacingOccurrences(of: "h", with: "")) { seconds += (hours * 60 * 60) From 2f04d23eac9144ac39c578b48e2ea61eb2ad989c Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 18 Feb 2021 19:19:30 -0700 Subject: [PATCH 7/8] Use Logger for Progress.updateFromAria2 --- Xcodes/Backend/Progress+.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Xcodes/Backend/Progress+.swift b/Xcodes/Backend/Progress+.swift index ad15f1c..b71b994 100644 --- a/Xcodes/Backend/Progress+.swift +++ b/Xcodes/Backend/Progress+.swift @@ -1,4 +1,4 @@ - +import os.log import Foundation extension Progress { @@ -39,7 +39,7 @@ extension Progress { let speed = Int(string[matchRange].replacingOccurrences(of: "B", with: "")) { self.throughput = speed } else { - print("Could not find speed") + Logger.appState.debug("Could not parse throughput from aria2 download output") } // MARK: Estimated Time Remaining From 22894e2d6dd9402b5424d01da7246fbc88af1b5b Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 18 Feb 2021 19:20:08 -0700 Subject: [PATCH 8/8] Move installation step beside cancel button in info pane --- Xcodes/Frontend/InfoPane/InfoPane.swift | 80 ++++++++++--------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/Xcodes/Frontend/InfoPane/InfoPane.swift b/Xcodes/Frontend/InfoPane/InfoPane.swift index 0a8cf03..93b7627 100644 --- a/Xcodes/Frontend/InfoPane/InfoPane.swift +++ b/Xcodes/Frontend/InfoPane/InfoPane.swift @@ -13,44 +13,46 @@ struct InfoPane: View { var body: some View { if let xcode = appState.allXcodes.first(where: { $0.id == selectedXcodeID }) { ScrollView { - VStack(spacing: 16) { + VStack(alignment: .leading, spacing: 16) { icon(for: xcode) + .frame(maxWidth: .infinity, alignment: .center) + + Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") + .font(.title) - VStack(alignment: .leading) { - Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") - .font(.title) - .frame(maxWidth: .infinity, alignment: .leading) + switch xcode.installState { + case .notInstalled: + InstallButton(xcode: xcode) + downloadFileSize(for: xcode) + case .installing(let installationStep): + InstallationStepDetailView(installationStep: installationStep) + .fixedSize(horizontal: false, vertical: true) + CancelInstallButton(xcode: xcode) + case let .installed(path): + HStack { + Text(path.string) + Button(action: { appState.reveal(id: xcode.id) }) { + Image(systemName: "arrow.right.circle.fill") + } + .buttonStyle(PlainButtonStyle()) + .help("Reveal in Finder") + } - switch xcode.installState { - case .notInstalled: - InstallButton(xcode: xcode) - case .installing: - CancelInstallButton(xcode: xcode) - case let .installed(path): - HStack { - Text(path.string) - Button(action: { appState.reveal(id: xcode.id) }) { - Image(systemName: "arrow.right.circle.fill") - } - .buttonStyle(PlainButtonStyle()) - .help("Reveal in Finder") - } + HStack { + SelectButton(xcode: xcode) + .disabled(xcode.selected) + .help("Selected") - HStack { - SelectButton(xcode: xcode) - .disabled(xcode.selected) - .help("Selected") - - OpenButton(xcode: xcode) - .help("Open") - - Spacer() - UninstallButton(xcode: xcode) - } + OpenButton(xcode: xcode) + .help("Open") + + Spacer() + UninstallButton(xcode: xcode) } } Divider() + Group{ releaseNotes(for: xcode) identicalBuilds(for: xcode) @@ -58,10 +60,6 @@ struct InfoPane: View { sdks(for: xcode) compilers(for: xcode) } - Group { - downloadFileSize(for: xcode) - downloadStats(for: xcode) - } Spacer() } @@ -214,20 +212,6 @@ struct InfoPane: View { } } - @ViewBuilder - private func downloadStats(for xcode: Xcode) -> some View { - switch xcode.installState { - case let .installing(installationStep): - Divider() - InstallationStepDetailView( - installationStep: installationStep - ) - default: - EmptyView() - } - - } - @ViewBuilder private var empty: some View { Text("No Xcode Selected")