Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
21 changes: 21 additions & 0 deletions src/Renci.SshNet/Common/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
#if !NET
using System.IO;
#endif
using System.Net;
using System.Net.Sockets;
using System.Numerics;
Expand Down Expand Up @@ -388,6 +391,24 @@ internal static T[] ToArray<T>(this ArraySegment<T> arraySegment)
Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
return array;
}

#pragma warning disable CA1859 // Use concrete types for improved performance
internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count)
#pragma warning restore CA1859
{
var totalRead = 0;

while (totalRead < count)
{
var read = stream.Read(buffer, offset + totalRead, count - totalRead);
if (read == 0)
{
throw new EndOfStreamException();
}

totalRead += read;
}
}
#endif
}
}
59 changes: 30 additions & 29 deletions src/Renci.SshNet/SftpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
Expand Down Expand Up @@ -1727,7 +1728,7 @@ public byte[] ReadAllBytes(string path)
using (var stream = OpenRead(path))
{
var buffer = new byte[stream.Length];
_ = stream.Read(buffer, 0, buffer.Length);
stream.ReadExactly(buffer, 0, buffer.Length);
return buffer;
}
}
Expand Down Expand Up @@ -1760,23 +1761,7 @@ public string[] ReadAllLines(string path)
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public string[] ReadAllLines(string path, Encoding encoding)
{
/*
* We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
* for the SftpFileStream. We may want to revisit this later.
*/

var lines = new List<string>();

using (var stream = new StreamReader(OpenRead(path), encoding))
{
string? line;
while ((line = stream.ReadLine()) != null)
{
lines.Add(line);
}
}

return lines.ToArray();
return ReadLines(path, encoding).ToArray();
}

/// <summary>
Expand Down Expand Up @@ -1807,15 +1792,8 @@ public string ReadAllText(string path)
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public string ReadAllText(string path, Encoding encoding)
{
/*
* We use the default buffer size for StreamReader - which is 1024 bytes - and the configured buffer size
* for the SftpFileStream. We may want to revisit this later.
*/

using (var stream = new StreamReader(OpenRead(path), encoding))
{
return stream.ReadToEnd();
}
using var sr = new StreamReader(OpenRead(path), encoding);
return sr.ReadToEnd();
}

/// <summary>
Expand All @@ -1830,7 +1808,7 @@ public string ReadAllText(string path, Encoding encoding)
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public IEnumerable<string> ReadLines(string path)
{
return ReadAllLines(path);
return ReadLines(path, Encoding.UTF8);
}

/// <summary>
Expand All @@ -1846,7 +1824,30 @@ public IEnumerable<string> ReadLines(string path)
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public IEnumerable<string> ReadLines(string path, Encoding encoding)
{
return ReadAllLines(path, encoding);
// We open the file eagerly i.e. outside of the state machine created by yield,
// in order to a) throw resulting (e.g. file-related) exceptions eagerly; and b)
// to match what File.ReadLines does.
// This probably makes it behave more predictably/closer to what most people expect.
// The downside is that if the return value is never enumerated, the file
// is never closed (we can't do "using" here because it would be disposed
// as soon as we return). This conundrum also exists with File.ReadLines.

var sr = new StreamReader(OpenRead(path), encoding);

return Enumerate(sr);

static IEnumerable<string> Enumerate(StreamReader sr)
{
using (sr)
{
string? line;

while ((line = sr.ReadLine()) != null)
{
yield return line;
}
}
}
}

/// <summary>
Expand Down
Loading