Skip to content

Commit

Permalink
Add {EnumerationOptions,DownloadEntriesOptions}.FileTypeFilter.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmds committed Apr 1, 2024
1 parent 01b1d95 commit 91a730c
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 18 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,32 +222,42 @@ enum UnixFileType
Socket,
Fifo,
}
[Flags]
enum UnixFileTypeFilter
{
RegularFile,
Directory,
SymbolicLink,
CharacterDevice,
BlockDevice,
Socket,
Fifo,
}
class FileEntryAttributes
{
long? Length { get; set; }
int? Uid { get; set; }
int? Gid { get; set; }
public UnixFileType? FileType { get; set; }
public UnixFilePermissions? Permissions { get; set; }
UnixFileType? FileType { get; set; }
UnixFilePermissions? Permissions { get; set; }
DateTimeOffset? LastAccessTime { get; set; }
DateTimeOffset? LastWriteTime { get; set; }
Dictionary<string, string>? ExtendedAttributes { get; set; }

UnixFileType? FileType { get; }
UnixFilePermissions? Permissions { get; }
}
class EnumerationOptions
{
bool RecurseSubdirectories { get; set; } = false;
bool FollowFileLinks { get; set; } = true;
bool FollowDirectoryLinks { get; set; } = true;
UnixFileTypeFilter FileTypeFilter { get; set; } = RegularFile | Directory | SymbolicLink | CharacterDevice | BlockDevice | Socket | Fifo;
}
class DownloadEntriesOptions
{
bool Overwrite { get; set; } = false;
bool RecurseSubdirectories { get; set; } = true;
bool FollowFileLinks { get; set; } = true;
bool FollowDirectoryLinks { get; set; } = true;
UnixFileTypeFilter FileTypeFilter { get; set; } = RegularFile | Directory | SymbolicLink;
}
class UploadEntriesOptions
{
Expand Down
4 changes: 4 additions & 0 deletions src/Tmds.Ssh/DownloadEntriesOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ public sealed class DownloadEntriesOptions
public bool RecurseSubdirectories { get; set; } = true;
public bool FollowFileLinks { get; set; } = true;
public bool FollowDirectoryLinks { get; set; } = true;
public UnixFileTypeFilter FileTypeFilter { get; set; } =
UnixFileTypeFilter.RegularFile |
UnixFileTypeFilter.Directory |
UnixFileTypeFilter.SymbolicLink;
}
8 changes: 8 additions & 0 deletions src/Tmds.Ssh/EnumerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@ public sealed class EnumerationOptions
public bool RecurseSubdirectories { get; set; }
public bool FollowFileLinks { get; set; } = true;
public bool FollowDirectoryLinks { get; set; } = true;
public UnixFileTypeFilter FileTypeFilter { get; set; } =
UnixFileTypeFilter.RegularFile |
UnixFileTypeFilter.Directory |
UnixFileTypeFilter.SymbolicLink |
UnixFileTypeFilter.CharacterDevice |
UnixFileTypeFilter.BlockDevice |
UnixFileTypeFilter.Socket |
UnixFileTypeFilter.Fifo;
}
20 changes: 17 additions & 3 deletions src/Tmds.Ssh/SftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -571,8 +571,18 @@ public ValueTask DownloadDirectoryEntriesAsync(string remoteDirPath, string loca
public async ValueTask DownloadDirectoryEntriesAsync(string remoteDirPath, string localDirPath, DownloadEntriesOptions? options, CancellationToken cancellationToken = default)
{
options ??= DefaultDownloadEntriesOptions;

const UnixFileTypeFilter SupportedFileTypes =
UnixFileTypeFilter.RegularFile |
UnixFileTypeFilter.Directory |
UnixFileTypeFilter.SymbolicLink;
UnixFileTypeFilter unsupportedFileTypes = options.FileTypeFilter & ~SupportedFileTypes;
if (unsupportedFileTypes != 0)
{
throw new NotSupportedException($"{nameof(options.FileTypeFilter)} includes unsupported file types: {unsupportedFileTypes}. {nameof(options.FileTypeFilter)} can only include {SupportedFileTypes}.");
}

bool overwrite = options.Overwrite;
bool recurse = options.RecurseSubdirectories;

int trimRemoteDirectory = remoteDirPath.Length;
if (!LocalPath.EndsInDirectorySeparator(remoteDirPath))
Expand All @@ -591,7 +601,11 @@ public async ValueTask DownloadDirectoryEntriesAsync(string remoteDirPath, strin
localPathBuilder.Append(remotePath.Substring(trimRemoteDirectory));
return (localPathBuilder.ToString(), remotePath, entry.FileType, entry.Permissions, entry.Length);
},
new EnumerationOptions() { RecurseSubdirectories = recurse, FollowDirectoryLinks = options.FollowDirectoryLinks, FollowFileLinks = options.FollowFileLinks });
new EnumerationOptions() {
RecurseSubdirectories = options.RecurseSubdirectories,
FollowDirectoryLinks = options.FollowDirectoryLinks,
FollowFileLinks = options.FollowFileLinks,
FileTypeFilter = options.FileTypeFilter });

var onGoing = new Queue<ValueTask>();
try
Expand Down Expand Up @@ -622,7 +636,7 @@ public async ValueTask DownloadDirectoryEntriesAsync(string remoteDirPath, strin
onGoing.Enqueue(DownloadLinkAsync(item.RemotePath, item.LocalPath, overwrite, cancellationToken));
break;
default:
break;
throw new NotSupportedException($"Downloading file type '{item.Type}' is not supported.");
}
}
while (onGoing.TryDequeue(out ValueTask pending))
Expand Down
25 changes: 16 additions & 9 deletions src/Tmds.Ssh/SftpFileSystemEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ sealed class SftpFileSystemEnumerator<T> : IAsyncEnumerator<T>
private readonly bool _recurseSubdirectories;
private readonly bool _followFileLinks;
private readonly bool _followDirectoryLinks;
private readonly UnixFileTypeFilter _fileTypeFilter;

private Queue<string>? _pending;

Expand All @@ -64,6 +65,7 @@ public SftpFileSystemEnumerator(SftpClient client, string path, SftpFileEntryTra
_recurseSubdirectories = options.RecurseSubdirectories;
_followDirectoryLinks = options.FollowDirectoryLinks;
_followFileLinks = options.FollowFileLinks;
_fileTypeFilter = options.FileTypeFilter;
}

public T Current => _current!;
Expand Down Expand Up @@ -103,9 +105,9 @@ public async ValueTask<bool> MoveNextAsync()
{
return true;
}
if (linkPath is not null)
if (linkPath is not null &&
await ReadLinkTargetEntry(linkPath, linkEntry))
{
await ReadLinkTargetEntry(linkPath, linkEntry);
return true;
}
}
Expand Down Expand Up @@ -184,22 +186,27 @@ private bool ReadNextEntry(bool followLink, out string? linkPath, out Memory<byt
return false;
}

SetCurrent(ref entry);
return true;
return SetCurrent(ref entry);
}

private void SetCurrent(ref SftpFileEntry entry)
private bool SetCurrent(ref SftpFileEntry entry)
{
if (_recurseSubdirectories && entry.FileType == UnixFileType.Directory)
{
_pending ??= new();
_pending.Enqueue(entry.ToPath());
}

if (!_fileTypeFilter.Matches(entry.FileType))
{
return false;
}

_current = _transform(ref entry);
return true;
}

private async Task ReadLinkTargetEntry(string linkPath, Memory<byte> linkEntry)
private async Task<bool> ReadLinkTargetEntry(string linkPath, Memory<byte> linkEntry)
{
FileEntryAttributes? attributes = await _client.GetAttributesAsync(linkPath, followLinks: true, _cancellationToken);
if (attributes is not null)
Expand All @@ -210,12 +217,12 @@ private async Task ReadLinkTargetEntry(string linkPath, Memory<byte> linkEntry)
attributes = null;
}
}
SetCurrentEntry();
return SetCurrentEntry();

void SetCurrentEntry()
bool SetCurrentEntry()
{
SftpFileEntry entry = new SftpFileEntry(_path, linkEntry.Span, _pathBuffer, _nameBuffer, out int _, attributes);
SetCurrent(ref entry);
return SetCurrent(ref entry);
}
}
}
2 changes: 1 addition & 1 deletion src/Tmds.Ssh/UnixFileTypeFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace Tmds.Ssh
{
[Flags]
enum UnixFileTypeFilter : byte
public enum UnixFileTypeFilter : byte
{
RegularFile = 1,
Directory = 2,
Expand Down
22 changes: 22 additions & 0 deletions test/Tmds.Ssh.Tests/SftpClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,28 @@ public async Task EnumerateDirectoryRecursive(bool recurse)
entries.Select(entry => entry.Path).ToHashSet()
);
}

}

[Fact]
public async Task EnumerateDirectoryFileTypeFilter()
{
using var client = await _sshServer.CreateClientAsync();
using var sftpClient = await client.CreateSftpClientAsync();
string directoryPath = $"/tmp/{Path.GetRandomFileName()}";

await sftpClient.CreateNewDirectoryAsync($"{directoryPath}/child1/child2/", createParents: true);

using var file = await sftpClient.CreateNewFileAsync($"{directoryPath}/child1/child2/file", FileAccess.Write);
await file.CloseAsync();

List<(string Path, FileEntryAttributes Attributes)> entries = await sftpClient.GetDirectoryEntriesAsync(directoryPath,
new EnumerationOptions() { RecurseSubdirectories = true, FileTypeFilter = UnixFileTypeFilter.RegularFile }).ToListAsync();

Assert.Single(entries);
var entry = entries[0];
Assert.Equal(UnixFileType.RegularFile, entry.Attributes.FileType);
Assert.Equal($"{directoryPath}/child1/child2/file", entry.Path);
}

[InlineData(true, true)]
Expand Down

0 comments on commit 91a730c

Please sign in to comment.