Files
vnidrop/apple/VniDrop/Core/FileSystemService.swift
cdricms a0ebd7c71b fix(apple): restore macOS approval modal and sandboxed file sharing
Approval modal: since the Share/QR sheet auto-opens after creating a transfer,
it is always up when a receiver request arrives, and macOS silently drops a sheet
presented while another is still dismissing — so the approval sheet never appeared.
Drive the approval sheet from explicit state (not a constant binding) and, on
macOS, defer its presentation one dismiss-beat after closing the Share/QR sheet so
the hand-off is serialized. Still a non-dismissable sheet; iOS timing unchanged.

Sandboxed file sharing: the macOS picker released its security scope immediately,
so the core's later import failed with EPERM under the App Store sandbox (the
non-sandboxed .dmg was unaffected). Capture a security-scoped bookmark at pick
time and re-acquire access across shareFiles() — during which the core imports the
bytes — mirroring the receive-folder scoped-access pattern.
2026-07-27 15:01:01 +02:00

64 lines
2.3 KiB
Swift

import Foundation
import VnidropCore
/// A file/folder selected for sharing, ported from `PickedShareFile` in
/// `core/FilePicker.kt`.
struct PickedShareFile: Equatable, Identifiable, Sendable {
let value: String
let displayName: String
var sizeBytes: UInt64? = nil
var thumbnailData: Data? = nil
/// App-owned picker copy that may be deleted after import or abandonment.
var isTemporaryCopy: Bool = false
/// When true, `value` is a directory (path or security-scoped folder URL).
var isDirectory: Bool = false
/// macOS sandbox: a security-scoped bookmark captured at pick time so access to
/// `value` can be re-acquired when the core imports the file (the picker's own
/// scope ends immediately). Nil on iOS (which copies into the container instead).
var securityScopeBookmark: Data? = nil
var id: String { value }
}
/// Receive-destination and share-source platform bridge, ported from
/// `core/FileSystemService.kt` and its iOS/desktop actuals.
@MainActor
protocol FileSystemService {
var supportsCustomReceiveFolders: Bool { get }
func defaultReceiveFolder() -> ReceiveFolder
func effectiveReceiveFolder(_ configured: ReceiveFolder) -> ReceiveFolder
func validateReceiveFolder(_ folder: ReceiveFolder) async -> FolderAccessStatus
func canRevealReceiveFolder(_ folder: ReceiveFolder) -> Bool
func revealReceiveFolder(_ folder: ReceiveFolder) async -> Result<Void, Error>
/// Releases only app-owned picker copies; never deletes original user sources.
func discardPickedFiles(_ files: [PickedShareFile]) async
func sharePickedFiles(
repository: CoreGateway,
files: [PickedShareFile],
transferName: String,
senderName: String,
accessPolicy: ShareAccessPolicy
) async -> Result<Share, Error>
}
extension FileSystemService {
var supportsCustomReceiveFolders: Bool { true }
func effectiveReceiveFolder(_ configured: ReceiveFolder) -> ReceiveFolder {
supportsCustomReceiveFolders ? configured : defaultReceiveFolder()
}
func canRevealReceiveFolder(_ folder: ReceiveFolder) -> Bool { false }
func revealReceiveFolder(_ folder: ReceiveFolder) async -> Result<Void, Error> {
.failure(InvitationError.message("Revealing the receive folder is not supported"))
}
func discardPickedFiles(_ files: [PickedShareFile]) async {}
}
extension ReceiveFolder {
var isFileSystemPath: Bool { kind == .fileSystemPath }
}