mirror of
https://github.com/xtool-org/xtool.git
synced 2026-02-04 11:53:30 +01:00
199 lines
6.3 KiB
Swift
199 lines
6.3 KiB
Swift
#if false
|
|
|
|
//
|
|
// DDIMounter.swift
|
|
// XKit
|
|
//
|
|
// Created by Kabir Oberai on 25/03/21.
|
|
// Copyright © 2021 Kabir Oberai. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftyMobileDevice
|
|
#if os(Linux)
|
|
import FoundationNetworking
|
|
#endif
|
|
|
|
public final class DDIMounter {
|
|
|
|
public struct DDILoc {
|
|
public let dmg: URL
|
|
public let signature: URL
|
|
|
|
public init(dmg: URL, signature: URL) {
|
|
self.dmg = dmg
|
|
self.signature = signature
|
|
}
|
|
}
|
|
|
|
private class RequestDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate {
|
|
enum Verdict {
|
|
case streamData(size: Int)
|
|
case downloadFull(URL)
|
|
case failed(Swift.Error)
|
|
}
|
|
|
|
private var verdict: Verdict?
|
|
private let cache: URL
|
|
private let fileStream: OutputStream
|
|
private let pipedStream: OutputStream
|
|
private let onVerdict: (Verdict) -> Void
|
|
init(stream: OutputStream, cache: URL, onVerdict: @escaping (Verdict) -> Void) {
|
|
self.pipedStream = stream
|
|
self.cache = cache
|
|
self.fileStream = OutputStream(url: cache, append: false)!
|
|
self.onVerdict = onVerdict
|
|
stream.open()
|
|
fileStream.open()
|
|
}
|
|
|
|
func urlSession(
|
|
_ session: URLSession,
|
|
dataTask: URLSessionDataTask,
|
|
didReceive response: URLResponse,
|
|
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
|
) {
|
|
let len = response.expectedContentLength
|
|
completionHandler(.allow)
|
|
if len == -1 {
|
|
verdict = .downloadFull(cache)
|
|
} else {
|
|
let verdict: Verdict = .streamData(size: Int(len))
|
|
self.verdict = verdict
|
|
onVerdict(verdict)
|
|
}
|
|
}
|
|
|
|
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
|
data.withUnsafeBytes { bytes in
|
|
let buf = bytes.bindMemory(to: UInt8.self)
|
|
_ = pipedStream.write(buf.baseAddress!, maxLength: buf.count)
|
|
_ = fileStream.write(buf.baseAddress!, maxLength: buf.count)
|
|
}
|
|
}
|
|
|
|
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Swift.Error?) {
|
|
pipedStream.close()
|
|
fileStream.close()
|
|
if case let .downloadFull(url) = verdict {
|
|
onVerdict(error.map(Verdict.failed) ?? .downloadFull(url))
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum MounterStatus: String, Decodable {
|
|
case complete = "Complete"
|
|
}
|
|
|
|
private struct MountedImage: Decodable {
|
|
let status: MounterStatus
|
|
let signature: [Data]?
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case signature = "ImageSignature"
|
|
case status = "Status"
|
|
}
|
|
}
|
|
|
|
private struct MountResult: Decodable {
|
|
let status: MounterStatus?
|
|
let error: String?
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case status = "Status"
|
|
case error = "Error"
|
|
}
|
|
}
|
|
|
|
public struct Error: Swift.Error, LocalizedError {
|
|
public let message: String?
|
|
public var errorDescription: String? { message }
|
|
}
|
|
|
|
private let client: MobileImageMounterClient
|
|
public init(connection: Connection) throws {
|
|
self.client = try connection.startClient()
|
|
}
|
|
|
|
private func mount(file: InputStream, size: Int, signature: Data) throws {
|
|
file.open()
|
|
defer { file.close() }
|
|
|
|
try client.upload(imageType: "Developer", file: file, size: size, signature: signature)
|
|
let result = try client.mount(imageType: "Developer", signature: signature, resultType: MountResult.self)
|
|
guard result.status == .complete else {
|
|
throw Error(message: result.error)
|
|
}
|
|
}
|
|
|
|
public func mountIfNeeded(local: DDILoc, fetchRemote: () throws -> DDILoc) throws {
|
|
#warning("we can't use CFSocketStream on Linux at all")
|
|
#if os(Linux)
|
|
// maybe just tear all of this down and download first, then mount the ddi.
|
|
fatalError("DDIMounter does not yet work on Linux")
|
|
#else
|
|
let mounted = try client.lookup(imageType: "Developer", resultType: MountedImage.self)
|
|
if mounted.signature != nil { return }
|
|
|
|
if local.dmg.exists,
|
|
local.signature.exists,
|
|
let file = InputStream(url: local.dmg),
|
|
let fileSize = try? local.dmg.resourceValues(forKeys: [.fileSizeKey]).fileSize,
|
|
let signature = try? Data(contentsOf: local.signature) {
|
|
return try mount(file: file, size: fileSize, signature: signature)
|
|
}
|
|
|
|
let remote = try fetchRemote()
|
|
|
|
var istream: InputStream!
|
|
var ostream: OutputStream!
|
|
Stream.getBoundStreams(withBufferSize: 1024, inputStream: &istream, outputStream: &ostream)
|
|
|
|
let group = DispatchGroup()
|
|
|
|
var mode: RequestDelegate.Verdict!
|
|
group.enter()
|
|
let delegate = RequestDelegate(stream: ostream, cache: local.dmg) { m in
|
|
mode = m
|
|
group.leave()
|
|
}
|
|
|
|
let dmgReq = URLRequest(url: remote.dmg)
|
|
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
|
|
session.dataTask(with: dmgReq).resume()
|
|
|
|
let sigReq = URLRequest(url: remote.signature)
|
|
var sigRes: Result<Data, Swift.Error>!
|
|
group.enter()
|
|
URLSession.shared.dataTask(with: sigReq) { data, _, err in
|
|
if let data = data {
|
|
sigRes = .success(data)
|
|
} else {
|
|
sigRes = .failure(err ?? Error(message: nil))
|
|
}
|
|
group.leave()
|
|
try? data?.write(to: local.signature)
|
|
}.resume()
|
|
|
|
group.wait()
|
|
let signature = try sigRes.get()
|
|
|
|
switch mode! {
|
|
case .downloadFull(let url):
|
|
let file = InputStream(url: url)!
|
|
let size = try url.resourceValues(forKeys: [.fileSizeKey]).fileSize!
|
|
try mount(file: file, size: size, signature: signature)
|
|
case .streamData(let size):
|
|
try mount(file: istream, size: size, signature: signature)
|
|
case .failed(let error):
|
|
throw error
|
|
}
|
|
|
|
_ = delegate
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|