From 1e436bce837d60eed2c3036f41dbe06d6ee37a42 Mon Sep 17 00:00:00 2001 From: Lukas Pistrol Date: Tue, 15 Oct 2024 10:09:29 +0200 Subject: [PATCH] improve current state parsing, don't crash when state is unknown --- TimeMachineStatus.xcodeproj/project.pbxproj | 8 +++ TimeMachineStatus/Localizable.xcstrings | 16 ++++++ .../Model/BackupState/BackupState.swift | 21 +++++-- .../Model/BackupState/Copying.swift | 57 +++++++++++++++++++ .../BackupState/FindingBackupVolume.swift | 20 +++++++ .../Model/BackupState/FindingChanges.swift | 37 ++++++++++++ .../Model/BackupState/Finishing.swift | 26 +++++++++ .../Model/BackupState/Idle.swift | 9 +++ .../BackupState/Internal/_BaseState.swift | 11 ++++ .../Internal/_InProgressState.swift | 12 ++++ .../Model/BackupState/Internal/_State.swift | 15 +++-- .../Model/BackupState/Mounting.swift | 20 +++++++ .../Model/BackupState/Preparing.swift | 26 +++++++++ .../Model/BackupState/Starting.swift | 16 ++++++ .../Model/BackupState/Stopping.swift | 26 +++++++++ .../Model/BackupState/Thinning.swift | 26 +++++++++ .../Model/BackupState/Unknown.swift | 44 ++++++++++++++ TimeMachineStatus/Views/MenuView.swift | 46 ++++++++++++++- TimeMachineStatus/Views/StatusBarItem.swift | 17 +++++- 19 files changed, 440 insertions(+), 13 deletions(-) create mode 100644 TimeMachineStatus/Model/BackupState/Unknown.swift diff --git a/TimeMachineStatus.xcodeproj/project.pbxproj b/TimeMachineStatus.xcodeproj/project.pbxproj index d70d1c2..523adf5 100644 --- a/TimeMachineStatus.xcodeproj/project.pbxproj +++ b/TimeMachineStatus.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 2818227A2AFE292A0067E564 /* UserfacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 281822792AFE292A0067E564 /* UserfacingError.swift */; }; 2818227C2AFE33CA0067E564 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2818227B2AFE33CA0067E564 /* Constants.swift */; }; 2818227E2AFE36780067E564 /* Bundle+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2818227D2AFE36780067E564 /* Bundle+.swift */; }; + 281C819E2CBC887D00CDFED6 /* HiddenViewModifier+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 281C819D2CBC887D00CDFED6 /* HiddenViewModifier+.swift */; }; 28263F912B023D4E00F74655 /* HelperAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28263F902B023D4E00F74655 /* HelperAppDelegate.swift */; }; 28263FA32B023FFE00F74655 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28263FA22B023FFE00F74655 /* ServiceManagement.framework */; }; 28710D2F2CAF16AF00033855 /* Timer+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28710D2E2CAF16AF00033855 /* Timer+Sendable.swift */; }; @@ -58,6 +59,7 @@ 28F2D4412B0257E700B75998 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2818227B2AFE33CA0067E564 /* Constants.swift */; }; 28F2D4452B02F2F300B75998 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 28F2D4442B02F2F300B75998 /* Sparkle */; }; 28F2D4472B02F62400B75998 /* UpdaterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F2D4462B02F62400B75998 /* UpdaterViewModel.swift */; }; + 28F7A65E2CBE5323000158D5 /* Unknown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F7A65D2CBE5323000158D5 /* Unknown.swift */; }; 28FAD5A72AFE3DBF00F642E7 /* CardViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FAD5A62AFE3DBF00F642E7 /* CardViewModifier.swift */; }; 28FAD5A92AFE9BCB00F642E7 /* DestinationInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FAD5A82AFE9BCB00F642E7 /* DestinationInfoView.swift */; }; 28FAD5AB2AFE9BF300F642E7 /* CustomLabeledContentStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FAD5AA2AFE9BF300F642E7 /* CustomLabeledContentStyle.swift */; }; @@ -101,6 +103,7 @@ 281822792AFE292A0067E564 /* UserfacingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserfacingError.swift; sourceTree = ""; }; 2818227B2AFE33CA0067E564 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 2818227D2AFE36780067E564 /* Bundle+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+.swift"; sourceTree = ""; }; + 281C819D2CBC887D00CDFED6 /* HiddenViewModifier+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HiddenViewModifier+.swift"; sourceTree = ""; }; 28263F902B023D4E00F74655 /* HelperAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperAppDelegate.swift; sourceTree = ""; }; 28263FA22B023FFE00F74655 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 28263FA42B0241E200F74655 /* TimeMachineStatusHelper.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = TimeMachineStatusHelper.entitlements; sourceTree = ""; }; @@ -131,6 +134,7 @@ 28F2D43F2B02560400B75998 /* LaunchItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchItemProvider.swift; sourceTree = ""; }; 28F2D4462B02F62400B75998 /* UpdaterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdaterViewModel.swift; sourceTree = ""; }; 28F2D4482B03000900B75998 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 28F7A65D2CBE5323000158D5 /* Unknown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unknown.swift; sourceTree = ""; }; 28FAD5A62AFE3DBF00F642E7 /* CardViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardViewModifier.swift; sourceTree = ""; }; 28FAD5A82AFE9BCB00F642E7 /* DestinationInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationInfoView.swift; sourceTree = ""; }; 28FAD5AA2AFE9BF300F642E7 /* CustomLabeledContentStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLabeledContentStyle.swift; sourceTree = ""; }; @@ -277,6 +281,7 @@ 281822662AFD86AC0067E564 /* Color+RawRepresentable.swift */, 2888D17B2C99B3E80081FBBB /* KeyedDecodingContainer+.swift */, 28710D2E2CAF16AF00033855 /* Timer+Sendable.swift */, + 281C819D2CBC887D00CDFED6 /* HiddenViewModifier+.swift */, ); path = Extensions; sourceTree = ""; @@ -315,6 +320,7 @@ 281822682AFD8E5A0067E564 /* Internal */, 28A002542AFC03E300E2A01E /* BackupState.swift */, 28A0025B2AFC04B300E2A01E /* Idle.swift */, + 28F7A65D2CBE5323000158D5 /* Unknown.swift */, 2818225C2AFD3FB80067E564 /* FindingBackupVolume.swift */, 28A0026F2AFC342100E2A01E /* Starting.swift */, 28A0025D2AFC04D300E2A01E /* Mounting.swift */, @@ -534,7 +540,9 @@ 28F2D4472B02F62400B75998 /* UpdaterViewModel.swift in Sources */, 28A0026E2AFC312000E2A01E /* Finishing.swift in Sources */, 28A0026C2AFC0CC700E2A01E /* Preferences.swift in Sources */, + 28F7A65E2CBE5323000158D5 /* Unknown.swift in Sources */, 281822512AFCF8750067E564 /* DestinationCell.swift in Sources */, + 281C819E2CBC887D00CDFED6 /* HiddenViewModifier+.swift in Sources */, 28A002552AFC03E300E2A01E /* BackupState.swift in Sources */, 281822592AFCFAE50067E564 /* AppDelegate.swift in Sources */, ); diff --git a/TimeMachineStatus/Localizable.xcstrings b/TimeMachineStatus/Localizable.xcstrings index 7d5795d..234ad11 100644 --- a/TimeMachineStatus/Localizable.xcstrings +++ b/TimeMachineStatus/Localizable.xcstrings @@ -425,6 +425,22 @@ } } }, + "D" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "D" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "D" + } + } + } + }, "dest_info_encrypted" : { "extractionState" : "manual", "localizations" : { diff --git a/TimeMachineStatus/Model/BackupState/BackupState.swift b/TimeMachineStatus/Model/BackupState/BackupState.swift index 8ea1ef7..5a1d905 100644 --- a/TimeMachineStatus/Model/BackupState/BackupState.swift +++ b/TimeMachineStatus/Model/BackupState/BackupState.swift @@ -25,10 +25,15 @@ enum BackupState { throw BackupStateError.couldNotConvertStringToData(string: result) } - if let state = try _decodePlist(data) { + if let state = try? _decodePlist(data, raw: result) { return state } + #if DEBUG + log.error("Unknown state: \(result)") + fatalError("Unknown state: \(result)") + #else throw BackupStateError.invalidState(raw: result) + #endif } } @@ -42,7 +47,7 @@ extension BackupState { }() // swiftlint:disable:next cyclomatic_complexity - private static func _decodePlist(_ data: Data) throws -> _State? { + private static func _decodePlist(_ data: Data, raw: String) throws -> _State { let decoder = PropertyListDecoder() let state = try decoder.decode(BackupState._State.self, from: data) switch state.state { @@ -66,8 +71,16 @@ extension BackupState { return try decoder.decode(BackupState.Stopping.self, from: data) case ._thinning: return try decoder.decode(BackupState.Thinning.self, from: data) - case ._unknown: - return nil + case ._unknown(let state): + #if DEBUG + throw BackupStateError.invalidState(raw: raw) + #else + return BackupState.Unknown(title: state, raw: raw) + #endif } } } + +extension BackupState._State { + enum Mock {} +} diff --git a/TimeMachineStatus/Model/BackupState/Copying.swift b/TimeMachineStatus/Model/BackupState/Copying.swift index 953d7d6..bef49ef 100644 --- a/TimeMachineStatus/Model/BackupState/Copying.swift +++ b/TimeMachineStatus/Model/BackupState/Copying.swift @@ -33,6 +33,26 @@ extension BackupState { try super.init(from: decoder) } + fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int, + stateChange: Date?, + destinationMountPoint: String, + fractionOfProgressBar: Double, + progress: Progress + ) { + self.fractionOfProgressBar = fractionOfProgressBar + self.progress = progress + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions, + stateChange: stateChange, + destinationMountPoint: destinationMountPoint + ) + } + let fractionOfProgressBar: Double let progress: Progress @@ -72,6 +92,22 @@ extension BackupState.Copying { self.totalFiles = Int(totalFilesString ?? "") } + init( + percent: Double?, + timeRemaining: String?, + bytes: Int?, + files: Int?, + totalBytes: Int?, + totalFiles: Int? + ) { + self.percent = percent + self.timeRemaining = timeRemaining + self.bytes = bytes + self.files = files + self.totalBytes = totalBytes + self.totalFiles = totalFiles + } + let percent: Double? let timeRemaining: String? let bytes: Int? @@ -80,3 +116,24 @@ extension BackupState.Copying { let totalFiles: Int? } } + +extension BackupState._State.Mock { + static func copying(_ id: UUID = UUID()) -> BackupState.Copying { + .init( + clientID: "1234", + destinationID: id, + attemptOptions: 0, + stateChange: Date(), + destinationMountPoint: "/Volumes/BackupDrive", + fractionOfProgressBar: 0.5, + progress: BackupState.Copying.Progress( + percent: 50, + timeRemaining: "2 hours", + bytes: 1_024, + files: 10, + totalBytes: 2_048, + totalFiles: 20 + ) + ) + } +} diff --git a/TimeMachineStatus/Model/BackupState/FindingBackupVolume.swift b/TimeMachineStatus/Model/BackupState/FindingBackupVolume.swift index 34f086c..3fd6cf5 100644 --- a/TimeMachineStatus/Model/BackupState/FindingBackupVolume.swift +++ b/TimeMachineStatus/Model/BackupState/FindingBackupVolume.swift @@ -18,6 +18,18 @@ extension BackupState { try super.init(from: decoder) } + override fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int + ) { + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions + ) + } + override var statusString: String { "Finding Backup Volume" } @@ -27,3 +39,11 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static let findingBackupVolume = BackupState.FindingBackupVolume( + clientID: "1234", + destinationID: UUID(uuidString: "1234")!, // swiftlint:disable:this force_unwrapping + attemptOptions: 0 + ) +} diff --git a/TimeMachineStatus/Model/BackupState/FindingChanges.swift b/TimeMachineStatus/Model/BackupState/FindingChanges.swift index fd7fb41..6cc7470 100644 --- a/TimeMachineStatus/Model/BackupState/FindingChanges.swift +++ b/TimeMachineStatus/Model/BackupState/FindingChanges.swift @@ -31,6 +31,28 @@ extension BackupState { try super.init(from: decoder) } + fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int, + stateChange: Date?, + destinationMountPoint: String, + itemsFound: Int, + fractionDone: Double, + fractionOfProgressBar: Double + ) { + self.itemsFound = itemsFound + self.fractionDone = fractionDone + self.fractionOfProgressBar = fractionOfProgressBar + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions, + stateChange: stateChange, + destinationMountPoint: destinationMountPoint + ) + } + let itemsFound: Int let fractionDone: Double let fractionOfProgressBar: Double @@ -44,3 +66,18 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static func findingChanges(_ id: UUID = UUID()) -> BackupState.FindingChanges { + .init( + clientID: "1234", + destinationID: id, + attemptOptions: 0, + stateChange: Date(), + destinationMountPoint: "/Volumes/Backup", + itemsFound: .random(in: 0...100), + fractionDone: .random(in: 0...1), + fractionOfProgressBar: 0.1 + ) + } +} diff --git a/TimeMachineStatus/Model/BackupState/Finishing.swift b/TimeMachineStatus/Model/BackupState/Finishing.swift index 2c8277c..c74d3a9 100644 --- a/TimeMachineStatus/Model/BackupState/Finishing.swift +++ b/TimeMachineStatus/Model/BackupState/Finishing.swift @@ -21,8 +21,34 @@ extension BackupState { "Finishing" } + override fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int, + stateChange: Date?, + destinationMountPoint: String + ) { + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions, + stateChange: stateChange, + destinationMountPoint: destinationMountPoint + ) + } + override var shortStatusString: String { statusString } } } + +extension BackupState._State.Mock { + static let finishing = BackupState.Finishing( + clientID: "1234", + destinationID: UUID(uuidString: "1234")!, // swiftlint:disable:this force_unwrapping + attemptOptions: 0, + stateChange: Date(), + destinationMountPoint: "/Volumes/Backup" + ) +} diff --git a/TimeMachineStatus/Model/BackupState/Idle.swift b/TimeMachineStatus/Model/BackupState/Idle.swift index 3532639..485f32c 100644 --- a/TimeMachineStatus/Model/BackupState/Idle.swift +++ b/TimeMachineStatus/Model/BackupState/Idle.swift @@ -22,6 +22,11 @@ extension BackupState { try super.init(from: decoder) } + fileprivate init(clientID: String) { + self.clientID = clientID + super.init() + } + let clientID: String override var statusString: String { @@ -33,3 +38,7 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static let idle = BackupState.Idle(clientID: "1234") +} diff --git a/TimeMachineStatus/Model/BackupState/Internal/_BaseState.swift b/TimeMachineStatus/Model/BackupState/Internal/_BaseState.swift index 8bbe2d9..a995c69 100644 --- a/TimeMachineStatus/Model/BackupState/Internal/_BaseState.swift +++ b/TimeMachineStatus/Model/BackupState/Internal/_BaseState.swift @@ -33,6 +33,17 @@ extension BackupState { try super.init(from: decoder) } + init( + clientID: String, + destinationID: UUID, + attemptOptions: Int + ) { + self.clientID = clientID + self.destinationID = destinationID + self.attemptOptions = attemptOptions + super.init() + } + let clientID: String let destinationID: UUID let attemptOptions: Int diff --git a/TimeMachineStatus/Model/BackupState/Internal/_InProgressState.swift b/TimeMachineStatus/Model/BackupState/Internal/_InProgressState.swift index 7b0d05f..9bfe3ec 100644 --- a/TimeMachineStatus/Model/BackupState/Internal/_InProgressState.swift +++ b/TimeMachineStatus/Model/BackupState/Internal/_InProgressState.swift @@ -33,6 +33,18 @@ extension BackupState { try super.init(from: decoder) } + init( + clientID: String, + destinationID: UUID, + attemptOptions: Int, + stateChange: Date?, + destinationMountPoint: String + ) { + self.stateChange = stateChange + self.destinationMountPoint = destinationMountPoint + super.init(clientID: clientID, destinationID: destinationID, attemptOptions: attemptOptions) + } + let stateChange: Date? let destinationMountPoint: String } diff --git a/TimeMachineStatus/Model/BackupState/Internal/_State.swift b/TimeMachineStatus/Model/BackupState/Internal/_State.swift index 01de7e5..1403de4 100644 --- a/TimeMachineStatus/Model/BackupState/Internal/_State.swift +++ b/TimeMachineStatus/Model/BackupState/Internal/_State.swift @@ -15,7 +15,8 @@ extension BackupState { class _State: Decodable { enum _BState { case _idle, _findingBackupVol, _starting, _mounting, _preparing, _findingChanges, _copying, _finishing - case _stopping, _thinning, _unknown + case _stopping, _thinning + case _unknown(state: String) } enum CodingKeys: String, CodingKey { @@ -23,9 +24,9 @@ extension BackupState { case running = "Running" } - init() { + init(running: Bool = false) { self.phase = "" - self.running = false + self.running = running } required init(from decoder: Decoder) throws { @@ -69,7 +70,11 @@ extension BackupState { return ._thinning default: log.error("Unknown phase: \(phase)") - fatalError("Unknown phase: \(phase)") + #if DEBUG + return ._unknown(state: phase) + #else + return ._unknown(state: phase) + #endif } } else { return ._idle @@ -78,7 +83,7 @@ extension BackupState { } class None: _State { - override init() { + init() { super.init() self.phase = "None" } diff --git a/TimeMachineStatus/Model/BackupState/Mounting.swift b/TimeMachineStatus/Model/BackupState/Mounting.swift index 11dc19f..cc01b0d 100644 --- a/TimeMachineStatus/Model/BackupState/Mounting.swift +++ b/TimeMachineStatus/Model/BackupState/Mounting.swift @@ -17,6 +17,18 @@ extension BackupState { try super.init(from: decoder) } + override fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int + ) { + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions + ) + } + override var statusString: String { "Mounting Disk Image" } @@ -26,3 +38,11 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static let mounting = BackupState.Mounting( + clientID: "1234", + destinationID: UUID(uuidString: "1234")!, // swiftlint:disable:this force_unwrapping + attemptOptions: 0 + ) +} diff --git a/TimeMachineStatus/Model/BackupState/Preparing.swift b/TimeMachineStatus/Model/BackupState/Preparing.swift index 536b97a..cd28500 100644 --- a/TimeMachineStatus/Model/BackupState/Preparing.swift +++ b/TimeMachineStatus/Model/BackupState/Preparing.swift @@ -17,6 +17,22 @@ extension BackupState { try super.init(from: decoder) } + override fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int, + stateChange: Date?, + destinationMountPoint: String + ) { + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions, + stateChange: stateChange, + destinationMountPoint: destinationMountPoint + ) + } + override var statusString: String { "Preparing" } @@ -26,3 +42,13 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static let preparing = BackupState.Preparing( + clientID: "1234", + destinationID: UUID(uuidString: "1234")!, // swiftlint:disable:this force_unwrapping + attemptOptions: 0, + stateChange: Date(), + destinationMountPoint: "/Volumes/Backup" + ) +} diff --git a/TimeMachineStatus/Model/BackupState/Starting.swift b/TimeMachineStatus/Model/BackupState/Starting.swift index 66bd3fb..c3d2f0c 100644 --- a/TimeMachineStatus/Model/BackupState/Starting.swift +++ b/TimeMachineStatus/Model/BackupState/Starting.swift @@ -17,6 +17,14 @@ extension BackupState { try super.init(from: decoder) } + override fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int + ) { + super.init(clientID: clientID, destinationID: destinationID, attemptOptions: attemptOptions) + } + override var statusString: String { "Starting" } @@ -26,3 +34,11 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static let starting = BackupState.Starting( + clientID: "1234", + destinationID: UUID(uuidString: "1234")!, // swiftlint:disable:this force_unwrapping + attemptOptions: 0 + ) +} diff --git a/TimeMachineStatus/Model/BackupState/Stopping.swift b/TimeMachineStatus/Model/BackupState/Stopping.swift index 20804cc..8dbdcdb 100644 --- a/TimeMachineStatus/Model/BackupState/Stopping.swift +++ b/TimeMachineStatus/Model/BackupState/Stopping.swift @@ -17,6 +17,22 @@ extension BackupState { try super.init(from: decoder) } + override fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int, + stateChange: Date?, + destinationMountPoint: String + ) { + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions, + stateChange: stateChange, + destinationMountPoint: destinationMountPoint + ) + } + override var statusString: String { "Stopping" } @@ -26,3 +42,13 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static let stopping = BackupState.Stopping( + clientID: "1234", + destinationID: UUID(uuidString: "1234")!, // swiftlint:disable:this force_unwrapping + attemptOptions: 0, + stateChange: Date(), + destinationMountPoint: "/Volumes/Backup" + ) +} diff --git a/TimeMachineStatus/Model/BackupState/Thinning.swift b/TimeMachineStatus/Model/BackupState/Thinning.swift index e4e2fb5..284e134 100644 --- a/TimeMachineStatus/Model/BackupState/Thinning.swift +++ b/TimeMachineStatus/Model/BackupState/Thinning.swift @@ -17,6 +17,22 @@ extension BackupState { try super.init(from: decoder) } + override fileprivate init( + clientID: String, + destinationID: UUID, + attemptOptions: Int, + stateChange: Date?, + destinationMountPoint: String + ) { + super.init( + clientID: clientID, + destinationID: destinationID, + attemptOptions: attemptOptions, + stateChange: stateChange, + destinationMountPoint: destinationMountPoint + ) + } + override var statusString: String { "Thinning Backup Image" } @@ -26,3 +42,13 @@ extension BackupState { } } } + +extension BackupState._State.Mock { + static let thinning = BackupState.Thinning( + clientID: "1234", + destinationID: UUID(uuidString: "1234")!, // swiftlint:disable:this force_unwrapping + attemptOptions: 0, + stateChange: Date(), + destinationMountPoint: "/Volumes/Backup" + ) +} diff --git a/TimeMachineStatus/Model/BackupState/Unknown.swift b/TimeMachineStatus/Model/BackupState/Unknown.swift new file mode 100644 index 0000000..9b1a7a7 --- /dev/null +++ b/TimeMachineStatus/Model/BackupState/Unknown.swift @@ -0,0 +1,44 @@ +// +// Unknown.swift +// TimeMachineStatus +// +// Created by Lukas Pistrol on 15.10.24. +// +// Copyright © 2024 Lukas Pistrol. All rights reserved. +// +// See LICENSE.md for license information. +// + +import Foundation + +extension BackupState { + + class Unknown: _State { + + let title: String + let rawState: String + + init(title: String, raw: String) { + self.title = title + self.rawState = raw + super.init(running: true) + } + + @available(*, unavailable) + required init(from decoder: Decoder) throws { + fatalError("init(from:) has not been implemented") + } + + override var statusString: String { + title + } + + override var shortStatusString: String { + title + } + } +} + +extension BackupState._State.Mock { + static let unknown = BackupState.Unknown(title: "Unknown", raw: "{ Some State Content }") +} diff --git a/TimeMachineStatus/Views/MenuView.swift b/TimeMachineStatus/Views/MenuView.swift index a24f278..54489af 100644 --- a/TimeMachineStatus/Views/MenuView.swift +++ b/TimeMachineStatus/Views/MenuView.swift @@ -170,6 +170,14 @@ struct MenuView: View { } .foregroundStyle(.secondary) .font(.caption2) + } else if let status = utility.status as? BackupState.Unknown { + Text(status.statusString) + .foregroundStyle(.secondary) + .font(.caption2) + .help(status.rawState) + .onTapGesture { + NSPasteboard.general.setString(status.rawState, forType: .string) + } } else { if let latestDate = utility.preferences?.latestBackupDate, let latestVolume = utility.preferences?.latestBackupVolume { @@ -267,7 +275,6 @@ struct MenuView: View { #Preview("Light Error") { MenuView( utility: TMUtilityMock( - preferences: .mock, error: .preferencesFilePermissionNotGranted, canReadPreferences: false ), @@ -276,9 +283,42 @@ struct MenuView: View { .preferredColorScheme(.light) } -#Preview("Dark") { +#Preview("Dark Copying") { + let preferences = Preferences.mock + // swiftlint:disable:next force_unwrapping + let status = BackupState._State.Mock.copying(preferences.destinations!.first!.destinationID) + MenuView( + utility: TMUtilityMock( + status: status, + preferences: preferences + ), + updater: SPUStandardUpdaterController(updaterDelegate: nil, userDriverDelegate: nil).updater + ) + .preferredColorScheme(.dark) +} + +#Preview("Dark Finding Changes") { + let preferences = Preferences.mock + // swiftlint:disable:next force_unwrapping + let status = BackupState._State.Mock.findingChanges(preferences.destinations!.first!.destinationID) MenuView( - utility: TMUtilityImpl(), + utility: TMUtilityMock( + status: status, + preferences: preferences + ), + updater: SPUStandardUpdaterController(updaterDelegate: nil, userDriverDelegate: nil).updater + ) + .preferredColorScheme(.dark) +} + +#Preview("Dark Unknown") { + let preferences = Preferences.mock + let status = BackupState._State.Mock.unknown + MenuView( + utility: TMUtilityMock( + status: status, + preferences: preferences + ), updater: SPUStandardUpdaterController(updaterDelegate: nil, userDriverDelegate: nil).updater ) .preferredColorScheme(.dark) diff --git a/TimeMachineStatus/Views/StatusBarItem.swift b/TimeMachineStatus/Views/StatusBarItem.swift index edff2d8..c655946 100644 --- a/TimeMachineStatus/Views/StatusBarItem.swift +++ b/TimeMachineStatus/Views/StatusBarItem.swift @@ -108,6 +108,15 @@ struct StatusBarItem: View { .onChange(of: utility.isIdle) { oldValue, newValue in log.trace("Changed: \(oldValue) -> \(newValue)") } + .overlay(alignment: .topTrailing) { + #if DEBUG + Text("D") + .font(.system(size: 6)) + .bold() + .padding(.horizontal, 2) + .background(.red, in: .rect(cornerRadius: 3)) + #endif + } } struct AnimatedIcon: View { @@ -126,7 +135,13 @@ struct StatusBarItem: View { } } -#Preview { +#Preview("Light") { + StatusBarItem(sizePassthrough: .init(), utility: .init()) + .frame(height: 24) +} + +#Preview("Dark") { StatusBarItem(sizePassthrough: .init(), utility: .init()) .frame(height: 24) + .preferredColorScheme(.dark) }