This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @main | |
| struct FakeTwitterApp: App { | |
| @UIApplicationDelegateAdaptor(FakeTwitterAppDelegate.self) private var appDelegate | |
| private let modelContainer: ModelContainer | |
| private let level4Engine: UploadJobEngine | |
| init() { | |
| let schema = Schema([PersistedUploadJob.self]) | |
| let configuration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private func processQueueIfNeeded() async { | |
| guard !isProcessing else { return } | |
| isProcessing = true | |
| defer { isProcessing = false } | |
| while let nextJob = fetchNextOutstandingJobSnapshot() { | |
| do { | |
| try withJob(nextJob.id) { _, job in | |
| job.state = .uploading | |
| job.attempts += 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| func postTweet( | |
| text: String, | |
| strategy _: RetryStrategy, | |
| retryOptions _: RetryOptions | |
| ) async throws { | |
| await engine.enqueue(text: text) | |
| } | |
| func enqueue(text: String) async { | |
| let context = ModelContext(container) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| enum UploadJobState: String, Codable { | |
| case pending | |
| case uploading | |
| case succeeded | |
| case failed | |
| } | |
| @Model | |
| final class PersistedUploadJob { | |
| @Attribute(.unique) var id: UUID |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| final class FakeTwitterAppDelegate: NSObject, UIApplicationDelegate { | |
| func application( | |
| _ application: UIApplication, | |
| handleEventsForBackgroundURLSession identifier: String, | |
| completionHandler: @escaping () -> Void | |
| ) { | |
| guard identifier == BackgroundSessionCoordinator.backgroundSessionIdentifier else { | |
| completionHandler() | |
| return | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| func uploadChunks(videoURL: URL, sessionId: String, fileSize: Int64) async throws { | |
| var offset: Int64 = 0 | |
| let chunkSize: Int64 = 512 * 1024 | |
| while offset < fileSize { | |
| let handle = try FileHandle(forReadingFrom: videoURL) | |
| defer { try? handle.close() } | |
| try handle.seek(toOffset: UInt64(offset)) | |
| let chunkData = try handle.read(upToCount: Int(min(chunkSize, fileSize - offset))) ?? Data() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private lazy var session: URLSession = { | |
| let configuration = URLSessionConfiguration.background(withIdentifier: Self.backgroundSessionIdentifier) | |
| configuration.isDiscretionary = false | |
| configuration.sessionSendsLaunchEvents = true | |
| configuration.waitsForConnectivity = true | |
| configuration.timeoutIntervalForRequest = 120 | |
| return URLSession(configuration: configuration, delegate: self, delegateQueue: .main) | |
| }() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| func resumableUpload(videoURL: URL, sessionId: String, fileSize: Int64) async throws { | |
| var offset = max( | |
| startResponse.nextOffset, | |
| await offsetStore.offset(for: sessionId) | |
| ) | |
| while offset < fileSize { | |
| do { | |
| let chunkFile = try temporaryChunkFile( | |
| sourceVideoURL: videoURL, offset: offset, length: Int(chunkSize) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private func makeChunkRequest(sessionId: String, offset: Int64, totalBytes: Int64) -> URLRequest { | |
| var request = URLRequest(url: url("level3/uploads/\(sessionId)/chunk")) | |
| request.httpMethod = "PUT" | |
| request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") | |
| request.setValue("\(offset)", forHTTPHeaderField: "Upload-Offset") | |
| request.setValue("\(totalBytes)", forHTTPHeaderField: "Upload-Length") | |
| return request | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private func makeIdempotencyKey(enabled: Bool) -> String? { | |
| guard enabled else { return nil } | |
| let key = UUID().uuidString | |
| return key | |
| } | |
| private func makePostRequest(text: String, idempotencyKey: String?) throws -> URLRequest { | |
| var request = makeRequest(path: "level2/tweets", method: "POST") | |
| request.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
| if let idempotencyKey { |
NewerOlder