Files

167 lines
6.6 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import AgentKit
import Foundation
import TermindApplication
import TermindDomain
/// Admit new operations without cancelling a transfer already delegated to the channel.
nonisolated final class AccessControlledSFTPChannel: SFTPChannelProtocol, Sendable {
private let channel: any SFTPChannelProtocol
private let accessCheck: HostOperationAccessCheck?
init(channel: any SFTPChannelProtocol, accessCheck: HostOperationAccessCheck?) {
self.channel = channel
self.accessCheck = accessCheck
}
func disconnect() async { await channel.disconnect() }
public func openSiblingChannel() async throws -> any SFTPChannelProtocol {
AccessControlledSFTPChannel(
channel: try await connectedChannel().openSiblingChannel(), accessCheck: accessCheck)
}
public func homeDirectory() async throws -> String {
try await connectedChannel().homeDirectory()
}
public func listDirectory(at path: String) async throws -> [RemoteFile] {
try await connectedChannel().listDirectory(at: path)
}
public func attributes(at path: String) async throws -> RemoteFile? {
try await connectedChannel().attributes(at: path)
}
public func readFile(at path: String, maximumBytes: Int) async throws -> Data {
try await connectedChannel().readFile(at: path, maximumBytes: maximumBytes)
}
public func readTextPage(at path: String, offset: Int, limit: Int) async throws -> AgentTextPage {
try await connectedChannel().readTextPage(at: path, offset: offset, limit: limit)
}
public func writeFile(_ data: Data, at path: String, overwrite: Bool) async throws {
try await connectedChannel().writeFile(data, at: path, overwrite: overwrite)
}
public func setPermissions(at path: String, to permissions: UInt32) async throws {
try await connectedChannel().setPermissions(at: path, to: permissions)
}
public func createDirectory(at path: String) async throws {
try await connectedChannel().createDirectory(at: path)
}
public func rename(from source: String, to destination: String, overwrite: Bool) async throws {
try await connectedChannel().rename(
from: source, to: destination, overwrite: overwrite)
}
public func removeFile(at path: String) async throws {
try await connectedChannel().removeFile(at: path)
}
public func removeDirectory(at path: String) async throws {
try await connectedChannel().removeDirectory(at: path)
}
public func uploadFile(
from localURL: URL, to remotePath: String, overwrite: Bool,
cancellation: TransferCancellationToken, resume: SFTPResumeState,
progress: @escaping @Sendable (SFTPProgress) -> Void
) async throws {
try await connectedChannel().uploadFile(
from: localURL, to: remotePath, overwrite: overwrite,
cancellation: cancellation, resume: resume, progress: progress)
}
public func uploadFile(
from localURL: URL, to remotePath: String, overwrite: Bool,
cancellation: TransferCancellationToken,
progress: @escaping @Sendable (SFTPProgress) -> Void
) async throws {
try await connectedChannel().uploadFile(
from: localURL, to: remotePath, overwrite: overwrite,
cancellation: cancellation, progress: progress)
}
public func uploadStream(
fromSocket socket: Int32, to remotePath: String, byteCount: Int64,
overwrite: Bool, cancellation: TransferCancellationToken,
resume: SFTPResumeState,
progress: @escaping @Sendable (SFTPProgress) -> Void,
sourceSucceeded: @escaping @Sendable () -> Bool
) async throws {
try await connectedChannel().uploadStream(
fromSocket: socket, to: remotePath, byteCount: byteCount,
overwrite: overwrite, cancellation: cancellation, resume: resume,
progress: progress, sourceSucceeded: sourceSucceeded)
}
public func uploadStream(
fromSocket socket: Int32, to remotePath: String, byteCount: Int64,
overwrite: Bool, cancellation: TransferCancellationToken,
progress: @escaping @Sendable (SFTPProgress) -> Void,
sourceSucceeded: @escaping @Sendable () -> Bool
) async throws {
try await connectedChannel().uploadStream(
fromSocket: socket, to: remotePath, byteCount: byteCount,
overwrite: overwrite, cancellation: cancellation,
progress: progress, sourceSucceeded: sourceSucceeded)
}
public func downloadFile(
from remotePath: String, to localURL: URL, overwrite: Bool,
cancellation: TransferCancellationToken, resume: SFTPResumeState,
progress: @escaping @Sendable (SFTPProgress) -> Void
) async throws {
try await connectedChannel().downloadFile(
from: remotePath, to: localURL, overwrite: overwrite,
cancellation: cancellation, resume: resume, progress: progress)
}
public func downloadFile(
from remotePath: String, to localURL: URL, overwrite: Bool,
cancellation: TransferCancellationToken,
progress: @escaping @Sendable (SFTPProgress) -> Void
) async throws {
try await connectedChannel().downloadFile(
from: remotePath, to: localURL, overwrite: overwrite,
cancellation: cancellation, progress: progress)
}
public func openDownloadStream(
at path: String, maximumBytes: Int64,
cancellation: TransferCancellationToken
) async throws -> any SFTPDownloadStream {
try await connectedChannel().openDownloadStream(
at: path, maximumBytes: maximumBytes, cancellation: cancellation)
}
public func copyFile(
from sourcePath: String, to destinationPath: String,
on destination: any SFTPChannelProtocol, byteCount: Int64,
overwrite: Bool, cancellation: TransferCancellationToken,
progress: @escaping @Sendable (SFTPProgress) -> Void
) async throws {
let destinationChannel =
if let destination = destination as? AccessControlledSFTPChannel {
try destination.connectedChannel()
} else {
destination
}
try await connectedChannel().copyFile(
from: sourcePath, to: destinationPath, on: destinationChannel,
byteCount: byteCount, overwrite: overwrite,
cancellation: cancellation, progress: progress)
}
private func connectedChannel() throws -> any SFTPChannelProtocol {
try accessCheck?()
return channel
}
}