Skip to content

Commit

Permalink
Merge pull request #290 from RobotsAndPencils/NoLoginDownload
Browse files Browse the repository at this point in the history
Add ability to download Xcode without logging in using XcodeReleases
  • Loading branch information
MattKiazyk authored Sep 17, 2022
2 parents e987104 + 8b43903 commit c27b288
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 15 deletions.
24 changes: 11 additions & 13 deletions Xcodes/Backend/AppState+Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,10 @@ extension AppState {
}

private func install(_ installationType: InstallationType, downloader: Downloader, attemptNumber: Int) -> AnyPublisher<InstalledXcode, Error> {
// We need to check if the Apple ID that is logged in is an Apple Developer
// Since users can use xcodereleases, we don't check for Apple ID on a xcode list refresh
// If the Apple Id is not a developer, the download action will try and download a xip that is invalid, causing a `xcode13.0.xip is damaged and can't be expanded.`

Logger.appState.info("Using \(downloader) downloader")

return validateSession()
.flatMap { _ in
self.getXcodeArchive(installationType, downloader: downloader)
}
return self.getXcodeArchive(installationType, downloader: downloader)
.flatMap { xcode, url -> AnyPublisher<InstalledXcode, Swift.Error> in
self.installArchivedXcode(xcode, at: url)
}
Expand Down Expand Up @@ -98,13 +93,16 @@ extension AppState {
}

private func downloadXcode(availableXcode: AvailableXcode, downloader: Downloader) -> AnyPublisher<(AvailableXcode, URL), Error> {
downloadOrUseExistingArchive(for: availableXcode, downloader: downloader, progressChanged: { [unowned self] progress in
DispatchQueue.main.async {
self.setInstallationStep(of: availableXcode.version, to: .downloading(progress: progress))
return validateADCSession(path: availableXcode.downloadPath)
.flatMap { _ in
return self.downloadOrUseExistingArchive(for: availableXcode, downloader: downloader, progressChanged: { [unowned self] progress in
DispatchQueue.main.async {
self.setInstallationStep(of: availableXcode.version, to: .downloading(progress: progress))
}
})
.map { return (availableXcode, $0) }
}
})
.map { return (availableXcode, $0) }
.eraseToAnyPublisher()
.eraseToAnyPublisher()
}

public func downloadOrUseExistingArchive(for availableXcode: AvailableXcode, downloader: Downloader, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
Expand Down
40 changes: 39 additions & 1 deletion Xcodes/Backend/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,16 @@ class AppState: ObservableObject {
}
// MARK: - Authentication

func validateADCSession(path: String) -> AnyPublisher<Void, Error> {
return Current.network.dataTask(with: URLRequest.downloadADCAuth(path: path))
.receive(on: DispatchQueue.main)
.tryMap { _ in
}
.eraseToAnyPublisher()
}

func validateSession() -> AnyPublisher<Void, Error> {

return Current.network.validateSession()
.receive(on: DispatchQueue.main)
.handleEvents(receiveCompletion: { completion in
Expand Down Expand Up @@ -368,7 +377,12 @@ class AppState: ObservableObject {
}
}

install(id: id)
switch self.dataSource {
case .apple:
install(id: id)
case .xcodeReleases:
installWithoutLogin(id: id)
}
}

func install(id: Xcode.ID) {
Expand Down Expand Up @@ -439,6 +453,30 @@ class AppState: ObservableObject {
)
}

/// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading
func installWithoutLogin(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }

installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [unowned self] completion in
self.installationPublishers[id] = nil
if case let .failure(error) = completion {
// Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
if error as? AuthenticationError != .invalidSession {
self.error = error
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
}
if let index = self.allXcodes.firstIndex(where: { $0.id == id }) {
self.allXcodes[index].installState = .notInstalled
}
}
},
receiveValue: { _ in }
)
}

func cancelInstall(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }

Expand Down
3 changes: 3 additions & 0 deletions Xcodes/Backend/AvailableXcode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public struct AvailableXcode: Codable {
public let sdks: SDKs?
public let compilers: Compilers?
public let fileSize: Int64?
public var downloadPath: String {
return url.path
}

public init(
version: Version,
Expand Down
2 changes: 1 addition & 1 deletion Xcodes/Backend/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public struct Shell {
"--max-connection-per-server=16",
"--split=16",
"--summary-interval=1",
"--stop-with-process=\(ProcessInfo.processInfo.processIdentifier)",
"--stop-with-process=\(ProcessInfo.processInfo.processIdentifier)", // if xcodes quits, stop aria2 process
"--dir=\(destination.parent.string)",
"--out=\(destination.basename())",
"--human-readable=false", // sets the output to use bytes instead of formatting
Expand Down
10 changes: 10 additions & 0 deletions Xcodes/Backend/URLRequest+Apple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extension URL {
static let download = URL(string: "https://developer.apple.com/download")!
static let downloads = URL(string: "https://developer.apple.com/services-account/QH65B2/downloadws/listDownloads.action")!
static let downloadXcode = URL(string: "https://developer.apple.com/devcenter/download.action")!
static let downloadADCAuth = URL(string: "https://developerservices2.apple.com/services/download")!
}

extension URLRequest {
Expand All @@ -25,4 +26,13 @@ extension URLRequest {
request.allHTTPHeaderFields?["Accept"] = "*/*"
return request
}

static func downloadADCAuth(path: String) -> URLRequest {
var components = URLComponents(url: .downloadADCAuth, resolvingAgainstBaseURL: false)!
components.queryItems = [URLQueryItem(name: "path", value: path)]
var request = URLRequest(url: components.url!)
request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
request.allHTTPHeaderFields?["Accept"] = "*/*"
return request
}
}

0 comments on commit c27b288

Please sign in to comment.