mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
refactor(apple): adopt Swift 6 language mode
Enable complete strict concurrency and switch the app target to Swift 6. - Isolate model dependency protocols to @MainActor - Make the CoreRepository blocking-FFI bridge race-free: nonisolated(unsafe) core handle, nonisolated runCore/readSnapshot, @Sendable work block, Sendable domain models - Fix Binding method-reference captures; @preconcurrency imports for CoreNFC/AVFoundation/VnidropCore; isolate the NFC/QR delegate helpers
This commit is contained in:
@@ -2,12 +2,12 @@ import Foundation
|
||||
import Combine
|
||||
|
||||
/// Receive-destination descriptor, ported from `core/FileSystemService.kt`.
|
||||
enum ReceiveFolderKind: String, Codable {
|
||||
enum ReceiveFolderKind: String, Codable, Sendable {
|
||||
case fileSystemPath
|
||||
case iosSecurityScopedUrl
|
||||
}
|
||||
|
||||
struct ReceiveFolder: Equatable, Codable {
|
||||
struct ReceiveFolder: Equatable, Codable, Sendable {
|
||||
let kind: ReceiveFolderKind
|
||||
let value: String
|
||||
let displayName: String
|
||||
|
||||
@@ -4,13 +4,13 @@ import Foundation
|
||||
/// the generated UniFFI records/enums into these so the UI never depends on the
|
||||
/// binding surface directly.
|
||||
|
||||
struct CoreStatus: Equatable {
|
||||
struct CoreStatus: Equatable, Sendable {
|
||||
let endpointId: String
|
||||
let activeTransfers: UInt64
|
||||
let activeShares: UInt64
|
||||
}
|
||||
|
||||
struct CoreEventModel: Equatable, Identifiable {
|
||||
struct CoreEventModel: Equatable, Identifiable, Sendable {
|
||||
let id: String
|
||||
let timestamp: Int64
|
||||
let scope: String
|
||||
@@ -21,17 +21,17 @@ struct CoreEventModel: Equatable, Identifiable {
|
||||
let dataJson: String
|
||||
}
|
||||
|
||||
enum ShareAccessPolicy: Equatable {
|
||||
enum ShareAccessPolicy: Equatable, Sendable {
|
||||
case requireApproval
|
||||
case anyoneWithTransfer
|
||||
}
|
||||
|
||||
enum TransferDirection: Equatable {
|
||||
enum TransferDirection: Equatable, Sendable {
|
||||
case send
|
||||
case receive
|
||||
}
|
||||
|
||||
enum TransferStatus: Equatable {
|
||||
enum TransferStatus: Equatable, Sendable {
|
||||
case importing
|
||||
case sharing
|
||||
case receiving
|
||||
@@ -41,7 +41,7 @@ enum TransferStatus: Equatable {
|
||||
case stopped
|
||||
}
|
||||
|
||||
struct Transfer: Equatable, Identifiable {
|
||||
struct Transfer: Equatable, Identifiable, Sendable {
|
||||
let localId: String
|
||||
let transferId: UInt64
|
||||
let direction: TransferDirection
|
||||
@@ -59,7 +59,7 @@ struct Transfer: Equatable, Identifiable {
|
||||
var id: String { localId }
|
||||
}
|
||||
|
||||
struct Share: Equatable {
|
||||
struct Share: Equatable, Sendable {
|
||||
let transferId: UInt64
|
||||
let ticket: String
|
||||
let transferName: String
|
||||
@@ -68,7 +68,7 @@ struct Share: Equatable {
|
||||
let totalSize: UInt64
|
||||
}
|
||||
|
||||
struct TransferMetadataModel: Equatable {
|
||||
struct TransferMetadataModel: Equatable, Sendable {
|
||||
let transferId: UInt64
|
||||
let transferName: String
|
||||
let senderName: String?
|
||||
@@ -77,12 +77,12 @@ struct TransferMetadataModel: Equatable {
|
||||
let totalSize: UInt64
|
||||
}
|
||||
|
||||
struct TicketInspectionModel: Equatable {
|
||||
struct TicketInspectionModel: Equatable, Sendable {
|
||||
let kind: String
|
||||
let metadata: TransferMetadataModel
|
||||
}
|
||||
|
||||
enum ReceiverDeliveryStatus: Equatable {
|
||||
enum ReceiverDeliveryStatus: Equatable, Sendable {
|
||||
case requested
|
||||
case accepted
|
||||
case refused
|
||||
@@ -91,7 +91,7 @@ enum ReceiverDeliveryStatus: Equatable {
|
||||
case unknown
|
||||
}
|
||||
|
||||
struct ReceiverRequestModel: Equatable, Identifiable {
|
||||
struct ReceiverRequestModel: Equatable, Identifiable, Sendable {
|
||||
let id: String
|
||||
let transferId: UInt64
|
||||
let remoteEndpointId: String
|
||||
@@ -106,7 +106,7 @@ struct ReceiverRequestModel: Equatable, Identifiable {
|
||||
let completedAt: Int64?
|
||||
}
|
||||
|
||||
struct CoreState: Equatable {
|
||||
struct CoreState: Equatable, Sendable {
|
||||
var isInitialized: Bool = false
|
||||
var status: CoreStatus?
|
||||
var events: [CoreEventModel] = []
|
||||
@@ -116,7 +116,7 @@ struct CoreState: Equatable {
|
||||
}
|
||||
|
||||
/// Coalesced change hints emitted from the event sink, ported from `CoreSignal`.
|
||||
enum CoreSignal: Equatable {
|
||||
enum CoreSignal: Equatable, Sendable {
|
||||
case approvalChanged(transferId: UInt64)
|
||||
case receiverHistoryChanged(transferId: UInt64)
|
||||
/// Transfer status/history changed enough to re-read the durable snapshot.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
import VnidropCore
|
||||
@preconcurrency import VnidropCore
|
||||
|
||||
/// Swift port of `core/CoreRepository.kt`. Owns the `VnidropCore` handle, maps the
|
||||
/// generated UniFFI records into app domain models, publishes an observable
|
||||
@@ -16,13 +16,16 @@ final class CoreRepository: ObservableObject {
|
||||
/// Coalesced change hints; subscribe to react to approval/history/transfer changes.
|
||||
var signals: AnyPublisher<CoreSignal, Never> { signalsSubject.eraseToAnyPublisher() }
|
||||
|
||||
private var core: VnidropCore?
|
||||
// Set on the main actor (initialize/shutdown) but read from `queue` inside
|
||||
// `runCore`; the underlying core is internally synchronized, so this crossing
|
||||
// is safe. `nonisolated(unsafe)` documents that contract for Swift 6.
|
||||
private nonisolated(unsafe) var core: VnidropCore?
|
||||
private let queue = DispatchQueue(label: "com.vnidrop.core", qos: .userInitiated)
|
||||
private lazy var sink = RepositoryEventSink { [weak self] event in
|
||||
Task { @MainActor in self?.handle(event: event) }
|
||||
}
|
||||
|
||||
private static let maxEvents = 200
|
||||
private nonisolated static let maxEvents = 200
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
@@ -186,7 +189,7 @@ final class CoreRepository: ObservableObject {
|
||||
// MARK: - Internals
|
||||
|
||||
/// Snapshot of the values read from the core in one pass.
|
||||
private struct CoreSnapshot {
|
||||
private struct CoreSnapshot: Sendable {
|
||||
let status: CoreStatus
|
||||
let transfers: [Transfer]
|
||||
let events: [CoreEventModel]
|
||||
@@ -194,11 +197,11 @@ final class CoreRepository: ObservableObject {
|
||||
|
||||
/// Reads the current core state. Safe to call off the main actor (pure core
|
||||
/// FFI reads); does not touch `@Published` state.
|
||||
private func readSnapshot() -> CoreSnapshot? {
|
||||
private nonisolated func readSnapshot() -> CoreSnapshot? {
|
||||
guard let core = self.core else { return nil }
|
||||
let status = core.status()
|
||||
let transfers = (try? core.listTransfers())?.map { $0.toModel() } ?? state.transfers
|
||||
let events = (try? core.listEvents(transferId: nil))?.prefix(Self.maxEvents).map { $0.toModel() } ?? state.events
|
||||
let transfers = (try? core.listTransfers())?.map { $0.toModel() } ?? []
|
||||
let events = (try? core.listEvents(transferId: nil))?.prefix(Self.maxEvents).map { $0.toModel() } ?? []
|
||||
return CoreSnapshot(
|
||||
status: CoreStatus(
|
||||
endpointId: status.endpointId,
|
||||
@@ -221,7 +224,7 @@ final class CoreRepository: ObservableObject {
|
||||
if let snapshot = readSnapshot() { applySnapshot(snapshot) }
|
||||
}
|
||||
|
||||
private func requireCore() throws -> VnidropCore {
|
||||
private nonisolated func requireCore() throws -> VnidropCore {
|
||||
guard let core = self.core else {
|
||||
throw InvitationError.message("Initialize the core first.")
|
||||
}
|
||||
@@ -229,7 +232,7 @@ final class CoreRepository: ObservableObject {
|
||||
}
|
||||
|
||||
/// Runs a blocking core call off the main actor and hops the result back.
|
||||
private func runCore<T>(_ block: @escaping () throws -> T) async -> Result<T, Error> {
|
||||
private nonisolated func runCore<T: Sendable>(_ block: @escaping @Sendable () throws -> T) async -> Result<T, Error> {
|
||||
await withCheckedContinuation { continuation in
|
||||
queue.async {
|
||||
let result: Result<T, Error>
|
||||
@@ -243,7 +246,7 @@ final class CoreRepository: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private static func nextTransferId() -> UInt64 {
|
||||
private nonisolated static func nextTransferId() -> UInt64 {
|
||||
UInt64.random(in: 1...UInt64(Int64.max))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import VnidropCore
|
||||
|
||||
/// A file/folder selected for sharing, ported from `PickedShareFile` in
|
||||
/// `core/FilePicker.kt`.
|
||||
struct PickedShareFile: Equatable, Identifiable {
|
||||
struct PickedShareFile: Equatable, Identifiable, Sendable {
|
||||
let value: String
|
||||
let displayName: String
|
||||
var sizeBytes: UInt64? = nil
|
||||
@@ -18,6 +18,7 @@ struct PickedShareFile: Equatable, Identifiable {
|
||||
|
||||
/// Receive-destination and share-source platform bridge, ported from
|
||||
/// `core/FileSystemService.kt` and its iOS/desktop actuals.
|
||||
@MainActor
|
||||
protocol FileSystemService {
|
||||
var supportsCustomReceiveFolders: Bool { get }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user