diff --git a/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift b/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift index a76580c..c8e2810 100644 --- a/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift +++ b/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift @@ -200,6 +200,7 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable { case badStatusCode(statusCode: Int, data: Data, response: HTTPURLResponse) case notDeveloperAppleId case notAuthorized + case invalidResult(resultString: String?) public var errorDescription: String? { switch self { @@ -230,6 +231,8 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable { return "You are not registered as an Apple Developer. Please visit Apple Developer Registration. https://developer.apple.com/register/" case .notAuthorized: return "You are not authorized. Please Sign in with your Apple ID first." + case let .invalidResult(resultString): + return resultString ?? "If you continue to have problems, please submit a bug report in the Help menu." } } } diff --git a/Xcodes/Backend/AppState+Update.swift b/Xcodes/Backend/AppState+Update.swift index ed998af..58b3626 100644 --- a/Xcodes/Backend/AppState+Update.swift +++ b/Xcodes/Backend/AppState+Update.swift @@ -130,15 +130,20 @@ extension AppState { extension AppState { // MARK: - Apple - private func releasedXcodes() -> AnyPublisher<[AvailableXcode], Error> { + private func releasedXcodes() -> AnyPublisher<[AvailableXcode], Swift.Error> { Current.network.dataTask(with: URLRequest.downloads) .map(\.data) .decode(type: Downloads.self, decoder: configure(JSONDecoder()) { $0.dateDecodingStrategy = .formatted(.downloadsDateModified) }) - .map { downloads -> [AvailableXcode] in - let xcodes = downloads - .downloads + .tryMap { downloads -> [AvailableXcode] in + if downloads.hasError { + throw AuthenticationError.invalidResult(resultString: downloads.resultsString) + } + guard let downloadList = downloads.downloads else { + throw AuthenticationError.invalidResult(resultString: "No download information found") + } + let xcodes = downloadList .filter { $0.name.range(of: "^Xcode [0-9]", options: .regularExpression) != nil } .compactMap { download -> AvailableXcode? in let urlPrefix = URL(string: "https://download.developer.apple.com/")! diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index f6090cb..78755e6 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -337,8 +337,18 @@ class AppState: ObservableObject { // We need the cookies from its response in order to download Xcodes though, // so perform it here first just to be sure. Current.network.dataTask(with: URLRequest.downloads) - .receive(on: DispatchQueue.main) - .map { _ in Void() } + .map(\.data) + .decode(type: Downloads.self, decoder: configure(JSONDecoder()) { + $0.dateDecodingStrategy = .formatted(.downloadsDateModified) + }) + .tryMap { downloads -> Void in + if downloads.hasError { + throw AuthenticationError.invalidResult(resultString: downloads.resultsString) + } + if downloads.downloads == nil { + throw AuthenticationError.invalidResult(resultString: "No download information found") + } + } .mapError { $0 as Error } } .flatMap { [unowned self] in diff --git a/Xcodes/Backend/Downloads.swift b/Xcodes/Backend/Downloads.swift index 55096e4..aab9779 100644 --- a/Xcodes/Backend/Downloads.swift +++ b/Xcodes/Backend/Downloads.swift @@ -3,7 +3,13 @@ import Path import Version struct Downloads: Codable { - let downloads: [Download] + let resultCode: Int + let resultsString: String? + let downloads: [Download]? + + var hasError: Bool { + return resultCode != 0 + } } // Set to Int64 as ByteCountFormatter uses it. diff --git a/Xcodes/Resources/Licenses.rtf b/Xcodes/Resources/Licenses.rtf index 58f4a3d..96bf513 100644 --- a/Xcodes/Resources/Licenses.rtf +++ b/Xcodes/Resources/Licenses.rtf @@ -1,4 +1,4 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2578 +{\rtf1\ansi\ansicpg1252\cocoartf2580 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;} {\colortbl;\red255\green255\blue255;} {\*\expandedcolortbl;;} @@ -363,73 +363,6 @@ SOFTWARE.\ \ \ -\fs34 Sparkle\ -\ - -\fs26 Copyright (c) 2006-2013 Andy Matuschak.\ -Copyright (c) 2009-2013 Elgato Systems GmbH.\ -Copyright (c) 2011-2014 Kornel Lesi\uc0\u324 ski.\ -Copyright (c) 2015-2017 Mayur Pawashe.\ -Copyright (c) 2014 C.W. Betts.\ -Copyright (c) 2014 Petroules Corporation.\ -Copyright (c) 2014 Big Nerd Ranch.\ -All rights reserved.\ -\ -Permission is hereby granted, free of charge, to any person obtaining a copy of\ -this software and associated documentation files (the "Software"), to deal in\ -the Software without restriction, including without limitation the rights to\ -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\ -the Software, and to permit persons to whom the Software is furnished to do so,\ -subject to the following conditions:\ -\ -The above copyright notice and this permission notice shall be included in all\ -copies or substantial portions of the Software.\ -\ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\ -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\ -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\ -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\ -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ -\ -=================\ -EXTERNAL LICENSES\ -=================\ -\ -bspatch.c and bsdiff.c, from bsdiff 4.3 :\ - Copyright (c) 2003-2005 Colin Percival.\ -\ -sais.c and sais.c, from sais-lite (2010/08/07) :\ - Copyright (c) 2008-2010 Yuta Mori.\ -\ -SUDSAVerifier.m:\ - Copyright (c) 2011 Mark Hamlin.\ -\ -All rights reserved.\ -\ -Redistribution and use in source and binary forms, with or without\ -modification, are permitted providing that the following conditions\ -are met:\ -1. Redistributions of source code must retain the above copyright\ - notice, this list of conditions and the following disclaimer.\ -2. Redistributions in binary form must reproduce the above copyright\ - notice, this list of conditions and the following disclaimer in the\ - documentation and/or other materials provided with the distribution.\ -\ -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\ -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\ -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\ -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\ -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\ -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\ -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\ -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\ -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\ -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\ -POSSIBILITY OF SUCH DAMAGE.\ -\ -\ - \fs34 LegibleError\ \ diff --git a/XcodesTests/AppStateTests.swift b/XcodesTests/AppStateTests.swift index c708d9d..ce38607 100644 --- a/XcodesTests/AppStateTests.swift +++ b/XcodesTests/AppStateTests.swift @@ -93,7 +93,7 @@ class AppStateTests: XCTestCase { } // It's an available release version else if urlRequest.url! == URLRequest.downloads.url! { - let downloads = Downloads(downloads: [Download(name: "Xcode 0.0.0", files: [Download.File(remotePath: "https://apple.com/xcode.xip", fileSize: 9484444)], dateModified: Date())]) + let downloads = Downloads(resultCode: 0, resultsString: nil, downloads: [Download(name: "Xcode 0.0.0", files: [Download.File(remotePath: "https://apple.com/xcode.xip", fileSize: 9484444)], dateModified: Date())]) let encoder = JSONEncoder() encoder.dateEncodingStrategy = .formatted(.downloadsDateModified) let downloadsData = try! encoder.encode(downloads) @@ -203,7 +203,7 @@ class AppStateTests: XCTestCase { } // It's an available release version else if urlRequest.url! == URLRequest.downloads.url! { - let downloads = Downloads(downloads: [Download(name: "Xcode 0.0.0", files: [Download.File(remotePath: "https://apple.com/xcode.xip", fileSize: 9494944)], dateModified: Date())]) + let downloads = Downloads(resultCode: 0, resultsString: nil, downloads: [Download(name: "Xcode 0.0.0", files: [Download.File(remotePath: "https://apple.com/xcode.xip", fileSize: 9494944)], dateModified: Date())]) let encoder = JSONEncoder() encoder.dateEncodingStrategy = .formatted(.downloadsDateModified) let downloadsData = try! encoder.encode(downloads)