167 lines
6.6 KiB
Swift
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
|
|
}
|
|
}
|