diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index 594ce9e..40338b9 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -25,7 +25,10 @@ public async void StatAsyncTest() async () => { using SyncService service = new(Socket, Device); - return await service.StatAsync("/fstab.donatello"); + FileStatistics value = await service.StatAsync("/fstab.donatello"); + Assert.False(service.IsProcessing); + Assert.False(service.IsOutdate); + return value; }); Assert.Equal(UnixFileStatus.Regular, value.FileMode.GetFileType()); @@ -56,7 +59,10 @@ public async void GetListingAsyncTest() async () => { using SyncService service = new(Socket, Device); - return await service.GetDirectoryListingAsync("/storage"); + List value = await service.GetDirectoryListingAsync("/storage"); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + return value; }); Assert.Equal(4, value.Count); @@ -110,7 +116,10 @@ public async void GetAsyncListingTest() async () => { using SyncService service = new(Socket, Device); - return await service.GetDirectoryAsyncListing("/storage").ToListAsync(); + List value = await service.GetDirectoryAsyncListing("/storage").ToListAsync(); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + return value; }); Assert.Equal(4, value.Count); @@ -171,6 +180,8 @@ await RunTestAsync( { using SyncService service = new(Socket, Device); await service.PullAsync("/fstab.donatello", stream, null); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); }); // Make sure the data that has been sent to the stream is the expected data @@ -207,6 +218,48 @@ await RunTestAsync( { using SyncService service = new(Socket, Device); await service.PushAsync(stream, "/sdcard/test", UnixFileStatus.StickyBit | UnixFileStatus.UserWrite | UnixFileStatus.OtherRead, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), null); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }); + } + + /// + /// Tests the field. + /// + [Fact] + public async void IsProcessingAsyncTest() + { + await RunTestAsync( + OkResponses(2), + [".", "..", "sdcard0", "emulated"], + ["host:transport:169.254.109.177:5555", "sync:"], + [(SyncCommand.LIST, "/storage")], + [SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DONE], + [ + [233, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86], + [237, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86], + [255, 161, 0, 0, 24, 0, 0, 0, 152, 130, 56, 86], + [109, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86] + ], + null, + async () => + { + using SyncService service = new(Socket, Device); + await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage")) + { + Assert.False(service.IsOutdate); + Assert.True(service.IsProcessing); + _ = await Assert.ThrowsAsync(() => service.PushAsync((Stream)null, null, default, default)); + _ = await Assert.ThrowsAsync(() => service.PullAsync(null, (Stream)null)); +#if WINDOWS10_0_17763_0_OR_GREATER + _ = await Assert.ThrowsAsync(() => service.PushAsync((IInputStream)null, null, default, default)); + _ = await Assert.ThrowsAsync(() => service.PullAsync(null, (IOutputStream)null)); +#endif + _ = await Assert.ThrowsAsync(() => service.GetDirectoryListingAsync(null)); + _ = await Assert.ThrowsAsync(() => service.GetDirectoryAsyncListing(null).ToListAsync().AsTask()); + } + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); }); } @@ -240,6 +293,8 @@ await RunTestAsync( { using SyncService service = new(Socket, Device); await service.PullAsync("/fstab.donatello", stream, null); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); }); IBuffer buffer = await stream.GetInputStreamAt(0).ReadAsync(new byte[(int)stream.Size].AsBuffer(), (uint)stream.Size, InputStreamOptions.None); @@ -278,6 +333,8 @@ await RunTestAsync( { using SyncService service = new(Socket, Device); await service.PushAsync(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), null); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); }); } #endif diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index 170aba8..92a0699 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -35,7 +35,10 @@ public void StatTest() () => { using SyncService service = new(Socket, Device); - return service.Stat("/fstab.donatello"); + FileStatistics value = service.Stat("/fstab.donatello"); + Assert.False(service.IsProcessing); + Assert.False(service.IsOutdate); + return value; }); Assert.Equal(UnixFileStatus.Regular, value.FileMode.GetFileType()); @@ -66,7 +69,10 @@ public void GetListingTest() () => { using SyncService service = new(Socket, Device); - return service.GetDirectoryListing("/storage").ToArray(); + FileStatistics[] value = service.GetDirectoryListing("/storage").ToArray(); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + return value; }); Assert.Equal(4, value.Length); @@ -127,6 +133,8 @@ public void PullTest() { using SyncService service = new(Socket, Device); service.Pull("/fstab.donatello", stream); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); }); // Make sure the data that has been sent to the stream is the expected data @@ -163,6 +171,43 @@ .. BitConverter.GetBytes(content.Length), { using SyncService service = new(Socket, Device); service.Push(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc)); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }); + } + + /// + /// Tests the field. + /// + [Fact] + public void IsProcessingTest() + { + RunTest( + OkResponses(2), + [".", "..", "sdcard0", "emulated"], + ["host:transport:169.254.109.177:5555", "sync:"], + [(SyncCommand.LIST, "/storage")], + [SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DONE], + [ + [233, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86], + [237, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86], + [255, 161, 0, 0, 24, 0, 0, 0, 152, 130, 56, 86], + [109, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86] + ], + null, + () => + { + using SyncService service = new(Socket, Device); + foreach (FileStatistics stat in service.GetDirectoryListing("/storage")) + { + Assert.False(service.IsOutdate); + Assert.True(service.IsProcessing); + _ = Assert.Throws(() => service.Push(null, null, default, default)); + _ = Assert.Throws(() => service.Pull(null, null)); + _ = Assert.Throws(() => service.GetDirectoryListing(null).FirstOrDefault()); + } + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); }); } } diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 7dd959b..23c5449 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -32,7 +32,7 @@ namespace AdvancedSharpAdbClient [DebuggerDisplay($"{nameof(AdbClient)} \\{{ {nameof(EndPoint)} = {{{nameof(EndPoint)}}} }}")] public partial class AdbClient : IAdbClient, ICloneable, ICloneable #if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER - , IAdbClient.IWinRT + , IAdbClient.IWinRT, ICloneable #endif { /// @@ -1133,14 +1133,20 @@ public IEnumerable GetFeatureSet(DeviceData device) public override string ToString() => $"The {nameof(AdbClient)} communicate with adb server at {EndPoint}"; /// - /// Creates a new object that is a copy of the current instance with new . + /// Creates a new object that is a copy of the current instance with new . /// /// The new to use. - /// A new object that is a copy of this instance with new . - public AdbClient Clone(EndPoint endPoint) => new(endPoint, AdbSocketFactory); + /// A new object that is a copy of this instance with new . + public virtual IAdbClient Clone(EndPoint endPoint) => new AdbClient(endPoint, AdbSocketFactory); /// - public IAdbClient Clone() => new AdbClient(EndPoint, AdbSocketFactory); + public IAdbClient Clone() => Clone(EndPoint); + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + IAdbClient.IWinRT ICloneable.Clone() => Clone(EndPoint) is IAdbClient.IWinRT client ? client + : throw new NotSupportedException($"The {nameof(Clone)} method does not return a {nameof(IAdbClient.IWinRT)} object."); +#endif /// object ICloneable.Clone() => Clone(); diff --git a/AdvancedSharpAdbClient/AdbServer.cs b/AdvancedSharpAdbClient/AdbServer.cs index 5ed5ff7..933265f 100644 --- a/AdvancedSharpAdbClient/AdbServer.cs +++ b/AdvancedSharpAdbClient/AdbServer.cs @@ -274,16 +274,16 @@ public AdbServerStatus GetStatus() public override string ToString() => $"The {nameof(AdbServer)} communicate with adb at {EndPoint}"; /// - /// Creates a new object that is a copy of the current instance with new . + /// Creates a new object that is a copy of the current instance with new . /// /// The new to use. - /// A new object that is a copy of this instance with new . - public AdbServer Clone(EndPoint endPoint) => new(endPoint, AdbSocketFactory, AdbCommandLineClientFactory); + /// A new object that is a copy of this instance with new . + public virtual IAdbServer Clone(EndPoint endPoint) => new AdbServer(endPoint, AdbSocketFactory, AdbCommandLineClientFactory); /// - public IAdbServer Clone() => new AdbServer(EndPoint, AdbSocketFactory, AdbCommandLineClientFactory); + public IAdbServer Clone() => Clone(EndPoint); /// - object ICloneable.Clone() => Clone(); + object ICloneable.Clone() => Clone(EndPoint); } } diff --git a/AdvancedSharpAdbClient/AdbSocket.cs b/AdvancedSharpAdbClient/AdbSocket.cs index f997654..a2bf671 100644 --- a/AdvancedSharpAdbClient/AdbSocket.cs +++ b/AdvancedSharpAdbClient/AdbSocket.cs @@ -560,7 +560,7 @@ public override string ToString() => public void Close() => Socket.Dispose(); /// - public IAdbSocket Clone() + public virtual IAdbSocket Clone() { if (Socket is not ICloneable cloneable) { diff --git a/AdvancedSharpAdbClient/DeviceMonitor.cs b/AdvancedSharpAdbClient/DeviceMonitor.cs index 610cf27..3fb52b8 100644 --- a/AdvancedSharpAdbClient/DeviceMonitor.cs +++ b/AdvancedSharpAdbClient/DeviceMonitor.cs @@ -418,7 +418,7 @@ public override string ToString() => .ToString(); /// - public IDeviceMonitor Clone() => + public virtual IDeviceMonitor Clone() => Socket is not ICloneable cloneable ? throw new NotSupportedException($"{Socket.GetType()} does not support cloning.") : new DeviceMonitor(cloneable.Clone(), logger); diff --git a/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs b/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs index 5797dbc..4e4ac82 100644 --- a/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs @@ -216,7 +216,7 @@ public static string ToPermissionCode(this UnixFileStatus mode) #else char[] code = new char[10]; #endif - BitArray array = new([(int)mode]); + BitArray array = new(new[] { (int)mode }); code[9] = array[0] ? array[9] ? 't' : 'x' diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index 89fbb6c..ae06d8a 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -38,6 +38,8 @@ public async Task ReopenAsync(CancellationToken cancellationToken = default) /// public virtual async Task PushAsync(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action? callback = null, CancellationToken cancellationToken = default) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } + ExceptionExtensions.ThrowIfNull(stream); ExceptionExtensions.ThrowIfNull(remotePath); @@ -51,6 +53,7 @@ public virtual async Task PushAsync(Stream stream, string remotePath, UnixFileSt try { await Socket.SendSyncRequestAsync(SyncCommand.SEND, remotePath, permission, cancellationToken).ConfigureAwait(false); + IsProcessing = true; // create the buffer used to read. // we read max SYNC_DATA_MAX. @@ -129,12 +132,15 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi finally { IsOutdate = true; + IsProcessing = false; } } /// public virtual async Task PullAsync(string remotePath, Stream stream, Action? callback = null, CancellationToken cancellationToken = default) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } + ExceptionExtensions.ThrowIfNull(remotePath); ExceptionExtensions.ThrowIfNull(stream); @@ -150,6 +156,7 @@ public virtual async Task PullAsync(string remotePath, Stream stream, Action public virtual async Task PushAsync(IInputStream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action? progress = null, CancellationToken cancellationToken = default) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } + ExceptionExtensions.ThrowIfNull(stream); ExceptionExtensions.ThrowIfNull(remotePath); @@ -219,6 +229,7 @@ public virtual async Task PushAsync(IInputStream stream, string remotePath, Unix try { await Socket.SendSyncRequestAsync(SyncCommand.SEND, remotePath, permission, cancellationToken).ConfigureAwait(false); + IsProcessing = true; // create the buffer used to read. // we read max SYNC_DATA_MAX. @@ -305,12 +316,15 @@ public virtual async Task PushAsync(IInputStream stream, string remotePath, Unix finally { IsOutdate = true; + IsProcessing = false; } } /// public virtual async Task PullAsync(string remotePath, IOutputStream stream, Action? progress = null, CancellationToken cancellationToken = default) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } + ExceptionExtensions.ThrowIfNull(remotePath); ExceptionExtensions.ThrowIfNull(stream); @@ -326,6 +340,7 @@ public virtual async Task PullAsync(string remotePath, IOutputStream stream, Act try { await Socket.SendSyncRequestAsync(SyncCommand.RECV, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; while (true) { @@ -374,6 +389,7 @@ public virtual async Task PullAsync(string remotePath, IOutputStream stream, Act finally { IsOutdate = true; + IsProcessing = false; } } #endif @@ -401,6 +417,7 @@ public async Task StatAsync(string remotePath, CancellationToken /// public async Task> GetDirectoryListingAsync(string remotePath, CancellationToken cancellationToken = default) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } if (IsOutdate) { await ReopenAsync(cancellationToken).ConfigureAwait(false); } bool isLocked = false; @@ -411,6 +428,7 @@ public async Task> GetDirectoryListingAsync(string remotePa { // create the stat request message. await Socket.SendSyncRequestAsync(SyncCommand.LIST, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; while (true) { @@ -442,6 +460,7 @@ public async Task> GetDirectoryListingAsync(string remotePa finally { IsOutdate = true; + IsProcessing = false; } } @@ -449,6 +468,7 @@ public async Task> GetDirectoryListingAsync(string remotePa /// public async IAsyncEnumerable GetDirectoryAsyncListing(string remotePath, [EnumeratorCancellation] CancellationToken cancellationToken = default) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } if (IsOutdate) { await ReopenAsync(cancellationToken).ConfigureAwait(false); } bool isLocked = false; @@ -457,6 +477,7 @@ public async IAsyncEnumerable GetDirectoryAsyncListing(string re start: // create the stat request message. await Socket.SendSyncRequestAsync(SyncCommand.LIST, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; while (true) { @@ -489,6 +510,7 @@ public async IAsyncEnumerable GetDirectoryAsyncListing(string re finally { IsOutdate = true; + IsProcessing = false; } } #endif diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index 55c4e44..b0a6a48 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -44,7 +44,7 @@ namespace AdvancedSharpAdbClient [DebuggerDisplay($"{nameof(SyncService)} \\{{ {nameof(IsOpen)} = {{{nameof(IsOpen)}}}, {nameof(Device)} = {{{nameof(Device)}}}, {nameof(Socket)} = {{{nameof(Socket)}}}, {nameof(MaxBufferSize)} = {{{nameof(MaxBufferSize)}}} }}")] public partial class SyncService : ISyncService, ICloneable, ICloneable #if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER - , ISyncService.IWinRT + , ISyncService.IWinRT, ICloneable #endif { /// @@ -56,7 +56,13 @@ public partial class SyncService : ISyncService, ICloneable, IClon /// if the is out of date; otherwise, . /// /// Need to invoke before using the again. - protected bool IsOutdate = false; + protected internal bool IsOutdate = false; + + /// + /// if the is processing; otherwise, . + /// + /// Recommend to a new or wait until the process is finished. + protected internal bool IsProcessing = false; /// /// Initializes a new instance of the class. @@ -142,6 +148,8 @@ public void Reopen() /// public virtual void Push(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action? callback = null, in bool isCancelled = false) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } + ExceptionExtensions.ThrowIfNull(stream); ExceptionExtensions.ThrowIfNull(remotePath); @@ -155,6 +163,7 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis try { Socket.SendSyncRequest(SyncCommand.SEND, remotePath, permission); + IsProcessing = true; // create the buffer used to read. // we read max SYNC_DATA_MAX. @@ -230,12 +239,15 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis finally { IsOutdate = true; + IsProcessing = false; } } /// public virtual void Pull(string remoteFilePath, Stream stream, Action? callback = null, in bool isCancelled = false) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } + ExceptionExtensions.ThrowIfNull(remoteFilePath); ExceptionExtensions.ThrowIfNull(stream); @@ -251,6 +263,7 @@ public virtual void Pull(string remoteFilePath, Stream stream, Action public IEnumerable GetDirectoryListing(string remotePath) { + if (IsProcessing) { throw new InvalidOperationException($"The {nameof(SyncService)} is currently processing a request. Please {nameof(Clone)} a new {nameof(ISyncService)} or wait until the process is finished."); } if (IsOutdate) { Reopen(); } bool isLocked = false; @@ -331,6 +346,7 @@ public IEnumerable GetDirectoryListing(string remotePath) start: // create the stat request message. Socket.SendSyncRequest(SyncCommand.LIST, remotePath); + IsProcessing = true; while (true) { @@ -363,6 +379,7 @@ public IEnumerable GetDirectoryListing(string remotePath) finally { IsOutdate = true; + IsProcessing = false; } } @@ -400,23 +417,26 @@ public override string ToString() => .ToString(); /// - /// Creates a new object that is a copy of the current instance with new . + /// Creates a new object that is a copy of the current instance with new . /// /// The new to use. - /// A new object that is a copy of this instance with new . - public SyncService Clone(DeviceData device) => + /// A new object that is a copy of this instance with new . + public virtual ISyncService Clone(DeviceData device) => Socket is not ICloneable cloneable ? throw new NotSupportedException($"{Socket.GetType()} does not support cloning.") : new SyncService(cloneable.Clone(), device); /// - public ISyncService Clone() => - Socket is not ICloneable cloneable - ? throw new NotSupportedException($"{Socket.GetType()} does not support cloning.") - : new SyncService(cloneable.Clone(), Device); + public ISyncService Clone() => Clone(Device); + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + ISyncService.IWinRT ICloneable.Clone() => Clone(Device) is ISyncService.IWinRT service ? service + : throw new NotSupportedException($"The {nameof(Clone)} method does not return a {nameof(ISyncService.IWinRT)} object."); +#endif /// - object ICloneable.Clone() => Clone(); + object ICloneable.Clone() => Clone(Device); /// /// Reads the statistics of a file from the socket.