Skip to content

Commit 13bcb77

Browse files
authored
Remove unnecessary SftpFileStream unit tests and dedup Open{Async} (#1680)
* deletions * test additions/tweaks * dedup SftpFileStream init logic
1 parent dab8a11 commit 13bcb77

File tree

110 files changed

+560
-12080
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+560
-12080
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ dotnet_code_quality.CA1828.api_surface = all
719719
dotnet_diagnostic.CA1852.severity = none
720720

721721
# CA1848: don't enforce LoggerMessage pattern
722-
dotnet_diagnostic.CA1848.severity = suggestion
722+
dotnet_diagnostic.CA1848.severity = silent
723723

724724
# CA1859: Change return type for improved performance
725725
#

src/Renci.SshNet/Common/Extensions.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics;
43
using System.Globalization;
54
using System.Net;
65
using System.Net.Sockets;
76
using System.Numerics;
87
using System.Runtime.CompilerServices;
9-
using System.Text;
108
using System.Threading;
119

1210
using Renci.SshNet.Abstractions;
@@ -153,22 +151,6 @@ public static void SetIgnoringObjectDisposed(this EventWaitHandle waitHandle)
153151
}
154152
}
155153

156-
/// <summary>
157-
/// Prints out the specified bytes.
158-
/// </summary>
159-
/// <param name="bytes">The bytes.</param>
160-
internal static void DebugPrint(this IEnumerable<byte> bytes)
161-
{
162-
var sb = new StringBuilder();
163-
164-
foreach (var b in bytes)
165-
{
166-
_ = sb.AppendFormat(CultureInfo.CurrentCulture, "0x{0:x2}, ", b);
167-
}
168-
169-
Debug.WriteLine(sb.ToString());
170-
}
171-
172154
internal static void ValidatePort(this uint value, [CallerArgumentExpression(nameof(value))] string argument = null)
173155
{
174156
if (value > IPEndPoint.MaxPort)

src/Renci.SshNet/ISubsystemSession.cs

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -84,57 +84,11 @@ internal interface ISubsystemSession : IDisposable
8484
/// <returns>A <see cref="Task"/> representing the wait.</returns>
8585
Task<T> WaitOnHandleAsync<T>(TaskCompletionSource<T> tcs, int millisecondsTimeout, CancellationToken cancellationToken);
8686

87-
/// <summary>
88-
/// Blocks the current thread until the specified <see cref="WaitHandle"/> gets signaled, using a
89-
/// 32-bit signed integer to specify the time interval in milliseconds.
90-
/// </summary>
91-
/// <param name="waitHandle">The handle to wait for.</param>
92-
/// <param name="millisecondsTimeout">To number of milliseconds to wait for <paramref name="waitHandle"/> to get signaled, or <c>-1</c> to wait indefinitely.</param>
93-
/// <returns>
94-
/// <see langword="true"/> if <paramref name="waitHandle"/> received a signal within the specified timeout;
95-
/// otherwise, <see langword="false"/>.
96-
/// </returns>
97-
/// <exception cref="SshException">The connection was closed by the server.</exception>
98-
/// <exception cref="SshException">The channel was closed.</exception>
99-
/// <remarks>
100-
/// The blocking wait is also interrupted when either the established channel is closed, the current
101-
/// session is disconnected or an unexpected <see cref="Exception"/> occurred while processing a channel
102-
/// or session event.
103-
/// </remarks>
104-
bool WaitOne(WaitHandle waitHandle, int millisecondsTimeout);
105-
106-
/// <summary>
107-
/// Blocks the current thread until the specified <see cref="WaitHandle"/> gets signaled, using a
108-
/// 32-bit signed integer to specify the time interval in milliseconds.
109-
/// </summary>
110-
/// <param name="waitHandleA">The first handle to wait for.</param>
111-
/// <param name="waitHandleB">The second handle to wait for.</param>
112-
/// <param name="millisecondsTimeout">To number of milliseconds to wait for a <see cref="WaitHandle"/> to get signaled, or <c>-1</c> to wait indefinitely.</param>
113-
/// <returns>
114-
/// <c>0</c> if <paramref name="waitHandleA"/> received a signal within the specified timeout and <c>1</c>
115-
/// if <paramref name="waitHandleB"/> received a signal within the specified timeout, or <see cref="WaitHandle.WaitTimeout"/>
116-
/// if no object satisfied the wait.
117-
/// </returns>
118-
/// <exception cref="SshException">The connection was closed by the server.</exception>
119-
/// <exception cref="SshException">The channel was closed.</exception>
120-
/// <remarks>
121-
/// <para>
122-
/// The blocking wait is also interrupted when either the established channel is closed, the current
123-
/// session is disconnected or an unexpected <see cref="Exception"/> occurred while processing a channel
124-
/// or session event.
125-
/// </para>
126-
/// <para>
127-
/// When both <paramref name="waitHandleA"/> and <paramref name="waitHandleB"/> are signaled during the call,
128-
/// then <c>0</c> is returned.
129-
/// </para>
130-
/// </remarks>
131-
int WaitAny(WaitHandle waitHandleA, WaitHandle waitHandleB, int millisecondsTimeout);
132-
13387
/// <summary>
13488
/// Waits for any of the elements in the specified array to receive a signal, using a 32-bit signed
13589
/// integer to specify the time interval.
13690
/// </summary>
137-
/// <param name="waitHandles">A <see cref="WaitHandle"/> array - constructed using <see cref="CreateWaitHandleArray(WaitHandle[])"/> - containing the objects to wait for.</param>
91+
/// <param name="waitHandles">A <see cref="WaitHandle"/> array - constructed using <see cref="CreateWaitHandleArray"/> - containing the objects to wait for.</param>
13892
/// <param name="millisecondsTimeout">To number of milliseconds to wait for a <see cref="WaitHandle"/> to get signaled, or <c>-1</c> to wait indefinitely.</param>
13993
/// <returns>
14094
/// The array index of the first non-system object that satisfied the wait.
@@ -147,16 +101,6 @@ internal interface ISubsystemSession : IDisposable
147101
/// </remarks>
148102
int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout);
149103

150-
/// <summary>
151-
/// Creates a <see cref="WaitHandle"/> array that is composed of system objects and the specified
152-
/// elements.
153-
/// </summary>
154-
/// <param name="waitHandles">A <see cref="WaitHandle"/> array containing the objects to wait for.</param>
155-
/// <returns>
156-
/// A <see cref="WaitHandle"/> array that is composed of system objects and the specified elements.
157-
/// </returns>
158-
WaitHandle[] CreateWaitHandleArray(params WaitHandle[] waitHandles);
159-
160104
/// <summary>
161105
/// Creates a <see cref="WaitHandle"/> array that is composed of system objects and the specified
162106
/// elements.

src/Renci.SshNet/Sftp/ISftpSession.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ internal interface ISftpSession : ISubsystemSession
4040
/// <param name="path">The new working directory.</param>
4141
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
4242
/// <returns>A <see cref="Task"/> that tracks the asynchronous change working directory request.</returns>
43-
Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken = default);
43+
Task ChangeDirectoryAsync(string path, CancellationToken cancellationToken);
4444

4545
/// <summary>
4646
/// Resolves a given path into an absolute path on the server.
@@ -168,7 +168,7 @@ internal interface ISftpSession : ISubsystemSession
168168
/// <param name="path">The path.</param>
169169
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
170170
/// <returns>A <see cref="Task"/> that represents the asynchronous <c>SSH_FXP_MKDIR</c> operation.</returns>
171-
Task RequestMkDirAsync(string path, CancellationToken cancellationToken = default);
171+
Task RequestMkDirAsync(string path, CancellationToken cancellationToken);
172172

173173
/// <summary>
174174
/// Performs a <c>SSH_FXP_OPEN</c> request.
@@ -389,7 +389,7 @@ internal interface ISftpSession : ISubsystemSession
389389
/// <returns>
390390
/// A task that represents the asynchronous <c>SSH_FXP_RMDIR</c> request.
391391
/// </returns>
392-
Task RequestRmDirAsync(string path, CancellationToken cancellationToken = default);
392+
Task RequestRmDirAsync(string path, CancellationToken cancellationToken);
393393

394394
/// <summary>
395395
/// Performs SSH_FXP_SETSTAT request.

src/Renci.SshNet/Sftp/SftpFileStream.cs

Lines changed: 40 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Globalization;
45
using System.IO;
@@ -191,7 +192,7 @@ public TimeSpan Timeout
191192
}
192193
}
193194

194-
private SftpFileStream(ISftpSession session, string path, FileAccess access, int bufferSize, byte[] handle, long position)
195+
private SftpFileStream(ISftpSession session, string path, FileAccess access, int readBufferSize, int writeBufferSize, byte[] handle, long position)
195196
{
196197
Timeout = TimeSpan.FromSeconds(30);
197198
Name = path;
@@ -202,25 +203,24 @@ private SftpFileStream(ISftpSession session, string path, FileAccess access, int
202203
_canWrite = (access & FileAccess.Write) == FileAccess.Write;
203204

204205
_handle = handle;
206+
_readBufferSize = readBufferSize;
207+
_writeBufferSize = writeBufferSize;
208+
_position = position;
209+
}
205210

206-
/*
207-
* Instead of using the specified buffer size as is, we use it to calculate a buffer size
208-
* that ensures we always receive or send the max. number of bytes in a single SSH_FXP_READ
209-
* or SSH_FXP_WRITE message.
210-
*/
211-
212-
_readBufferSize = (int)session.CalculateOptimalReadLength((uint)bufferSize);
213-
_writeBufferSize = (int)session.CalculateOptimalWriteLength((uint)bufferSize, _handle);
211+
internal static SftpFileStream Open(ISftpSession session, string path, FileMode mode, FileAccess access, int bufferSize)
212+
{
213+
return Open(session, path, mode, access, bufferSize, isAsync: false, CancellationToken.None).GetAwaiter().GetResult();
214+
}
214215

215-
_position = position;
216+
internal static Task<SftpFileStream> OpenAsync(ISftpSession session, string path, FileMode mode, FileAccess access, int bufferSize, CancellationToken cancellationToken)
217+
{
218+
return Open(session, path, mode, access, bufferSize, isAsync: true, cancellationToken);
216219
}
217220

218-
internal SftpFileStream(ISftpSession session, string path, FileMode mode, FileAccess access, int bufferSize)
221+
private static async Task<SftpFileStream> Open(ISftpSession session, string path, FileMode mode, FileAccess access, int bufferSize, bool isAsync, CancellationToken cancellationToken)
219222
{
220-
if (session is null)
221-
{
222-
throw new SshConnectionException("Client not connected.");
223-
}
223+
Debug.Assert(isAsync || cancellationToken == default);
224224

225225
ThrowHelper.ThrowIfNull(path);
226226

@@ -229,14 +229,10 @@ internal SftpFileStream(ISftpSession session, string path, FileMode mode, FileAc
229229
throw new ArgumentOutOfRangeException(nameof(bufferSize), "Cannot be less than or equal to zero.");
230230
}
231231

232-
Timeout = TimeSpan.FromSeconds(30);
233-
Name = path;
234-
235-
// Initialize the object state.
236-
_session = session;
237-
_canRead = (access & FileAccess.Read) == FileAccess.Read;
238-
_canSeek = true;
239-
_canWrite = (access & FileAccess.Write) == FileAccess.Write;
232+
if (session is null)
233+
{
234+
throw new SshConnectionException("Client not connected.");
235+
}
240236

241237
var flags = Flags.None;
242238

@@ -284,16 +280,7 @@ internal SftpFileStream(ISftpSession session, string path, FileMode mode, FileAc
284280
flags |= Flags.Append | Flags.CreateNewOrOpen;
285281
break;
286282
case FileMode.Create:
287-
_handle = _session.RequestOpen(path, flags | Flags.Truncate, nullOnError: true);
288-
if (_handle is null)
289-
{
290-
flags |= Flags.CreateNew;
291-
}
292-
else
293-
{
294-
flags |= Flags.Truncate;
295-
}
296-
283+
flags |= Flags.CreateNewOrOpen | Flags.Truncate;
297284
break;
298285
case FileMode.CreateNew:
299286
flags |= Flags.CreateNew;
@@ -310,127 +297,44 @@ internal SftpFileStream(ISftpSession session, string path, FileMode mode, FileAc
310297
throw new ArgumentOutOfRangeException(nameof(mode));
311298
}
312299

313-
_handle ??= _session.RequestOpen(path, flags);
300+
byte[] handle;
301+
302+
if (isAsync)
303+
{
304+
handle = await session.RequestOpenAsync(path, flags, cancellationToken).ConfigureAwait(false);
305+
}
306+
else
307+
{
308+
handle = session.RequestOpen(path, flags);
309+
}
314310

315311
/*
316312
* Instead of using the specified buffer size as is, we use it to calculate a buffer size
317313
* that ensures we always receive or send the max. number of bytes in a single SSH_FXP_READ
318314
* or SSH_FXP_WRITE message.
319315
*/
320316

321-
_readBufferSize = (int)session.CalculateOptimalReadLength((uint)bufferSize);
322-
_writeBufferSize = (int)session.CalculateOptimalWriteLength((uint)bufferSize, _handle);
317+
var readBufferSize = (int)session.CalculateOptimalReadLength((uint)bufferSize);
318+
var writeBufferSize = (int)session.CalculateOptimalWriteLength((uint)bufferSize, handle);
323319

320+
long position = 0;
324321
if (mode == FileMode.Append)
325322
{
326-
var attributes = _session.RequestFStat(_handle, nullOnError: false);
327-
_position = attributes.Size;
328-
}
329-
}
330-
331-
internal static async Task<SftpFileStream> OpenAsync(ISftpSession session, string path, FileMode mode, FileAccess access, int bufferSize, CancellationToken cancellationToken)
332-
{
333-
if (session is null)
334-
{
335-
throw new SshConnectionException("Client not connected.");
336-
}
337-
338-
ThrowHelper.ThrowIfNull(path);
339-
340-
if (bufferSize <= 0)
341-
{
342-
throw new ArgumentOutOfRangeException(nameof(bufferSize), "Cannot be less than or equal to zero.");
343-
}
344-
345-
var flags = Flags.None;
346-
347-
switch (access)
348-
{
349-
case FileAccess.Read:
350-
flags |= Flags.Read;
351-
break;
352-
case FileAccess.Write:
353-
flags |= Flags.Write;
354-
break;
355-
case FileAccess.ReadWrite:
356-
flags |= Flags.Read;
357-
flags |= Flags.Write;
358-
break;
359-
default:
360-
throw new ArgumentOutOfRangeException(nameof(access));
361-
}
362-
363-
if ((access & FileAccess.Read) == FileAccess.Read && mode == FileMode.Append)
364-
{
365-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
366-
"{0} mode can be requested only when combined with write-only access.",
367-
mode.ToString("G")),
368-
nameof(mode));
369-
}
323+
SftpFileAttributes attributes;
370324

371-
if ((access & FileAccess.Write) != FileAccess.Write)
372-
{
373-
if (mode is FileMode.Create or FileMode.CreateNew or FileMode.Truncate or FileMode.Append)
325+
if (isAsync)
374326
{
375-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
376-
"Combining {0}: {1} with {2}: {3} is invalid.",
377-
nameof(FileMode),
378-
mode,
379-
nameof(FileAccess),
380-
access),
381-
nameof(mode));
327+
attributes = await session.RequestFStatAsync(handle, cancellationToken).ConfigureAwait(false);
382328
}
383-
}
384-
385-
switch (mode)
386-
{
387-
case FileMode.Append:
388-
flags |= Flags.Append | Flags.CreateNewOrOpen;
389-
break;
390-
case FileMode.Create:
391-
flags |= Flags.CreateNewOrOpen | Flags.Truncate;
392-
break;
393-
case FileMode.CreateNew:
394-
flags |= Flags.CreateNew;
395-
break;
396-
case FileMode.Open:
397-
break;
398-
case FileMode.OpenOrCreate:
399-
flags |= Flags.CreateNewOrOpen;
400-
break;
401-
case FileMode.Truncate:
402-
flags |= Flags.Truncate;
403-
break;
404-
default:
405-
throw new ArgumentOutOfRangeException(nameof(mode));
406-
}
407-
408-
var handle = await session.RequestOpenAsync(path, flags, cancellationToken).ConfigureAwait(false);
409-
410-
long position = 0;
411-
if (mode == FileMode.Append)
412-
{
413-
try
329+
else
414330
{
415-
var attributes = await session.RequestFStatAsync(handle, cancellationToken).ConfigureAwait(false);
416-
position = attributes.Size;
331+
attributes = session.RequestFStat(handle, nullOnError: false);
417332
}
418-
catch
419-
{
420-
try
421-
{
422-
await session.RequestCloseAsync(handle, cancellationToken).ConfigureAwait(false);
423-
}
424-
catch
425-
{
426-
// The original exception is presumably more informative, so we just ignore this one.
427-
}
428333

429-
throw;
430-
}
334+
position = attributes.Size;
431335
}
432336

433-
return new SftpFileStream(session, path, access, bufferSize, handle, position);
337+
return new SftpFileStream(session, path, access, readBufferSize, writeBufferSize, handle, position);
434338
}
435339

436340
/// <summary>

0 commit comments

Comments
 (0)