mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-12-21 03:10:06 +01:00
♻️ Reworked how getting a CaskFile via URL works
This commit is contained in:
@@ -84,6 +84,11 @@
|
|||||||
039E1D7A2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
|
039E1D7A2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
|
||||||
039E1D7B2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
|
039E1D7B2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
|
||||||
039E1D7C2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
|
039E1D7C2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
|
||||||
|
03ACC6452ECCAB190070D4CD /* RealWebApiTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03ACC6442ECCAB140070D4CD /* RealWebApiTest.swift */; };
|
||||||
|
03ACC6472ECCBA130070D4CD /* CaskFile+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03ACC6462ECCBA100070D4CD /* CaskFile+API.swift */; };
|
||||||
|
03ACC6482ECCBA130070D4CD /* CaskFile+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03ACC6462ECCBA100070D4CD /* CaskFile+API.swift */; };
|
||||||
|
03ACC6492ECCBA130070D4CD /* CaskFile+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03ACC6462ECCBA100070D4CD /* CaskFile+API.swift */; };
|
||||||
|
03ACC64A2ECCBA130070D4CD /* CaskFile+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03ACC6462ECCBA100070D4CD /* CaskFile+API.swift */; };
|
||||||
03B675E92EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; };
|
03B675E92EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; };
|
||||||
03B675EA2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; };
|
03B675EA2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; };
|
||||||
03B675EB2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; };
|
03B675EB2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; };
|
||||||
@@ -1038,6 +1043,8 @@
|
|||||||
039C29172E8AA311007F5FAB /* TestableWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApi.swift; sourceTree = "<group>"; };
|
039C29172E8AA311007F5FAB /* TestableWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApi.swift; sourceTree = "<group>"; };
|
||||||
039C291C2E8AA399007F5FAB /* TestableWebApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApiTest.swift; sourceTree = "<group>"; };
|
039C291C2E8AA399007F5FAB /* TestableWebApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApiTest.swift; sourceTree = "<group>"; };
|
||||||
039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetUpgrader.swift; sourceTree = "<group>"; };
|
039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetUpgrader.swift; sourceTree = "<group>"; };
|
||||||
|
03ACC6442ECCAB140070D4CD /* RealWebApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealWebApiTest.swift; sourceTree = "<group>"; };
|
||||||
|
03ACC6462ECCBA100070D4CD /* CaskFile+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaskFile+API.swift"; sourceTree = "<group>"; };
|
||||||
03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = "<group>"; };
|
03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = "<group>"; };
|
||||||
03BFF5262E312C39007F96FA /* Startup+Timers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Startup+Timers.swift"; sourceTree = "<group>"; };
|
03BFF5262E312C39007F96FA /* Startup+Timers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Startup+Timers.swift"; sourceTree = "<group>"; };
|
||||||
03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Driver.swift"; sourceTree = "<group>"; };
|
03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Driver.swift"; sourceTree = "<group>"; };
|
||||||
@@ -1455,11 +1462,19 @@
|
|||||||
039C291E2E8AA39B007F5FAB /* Api */ = {
|
039C291E2E8AA39B007F5FAB /* Api */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
039C291C2E8AA399007F5FAB /* TestableWebApiTest.swift */,
|
|
||||||
);
|
);
|
||||||
path = Api;
|
path = Api;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
03ACC6432ECCAAF70070D4CD /* WebApi */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
039C291C2E8AA399007F5FAB /* TestableWebApiTest.swift */,
|
||||||
|
03ACC6442ECCAB140070D4CD /* RealWebApiTest.swift */,
|
||||||
|
);
|
||||||
|
path = WebApi;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
03BFF1D12E3CF4F2004C56A9 /* Provision */ = {
|
03BFF1D12E3CF4F2004C56A9 /* Provision */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1485,6 +1500,7 @@
|
|||||||
C4E2E84628FC1D8C003B070C /* TestableConfigurationTest.swift */,
|
C4E2E84628FC1D8C003B070C /* TestableConfigurationTest.swift */,
|
||||||
C413E43328DA3E8F00AE33C7 /* Shell */,
|
C413E43328DA3E8F00AE33C7 /* Shell */,
|
||||||
C471E6DA28F9AFCB0021E251 /* Filesystem */,
|
C471E6DA28F9AFCB0021E251 /* Filesystem */,
|
||||||
|
03ACC6432ECCAAF70070D4CD /* WebApi */,
|
||||||
);
|
);
|
||||||
path = Testables;
|
path = Testables;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2145,6 +2161,7 @@
|
|||||||
C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */,
|
C4B79EB529CA387F00A483EE /* BrewPhpFormulaeHandler.swift */,
|
||||||
C4F2E4362752F0870020E974 /* BrewDiagnostics.swift */,
|
C4F2E4362752F0870020E974 /* BrewDiagnostics.swift */,
|
||||||
C40934A1298EEB2C00D25014 /* CaskFile.swift */,
|
C40934A1298EEB2C00D25014 /* CaskFile.swift */,
|
||||||
|
03ACC6462ECCBA100070D4CD /* CaskFile+API.swift */,
|
||||||
C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */,
|
C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */,
|
||||||
031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */,
|
031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */,
|
||||||
);
|
);
|
||||||
@@ -2914,6 +2931,7 @@
|
|||||||
C45D654C29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */,
|
C45D654C29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */,
|
||||||
C4D3660B29113F20006BD146 /* System.swift in Sources */,
|
C4D3660B29113F20006BD146 /* System.swift in Sources */,
|
||||||
C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */,
|
C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */,
|
||||||
|
03ACC6472ECCBA130070D4CD /* CaskFile+API.swift in Sources */,
|
||||||
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
|
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
|
||||||
C40C7F1E2772136000DDDCDC /* PhpEnvironments.swift in Sources */,
|
C40C7F1E2772136000DDDCDC /* PhpEnvironments.swift in Sources */,
|
||||||
C4B79EB629CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
|
C4B79EB629CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
|
||||||
@@ -3143,6 +3161,7 @@
|
|||||||
C441CC582AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
|
C441CC582AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
|
||||||
C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */,
|
C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */,
|
||||||
C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */,
|
C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */,
|
||||||
|
03ACC6482ECCBA130070D4CD /* CaskFile+API.swift in Sources */,
|
||||||
C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */,
|
C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */,
|
||||||
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
|
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
|
||||||
C471E82728F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
|
C471E82728F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
|
||||||
@@ -3251,6 +3270,7 @@
|
|||||||
C471E8B428F9BB8F0021E251 /* MainMenu+Switcher.swift in Sources */,
|
C471E8B428F9BB8F0021E251 /* MainMenu+Switcher.swift in Sources */,
|
||||||
C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */,
|
C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */,
|
||||||
C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */,
|
C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */,
|
||||||
|
03ACC64A2ECCBA130070D4CD /* CaskFile+API.swift in Sources */,
|
||||||
C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */,
|
C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */,
|
||||||
032DAC302E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
|
032DAC302E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
|
||||||
C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */,
|
C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */,
|
||||||
@@ -3547,6 +3567,7 @@
|
|||||||
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */,
|
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */,
|
||||||
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
|
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
|
||||||
C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */,
|
C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */,
|
||||||
|
03ACC6492ECCBA130070D4CD /* CaskFile+API.swift in Sources */,
|
||||||
C40C5C9D2846A40600E28255 /* Preset.swift in Sources */,
|
C40C5C9D2846A40600E28255 /* Preset.swift in Sources */,
|
||||||
C4CE7F9729683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
|
C4CE7F9729683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
|
||||||
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
|
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
|
||||||
@@ -3557,6 +3578,7 @@
|
|||||||
C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */,
|
C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */,
|
||||||
C4611E612AEAD3110010BE24 /* ByteLimitView.swift in Sources */,
|
C4611E612AEAD3110010BE24 /* ByteLimitView.swift in Sources */,
|
||||||
C40175B92903108900763A68 /* ValetInteractor.swift in Sources */,
|
C40175B92903108900763A68 /* ValetInteractor.swift in Sources */,
|
||||||
|
03ACC6452ECCAB190070D4CD /* RealWebApiTest.swift in Sources */,
|
||||||
039E1D792E5F0F300072D13D /* ValetUpgrader.swift in Sources */,
|
039E1D792E5F0F300072D13D /* ValetUpgrader.swift in Sources */,
|
||||||
C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */,
|
C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */,
|
||||||
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
|
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
|
||||||
|
|||||||
@@ -11,15 +11,81 @@ import Foundation
|
|||||||
class RealWebApi: WebApiProtocol {
|
class RealWebApi: WebApiProtocol {
|
||||||
var container: Container
|
var container: Container
|
||||||
|
|
||||||
|
var defaultHeaders: HttpHeaders {
|
||||||
|
return [
|
||||||
|
"User-Agent": "phpmon-nur/2.0",
|
||||||
|
"X-phpmon-version": "\(App.shortVersion) (\(App.bundleVersion))",
|
||||||
|
"X-phpmon-os-version": "\(App.macVersion)",
|
||||||
|
"X-phpmon-bundle-id": "\(App.identifier)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
init(container: Container) {
|
init(container: Container) {
|
||||||
self.container = container
|
self.container = container
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(_ url: URL, withHeaders: HttpHeaders, withTimeout: TimeInterval) async throws -> WebApiResponse {
|
private func request(
|
||||||
return WebApiResponse(statusCode: 200, headers: [:], data: Data())
|
url: URL,
|
||||||
|
method: String,
|
||||||
|
data: Data?,
|
||||||
|
headers: HttpHeaders,
|
||||||
|
timeout: TimeInterval
|
||||||
|
) async throws -> WebApiResponse {
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.httpMethod = method
|
||||||
|
request.timeoutInterval = timeout
|
||||||
|
for header in headers {
|
||||||
|
request.setValue(header.value, forHTTPHeaderField: header.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let (data, response) = try await URLSession.shared.data(for: request)
|
||||||
|
|
||||||
|
guard let response = response as? HTTPURLResponse else {
|
||||||
|
throw WebApiError.networkError
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebApiResponse(
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
headers: response.allHeaderFields as! HttpHeaders,
|
||||||
|
data: data
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
if let urlError = error as? URLError {
|
||||||
|
if urlError.code == .timedOut {
|
||||||
|
throw WebApiError.timedOut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw WebApiError.networkError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func post(_ url: URL, withHeaders: HttpHeaders, withData: String, withTimeout: TimeInterval) async throws -> WebApiResponse {
|
func get(
|
||||||
return WebApiResponse(statusCode: 200, headers: [:], data: Data())
|
_ url: URL,
|
||||||
|
withHeaders headers: HttpHeaders = [:],
|
||||||
|
withTimeout timeout: TimeInterval = URLSession.shared.configuration.timeoutIntervalForRequest
|
||||||
|
) async throws -> WebApiResponse {
|
||||||
|
try await self.request(
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
|
data: nil,
|
||||||
|
headers: headers,
|
||||||
|
timeout: timeout
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func post(
|
||||||
|
_ url: URL,
|
||||||
|
withHeaders headers: HttpHeaders,
|
||||||
|
withData data: String,
|
||||||
|
withTimeout timeout: TimeInterval
|
||||||
|
) async throws -> WebApiResponse {
|
||||||
|
try await self.request(
|
||||||
|
url: url,
|
||||||
|
method: "POST",
|
||||||
|
data: data.data(using: .utf8),
|
||||||
|
headers: headers,
|
||||||
|
timeout: timeout
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,33 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class TestableWebApi: WebApiProtocol {
|
class TestableWebApi: WebApiProtocol {
|
||||||
|
|
||||||
|
// MARK: - Internal Fake Responses
|
||||||
|
|
||||||
private var fakeGetResponses: [URL: FakeWebApiResponse] = [:]
|
private var fakeGetResponses: [URL: FakeWebApiResponse] = [:]
|
||||||
private var fakePostResponses: [URL: FakeWebApiResponse] = [:]
|
private var fakePostResponses: [URL: FakeWebApiResponse] = [:]
|
||||||
|
|
||||||
|
// MARK: - Slow Mode
|
||||||
|
|
||||||
private var slow: Bool = false
|
private var slow: Bool = false
|
||||||
|
|
||||||
public func setSlowMode(_ slow: Bool) {
|
public func setSlowMode(_ slow: Bool) {
|
||||||
self.slow = slow
|
self.slow = slow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Default Headers
|
||||||
|
|
||||||
|
var defaultHeaders: HttpHeaders {
|
||||||
|
return [
|
||||||
|
"User-Agent": "phpmon-nur/2.0",
|
||||||
|
"X-phpmon-version": "\(App.shortVersion) (\(App.bundleVersion))",
|
||||||
|
"X-phpmon-os-version": "\(App.macVersion)",
|
||||||
|
"X-phpmon-bundle-id": "\(App.identifier)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Constructor
|
||||||
|
|
||||||
init(
|
init(
|
||||||
getResponses: [URL: FakeWebApiResponse],
|
getResponses: [URL: FakeWebApiResponse],
|
||||||
postResponses: [URL: FakeWebApiResponse]
|
postResponses: [URL: FakeWebApiResponse]
|
||||||
@@ -25,6 +44,8 @@ class TestableWebApi: WebApiProtocol {
|
|||||||
self.fakePostResponses = postResponses
|
self.fakePostResponses = postResponses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Public API
|
||||||
|
|
||||||
public func hasGetResponse(for url: URL) -> Bool {
|
public func hasGetResponse(for url: URL) -> Bool {
|
||||||
return fakeGetResponses.keys.contains(url)
|
return fakeGetResponses.keys.contains(url)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ enum WebApiError: Error {
|
|||||||
case invalidURL
|
case invalidURL
|
||||||
case networkError
|
case networkError
|
||||||
case timedOut
|
case timedOut
|
||||||
|
case other
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WebApiResponse {
|
struct WebApiResponse {
|
||||||
@@ -37,6 +38,8 @@ struct WebApiResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protocol WebApiProtocol {
|
protocol WebApiProtocol {
|
||||||
|
var defaultHeaders: HttpHeaders { get }
|
||||||
|
|
||||||
func get(
|
func get(
|
||||||
_ url: URL,
|
_ url: URL,
|
||||||
withHeaders headers: HttpHeaders,
|
withHeaders headers: HttpHeaders,
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ class AppUpdater {
|
|||||||
|
|
||||||
let caskUrl = Constants.Urls.UpdateCheckEndpoint
|
let caskUrl = Constants.Urls.UpdateCheckEndpoint
|
||||||
|
|
||||||
guard let caskFile = await CaskFile.from(App.shared.container, url: caskUrl) else {
|
guard let caskFile = try? await CaskFile.fromUrl(App.shared.container, caskUrl) else {
|
||||||
Log.err("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.")
|
|
||||||
presentCouldNotRetrieveUpdateIfInteractive()
|
presentCouldNotRetrieveUpdateIfInteractive()
|
||||||
return .networkError
|
return .networkError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,15 +101,11 @@ class CrashReporter {
|
|||||||
request.setValue("text/crash", forHTTPHeaderField: "Content-Type")
|
request.setValue("text/crash", forHTTPHeaderField: "Content-Type")
|
||||||
request.setValue("phpmon-crashrep/1.0", forHTTPHeaderField: "User-Agent")
|
request.setValue("phpmon-crashrep/1.0", forHTTPHeaderField: "User-Agent")
|
||||||
request.httpBody = text.data(using: .utf8)
|
request.httpBody = text.data(using: .utf8)
|
||||||
|
request.timeoutInterval = timeout
|
||||||
|
|
||||||
// Send the request synchronously, we want the report to be sent before anything else
|
|
||||||
let config = URLSessionConfiguration.default
|
|
||||||
config.timeoutIntervalForRequest = timeout
|
|
||||||
|
|
||||||
let session = URLSession(configuration: config)
|
|
||||||
let semaphore = DispatchSemaphore(value: 0)
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
|
||||||
let task = session.dataTask(with: request) { _, response, error in
|
let task = URLSession.shared.dataTask(with: request) { _, response, error in
|
||||||
defer { semaphore.signal() }
|
defer { semaphore.signal() }
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
|
|||||||
52
phpmon/Domain/Integrations/Homebrew/CaskFile+API.swift
Normal file
52
phpmon/Domain/Integrations/Homebrew/CaskFile+API.swift
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// CaskFile+API.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 18/11/2025.
|
||||||
|
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum CaskFileError: Error {
|
||||||
|
case requestFailed
|
||||||
|
case invalidData
|
||||||
|
case invalidFile
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CaskFile {
|
||||||
|
public static func fromUrl(
|
||||||
|
_ container: Container,
|
||||||
|
_ url: URL
|
||||||
|
) async throws -> CaskFile? {
|
||||||
|
// First, determine if we're loading a local URL or not
|
||||||
|
if url.scheme == "file" {
|
||||||
|
if let string = try? container.filesystem.getStringFromFile(url.relativePath) {
|
||||||
|
return CaskFile.from(string)
|
||||||
|
} else {
|
||||||
|
throw CaskFileError.invalidFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a URL via the network we need to know if to use the complex API or not
|
||||||
|
if isRunningTests || App.hasLoadedTestableConfiguration || url.absoluteString.contains("https://raw.githubusercontent.com") {
|
||||||
|
// For testing simplicity, we will use curl, since we need no complex rules or headers
|
||||||
|
return CaskFile.from(await container.shell.pipe("curl -s --max-time 10 '\(url.absoluteString)'").out)
|
||||||
|
} else {
|
||||||
|
// However, for the real deal, we will use the Web API
|
||||||
|
guard let response = try? await container.webApi.get(
|
||||||
|
url,
|
||||||
|
withHeaders: container.webApi.defaultHeaders,
|
||||||
|
withTimeout: .seconds(10)
|
||||||
|
) else {
|
||||||
|
throw CaskFileError.requestFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let text = response.plainText else {
|
||||||
|
throw CaskFileError.invalidData
|
||||||
|
}
|
||||||
|
|
||||||
|
return CaskFile.from(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,35 +24,7 @@ struct CaskFile {
|
|||||||
return self.properties["version"]!
|
return self.properties["version"]!
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func loadFromApi(_ container: Container, _ url: URL) async -> String {
|
public static func from(_ string: String) -> CaskFile? {
|
||||||
if isRunningTests || App.hasLoadedTestableConfiguration || url.absoluteString.contains("https://raw.githubusercontent.com") {
|
|
||||||
return await container.shell.pipe("curl -s --max-time 10 '\(url.absoluteString)'").out
|
|
||||||
} else {
|
|
||||||
return await container.shell.pipe("""
|
|
||||||
curl -s --max-time 10 \
|
|
||||||
-H "User-Agent: phpmon-curl/1.0" \
|
|
||||||
-H "X-phpmon-version: \(App.shortVersion) (\(App.bundleVersion))" \
|
|
||||||
-H "X-phpmon-os-version: \(App.macVersion)" \
|
|
||||||
-H "X-phpmon-bundle-id: \(App.identifier)" \
|
|
||||||
'\(url.absoluteString)'
|
|
||||||
""").out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func from(_ container: Container, url: URL) async -> CaskFile? {
|
|
||||||
var string: String?
|
|
||||||
|
|
||||||
if url.scheme == "file" {
|
|
||||||
string = try? String(contentsOf: url)
|
|
||||||
} else {
|
|
||||||
string = await CaskFile.loadFromApi(container, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
let lines = string.split(separator: "\n")
|
||||||
.map { line in
|
.map { line in
|
||||||
return line.trimmingCharacters(in: .whitespacesAndNewlines)
|
return line.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct CaskFileParserTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func can_extract_fields_from_cask_file() async throws {
|
@Test func can_extract_fields_from_cask_file() async throws {
|
||||||
guard let caskFile = await CaskFile.from(container, url: CaskFileParserTest.exampleFilePath) else {
|
guard let caskFile = try? await CaskFile.fromUrl(container, CaskFileParserTest.exampleFilePath) else {
|
||||||
Issue.record("The CaskFile could not be parsed, check the log for more info")
|
Issue.record("The CaskFile could not be parsed, check the log for more info")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ struct CaskFileParserTest {
|
|||||||
@Test func can_extract_fields_from_remote_cask_file() async throws {
|
@Test func can_extract_fields_from_remote_cask_file() async throws {
|
||||||
let url = URL(string: "https://raw.githubusercontent.com/nicoverbruggen/homebrew-cask/master/Casks/phpmon.rb")!
|
let url = URL(string: "https://raw.githubusercontent.com/nicoverbruggen/homebrew-cask/master/Casks/phpmon.rb")!
|
||||||
|
|
||||||
guard let caskFile = await CaskFile.from(container, url: url) else {
|
guard let caskFile = try? await CaskFile.fromUrl(container, url) else {
|
||||||
Issue.record("The remote CaskFile could not be parsed, check the log for more info")
|
Issue.record("The remote CaskFile could not be parsed, check the log for more info")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
41
tests/unit/Testables/WebApi/RealWebApiTest.swift
Normal file
41
tests/unit/Testables/WebApi/RealWebApiTest.swift
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// RealWebApiTest.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 18/11/2025.
|
||||||
|
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Testing
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct RealWebApiTest {
|
||||||
|
private var container: Container
|
||||||
|
|
||||||
|
init() throws {
|
||||||
|
self.container = Container()
|
||||||
|
container.bind()
|
||||||
|
}
|
||||||
|
|
||||||
|
var WebApi: RealWebApi {
|
||||||
|
return container.webApi as! RealWebApi
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func requestSucceeds() async {
|
||||||
|
let response = try! await WebApi.get(
|
||||||
|
url("https://api.phpmon.test/up")
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(response.statusCode == 200)
|
||||||
|
#expect(response.plainText!.contains("Response rendered in"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func requestTimesOut() async {
|
||||||
|
await #expect(throws: WebApiError.timedOut) {
|
||||||
|
try await WebApi.get(
|
||||||
|
url("https://api.phpmon.test/up"),
|
||||||
|
withTimeout: .seconds(0.01)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user