Skip to content

Commit e7b6ade

Browse files
committed
feat(Core): use Tmds.Ssh instead of SSH.NET
Built to be async, created by a developer I trust
1 parent bba7ffd commit e7b6ade

2 files changed

Lines changed: 134 additions & 116 deletions

File tree

SnapX.Core/Upload/BaseUploaders/Uploader.cs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -439,45 +439,64 @@ public override int Read(byte[] buffer, int offset, int count)
439439

440440
#region Helper methods
441441

442-
protected bool TransferData(Stream dataStream, Stream requestStream, long dataPosition = 0, long dataLength = -1)
442+
protected async Task<bool> TransferDataAsync(Stream dataStream, Stream requestStream, long dataPosition = 0, long dataLength = -1)
443443
{
444444
if (dataPosition >= dataStream.Length)
445-
{
446445
return true;
447-
}
448446

449447
if (dataStream.CanSeek)
450-
{
451448
dataStream.Position = dataPosition;
452-
}
453449

454450
if (dataLength == -1)
455-
{
456451
dataLength = dataStream.Length;
457-
}
452+
458453
dataLength = Math.Min(dataLength, dataStream.Length - dataPosition);
459454

460-
ProgressManager progress = new ProgressManager(dataStream.Length, dataPosition);
461-
int length = (int)Math.Min(BufferSize, dataLength);
462-
byte[] buffer = new byte[length];
463-
int bytesRead;
455+
var progress = new ProgressManager(dataStream.Length, dataPosition);
456+
var bytesRemaining = dataLength;
457+
458+
var maxBuffer = 4 * 1024 * 1024;
464459

465-
long bytesRemaining = dataLength;
466-
while (!StopUploadRequested && (bytesRead = dataStream.Read(buffer, 0, length)) > 0)
460+
while (!StopUploadRequested && bytesRemaining > 0)
467461
{
468-
requestStream.Write(buffer, 0, bytesRead);
462+
int currentBufferSize = BufferSize;
463+
464+
if (dataLength >= 500 * 1024 * 1024)
465+
{
466+
currentBufferSize = (int)Math.Min(maxBuffer, Math.Max(BufferSize, bytesRemaining));
467+
}
468+
else
469+
{
470+
currentBufferSize = (int)Math.Min(BufferSize, bytesRemaining);
471+
}
472+
473+
var buffer = new byte[currentBufferSize];
474+
475+
var bytesRead = await dataStream.ReadAsync(buffer.AsMemory(0, currentBufferSize)).ConfigureAwait(false);
476+
if (bytesRead == 0)
477+
break;
478+
479+
await requestStream.WriteAsync(buffer.AsMemory(0, bytesRead)).ConfigureAwait(false);
480+
469481
bytesRemaining -= bytesRead;
470-
length = (int)Math.Min(buffer.Length, bytesRemaining);
471482

472483
if (AllowReportProgress && progress.UpdateProgress(bytesRead))
473-
{
474484
OnProgressChanged(progress);
475-
}
476485
}
477486

478487
return !StopUploadRequested;
479488
}
480489

490+
491+
protected bool TransferData(Stream dataStream, Stream requestStream, long dataPosition = 0, long dataLength = -1)
492+
{
493+
return TransferDataAsync(dataStream, requestStream, dataPosition, dataLength)
494+
.ConfigureAwait(false)
495+
.GetAwaiter()
496+
.GetResult();
497+
}
498+
499+
481500
private string? ProcessError(Exception e, string? requestURL)
482501
{
483502
string? responseText = null;

SnapX.Core/Upload/File/SFTP.cs

Lines changed: 98 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,23 @@
22
// SPDX-License-Identifier: GPL-3.0-or-later
33

44

5-
using Renci.SshNet;
6-
using Renci.SshNet.Common;
7-
using Renci.SshNet.Sftp;
8-
using SnapX.Core.Upload.BaseUploaders;
5+
using System.Runtime.InteropServices;
96
using SnapX.Core.Utils;
7+
using Tmds.Ssh;
108

119
namespace SnapX.Core.Upload.File;
1210

13-
public sealed class SFTP : FileUploader, IDisposable
11+
public sealed class SFTP : FtpBase
1412
{
1513
public FTPAccount Account { get; private set; }
1614

1715
public bool IsValidAccount => (!string.IsNullOrEmpty(Account.Keypath) && System.IO.File.Exists(Account.Keypath)) || !string.IsNullOrEmpty(Account.Password);
1816

19-
public bool IsConnected => client != null && client.IsConnected;
17+
public override bool IsConnected => client != null;
2018

21-
private SftpClient client;
19+
private SftpClient? client;
2220

23-
public SFTP(FTPAccount account)
21+
public SFTP(FTPAccount account) : base(account)
2422
{
2523
Account = account;
2624
}
@@ -38,8 +36,8 @@ public override UploadResult Upload(Stream stream, string? fileName)
3836
try
3937
{
4038
IsUploading = true;
41-
42-
bool uploadResult = UploadStream(stream, path, true);
39+
if (!IsConnected) Connect();
40+
bool uploadResult = UploadStream(stream, path, true).GetAwaiter().GetResult();
4341

4442
if (uploadResult && !StopUploadRequested && !IsError)
4543
{
@@ -72,55 +70,57 @@ public override void StopUpload()
7270
}
7371
}
7472
}
75-
76-
public bool Connect()
73+
public string ConstructSshDestination(string user, string host, int port = 22)
74+
{
75+
return $"{user}@{host}:{port}";
76+
}
77+
public override bool Connect()
7778
{
78-
if (client == null)
79+
var sshDestination = ConstructSshDestination(Account.Username, Account.Host, Account.Port);
80+
81+
var settings = new SshClientSettings(sshDestination)
7982
{
80-
if (!string.IsNullOrEmpty(Account.Keypath))
81-
{
82-
if (!System.IO.File.Exists(Account.Keypath))
83-
{
84-
throw new FileNotFoundException("Key file does not exist.", Account.Keypath);
85-
}
86-
87-
PrivateKeyFile keyFile;
88-
89-
if (string.IsNullOrEmpty(Account.Passphrase))
90-
{
91-
keyFile = new PrivateKeyFile(Account.Keypath);
92-
}
93-
else
94-
{
95-
keyFile = new PrivateKeyFile(Account.Keypath, Account.Passphrase);
96-
}
97-
98-
client = new SftpClient(Account.Host, Account.Port, Account.Username, keyFile);
99-
}
100-
else if (!string.IsNullOrEmpty(Account.Password))
83+
AutoConnect = true,
84+
AutoReconnect = true,
85+
};
86+
string? keyPath = Account.Keypath;
87+
88+
if (string.IsNullOrWhiteSpace(keyPath))
89+
{
90+
var home = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
91+
? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
92+
: Environment.GetEnvironmentVariable("HOME") ?? string.Empty;
93+
94+
var defaultKeys = new[]
10195
{
102-
client = new SftpClient(Account.Host, Account.Port, Account.Username, Account.Password);
103-
}
96+
"id_ed25519",
97+
"id_rsa",
98+
"id_ecdsa",
99+
"id_dsa"
100+
};
104101

105-
if (client != null)
102+
foreach (var key in defaultKeys)
106103
{
107-
client.BufferSize = (uint)BufferSize;
104+
var path = Path.Combine(home, ".ssh", key);
105+
if (!System.IO.File.Exists(path)) continue;
106+
keyPath = path;
107+
break;
108108
}
109109
}
110+
if (!string.IsNullOrEmpty(keyPath)) settings.Credentials.Add(new PrivateKeyCredential(keyPath, Account.Passphrase));
111+
if (!string.IsNullOrEmpty(Account.Password)) settings.Credentials.Add(new PasswordCredential(Account.Password));
112+
settings.Credentials.Add(new SshAgentCredentials());
110113

111-
if (client != null && !client.IsConnected)
112-
{
113-
client.Connect();
114-
}
115-
116-
return IsConnected;
114+
client = new SftpClient(settings);
115+
return client is not null;
117116
}
118117

119-
public void Disconnect()
118+
public override void Disconnect()
120119
{
121-
if (client != null && client.IsConnected)
120+
if (client != null)
122121
{
123-
client.Disconnect();
122+
client.Dispose();
123+
client = null;
124124
}
125125
}
126126

@@ -130,9 +130,9 @@ public void ChangeDirectory(string? path, bool autoCreateDirectory = false)
130130
{
131131
try
132132
{
133-
client.ChangeDirectory(path);
133+
client.GetDirectory(path);
134134
}
135-
catch (SftpPathNotFoundException) when (autoCreateDirectory)
135+
catch (SftpException) when (autoCreateDirectory)
136136
{
137137
CreateDirectory(path, true);
138138
ChangeDirectory(path);
@@ -142,80 +142,66 @@ public void ChangeDirectory(string? path, bool autoCreateDirectory = false)
142142

143143
public bool DirectoryExists(string? path)
144144
{
145-
if (Connect())
145+
if (string.IsNullOrEmpty(path))
146+
return false;
147+
148+
try
146149
{
147-
return client.Exists(path);
150+
var attributes = client.GetAttributesAsync(path, followLinks: true, filter: null).ConfigureAwait(false).GetAwaiter().GetResult();
151+
return attributes is { FileType: UnixFileType.Directory };
152+
}
153+
catch (SftpException)
154+
{
155+
return false;
156+
}
157+
catch (Exception)
158+
{
159+
return false;
148160
}
149-
150-
return false;
151161
}
152162

153-
public void CreateDirectory(string? path, bool createMultiDirectory = false)
163+
public async void CreateDirectory(string? path, bool createMultiDirectory = false)
154164
{
155165
if (Connect())
156166
{
157167
try
158168
{
159-
client.CreateDirectory(path);
169+
await client.CreateDirectoryAsync(path);
160170

161171
DebugHelper.WriteLine($"SFTP directory created: {path}");
162172
}
163-
catch (SftpPathNotFoundException) when (createMultiDirectory)
164-
{
165-
CreateMultiDirectory(path);
166-
}
167-
catch (SftpPermissionDeniedException)
173+
catch (SftpException e) when (createMultiDirectory)
168174
{
175+
if (e.Error == SftpError.PermissionDenied) return;
176+
await client.CreateDirectoryAsync(path, true);
177+
169178
}
170179
}
171180
}
172181

173-
public List<string?> CreateMultiDirectory(string? path)
182+
private async Task<bool> UploadStream(Stream stream, string? remotePath, bool autoCreateDirectory = false)
174183
{
175-
List<string?> directoryList = [];
176-
177-
List<string?> paths = URLHelpers.GetPaths(path);
178-
179-
foreach (string? directory in paths)
184+
try
180185
{
181-
if (!DirectoryExists(directory))
182-
{
183-
CreateDirectory(directory);
184-
directoryList.Add(directory);
185-
}
186+
await using var remoteStream = await client.OpenOrCreateFileAsync(remotePath, FileAccess.Write, null).ConfigureAwait(false);
187+
return await TransferDataAsync(stream, remoteStream);
186188
}
187-
188-
return directoryList;
189-
}
190-
191-
private bool UploadStream(Stream stream, string? remotePath, bool autoCreateDirectory = false)
192-
{
193-
if (Connect())
189+
catch (SftpException e) when (autoCreateDirectory)
194190
{
195-
try
196-
{
197-
using (SftpFileStream sftpStream = client.Create(remotePath))
198-
{
199-
return TransferData(stream, sftpStream);
200-
}
201-
}
202-
catch (SftpPathNotFoundException) when (autoCreateDirectory)
203-
{
204-
// Happens when directory not exist, create directory and retry uploading
205-
206-
CreateDirectory(URLHelpers.GetDirectoryPath(remotePath), true);
207-
return UploadStream(stream, remotePath);
208-
}
209-
catch (NullReferenceException)
210-
{
211-
// Happens when disconnect while uploading
212-
}
191+
var code = e.Error;
192+
if (code is not SftpError.NoSuchFile) return false;
193+
CreateDirectory(URLHelpers.GetDirectoryPath(remotePath), true);
194+
return await UploadStream(stream, remotePath).ConfigureAwait(false);
195+
}
196+
catch (Exception ex)
197+
{
198+
DebugHelper.WriteException(ex);
199+
return false;
213200
}
214-
215-
return false;
216201
}
217202

218-
public void Dispose()
203+
204+
public override void Dispose()
219205
{
220206
if (client != null)
221207
{
@@ -229,4 +215,17 @@ public void Dispose()
229215
}
230216
}
231217
}
218+
219+
public List<string?> CreateMultiDirectory(string? remotePath)
220+
{
221+
List<string?> paths = URLHelpers.GetPaths(remotePath);
222+
223+
foreach (string? path in paths)
224+
{
225+
CreateDirectory(path);
226+
DebugHelper.WriteLine($"FTP directory created: {path}");
227+
}
228+
229+
return paths;
230+
}
232231
}

0 commit comments

Comments
 (0)