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())