SwiftUI folder picker in the extension (WebDAV PROPFIND), account shared via a Keychain group, upload on a background session that outlives the extension and is finished by the app. The inbox + notification hand-off remains as the fallback (no account / 'choose later'). Shared code moves to ios/Shared. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
68 lines
2.4 KiB
Swift
68 lines
2.4 KiB
Swift
import Foundation
|
|
|
|
/// What a background upload/download task is, stored in the task's
|
|
/// `taskDescription` so it's still known if the app was relaunched by the
|
|
/// system to deliver the result - possibly for a task the Share Extension
|
|
/// started. Compiled into both targets.
|
|
struct TransferTaskInfo: Codable {
|
|
enum Kind: String, Codable { case upload, download }
|
|
|
|
let kind: Kind
|
|
let batch: String
|
|
let name: String
|
|
let remoteFolder: String?
|
|
/// A file to delete once the task ends (the upload's staged copy).
|
|
let stagedPath: String?
|
|
}
|
|
|
|
/// Running totals for one "upload these 3 files" / "download these 2 files"
|
|
/// request, so the one summary notification (and the Dart-side "folder
|
|
/// changed" event) fires exactly once, on the last file.
|
|
struct TransferBatch: Codable {
|
|
let kind: TransferTaskInfo.Kind
|
|
let remoteFolder: String?
|
|
let total: Int
|
|
var succeeded: Int
|
|
var failed: Int
|
|
|
|
var finished: Int { succeeded + failed }
|
|
}
|
|
|
|
/// Persists [TransferBatch]es in the App Group's `UserDefaults`, not the
|
|
/// process's own: the Share Extension starts an upload batch, and the app -
|
|
/// a different process, possibly relaunched in the background - finishes
|
|
/// counting it.
|
|
enum TransferBatchStore {
|
|
private static let lock = NSLock()
|
|
|
|
private static var defaults: UserDefaults {
|
|
UserDefaults(suiteName: AppGroup.identifier) ?? .standard
|
|
}
|
|
|
|
private static func key(_ id: String) -> String { "transfer.batch.\(id)" }
|
|
|
|
static func save(_ batch: TransferBatch, id: String) {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
if let data = try? JSONEncoder().encode(batch) { defaults.set(data, forKey: key(id)) }
|
|
}
|
|
|
|
/// Counts one file's outcome. Returns the batch when that was its last
|
|
/// file (and forgets it), nil while others are still running.
|
|
static func record(_ info: TransferTaskInfo, success: Bool) -> TransferBatch? {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
let storageKey = key(info.batch)
|
|
var batch =
|
|
defaults.data(forKey: storageKey).flatMap { try? JSONDecoder().decode(TransferBatch.self, from: $0) }
|
|
?? TransferBatch(kind: info.kind, remoteFolder: info.remoteFolder, total: 1, succeeded: 0, failed: 0)
|
|
if success { batch.succeeded += 1 } else { batch.failed += 1 }
|
|
if batch.finished >= batch.total {
|
|
defaults.removeObject(forKey: storageKey)
|
|
return batch
|
|
}
|
|
if let data = try? JSONEncoder().encode(batch) { defaults.set(data, forKey: storageKey) }
|
|
return nil
|
|
}
|
|
}
|