From 2c40f433d31f6216a7a37821b0d1437951b9bb96 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 5 Feb 2023 18:37:18 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20updater=20to=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If you want to see the source code to the updater, you can find it here: https://github.com/nicoverbruggen/phpmon-updater Starting with version 6.0, the code of the updater will be included in this repository. --- PHP Monitor.xcodeproj/project.pbxproj | 70 ++++--- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Common/Filesystem/RealFileSystem.swift | 11 +- phpmon/Common/Helpers/LocalNotification.swift | 4 +- phpmon/Common/Helpers/System.swift | 4 + phpmon/Domain/App/AppUpdateChecker.swift | 182 ----------------- phpmon/Domain/App/AppUpdater.swift | 184 ++++++++++++++++++ phpmon/Domain/App/AppVersion.swift | 26 ++- .../DomainList/Cells/DomainListPhpCell.swift | 3 +- .../Integrations/Homebrew/CaskFile.swift | 78 ++++++++ phpmon/Domain/Menu/MainMenu+Startup.swift | 5 +- phpmon/Domain/Menu/MainMenu.swift | 2 +- phpmon/Localizable.strings | 7 +- tests/unit/Parsers/CaskFileParserTest.swift | 53 +++++ tests/unit/Test Files/brew/phpmon-dev.rb | 14 ++ tests/unit/Versions/AppUpdaterCheckTest.swift | 47 ----- tests/unit/Versions/AppVersionTest.swift | 46 ++++- 17 files changed, 466 insertions(+), 272 deletions(-) delete mode 100644 phpmon/Domain/App/AppUpdateChecker.swift create mode 100644 phpmon/Domain/App/AppUpdater.swift create mode 100644 phpmon/Domain/Integrations/Homebrew/CaskFile.swift create mode 100644 tests/unit/Parsers/CaskFileParserTest.swift create mode 100644 tests/unit/Test Files/brew/phpmon-dev.rb delete mode 100644 tests/unit/Versions/AppUpdaterCheckTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 8e6848d..edc6837 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -182,9 +182,6 @@ C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; C469E706294CFDF700A82AB2 /* DomainsListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E702294CFDF700A82AB2 /* DomainsListTest.swift */; }; - C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; - C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; - C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */; }; C46EBC4428DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; @@ -324,7 +321,6 @@ C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; C471E84728F9BB650021E251 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; - C471E84928F9BB650021E251 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C471E84B28F9BB650021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C471E84C28F9BB650021E251 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; @@ -414,7 +410,6 @@ C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; C471E8AB28F9BB8F0021E251 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; - C471E8AC28F9BB8F0021E251 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C471E8AD28F9BB8F0021E251 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C471E8AF28F9BB8F0021E251 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; @@ -511,6 +506,17 @@ C48D6C70279CD2AC00F26D7E /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; C48D6C71279CD2AC00F26D7E /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */; }; + C491997729901DD6001F3A21 /* CaskFileParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997629901DD6001F3A21 /* CaskFileParserTest.swift */; }; + C491997929901DE2001F3A21 /* phpmon-dev.rb in Resources */ = {isa = PBXBuildFile; fileRef = C491997829901DE2001F3A21 /* phpmon-dev.rb */; }; + C491997B29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491997C29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491997D29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491997E29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491998029901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998129901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998229901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998329901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998A29902089001F3A21 /* PHP Monitor Self-Updater.app in Resources */ = {isa = PBXBuildFile; fileRef = C491998929902089001F3A21 /* PHP Monitor Self-Updater.app */; }; C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; C493084A279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; @@ -833,8 +839,6 @@ C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetProxy.swift; sourceTree = ""; }; C469E702294CFDF700A82AB2 /* DomainsListTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsListTest.swift; sourceTree = ""; }; - C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; - C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdaterCheckTest.swift; sourceTree = ""; }; C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = ""; }; C46EBC4628DB9644007ACC74 /* RealShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShell.swift; sourceTree = ""; }; C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = ""; }; @@ -858,6 +862,11 @@ C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = ""; }; C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumber.swift; sourceTree = ""; }; C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = ""; }; + C491997629901DD6001F3A21 /* CaskFileParserTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaskFileParserTest.swift; sourceTree = ""; }; + C491997829901DE2001F3A21 /* phpmon-dev.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = "phpmon-dev.rb"; sourceTree = ""; }; + C491997A29901DF7001F3A21 /* CaskFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaskFile.swift; sourceTree = ""; }; + C491997F29901E0F001F3A21 /* AppUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdater.swift; sourceTree = ""; }; + C491998929902089001F3A21 /* PHP Monitor Self-Updater.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "PHP Monitor Self-Updater.app"; sourceTree = ""; }; C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = ""; }; C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = ""; }; @@ -1140,6 +1149,7 @@ C4F5FBCC28218C93001065C5 /* .swiftlint.yml */, C4E713572570151400007428 /* docs */, C41C1B3522B0097F00E7CF16 /* phpmon */, + C491998829902061001F3A21 /* phpmon-updater */, C471E79628F9B4260021E251 /* tests */, C41C1B3422B0097F00E7CF16 /* Products */, C4D309E72770EF2F00958BCF /* Frameworks */, @@ -1300,6 +1310,7 @@ C459B4BF27F6094100E9B4B4 /* brew */ = { isa = PBXGroup; children = ( + C491997829901DE2001F3A21 /* phpmon-dev.rb */, C4E2E85228FC256B003B070C /* brew-services-normal.json */, C4E2E85128FC256B003B070C /* brew-services-sudo.json */, C43A8A1F25D9D1D700591B77 /* brew-formula.json */, @@ -1444,6 +1455,14 @@ path = "PHP Version"; sourceTree = ""; }; + C491998829902061001F3A21 /* phpmon-updater */ = { + isa = PBXGroup; + children = ( + C491998929902089001F3A21 /* PHP Monitor Self-Updater.app */, + ); + path = "phpmon-updater"; + sourceTree = ""; + }; C4AF9F6A275445C900D44ED0 /* Valet */ = { isa = PBXGroup; children = ( @@ -1471,6 +1490,7 @@ C4AF9F6C275445D900D44ED0 /* Homebrew */ = { isa = PBXGroup; children = ( + C491997A29901DF7001F3A21 /* CaskFile.swift */, C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */, ); path = Homebrew; @@ -1491,8 +1511,8 @@ C4EED88827A48778006D7272 /* InterAppHandler.swift */, C4D8016522B1584700C6DA1B /* Startup.swift */, C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */, - C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */, C40FE736282ABA4F00A302C2 /* AppVersion.swift */, + C491997F29901E0F001F3A21 /* AppUpdater.swift */, C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */, ); path = App; @@ -1580,6 +1600,7 @@ C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */, C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */, C4551656297AED18009B8466 /* ValetRcTest.swift */, + C491997629901DD6001F3A21 /* CaskFileParserTest.swift */, ); path = Parsers; sourceTree = ""; @@ -1592,7 +1613,6 @@ C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */, C4AF9F7C275454A900D44ED0 /* ValetVersionExtractorTest.swift */, C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */, - C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */, ); path = Versions; sourceTree = ""; @@ -1903,6 +1923,7 @@ C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */, C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */, C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */, + C491998A29902089001F3A21 /* PHP Monitor Self-Updater.app in Resources */, C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */, C4232EE52612526500158FC6 /* Credits.html in Resources */, 54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */, @@ -1958,6 +1979,7 @@ C455165B297AEDB5009B8466 /* valetrc.broken in Resources */, 54A18D40282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test in Resources */, C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */, + C491997929901DE2001F3A21 /* phpmon-dev.rb in Resources */, C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */, C4E2E85428FC256B003B070C /* brew-services-sudo.json in Resources */, ); @@ -2070,6 +2092,7 @@ C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */, 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, + C491997B29901DF7001F3A21 /* CaskFile.swift in Sources */, C4297F7A28970D59004C4630 /* WarningView.swift in Sources */, C4C0E8E227F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, C42F26732805B4B400938AC7 /* ValetListable.swift in Sources */, @@ -2081,7 +2104,6 @@ C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */, C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */, C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, - C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, 03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, @@ -2147,6 +2169,7 @@ C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, C493084A279F331F009C240B /* AddSiteVC.swift in Sources */, C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */, + C491998029901E0F001F3A21 /* AppUpdater.swift in Sources */, C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2178,13 +2201,13 @@ C471E84228F9BB650021E251 /* AppDelegate+InterApp.swift in Sources */, C471E84328F9BB650021E251 /* App.swift in Sources */, C4E2E85E28FC282B003B070C /* TestableConfiguration.swift in Sources */, + C491997D29901DF7001F3A21 /* CaskFile.swift in Sources */, C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */, C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */, C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */, C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */, C471E84728F9BB650021E251 /* Startup.swift in Sources */, C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */, - C471E84928F9BB650021E251 /* AppUpdateChecker.swift in Sources */, C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */, C471E84B28F9BB650021E251 /* ServicesManager.swift in Sources */, C471E84C28F9BB650021E251 /* EnvironmentManager.swift in Sources */, @@ -2226,6 +2249,7 @@ C471E86A28F9BB650021E251 /* PrefsVC.swift in Sources */, C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */, C471E86C28F9BB650021E251 /* Preferences.swift in Sources */, + C491998229901E0F001F3A21 /* AppUpdater.swift in Sources */, C4D3660D29113F20006BD146 /* System.swift in Sources */, C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */, C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */, @@ -2350,7 +2374,6 @@ C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */, C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */, C471E8AB28F9BB8F0021E251 /* EnvironmentCheck.swift in Sources */, - C471E8AC28F9BB8F0021E251 /* AppUpdateChecker.swift in Sources */, C471E8AD28F9BB8F0021E251 /* AppVersion.swift in Sources */, C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */, C471E8AF28F9BB8F0021E251 /* EnvironmentManager.swift in Sources */, @@ -2444,6 +2467,7 @@ C471E81028F9BAE80021E251 /* StringExtension.swift in Sources */, C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */, C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */, + C491997E29901DF7001F3A21 /* CaskFile.swift in Sources */, C471E82128F9BB2E0021E251 /* PhpFrameworks.swift in Sources */, C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */, C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */, @@ -2457,6 +2481,7 @@ C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */, C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */, C471E7F528F9BAC80021E251 /* PhpEnv.swift in Sources */, + C491998329901E0F001F3A21 /* AppUpdater.swift in Sources */, C471E7ED28F9BAC30021E251 /* Process.swift in Sources */, C471E81128F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */, @@ -2524,6 +2549,7 @@ C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */, + C491997C29901DF7001F3A21 /* CaskFile.swift in Sources */, C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C485707A28BF457800539B36 /* WarningListView.swift in Sources */, C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */, @@ -2574,6 +2600,7 @@ C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */, C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */, C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, + C491998129901E0F001F3A21 /* AppUpdater.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */, @@ -2600,7 +2627,6 @@ C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */, C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, - C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, C485707D28BF45A200539B36 /* WarningView.swift in Sources */, C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */, C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */, @@ -2623,6 +2649,7 @@ C40F505628ECA64E004AD45B /* TestableConfigurations.swift in Sources */, C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */, + C491997729901DD6001F3A21 /* CaskFileParserTest.swift in Sources */, C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */, C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */, C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */, @@ -2667,7 +2694,6 @@ C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */, C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */, C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */, - C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2831,7 +2857,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2843,7 +2869,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2860,7 +2886,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2872,7 +2898,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3088,7 +3114,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3099,7 +3125,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME) DEV"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3198,7 +3224,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3209,7 +3235,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 403e4f6..db36ef9 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "NO"> [String] { - return try FileManager.default.contentsOfDirectory(atPath: path) + return try FileManager.default.contentsOfDirectory(atPath: path.replacingTildeWithHomeDirectory) } func getDestinationOfSymlink(_ path: String) throws -> String { - return try FileManager.default.destinationOfSymbolicLink(atPath: path) + return try FileManager.default.destinationOfSymbolicLink(atPath: path.replacingTildeWithHomeDirectory) } // MARK: - Move & Delete Files func move(from path: String, to newPath: String) throws { - try FileManager.default.moveItem(atPath: path, toPath: newPath) + try FileManager.default.moveItem( + atPath: path.replacingTildeWithHomeDirectory, + toPath: newPath.replacingTildeWithHomeDirectory + ) } func remove(_ path: String) throws { - try FileManager.default.removeItem(atPath: path) + try FileManager.default.removeItem(atPath: path.replacingTildeWithHomeDirectory) } // MARK: — FS Attributes diff --git a/phpmon/Common/Helpers/LocalNotification.swift b/phpmon/Common/Helpers/LocalNotification.swift index 6e4f7bc..eb8de9a 100644 --- a/phpmon/Common/Helpers/LocalNotification.swift +++ b/phpmon/Common/Helpers/LocalNotification.swift @@ -10,8 +10,8 @@ import UserNotifications class LocalNotification { - @MainActor public static func send(title: String, subtitle: String, preference: PreferenceName) { - if !Preferences.isEnabled(preference) { + @MainActor public static func send(title: String, subtitle: String, preference: PreferenceName?) { + if preference != nil && !Preferences.isEnabled(preference!) { return } diff --git a/phpmon/Common/Helpers/System.swift b/phpmon/Common/Helpers/System.swift index 332bba2..bd54915 100644 --- a/phpmon/Common/Helpers/System.swift +++ b/phpmon/Common/Helpers/System.swift @@ -26,3 +26,7 @@ public func system(_ command: String) -> String { return output } + +public func system_quiet(_ command: String) { + _ = system(command) +} diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift deleted file mode 100644 index 0593b2b..0000000 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// Updater.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 09/05/2022. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import Foundation -import AppKit - -class AppUpdateChecker { - - public static var enabled: Bool = { - return Preferences.isEnabled(.automaticBackgroundUpdateCheck) - }() - - public static var isDev: Bool = { - return App.version.contains("-dev") - }() - - public static func retrieveVersionFromCask( - _ initiatedFromBackground: Bool = true - ) async -> String { - let caskFile = App.version.contains("-dev") - ? Constants.Urls.DevBuildCaskFile.absoluteString - : Constants.Urls.StableBuildCaskFile.absoluteString - - var command = "curl -s" - - if initiatedFromBackground { - command = "curl -s --max-time 5" - } - - return await Shell.pipe( - "\(command) '\(caskFile)' | grep version" - ).out - } - - public static func checkIfNewerVersionIsAvailable( - initiatedFromBackground: Bool = true - ) async { - if initiatedFromBackground { - if !Preferences.isEnabled(.automaticBackgroundUpdateCheck) { - Log.info("Automatic updates are disabled. No check will be performed.") - return - } - - Log.info("Automatic updates are enabled, a check will be performed.") - } - - let versionString = await retrieveVersionFromCask(initiatedFromBackground) - - guard let onlineVersion = AppVersion.from(versionString) else { - Log.err("We couldn't check for updates!") - - // Only notify about connection issues if the request to check for updates was explicit - if !initiatedFromBackground { - notifyAboutConnectionIssue() - } - - return - } - - let currentVersion = AppVersion.fromCurrentVersion() - - handleVersionComparison( - currentVersion, - onlineVersion, - initiatedFromBackground - ) - } - - private static func handleVersionComparison( - _ currentVersion: AppVersion, - _ onlineVersion: AppVersion, - _ background: Bool - ) { - switch onlineVersion.version.versionCompare(currentVersion.version) { - case .orderedAscending: - Log.info("You are running a newer version of PHP Monitor " - + "(\(currentVersion.computerReadable) > \(onlineVersion.computerReadable)).") - if !background { notifyVersionDoesNotNeedUpgrade() } - case .orderedDescending: - Log.info("There is a newer version (\(onlineVersion)) available! " - + "(\(onlineVersion.computerReadable) > \(currentVersion.computerReadable))") - notifyAboutNewerVersion(version: onlineVersion) - case .orderedSame: - if currentVersion.build != nil - && onlineVersion.build != nil - && buildDiffers(currentVersion, onlineVersion, background) { - return - } - - Log.info("The installed version (\(currentVersion.computerReadable)) matches the latest release " - + "(\(onlineVersion.computerReadable)).") - if !background { notifyVersionDoesNotNeedUpgrade() } - } - } - - private static func buildDiffers( - _ currentVersion: AppVersion, - _ onlineVersion: AppVersion, - _ background: Bool - ) -> Bool { - if Int(onlineVersion.build!)! > Int(currentVersion.build!)! { - Log.info("There is a newer build of PHP Monitor available! " - + "(\(onlineVersion.computerReadable) > \(currentVersion.computerReadable))") - notifyAboutNewerVersion(version: onlineVersion) - return true - } else if Int(onlineVersion.build!)! < Int(currentVersion.build!)! { - Log.info("You are running a newer build of PHP Monitor " - + "(\(currentVersion.computerReadable) > \(onlineVersion.computerReadable)).") - if !background { notifyVersionDoesNotNeedUpgrade() } - return true - } - - return false - } - - private static func notifyVersionDoesNotNeedUpgrade() { - Task { @MainActor in - BetterAlert().withInformation( - title: "updater.alerts.is_latest_version.title".localized, - subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion), - description: "" - ) - .withPrimary(text: "generic.ok".localized) - .show() - } - } - - private static func notifyAboutNewerVersion(version: AppVersion) { - let devSuffix = isDev ? "-dev" : "" - let command = isDev ? "brew upgrade phpmon-dev" : "brew upgrade phpmon" - - Task { @MainActor in - BetterAlert().withInformation( - title: "updater.alerts.newer_version_available.title".localized(version.humanReadable), - subtitle: "updater.alerts.newer_version_available.subtitle".localized, - description: HomebrewDiagnostics.customCaskInstalled - ? "updater.installation_source.brew".localized(command) - : "updater.installation_source.direct".localized - ) - .withPrimary( - text: "updater.alerts.buttons.release_notes".localized, - action: { vc in - vc.close(with: .OK) - - NSWorkspace.shared.open( - Constants.Urls.GitHubReleases.appendingPathComponent("/tag/v\(version.tagged)\(devSuffix)") - ) - } - ) - .withTertiary(text: "Dismiss", action: { vc in - vc.close(with: .OK) - }) - .show() - } - } - - private static func notifyAboutConnectionIssue() { - Task { @MainActor in - BetterAlert().withInformation( - title: "updater.alerts.cannot_check_for_update.title".localized, - subtitle: "updater.alerts.cannot_check_for_update.subtitle".localized, - description: "updater.alerts.cannot_check_for_update.description".localized( - App.version - ) - ) - .withTertiary( - text: "updater.alerts.buttons.releases_on_github".localized, - action: { _ in - NSWorkspace.shared.open(Constants.Urls.GitHubReleases) - } - ) - .withPrimary(text: "generic.ok".localized) - .show() - } - } - -} diff --git a/phpmon/Domain/App/AppUpdater.swift b/phpmon/Domain/App/AppUpdater.swift new file mode 100644 index 0000000..6ef3c74 --- /dev/null +++ b/phpmon/Domain/App/AppUpdater.swift @@ -0,0 +1,184 @@ +// +// AppUpdater.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation +import Cocoa + +class AppUpdater { + var caskFile: CaskFile! + var latestVersionOnline: AppVersion! + var interactive: Bool = false + + public func checkForUpdates(interactive: Bool) async { + self.interactive = interactive + + if interactive && !Preferences.isEnabled(.automaticBackgroundUpdateCheck) { + Log.info("Skipping automatic update check due to user preference.") + return + } + + Log.info("The app will search for updates...") + + let caskUrl = App.identifier.contains(".dev") + ? Constants.Urls.DevBuildCaskFile + : Constants.Urls.StableBuildCaskFile + + guard let caskFile = await CaskFile.from(url: caskUrl) else { + Log.err("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.") + return presentCouldNotRetrieveUpdateIfInteractive() + } + + self.caskFile = caskFile + + let currentVersion = AppVersion.fromCurrentVersion() + + guard let onlineVersion = AppVersion.from(caskFile.version) else { + Log.err("The version string from the CaskFile could not be read.") + return presentCouldNotRetrieveUpdateIfInteractive() + } + + latestVersionOnline = onlineVersion + Log.info("The latest version read from '\(caskUrl.lastPathComponent)' is: v\(onlineVersion.computerReadable).") + + if latestVersionOnline > currentVersion { + presentNewerVersionAvailableAlert() + } else if interactive { + presentNoNewerVersionAvailableAlert() + } + } + + private func presentCouldNotRetrieveUpdateIfInteractive() { + if interactive { + return presentCouldNotRetrieveUpdate() + } else { + return + } + } + + // MARK: - Alerts + + public func presentNewerVersionAvailableAlert() { + let command = App.identifier.contains(".dev") + ? "brew upgrade phpmon-dev" + : "brew upgrade phpmon" + + Task { @MainActor in + BetterAlert().withInformation( + title: "updater.alerts.newer_version_available.title" + .localized(latestVersionOnline.humanReadable), + subtitle: "updater.alerts.newer_version_available.subtitle" + .localized, + description: HomebrewDiagnostics.customCaskInstalled + ? "updater.installation_source.brew".localized(command) + : "updater.installation_source.direct".localized + ) + .withPrimary( + text: "updater.alerts.buttons.install".localized, + action: { vc in + self.prepareForDownload() + vc.close(with: .OK) + } + ) + .withSecondary( + text: "updater.alerts.buttons.release_notes".localized, + action: { _ in + let urlSegments = self.caskFile.url.split(separator: "/") + let tag = urlSegments[urlSegments.count - 2] // ../download/{tag}/{file.zip} + NSWorkspace.shared.open( + Constants.Urls.GitHubReleases.appendingPathComponent("/tag/\(tag)") + ) + } + ) + .withTertiary(text: "updater.alerts.buttons.dismiss".localized, action: { vc in + vc.close(with: .OK) + }) + .show() + } + } + + public func presentNoNewerVersionAvailableAlert() { + Task { @MainActor in + BetterAlert().withInformation( + title: "updater.alerts.is_latest_version.title".localized, + subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion), + description: "" + ) + .withPrimary(text: "generic.ok".localized) + .show() + } + } + + public func presentCouldNotRetrieveUpdate() { + Task { @MainActor in + BetterAlert().withInformation( + title: "updater.alerts.cannot_check_for_update.title".localized, + subtitle: "updater.alerts.cannot_check_for_update.subtitle".localized, + description: "updater.alerts.cannot_check_for_update.description".localized( + App.version + ) + ) + .withTertiary( + text: "updater.alerts.buttons.releases_on_github".localized, + action: { _ in + NSWorkspace.shared.open(Constants.Urls.GitHubReleases) + } + ) + .withPrimary(text: "generic.ok".localized) + .show() + } + } + + // MARK: - Preparing for Self-Updater + + private func prepareForDownload() { + let updater = Bundle.main.resourceURL!.path + "/PHP Monitor Self-Updater.app" + + system_quiet("mkdir -p ~/.config/phpmon/updater 2> /dev/null") + + let updaterDirectory = "~/.config/phpmon/updater" + .replacingOccurrences(of: "~", with: NSHomeDirectory()) + + system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/PHP Monitor Self-Updater.app\"") + + try! FileSystem.writeAtomicallyToFile( + "\(updaterDirectory)/update.json", + content: "{ \"url\": \"\(caskFile.url)\", \"sha256\": \"\(caskFile.sha256)\" }" + ) + + let updaterUrl = NSURL(fileURLWithPath: updater, isDirectory: true) as URL + let configuration = NSWorkspace.OpenConfiguration() + + NSWorkspace.shared.openApplication(at: updaterUrl, configuration: configuration) { _, _ in + Log.info("The updater has been launched successfully!") + } + } + + // MARK: - Checking if Self-Updater Worked + + public static func checkIfUpdateWasPerformed() { + // Cleanup the upgrade.success file + if FileSystem.fileExists("~/.config/phpmon/updater/upgrade.success") { + Task { @MainActor in + LocalNotification.send( + title: "notification.phpmon_updated.title".localized, + subtitle: "notification.phpmon_updated.desc".localized(App.shortVersion), + preference: nil + ) + } + + Log.info("The `upgrade.success` file was found! An update was installed. Cleaning up...") + try! FileSystem.remove("~/.config/phpmon/updater/upgrade.success") + } + + // Cleanup the previous updater + if FileSystem.anyExists("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") { + Log.info("A remnant of the self-updater must still be removed...") + try? FileSystem.remove("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") + } + } +} diff --git a/phpmon/Domain/App/AppVersion.swift b/phpmon/Domain/App/AppVersion.swift index 62231b4..5d020af 100644 --- a/phpmon/Domain/App/AppVersion.swift +++ b/phpmon/Domain/App/AppVersion.swift @@ -8,14 +8,14 @@ import Foundation -class AppVersion { +class AppVersion: Comparable { var version: String - var build: String? + var build: Int? var suffix: String? init(version: String, build: String?, suffix: String? = nil) { self.version = version - self.build = build + self.build = Int(build ?? "0") self.suffix = suffix } @@ -75,11 +75,27 @@ class AppVersion { } var computerReadable: String { - return "\(version)_\(build ?? "0")" + return "\(version)_\(build ?? 0)" } var humanReadable: String { - return "\(version) (\(build ?? "???"))" + return "\(version) (\(build ?? 0))" } + // MARK: - Comparable Protocol + + static func < (lhs: AppVersion, rhs: AppVersion) -> Bool { + let comparisonResult = lhs.version.versionCompare(rhs.version) + + if comparisonResult == .orderedAscending { + return true + } + + return lhs.build ?? 0 < rhs.build ?? 0 + } + + static func == (lhs: AppVersion, rhs: AppVersion) -> Bool { + lhs.version.versionCompare(rhs.version) == .orderedSame + && lhs.build == rhs.build + } } diff --git a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift index 9271a29..9d2d73c 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift @@ -37,7 +37,8 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol { imageViewPhpVersionOK.image = NSImage(named: "Isolated") imageViewPhpVersionOK.toolTip = "domain_list.tooltips.isolated".localized(site.servingPhpVersion) } else { - imageViewPhpVersionOK.isHidden = (site.preferredPhpVersion == "???" || !site.isCompatibleWithPreferredPhpVersion) + imageViewPhpVersionOK.isHidden = (site.preferredPhpVersion == "???" + || !site.isCompatibleWithPreferredPhpVersion) imageViewPhpVersionOK.image = NSImage(named: "Checkmark") imageViewPhpVersionOK.toolTip = "domain_list.tooltips.checkmark".localized(site.preferredPhpVersion) diff --git a/phpmon/Domain/Integrations/Homebrew/CaskFile.swift b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift new file mode 100644 index 0000000..c2ddf0b --- /dev/null +++ b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift @@ -0,0 +1,78 @@ +// +// CaskFile.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation + +struct CaskFile { + var properties: [String: String] + + var name: String { + return self.properties["name"]! + } + var url: String { + return self.properties["url"]! + } + var sha256: String { + return self.properties["sha256"]! + } + var version: String { + return self.properties["version"]! + } + + public static func from(url: URL) async -> CaskFile? { + var string: String? + + if url.scheme == "file" { + string = try? String(contentsOf: url) + } else { + string = await Shell.pipe("curl -s --max-time 10 '\(url.absoluteString)'").out + } + + guard let string else { + Log.err("The content of the URL for the CaskFile could not be retrieved") + return nil + } + + let lines = string.split(separator: "\n") + .filter { $0 != "" } + + if lines.count < 4 { + Log.err("The CaskFile is <4 lines long, which is too short") + return nil + } + + if !lines.first!.starts(with: "cask") || !lines.last!.starts(with: "end") { + Log.err("The CaskFile does not start with 'cask' or does not end with 'end'") + return nil + } + + var props: [String: String] = [:] + + let regex = try! NSRegularExpression(pattern: "(\\w+)\\s+'([^']+)'") + + for line in lines { + if let match = regex.firstMatch( + in: String(line), + range: NSRange(location: 0, length: line.utf16.count) + ) { + let keyRange = match.range(at: 1) + let valueRange = match.range(at: 2) + let key = (line as NSString).substring(with: keyRange) + let value = (line as NSString).substring(with: valueRange) + props[key] = value + } + } + + for required in ["version", "sha256", "url", "name"] where !props.keys.contains(required) { + Log.err("Property '\(required)' expected on CaskFile, assuming CaskFile is invalid") + return nil + } + + return CaskFile(properties: props) + } +} diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index e08035c..a83fe73 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -112,12 +112,15 @@ extension MainMenu { } } - await AppUpdateChecker.checkIfNewerVersionIsAvailable() + await AppUpdater().checkForUpdates(interactive: false) } // Check if the linked version has changed between launches of phpmon Stats.evaluateLastLinkedPhpVersion() + // Check if an update was performed earlier + AppUpdater.checkIfUpdateWasPerformed() + // We are ready! Log.info("PHP Monitor is ready to serve!") } diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index bf05c1f..b4b0656 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -193,7 +193,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate } @objc func checkForUpdates() { - Task { await AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false) } + Task { await AppUpdater().checkForUpdates(interactive: true) } } // MARK: - Menu Delegate diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index c15ad2b..660ffea 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -617,9 +617,8 @@ COMMON TROUBLESHOOTING TIPS "updater.alerts.newer_version_available.title" = "PHP Monitor v%@ is now available!"; "updater.alerts.newer_version_available.subtitle" = "Keeping PHP Monitor up-to-date is highly recommended, since newer versions usually fix bugs and include fixes to support the latest versions of Valet and PHP."; -"updater.alerts.newer_version_available.description" = "PHP Monitor is supposed to be updated via Homebrew, so there is no built-in updater. This check is only meant to inform you of the existence of a new version, you do not need to upgrade."; -"updater.installation_source.brew" = "You appear to have installed PHP Monitor via Homebrew (or have at least tapped the required Caskfile) so it is recommended that you upgrade via the terminal by running `%@`."; -"updater.installation_source.direct" = "You do not appear to have installed PHP Monitor via Homebrew, so you will need to visit GitHub to download the latest update."; +"updater.installation_source.brew" = "The recommended method of installing updates to PHP Monitor is to simply press 'Install Update'.\n\n(You may also upgrade via the terminal by running `%@`, but this is not recommended.)"; +"updater.installation_source.direct" = "The recommended method of installing updates to PHP Monitor is to simply press 'Install Update'."; "updater.alerts.buttons.release_notes" = "View Release Notes"; "updater.alerts.is_latest_version.title" = "PHP Monitor is up-to-date!"; @@ -629,6 +628,8 @@ COMMON TROUBLESHOOTING TIPS "updater.alerts.cannot_check_for_update.subtitle" = "You might not be connected to the internet, are blocking traffic or GitHub is down and won't allow you to check for updates. If you keep seeing this message, you may want to manually check the releases page."; "updater.alerts.cannot_check_for_update.description" = "The currently installed version is: %@. You can go to the list of the latest releases (on GitHub) by clicking on the button on the left."; "updater.alerts.buttons.releases_on_github" = "View Releases"; +"updater.alerts.buttons.install" = "Install Update"; +"updater.alerts.buttons.dismiss" = "Dismiss"; // WARNINGS ABOUT NON-DEFAULT TLD diff --git a/tests/unit/Parsers/CaskFileParserTest.swift b/tests/unit/Parsers/CaskFileParserTest.swift new file mode 100644 index 0000000..1b861f6 --- /dev/null +++ b/tests/unit/Parsers/CaskFileParserTest.swift @@ -0,0 +1,53 @@ +// +// CaskFileParserTest.swift +// Unit Tests +// +// Created by Nico Verbruggen on 04/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class CaskFileParserTest: XCTestCase { + + // MARK: - Test Files + static var exampleFilePath: URL { + return Bundle(for: Self.self) + .url(forResource: "phpmon-dev", withExtension: "rb")! + } + + func test_can_extract_fields_from_cask_file() async throws { + guard let caskFile = await CaskFile.from(url: CaskFileParserTest.exampleFilePath) else { + return XCTFail("The CaskFile could not be parsed, check the log for more info") + } + + XCTAssertEqual( + caskFile.version, + "5.7.2_1035" + ) + XCTAssertEqual( + caskFile.sha256, + "1cb147bd1b1fbd52971d90dff577465b644aee7c878f15ede57f46e8f217067a" + ) + XCTAssertEqual( + caskFile.name, + "PHP Monitor DEV" + ) + XCTAssertEqual( + caskFile.url, + "https://github.com/nicoverbruggen/phpmon/releases/download/v5.7.2/phpmon-dev.zip" + ) + } + + func test_can_extract_fields_from_remote_cask_file() async throws { + guard let caskFile = await CaskFile.from(url: Constants.Urls.StableBuildCaskFile) else { + return XCTFail("The remote CaskFile could not be parsed, check the log for more info") + } + + XCTAssertTrue(caskFile.properties.keys.contains("version")) + XCTAssertTrue(caskFile.properties.keys.contains("homepage")) + XCTAssertTrue(caskFile.properties.keys.contains("url")) + XCTAssertTrue(caskFile.properties.keys.contains("appcast")) + } + +} diff --git a/tests/unit/Test Files/brew/phpmon-dev.rb b/tests/unit/Test Files/brew/phpmon-dev.rb new file mode 100644 index 0000000..177d11d --- /dev/null +++ b/tests/unit/Test Files/brew/phpmon-dev.rb @@ -0,0 +1,14 @@ +cask 'phpmon-dev' do + depends_on formula: 'gnu-sed' + + version '5.7.2_1035' + sha256 '1cb147bd1b1fbd52971d90dff577465b644aee7c878f15ede57f46e8f217067a' + + url 'https://github.com/nicoverbruggen/phpmon/releases/download/v5.7.2/phpmon-dev.zip' + appcast 'https://github.com/nicoverbruggen/phpmon/releases.atom' + name 'PHP Monitor DEV' + homepage 'https://phpmon.app' + + app 'PHP Monitor DEV.app', target: "PHP Monitor DEV.app" + end + \ No newline at end of file diff --git a/tests/unit/Versions/AppUpdaterCheckTest.swift b/tests/unit/Versions/AppUpdaterCheckTest.swift deleted file mode 100644 index 463635e..0000000 --- a/tests/unit/Versions/AppUpdaterCheckTest.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// AppUpdaterCheckTest.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 10/05/2022. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import XCTest - -class AppUpdaterCheckTest: XCTestCase { - - func test_can_retrieve_version_from_cask() async { - let caskVersion = await AppUpdateChecker.retrieveVersionFromCask() - - let version = VersionExtractor.from(caskVersion) - - XCTAssertNotNil(version) - } - - func test_tagged_release_omits_zero_patch() { - let version = AppVersion.from("3.5.0_333")! - - XCTAssertEqual(version.tagged, "3.5") - XCTAssertEqual(version.version, "3.5.0") - } - - func test_tagged_release_doesnt_omit_non_zero_patch() { - let version = AppVersion.from("3.5.1_333")! - - XCTAssertEqual(version.tagged, "3.5.1") - XCTAssertEqual(version.version, "3.5.1") - } - - func test_tag_truncation_does_not_affect_major_versions() { - var version = AppVersion.from("5.0_333")! - - XCTAssertEqual(version.tagged, "5.0") - XCTAssertEqual(version.version, "5.0") - - version = AppVersion.from("5.0.0_333")! - - XCTAssertEqual(version.tagged, "5.0") - XCTAssertEqual(version.version, "5.0.0") - } - -} diff --git a/tests/unit/Versions/AppVersionTest.swift b/tests/unit/Versions/AppVersionTest.swift index c2ac087..2993ee7 100644 --- a/tests/unit/Versions/AppVersionTest.swift +++ b/tests/unit/Versions/AppVersionTest.swift @@ -28,7 +28,7 @@ class AppVersionTest: XCTestCase { XCTAssertNotNil(version) XCTAssertEqual("1.0.0", version?.version) - XCTAssertEqual("600", version?.build) + XCTAssertEqual(600, version?.build) XCTAssertEqual(nil, version?.suffix) } @@ -46,7 +46,7 @@ class AppVersionTest: XCTestCase { XCTAssertNotNil(version) XCTAssertEqual("1.0.0", version?.version) - XCTAssertEqual("870", version?.build) + XCTAssertEqual(870, version?.build) XCTAssertEqual("dev", version?.suffix) } @@ -55,8 +55,48 @@ class AppVersionTest: XCTestCase { XCTAssertNotNil(version) XCTAssertEqual("1.0.0", version?.version) - XCTAssertEqual("870", version?.build) + XCTAssertEqual(870, version?.build) XCTAssertEqual("dev", version?.suffix) } + func test_tagged_release_omits_zero_patch() { + let version = AppVersion.from("3.5.0_333")! + + XCTAssertEqual(version.tagged, "3.5") + XCTAssertEqual(version.version, "3.5.0") + } + + func test_tagged_release_doesnt_omit_non_zero_patch() { + let version = AppVersion.from("3.5.1_333")! + + XCTAssertEqual(version.tagged, "3.5.1") + XCTAssertEqual(version.version, "3.5.1") + } + + func test_tag_truncation_does_not_affect_major_versions() { + var version = AppVersion.from("5.0_333")! + + XCTAssertEqual(version.tagged, "5.0") + XCTAssertEqual(version.version, "5.0") + + version = AppVersion.from("5.0.0_333")! + + XCTAssertEqual(version.tagged, "5.0") + XCTAssertEqual(version.version, "5.0.0") + } + + func test_can_compare_version_numbers() { + // Build is newer + XCTAssertTrue(AppVersion.from("5.0_101")! > AppVersion.from("5.0_100")!) + + // Version and build is the same + XCTAssertFalse(AppVersion.from("5.0.0_100")! > AppVersion.from("5.0_100")!) + + // Version is newer + XCTAssertTrue(AppVersion.from("5.1_100")! > AppVersion.from("5.0_100")!) + + // Build is older + XCTAssertFalse(AppVersion.from("5.0_101")! > AppVersion.from("5.0_102")!) + } + }