From 4ef8428151bdf28a8f96c63e3c2353520b91bf79 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Thu, 24 Dec 2020 11:08:13 -0700 Subject: [PATCH] Convert XcodeList to Combine --- Xcodes/Backend/AppState.swift | 22 ++++------- Xcodes/Backend/Environment.swift | 8 ++-- Xcodes/Backend/XcodeList.swift | 67 ++++++++++++++++---------------- 3 files changed, 45 insertions(+), 52 deletions(-) diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index a554e15..3aac23c 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -2,7 +2,6 @@ import AppKit import AppleAPI import Combine import Path -import PromiseKit import LegibleError import KeychainAccess @@ -173,22 +172,15 @@ class AppState: ObservableObject { public func update() -> AnyPublisher<[Xcode], Never> { signInIfNeeded() .flatMap { - // Wrap the Promise API in a Publisher for now - Deferred { - Future { promise in - self.list.update() - .done { promise(.success($0)) } - .catch { promise(.failure($0)) } + self.list.update() + } + .handleEvents( + receiveCompletion: { completion in + if case let .failure(error) = completion { + self.error = AlertContent(title: "Update Error", message: error.legibleLocalizedDescription) } } - .handleEvents( - receiveCompletion: { completion in - if case let .failure(error) = completion { - self.error = AlertContent(title: "Update Error", message: error.legibleLocalizedDescription) - } - } - ) - } + ) .catch { _ in Just(self.list.availableXcodes) } diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index 166cbab..c0c6c73 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -111,10 +111,10 @@ private func _installedXcodes(destination: Path) -> [InstalledXcode] { public struct Network { private static let client = AppleAPI.Client() - - public var dataTask: (URLRequestConvertible) -> Promise<(data: Data, response: URLResponse)> = { AppleAPI.Current.network.session.dataTask(.promise, with: $0) } - public func dataTask(with convertible: URLRequestConvertible) -> Promise<(data: Data, response: URLResponse)> { - dataTask(convertible) + + public var dataTask: (URLRequest) -> URLSession.DataTaskPublisher = { AppleAPI.Current.network.session.dataTaskPublisher(for: $0) } + public func dataTask(with request: URLRequest) -> URLSession.DataTaskPublisher { + dataTask(request) } public var downloadTask: (URLRequestConvertible, URL, Data?) -> (Progress, Promise<(saveLocation: URL, response: URLResponse)>) = { AppleAPI.Current.network.session.downloadTask(with: $0, to: $1, resumingWith: $2) } diff --git a/Xcodes/Backend/XcodeList.swift b/Xcodes/Backend/XcodeList.swift index dae4046..25967d6 100644 --- a/Xcodes/Backend/XcodeList.swift +++ b/Xcodes/Backend/XcodeList.swift @@ -1,10 +1,10 @@ +import Combine import Foundation import Path import Version -import PromiseKit import SwiftSoup -/// Provides lists of available and installed Xcodes +/// Provides lists of available Xcodes public final class XcodeList { public init() { try? loadCachedAvailableXcodes() @@ -16,8 +16,9 @@ public final class XcodeList { return availableXcodes.isEmpty } - public func update() -> Promise<[Xcode]> { - return when(fulfilled: releasedXcodes(), prereleaseXcodes()) + public func update() -> AnyPublisher<[Xcode], Error> { + releasedXcodes().combineLatest(prereleaseXcodes()) + .receive(on: DispatchQueue.main) .map { releasedXcodes, prereleaseXcodes in // Starting with Xcode 11 beta 6, developer.apple.com/download and developer.apple.com/download/more both list some pre-release versions of Xcode. // Previously pre-release versions only appeared on developer.apple.com/download. @@ -30,6 +31,7 @@ public final class XcodeList { try? self.cacheAvailableXcodes(xcodes) return xcodes } + .eraseToAnyPublisher() } } @@ -49,38 +51,37 @@ extension XcodeList { } extension XcodeList { - private func releasedXcodes() -> Promise<[Xcode]> { - return firstly { () -> Promise<(data: Data, response: URLResponse)> in - Current.network.dataTask(with: URLRequest.downloads) - } - .map { (data, response) -> [Xcode] in - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(.downloadsDateModified) - let downloads = try decoder.decode(Downloads.self, from: data) - let xcodes = downloads - .downloads - .filter { $0.name.range(of: "^Xcode [0-9]", options: .regularExpression) != nil } - .compactMap { download -> Xcode? in - let urlPrefix = URL(string: "https://download.developer.apple.com/")! - guard - let xcodeFile = download.files.first(where: { $0.remotePath.hasSuffix("dmg") || $0.remotePath.hasSuffix("xip") }), - let version = Version(xcodeVersion: download.name) - else { return nil } + private func releasedXcodes() -> AnyPublisher<[Xcode], Error> { + Current.network.dataTask(with: URLRequest.downloads) + .map(\.data) + .decode(type: Downloads.self, decoder: configure(JSONDecoder()) { + $0.dateDecodingStrategy = .formatted(.downloadsDateModified) + }) + .map { downloads -> [Xcode] in + let xcodes = downloads + .downloads + .filter { $0.name.range(of: "^Xcode [0-9]", options: .regularExpression) != nil } + .compactMap { download -> Xcode? in + let urlPrefix = URL(string: "https://download.developer.apple.com/")! + guard + let xcodeFile = download.files.first(where: { $0.remotePath.hasSuffix("dmg") || $0.remotePath.hasSuffix("xip") }), + let version = Version(xcodeVersion: download.name) + else { return nil } - let url = urlPrefix.appendingPathComponent(xcodeFile.remotePath) - return Xcode(version: version, url: url, filename: String(xcodeFile.remotePath.suffix(fromLast: "/")), releaseDate: download.dateModified) - } - return xcodes - } + let url = urlPrefix.appendingPathComponent(xcodeFile.remotePath) + return Xcode(version: version, url: url, filename: String(xcodeFile.remotePath.suffix(fromLast: "/")), releaseDate: download.dateModified) + } + return xcodes + } + .eraseToAnyPublisher() } - private func prereleaseXcodes() -> Promise<[Xcode]> { - return firstly { () -> Promise<(data: Data, response: URLResponse)> in - Current.network.dataTask(with: URLRequest.download) - } - .map { (data, _) -> [Xcode] in - try self.parsePrereleaseXcodes(from: data) - } + private func prereleaseXcodes() -> AnyPublisher<[Xcode], Error> { + Current.network.dataTask(with: URLRequest.download) + .tryMap { (data, _) -> [Xcode] in + try self.parsePrereleaseXcodes(from: data) + } + .eraseToAnyPublisher() } func parsePrereleaseXcodes(from data: Data) throws -> [Xcode] {