8000 Add interface that represents an SFTP working directory. by tmds · Pull Request #389 · tmds/Tmds.Ssh · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

Add interface that represents an SFTP working directory. #389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,28 @@ class SshClient : IDisposable
// A connection must be established before calling this.
CancellationToken Disconnected { get; }

/** Execute remote processes **/
Task<RemoteProcess> ExecuteAsync(string command, CancellationToken cancellationToken);
Task<RemoteProcess> ExecuteAsync(string command, ExecuteOptions? options = null, CancellationToken cancellationToken = default);
Task<RemoteProcess> ExecuteShellAsync(CancellationToken cancellationToken);
Task<RemoteProcess> ExecuteShellAsync(ExecuteOptions? options = null, CancellationToken cancellationToken = default);
Task<RemoteProcess> ExecuteSubsystemAsync(string subsystem, CancellationToken cancellationToken);
Task<RemoteProcess> ExecuteSubsystemAsync(string subsystem, ExecuteOptions? options = null, CancellationToken cancellationToken = default);

/** Forward connections **/
Task<SshDataStream> OpenTcpConnectionAsync(string host, int port, CancellationToken cancellationToken = default);
Task<SshDataStream> OpenUnixConnectionAsync(string path, CancellationToken cancellationToken = default);

Task<RemoteListener> ListenTcpAsync(string address, int port, CancellationToken cancellationToken = default);
Task<RemoteListener> ListenUnixAsync(string path, CancellationToken cancellationToken = default);

// bindEP can be an IPEndPoint or a UnixDomainSocketEndPoint.
// remoteEP can be a RemoteHostEndPoint, a RemoteUnixEndPoint or a RemoteIPEndPoint.
Task<LocalForward> StartForwardAsync(EndPoint bindEP, RemoteEndPoint remoteEP, CancellationToken cancellationToken = default);
Task<SocksForward> StartForwardSocksAsync(EndPoint bindEP, CancellationToken cancellationToken = default);

// bindEP can be a RemoteIPListenEndPoint or a RemoteUnixEndPoint.
// localEP can be a DnsEndPoint, an IPEndPoint or a UnixDomainSocketEndPoint.
Task<RemoteForward> StartRemoteForwardAsync(RemoteEndPoint bindEP, EndPoint localEP, CancellationToken cancellationToken = default);

/** File system operations **/
Task<SftpClient> OpenSftpClientAsync(CancellationToken cancellationToken);
Task<SftpClient> OpenSftpClientAsync(SftpClientOptions? options = null, CancellationToken cancellationToken = default)
}
Expand Down Expand Up @@ -203,7 +203,7 @@ struct RemoteConnection : IDisposable
bool HasStream { get; }
Stream MoveStream(); // Transfers ownership of the Stream to the caller.
}
class SftpClient : IDisposable
class SftpClient : ISftpDirectory, IDisposable
{
// Note: umask is applied on the server.
const UnixFilePermissions DefaultCreateDirectoryPermissions; // = '-rwxrwxrwx'.
Expand All @@ -222,18 +222,14 @@ class SftpClient : IDisposable

// May be used once the client is connected.
SftpDirectory WorkingDirectory { get; } // The working directory used for SftpClient operations.
SftpDirectory GetDirectory(string path); // Change into another working directory.

// SftpDirectory operations
// ...
}
class SftpDirectory
class SftpDirectory : ISftpDirectory
{ }
interface ISftpDirectory // Represents a working directory.
{
string Path { get; }

SftpDirectory GetDirectory(string path);

// Directory operations
ISftpDirectory GetDirectory(string path);

ValueTask<SftpFile> OpenOrCreateFileAsync(string path, FileAccess access, CancellationToken cancellationToken = default);
ValueTask<SftpFile> OpenOrCreateFileAsync(string path, FileAccess access, FileOpenOptions? options, CancellationToken cancellationToken = default);
Expand Down
77 changes: 77 additions & 0 deletions src/Tmds.Ssh/ISftpDirectory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// This file is part of Tmds.Ssh which is released under MIT.
// See file LICENSE for full license details.

namespace Tmds.Ssh;

public interface ISftpDirectory
{
string Path { get; }

ISftpDirectory GetDirectory(string path);

ValueTask<SftpFile> OpenOrCreateFileAsync(string path, FileAccess access, FileOpenOptions? options, CancellationToken cancellationToken = default);
ValueTask<SftpFile> CreateNewFileAsync(string path, FileAccess access, FileOpenOptions? options, CancellationToken cancellationToken = default);
ValueTask<SftpFile?> OpenFileAsync(string path, FileAccess access, FileOpenOptions? options, CancellationToken cancellationToken = default);
ValueTask DeleteFileAsync(string path, CancellationToken cancellationToken = default);
ValueTask DeleteDirectoryAsync(string path, CancellationToken cancellationToken = default);
ValueTask RenameAsync(string oldPath, string newPath, CancellationToken cancellationToken = default);
ValueTask CopyFileAsync(string sourcePath, string destinationPath, bool overwrite = false, CancellationToken cancellationToken = default);
ValueTask<FileEntryAttributes?> GetAttributesAsync(string path, bool followLinks = true, CancellationToken cancellationToken = default);
ValueTask SetAttributesAsync(
string path,
UnixFilePermissions? permissions = default,
(DateTimeOffset LastAccess, DateTimeOffset LastWrite)? times = default,
long? length = default,
(int Uid, int Gid)? ids = default,
IEnumerable<KeyValuePair<string, Memory<byte>>>? extendedAttributes = default,
CancellationToken cancellationToken = default);
ValueTask<string> GetLinkTargetAsync(string linkPath, CancellationToken cancellationToken = default);
ValueTask<string> GetRealPathAsync(string path, CancellationToken cancellationToken = 10000 default);
ValueTask CreateSymbolicLinkAsync(string linkPath, string targetPath, CancellationToken cancellationToken = default);
IAsyncEnumerable<T> GetDirectoryEntriesAsync<T>(string path, SftpFileEntryTransform<T> transform, EnumerationOptions? options = null);
ValueTask CreateDirectoryAsync(string path, bool createParents = false, UnixFilePermissions permissions = SftpClient.DefaultCreateDirectoryPermissions, CancellationToken cancellationToken = default);
ValueTask CreateNewDirectoryAsync(string path, bool createParents = false, UnixFilePermissions permissions = SftpClient.DefaultCreateDirectoryPermissions, CancellationToken cancellationToken = default);
ValueTask UploadDirectoryEntriesAsync(string localDirPath, string remoteDirPath, UploadEntriesOptions? options, CancellationToken cancellationToken = default);
ValueTask UploadFileAsync(string localFilePath, string remoteFilePath, bool overwrite = false, UnixFilePermissions? createPermissions = default, CancellationToken cancellationToken = default);
ValueTask UploadFileAsync(Stream source, string remoteFilePath, bool overwrite = false, UnixFilePermissions createPermissions = SftpClient.DefaultCreateFilePermissions, CancellationToken cancellationToken = default);
ValueTask DownloadDirectoryEntriesAsync(string remoteDirPath, string localDirPath, DownloadEntriesOptions? options, CancellationToken cancellationToken = default);
ValueTask DownloadFileAsync(string remoteFilePath, string localFilePath, bool overwrite = false, CancellationToken cancellationToken = default);
ValueTask DownloadFileAsync(string remoteFilePath, Stream destination, CancellationToken cancellationToken = default);
}

public static class SftpDirectoryExtensions
{
public static ValueTask<SftpFile> OpenOrCreateFileAsync(this ISftpDirectory directory, string path, FileAccess access, CancellationToken cancellationToken = default)
=> directory.OpenOrCreateFileAsync(path, access, options: null, cancellationToken);

public static ValueTask<SftpFile> CreateNewFileAsync(this ISftpDirectory directory, string path, FileAccess access, CancellationToken cancellationToken = default)
=> directory.CreateNewFileAsync(path, access, options: null, cancellationToken);

public static ValueTask<SftpFile?> OpenFileAsync(this ISftpDirectory directory, string path, FileAccess access, CancellationToken cancellationToken = default)
=> directory.OpenFileAsync(path, access, options: null, cancellationToken);

public static IAsyncEnumerable<(string Path, FileEntryAttributes Attributes)> GetDirectoryEntriesAsync(this ISftpDirectory directory, string path, EnumerationOptions? options = null)
=> directory.GetDirectoryEntriesAsync(path, (ref SftpFileEntry entry) => (entry.ToPath(), entry.ToAttributes()), options);

public static ValueTask CreateDirectoryAsync(this ISftpDirectory directory, string path, CancellationToken cancellationToken)
=> directory.CreateDirectoryAsync(path, createParents: false, SftpClient.DefaultCreateDirectoryPermissions, cancellationToken);

public static ValueTask CreateNewDirectoryAsync(this ISftpDirectory directory, string path, CancellationToken cancellationToken)
=> directory.CreateNewDirectoryAsync(path, createParents: false, SftpClient.DefaultCreateDirectoryPermissions, cancellationToken);

public static ValueTask UploadDirectoryEntriesAsync(this ISftpDirectory directory, string localDirPath, string remoteDirPath, CancellationToken cancellationToken = default)
=> directory.UploadDirectoryEntriesAsync(localDirPath, remoteDirPath, options: null, cancellationToken);

public static ValueTask UploadFileAsync(this ISftpDirectory directory, string localFilePath, string remoteFilePath, CancellationToken cancellationToken)
=> directory.UploadFileAsync(localFilePath, remoteFilePath, overwrite: false, createPermissions: null, cancellationToken);

public static ValueTask UploadFileAsync(this ISftpDirectory directory, Stream source, string remoteFilePath, CancellationToken cancellationToken)
=> directory.UploadFileAsync(source, remoteFilePath, overwrite: false, createPermissions: SftpClient.DefaultCreateFilePermissions, cancellationToken);

public static ValueTask DownloadDirectoryEntriesAsync(this ISftpDirectory directory, string remoteDirPath, string localDirPath, CancellationToken cancellationToken = default)
=> directory.DownloadDirectoryEntriesAsync(remoteDirPath, localDirPath, options: null, cancellationToken);

public static ValueTask DownloadFileAsync(this ISftpDirectory directory, string remoteFilePath, string localFilePath, CancellationToken cancellationToken)
=> directory.DownloadFileAsync(remoteFilePath, localFilePath, overwrite: false, cancellationToken);

}
41 changes: 7 additions & 34 deletions src/Tmds.Ssh/SftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Tmds.Ssh;

public sealed partial class SftpClient : IDisposable
public sealed partial class SftpClient : ISftpDirectory, IDisposable
{
private readonly Lock _gate = new();

Expand Down Expand Up @@ -77,6 +77,12 @@ public SftpClient(SshClient client, SftpClientOptions? options = null) :
public SftpDirectory GetDirectory(string path)
=> WorkingDirectory.GetDirectory(path);

ISftpDirectory ISftpDirectory.GetDirectory(string path)
=> GetDirectory(path);

string ISftpDirectory.Path
=> WorkingDirectory.Path;

private SftpClient(SshClient client, SftpClientOptions? options, bool ownsClient)
{
ArgumentNullException.ThrowIfNull(client);
Expand Down Expand Up @@ -253,27 +259,18 @@ public SftpDirectory WorkingDirectory
}
}

public ValueTask<SftpFile> OpenOrCreateFileAsync(string path, FileAccess access, CancellationToken cancellationToken = default)
=> OpenOrCreateFileAsync(path, access, options: null, cancellationToken);

public async ValueTask<SftpFile> OpenOrCreateFileAsync(string path, FileAccess access, FileOpenOptions? options, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
return await dir.OpenOrCreateFileAsync(path, access, options, cancellationToken).ConfigureAwait(false);
}

public ValueTask<SftpFile> CreateNewFileAsync(string path, FileAccess access, CancellationToken cancellationToken = default)
=> CreateNewFileAsync(path, access, options: null, cancellationToken);

public async ValueTask<SftpFile> CreateNewFileAsync(string path, FileAccess access, FileOpenOptions? options, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
return await dir.CreateNewFileAsync(path, access, options, cancellationToken).ConfigureAwait(false);
}

public ValueTask<SftpFile?> OpenFileAsync(string path, FileAccess access, CancellationToken cancellationToken = default)
=> OpenFileAsync(path, access, options: null, cancellationToken);

public async ValueTask<SftpFile?> OpenFileAsync(string path, FileAccess access, FileOpenOptions? options, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -345,69 +342,45 @@ public async ValueTask CreateSymbolicLinkAsync(string linkPath, string targetPat
await dir.CreateSymbolicLinkAsync(linkPath, targetPath, cancellationToken).ConfigureAwait(false);
}

public IAsyncEnumerable<(string Path, FileEntryAttributes Attributes)> GetDirectoryEntriesAsync(string path, EnumerationOptions? options = null)
=> GetDirectoryEntriesAsync<(string, FileEntryAttributes)>(path, (ref SftpFileEntry entry) => (entry.ToPath(), entry.ToAttributes()), options);

public IAsyncEnumerable<T> GetDirectoryEntriesAsync<T>(string path, SftpFileEntryTransform<T> transform, EnumerationOptions? options = null)
=> new SftpFileSystemEnumerable<T>(this, path, transform, options ?? DefaultEnumerationOptions);

public ValueTask CreateDirectoryAsync(string path, CancellationToken cancellationToken)
=> CreateDirectoryAsync(path, createParents: false, DefaultCreateDirectoryPermissions, cancellationToken);

public async ValueTask CreateDirectoryAsync(string path, bool createParents = false, UnixFilePermissions permissions = DefaultCreateDirectoryPermissions, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
await dir.CreateDirectoryAsync(path, createParents, permissions, cancellationToken).ConfigureAwait(false);
}

public ValueTask CreateNewDirectoryAsync(string path, CancellationToken cancellationToken)
=> CreateNewDirectoryAsync(path, createParents: false, DefaultCreateDirectoryPermissions, cancellationToken);

public async ValueTask CreateNewDirectoryAsync(string path, bool createParents = false, UnixFilePermissions permissions = DefaultCreateDirectoryPermissions, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
await dir.CreateNewDirectoryAsync(path, createParents, permissions, cancellationToken).ConfigureAwait(false);
}

public ValueTask UploadDirectoryEntriesAsync(string localDirPath, string remoteDirPath, CancellationToken cancellationToken = default)
=> UploadDirectoryEntriesAsync(localDirPath, remoteDirPath, options: null, cancellationToken);

public async ValueTask UploadDirectoryEntriesAsync(string localDirPath, string remoteDirPath, UploadEntriesOptions? options, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
await dir.UploadDirectoryEntriesAsync(localDirPath, remoteDirPath, options, cancellationToken).ConfigureAwait(false);
}

public ValueTask UploadFileAsync(string localFilePath, string remoteFilePath, CancellationToken cancellationToken)
=> UploadFileAsync(localFilePath, remoteFilePath, overwrite: false, createPermissions: null, cancellationToken);

public async ValueTask UploadFileAsync(string localFilePath, string remoteFilePath, bool overwrite = false, UnixFilePermissions? createPermissions = default, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
await dir.UploadFileAsync(localFilePath, remoteFilePath, overwrite, createPermissions, cancellationToken).ConfigureAwait(false);
}

public ValueTask UploadFileAsync(Stream source, string remoteFilePath, CancellationToken cancellationToken)
=> UploadFileAsync(source, remoteFilePath, overwrite: false, createPermissions: DefaultCreateFilePermissions, cancellationToken);

public async ValueTask UploadFileAsync(Stream source, string remoteFilePath, bool overwrite = false, UnixFilePermissions createPermissions = DefaultCreateFilePermissions, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
await dir.UploadFileAsync(source, remoteFilePath, overwrite, createPermissions, cancellationToken).ConfigureAwait(false);
}

public ValueTask DownloadDirectoryEntriesAsync(string remoteDirPath, string localDirPath, CancellationToken cancellationToken = default)
=> DownloadDirectoryEntriesAsync(remoteDirPath, localDirPath, options: null, cancellationToken);

public async ValueTask DownloadDirectoryEntriesAsync(string remoteDirPath, string localDirPath, DownloadEntriesOptions? options, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
await dir.DownloadDirectoryEntriesAsync(remoteDirPath, localDirPath, options, cancellationToken).ConfigureAwait(false);
}

public ValueTask DownloadFileAsync(string remoteFilePath, string localFilePath, CancellationToken cancellationToken)
=> DownloadFileAsync(remoteFilePath, localFilePath, overwrite: false, cancellationToken);

public async ValueTask DownloadFileAsync(string remoteFilePath, string localFilePath, bool overwrite = false, CancellationToken cancellationToken = default)
{
var dir = await GetWorkingDirectoryAsync(cancellationToken).ConfigureAwait(false);
Expand Down
Loading
0