From 30523ce91a75ca04800cbbaf1be989260b7adfa4 Mon Sep 17 00:00:00 2001
From: manandre <2341261+manandre@users.noreply.github.com>
Date: Sat, 24 Aug 2019 16:13:31 +0200
Subject: [PATCH 1/5] Add XML doc to FollowingFileStream API
---
FollowingFileStream/FollowingFileStream.cs | 309 +++++++++++++++++++++
1 file changed, 309 insertions(+)
diff --git a/FollowingFileStream/FollowingFileStream.cs b/FollowingFileStream/FollowingFileStream.cs
index 81aa3e1..ffbf298 100644
--- a/FollowingFileStream/FollowingFileStream.cs
+++ b/FollowingFileStream/FollowingFileStream.cs
@@ -5,42 +5,266 @@
namespace FollowingFileStream
{
+ ///
+ /// Provides a System.IO.Stream for following a file being written,
+ /// supporting both synchronous and asynchronous read operations.
+ ///
public class FollowingFileStream : Stream
{
+ ///
+ /// The underlying filestream
+ ///
private readonly FileStream fileStream;
+ ///
+ /// Time before retrying write access to followed file
+ ///
private const int MillisecondsRetryTimeout = 100;
+ ///
+ /// Cancellation token source for retry attempts
+ ///
private readonly CancellationTokenSource cts = new CancellationTokenSource();
+ ///
+ /// Asynchronous lock to avoid race conditions
+ ///
private readonly AsyncLock locker = new AsyncLock();
#region Constructors
+ ///
+ /// Initializes a new instance of the FollowingFileStream class with the specified path.
+ ///
+ /// A relative or absolute path for the file
+ /// that the current FollowingFileStream object will encapsulate.
+ ///
+ /// path is an empty string (""), contains only white space, or contains
+ /// one or more invalid characters. -or- path refers to a non-file device, such as
+ /// "con:", "com1:", "lpt1:", etc. in an NTFS environment.
+ ///
+ ///
+ /// path refers to a non-file device, such as "con:", "com1:",
+ /// "lpt1:", etc. in a non-NTFS environment.
+ ///
+ ///
+ /// path is null.
+ ///
+ ///
+ /// The caller does not have the required permission.
+ ///
+ ///
+ /// The file cannot be found. The file must already exist.
+ ///
+ ///
+ /// The stream has been closed.
+ ///
+ ///
+ /// The specified path is invalid, such as being on an unmapped drive.
+ ///
+ ///
+ /// The specified path, file name, or both exceed the system-defined maximum length.
+ /// For example, on Windows-based platforms, paths must be less than 248 characters,
+ /// and file names must be less than 260 characters.
+ ///
public FollowingFileStream(string path)
{
fileStream = new FileStream(path, FileMode.Open,FileAccess.Read, FileShare.ReadWrite);
}
+ ///
+ ///
+ ///
+ /// A relative or absolute path for the file
+ /// that the current FollowingFileStream object will encapsulate.
+ /// A positive System.Int32 value greater than 0 indicating the buffer size. The
+ /// default buffer size is 4096.
+ /// Specifies whether to use asynchronous I/O or synchronous I/O. However, note that
+ /// the underlying operating system might not support asynchronous I/O, so when specifying
+ /// true, the handle might be opened synchronously depending on the platform. When
+ /// opened asynchronously, the System.IO.FileStream.BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)
+ /// and System.IO.FileStream.BeginWrite(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)
+ /// methods perform better on large reads or writes, but they might be much slower
+ /// for small reads or writes. If the application is designed to take advantage of
+ /// asynchronous I/O, set the useAsync parameter to true. Using asynchronous I/O
+ /// correctly can speed up applications by as much as a factor of 10, but using it
+ /// without redesigning the application for asynchronous I/O can decrease performance
+ /// by as much as a factor of 10.
+ ///
+ /// path is an empty string (""), contains only white space, or contains
+ /// one or more invalid characters. -or- path refers to a non-file device, such as
+ /// "con:", "com1:", "lpt1:", etc. in an NTFS environment.
+ ///
+ ///
+ /// path refers to a non-file device, such as "con:", "com1:",
+ /// "lpt1:", etc. in a non-NTFS environment.
+ ///
+ ///
+ /// path is null.
+ ///
+ ///
+ /// bufferSize is negative or zero.
+ ///
+ ///
+ /// The caller does not have the required permission.
+ ///
+ ///
+ /// The file cannot be found. The file must already exist.
+ ///
+ ///
+ /// The stream has been closed.
+ ///
+ ///
+ /// The specified path is invalid, such as being on an unmapped drive.
+ ///
+ ///
+ /// The specified path, file name, or both exceed the system-defined maximum length.
+ /// For example, on Windows-based platforms, paths must be less than 248 characters,
+ /// and file names must be less than 260 characters.
+ ///
public FollowingFileStream(string path, int bufferSize, bool useAsync)
{
fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize, useAsync);
}
+ ///
+ /// Gets the name of the FollowingFileStream that was passed to the constructor.
+ ///
+ /// A string that is the name of the FollowingFileStream.
+ public virtual string Name { get; }
+
+ ///
+ /// Gets a value indicating whether the FollowingFileStream was opened asynchronously or synchronously.
+ ///
+ ///
+ /// true if the FollowongFileStream was opened asynchronously; otherwise, false.
+ ///
+ public virtual bool IsAsync => fileStream.IsAsync;
#endregion
+
+ ///
+ /// Gets a value indicating whether the current stream supports writing.
+ ///
+ ///
+ /// Always false.
+ ///
public override bool CanWrite => false;
+ ///
+ /// Gets a value indicating whether the current stream supports reading.
+ ///
+ ///
+ /// true if the stream supports reading; false if the stream is closed.
+ ///
public override bool CanRead => fileStream.CanRead;
+ ///
+ /// Gets a value indicating whether the current stream supports seeking.
+ ///
+ ///
+ /// true if the stream supports seeking; false if the stream is closed.
+ ///
public override bool CanSeek => fileStream.CanSeek;
public override bool CanTimeout => fileStream.CanTimeout;
+ ///
+ /// Gets the length in bytes of the stream.
+ ///
+ ///
+ /// A long value representing the length of the stream in bytes.
+ ///
+ ///
+ /// FollowingFileStream.CanSeek for this stream is false.
+ ///
+ ///
+ /// An I/O error, such as the file being closed, occurred.
+ ///
public override long Length => fileStream.Length;
+ ///
+ /// Gets or sets the current position of this stream.
+ ///
+ /// The current position of this stream.
+ ///
+ /// FollowingFileStream.CanSeek for this stream is false.
+ ///
+ ///
+ /// An I/O error, such as the file being closed, occurred.
+ ///
+ ///
+ /// Attempted to set the position to a negative value.
+ ///
+ ///
+ /// Attempted seeking past the end of a stream that does not support this.
+ ///
public override long Position { get => fileStream.Position; set => fileStream.Position = value;}
+ ///
+ /// Reads a block of bytes from the stream and writes the data in a given buffer.
+ ///
+ /// When this method returns, contains the specified byte array with the values between
+ /// offset and (offset + count - 1) replaced by the bytes read from the current source.
+ /// The byte offset in array at which the read bytes will be placed.
+ /// The maximum number of bytes to read.
+ ///
+ /// The total number of bytes read into the buffer. This might be less than the number
+ /// of bytes requested if that number of bytes are not currently available, or zero
+ /// if the end of the stream is reached.
+ ///
+ ///
+ /// buffer is null.
+ ///
+ ///
+ /// offset and count describe an invalid range in array.
+ ///
+ ///
+ /// FollowingFileStream.CanRead for this stream is false.
+ ///
+ ///
+ /// An I/O error occurred.
+ ///
+ ///
+ /// offset or count is negative.
+ ///
+ ///
+ /// Methods were called after the stream was closed.
+ ///
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset,count, CancellationToken.None).GetAwaiter().GetResult();
}
+ ///
+ /// Asynchronously reads a sequence of bytes from the current stream, advances the
+ /// position within the stream by the number of bytes read, and monitors cancellation
+ /// requests.
+ ///
+ /// The buffer to write the data into.
+ /// The byte offset in buffer at which to begin writing data from the stream.
+ /// The maximum number of bytes to read.
+ /// The token to monitor for cancellation requests.
+ ///
+ /// A task that represents the asynchronous read operation. The value of the TResult
+ /// parameter contains the total number of bytes read into the buffer. The result
+ /// value can be less than the number of bytes requested if the number of bytes currently
+ /// available is less than the requested number, or it can be 0 (zero) if the end
+ /// of the stream has been reached.
+ ///
+ ///
+ /// buffer is null.
+ ///
+ ///
+ /// offset and count describe an invalid range in array.
+ ///
+ ///
+ /// FollowingFileStream.CanRead for this stream is false.
+ ///
+ ///
+ /// The stream is currently in use by a previous read operation.
+ ///
+ ///
+ /// offset or count is negative.
+ ///
+ ///
+ /// Methods were called after the stream was closed.
+ ///
public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int read = 0;
@@ -59,6 +283,15 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count,
return read;
}
+ ///
+ /// Asynchronously checks wheter the file is locked for writing
+ /// and waits for a while according to MillisecondsRetryTimeout.
+ ///
+ ///
+ /// A task that represents the asynchronous retry operation. The value of the TResult
+ /// parameter contains the boolean result. The result
+ /// value can be false if the stream is closed.
+ ///
private async Task RetryNeededAsync()
{
bool retry = IsFileLockedForWriting();
@@ -75,6 +308,10 @@ private async Task RetryNeededAsync()
return retry;
}
+ ///
+ /// Synchronously checks whether the file is locked for writing
+ ///
+ /// true if the file is locked for writing; false, otherwise.
private bool IsFileLockedForWriting()
{
FileStream stream = null;
@@ -103,6 +340,12 @@ private bool IsFileLockedForWriting()
bool disposed = false;
+ ///
+ /// Releases the unmanaged resources used by the FollowingFileStream and optionally
+ /// releases the managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ ///
protected override void Dispose(bool disposing)
{
if (disposed)
@@ -127,6 +370,10 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+ ///
+ /// Clears buffers for this stream and causes any buffered data to be written to the file.
+ ///
+ /// Not supported
public override void Flush()
{
throw new NotSupportedException();
@@ -140,21 +387,83 @@ public override long Seek(long offset, SeekOrigin origin)
}
}
+ ///
+ /// Sets the length of this stream to the given value.
+ ///
+ /// Not supported
public override void SetLength(long value)
{
throw new NotSupportedException();
}
+
+ ///
+ /// Writes a block of bytes to the file stream.
+ ///
+ /// Not supported
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
+ ///
+ /// Begins an asynchronous read operation. (Consider using FollowingFileStream.ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
+ /// instead.)
+ ///
+ /// The buffer to read data into.
+ /// The byte offset in array at which to begin reading.
+ /// The maximum number of bytes to read.
+ /// The method to be called when the asynchronous read operation is completed.
+ /// A user-provided object that distinguishes this particular asynchronous read request
+ /// from other requests.
+ /// An object that references the asynchronous read.
+ ///
+ /// buffer is null.
+ ///
+ ///
+ /// offset and count describe an invalid range in array.
+ ///
+ ///
+ /// FollowingFileStream.CanRead for this stream is false.
+ ///
+ ///
+ /// The stream is currently in use by a previous read operation.
+ ///
+ ///
+ /// offset or count is negative.
+ ///
+ ///
+ /// An asynchronous read was attempted past the end of the file.
+ ///
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return ReadAsync(buffer, offset, count, CancellationToken.None).AsApm(callback, state);
}
+ ///
+ /// Waits for the pending asynchronous read operation to complete. (Consider using
+ /// FollowingFileStream.ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
+ /// instead.)
+ ///
+ /// The reference to the pending asynchronous request to wait for.
+ ///
+ /// The number of bytes read from the stream, between 0 and the number of bytes you
+ /// requested. Streams only return 0 at the end of the stream, otherwise, they should
+ /// block until at least 1 byte is available.
+ ///
+ ///
+ /// asyncResult is null.
+ ///
+ ///
+ /// This System.IAsyncResult object was not created by calling FollwingFileStream.BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)
+ /// on this class.
+ ///
+ ///
+ /// FollowingFileStream.EndRead(System.IAsyncResult) is called multiple times.
+ ///
+ ///
+ /// The stream is closed or an internal error has occurred.
+ ///
public override int EndRead(IAsyncResult asyncResult)
{
return ((Task)asyncResult).Result;
From d0bc19b167553990caad7c65d7e8ac15af5c4d32 Mon Sep 17 00:00:00 2001
From: manandre <2341261+manandre@users.noreply.github.com>
Date: Sat, 24 Aug 2019 16:14:01 +0200
Subject: [PATCH 2/5] Fix vscode setup
---
.vscode/launch.json | 2 +-
.vscode/tasks.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 8fa76a7..f90276e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,7 +10,7 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/FollowingFileStream.ConsoleTestTool/bin/Debug/netcoreapp2.2/FollowingFileStream.dll",
+ "program": "${workspaceFolder}/FollowingFileStream.ConsoleTestTool/bin/Debug/netcoreapp2.2/FollowingFileStream.ConsoleTestTool.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index f899ded..719096b 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -7,7 +7,7 @@
"type": "process",
"args": [
"build",
- "${workspaceFolder}/FollowingFileStream/FollowingFileStream.csproj",
+ "${workspaceFolder}/All.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
From 2030156844bf1de6e4b2cb23072807cbf3a39f64 Mon Sep 17 00:00:00 2001
From: manandre <2341261+manandre@users.noreply.github.com>
Date: Sat, 24 Aug 2019 16:21:19 +0200
Subject: [PATCH 3/5] Add unit test cases for new properties
---
.../FollowingFileStreamTest.cs | 13 +++++++++++++
FollowingFileStream/FollowingFileStream.cs | 2 +-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/FollowingFileStream.Tests/FollowingFileStreamTest.cs b/FollowingFileStream.Tests/FollowingFileStreamTest.cs
index f89cc50..d56e398 100644
--- a/FollowingFileStream.Tests/FollowingFileStreamTest.cs
+++ b/FollowingFileStream.Tests/FollowingFileStreamTest.cs
@@ -71,6 +71,19 @@ public void FFS_Caps()
}
}
+ [DataTestMethod]
+ [DataRow(false)]
+ [DataRow(true)]
+ [TestMethod]
+ public void FFS_Properties(bool async)
+ {
+ using (var ffs = new FollowingFileStream(inputFilePath, 4*1096, async))
+ {
+ Assert.AreEqual(inputFilePath, ffs.Name);
+ Assert.AreEqual(async, ffs.IsAsync);
+ }
+ }
+
[TestMethod]
public void FFS_Modification()
{
diff --git a/FollowingFileStream/FollowingFileStream.cs b/FollowingFileStream/FollowingFileStream.cs
index ffbf298..8af6102 100644
--- a/FollowingFileStream/FollowingFileStream.cs
+++ b/FollowingFileStream/FollowingFileStream.cs
@@ -127,7 +127,7 @@ public FollowingFileStream(string path, int bufferSize, bool useAsync)
/// Gets the name of the FollowingFileStream that was passed to the constructor.
///
/// A string that is the name of the FollowingFileStream.
- public virtual string Name { get; }
+ public virtual string Name => fileStream.Name;
///
/// Gets a value indicating whether the FollowingFileStream was opened asynchronously or synchronously.
From ef185266dd8c5cdaa176036a1e22e0a7ce8bd8ed Mon Sep 17 00:00:00 2001
From: manandre <2341261+manandre@users.noreply.github.com>
Date: Sat, 24 Aug 2019 16:32:34 +0200
Subject: [PATCH 4/5] Generate XML doc file at build time
---
FollowingFileStream/FollowingFileStream.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/FollowingFileStream/FollowingFileStream.csproj b/FollowingFileStream/FollowingFileStream.csproj
index 57ba108..f486a95 100644
--- a/FollowingFileStream/FollowingFileStream.csproj
+++ b/FollowingFileStream/FollowingFileStream.csproj
@@ -2,6 +2,7 @@
Library
netcoreapp2.2
+ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
FollowingFileStream
From b59435c9177efa18ca9cc7384a79fec7f4425304 Mon Sep 17 00:00:00 2001
From: manandre <2341261+manandre@users.noreply.github.com>
Date: Sat, 24 Aug 2019 16:33:19 +0200
Subject: [PATCH 5/5] Fix missing XML documentation
---
.../FollowingFileStreamTest.cs | 1 -
FollowingFileStream/FollowingFileStream.cs | 25 ++++++++++++++++---
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/FollowingFileStream.Tests/FollowingFileStreamTest.cs b/FollowingFileStream.Tests/FollowingFileStreamTest.cs
index d56e398..f9d7410 100644
--- a/FollowingFileStream.Tests/FollowingFileStreamTest.cs
+++ b/FollowingFileStream.Tests/FollowingFileStreamTest.cs
@@ -67,7 +67,6 @@ public void FFS_Caps()
Assert.IsTrue(ffs.CanRead);
Assert.IsFalse(ffs.CanWrite);
Assert.IsTrue(ffs.CanSeek);
- Assert.IsFalse(ffs.CanTimeout);
}
}
diff --git a/FollowingFileStream/FollowingFileStream.cs b/FollowingFileStream/FollowingFileStream.cs
index 8af6102..d052b06 100644
--- a/FollowingFileStream/FollowingFileStream.cs
+++ b/FollowingFileStream/FollowingFileStream.cs
@@ -69,7 +69,9 @@ public FollowingFileStream(string path)
}
///
- ///
+ /// Initializes a new instance of the FollowingFileStream class with the specified
+ /// path, buffer size, and synchronous
+ /// or asynchronous state.
///
/// A relative or absolute path for the file
/// that the current FollowingFileStream object will encapsulate.
@@ -162,8 +164,6 @@ public FollowingFileStream(string path, int bufferSize, bool useAsync)
///
public override bool CanSeek => fileStream.CanSeek;
- public override bool CanTimeout => fileStream.CanTimeout;
-
///
/// Gets the length in bytes of the stream.
///
@@ -379,6 +379,25 @@ public override void Flush()
throw new NotSupportedException();
}
+ ///
+ /// Sets the current position of this stream to the given value.
+ ///
+ /// The point relative to origin from which to begin seeking.
+ /// Specifies the beginning, the end, or the current position as a reference point
+ /// for offset, using a value of type System.IO.SeekOrigin.
+ /// The new position in the stream.
+ ///
+ /// FollowingFileStream.CanSeek for this stream is false.
+ ///
+ ///
+ /// An I/O error, such as the file being closed, occurred.
+ ///
+ ///
+ /// Seeking is attempted before the beginning of the stream.
+ ///
+ ///
+ /// Methods were called after the stream was closed.
+ ///
public override long Seek(long offset, SeekOrigin origin)
{
using(locker.Lock())