From 5108b52c57b1cf685f8ac1cda5a96972daa19582 Mon Sep 17 00:00:00 2001 From: where where Date: Fri, 9 Jan 2026 10:52:17 +0800 Subject: [PATCH 1/6] Add stat v2 and list v2 #133 Use unsafe to read bytes --- .../Dummys/DummySyncService.cs | 22 ++- .../SyncServiceTests.Async.cs | 2 +- .../SyncServiceTests.cs | 2 +- AdvancedSharpAdbClient/AdbSocket.Async.cs | 2 +- AdvancedSharpAdbClient/AdbSocket.cs | 2 +- .../AdvancedSharpAdbClient.csproj | 5 +- .../DeviceCommands/DeviceExtensions.Async.cs | 10 +- .../DeviceCommands/DeviceExtensions.cs | 41 +++- .../Extensions/EnumerableBuilder.cs | 61 ++++-- .../Extensions/SyncCommandConverter.cs | 2 +- .../Extensions/SyncServiceExtensions.Async.cs | 12 +- .../Extensions/SyncServiceExtensions.cs | 6 +- .../Interfaces/ISyncService.Async.cs | 36 +++- .../Interfaces/ISyncService.cs | 20 +- .../Logs/LogReader.Async.cs | 6 +- AdvancedSharpAdbClient/Logs/LogReader.cs | 6 +- AdvancedSharpAdbClient/Models/ColorData.cs | 44 ++++- .../Models/Enums/SyncCommand.cs | 25 ++- .../Models/Enums/UnixFileStatus.cs | 2 +- .../Models/FileStatistics.V2.cs | 126 ++++++++++++ .../Models/FileStatistics.cs | 50 ++--- .../Models/FileStatisticsData.V2.cs | 143 ++++++++++++++ .../Models/FileStatisticsData.cs | 143 ++++++++++++++ .../Models/FramebufferHeader.cs | 122 ++++-------- .../Models/SyncService.EventArgs.cs | 10 +- .../Extensions/BitConverterExtensions.cs | 43 ++++ .../Polyfills/Extensions/SocketExtensions.cs | 5 +- AdvancedSharpAdbClient/SyncService.Async.cs | 186 +++++++++++++++--- AdvancedSharpAdbClient/SyncService.cs | 124 ++++++++++-- AdvancedSharpAdbClient/TcpSocket.Async.cs | 4 +- 30 files changed, 1045 insertions(+), 217 deletions(-) create mode 100644 AdvancedSharpAdbClient/Models/FileStatistics.V2.cs create mode 100644 AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs create mode 100644 AdvancedSharpAdbClient/Models/FileStatisticsData.cs create mode 100644 AdvancedSharpAdbClient/Polyfills/Extensions/BitConverterExtensions.cs diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs index f5008ad..8e78505 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs @@ -25,17 +25,17 @@ public async Task OpenAsync(CancellationToken cancellationToken = default) IsOpen = true; } - public void Pull(string remotePath, Stream stream, Action callback = null, in bool isCancelled = false) + public void Pull(string remotePath, Stream stream, Action callback = null, bool useV2 = false, in bool isCancelled = false) { - for (int i = 0; i <= 100; i++) + for (uint i = 0; i <= 100; i++) { callback?.Invoke(new SyncProgressChangedEventArgs(i, 100)); } } - public async Task PullAsync(string remotePath, Stream stream, Action callback = null, CancellationToken cancellationToken = default) + public async Task PullAsync(string remotePath, Stream stream, Action callback = null, bool useV2 = false, CancellationToken cancellationToken = default) { - for (int i = 0; i <= 100; i++) + for (uint i = 0; i <= 100; i++) { await Task.Yield(); callback?.Invoke(new SyncProgressChangedEventArgs(i, 100)); @@ -44,7 +44,7 @@ public async Task PullAsync(string remotePath, Stream stream, Action callback = null, in bool isCancelled = false) { - for (int i = 0; i <= 100; i++) + for (uint i = 0; i <= 100; i++) { if (i == 100) { @@ -56,7 +56,7 @@ public void Push(Stream stream, string remotePath, UnixFileStatus permission, Da public async Task PushAsync(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action callback = null, CancellationToken cancellationToken = default) { - for (int i = 0; i <= 100; i++) + for (uint i = 0; i <= 100; i++) { await Task.Yield(); if (i == 100) @@ -79,14 +79,24 @@ public async Task ReopenAsync(CancellationToken cancellationToken = default) IAsyncEnumerable ISyncService.GetDirectoryAsyncListing(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + IAsyncEnumerable ISyncService.GetDirectoryAsyncListingV2(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + IEnumerable ISyncService.GetDirectoryListing(string remotePath) => throw new NotImplementedException(); Task> ISyncService.GetDirectoryListingAsync(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + IEnumerable ISyncService.GetDirectoryListingV2(string remotePath) => throw new NotImplementedException(); + + Task> ISyncService.GetDirectoryListingV2Async(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + FileStatistics ISyncService.Stat(string remotePath) => throw new NotImplementedException(); Task ISyncService.StatAsync(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + FileStatisticsV2 ISyncService.StatV2(string remotePath) => throw new NotImplementedException(); + + Task ISyncService.StatV2Async(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + #endregion } } diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index 1f3c337..a2a5631 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -164,7 +164,7 @@ public async Task GetAsyncListingTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async Task PullAsyncTest() diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index f5e99e4..b78f8de 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -111,7 +111,7 @@ public void GetListingTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void PullTest() diff --git a/AdvancedSharpAdbClient/AdbSocket.Async.cs b/AdvancedSharpAdbClient/AdbSocket.Async.cs index f91fa73..bcad140 100644 --- a/AdvancedSharpAdbClient/AdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/AdbSocket.Async.cs @@ -221,7 +221,7 @@ public virtual async Task ReadSyncStringAsync(CancellationToken cancella } // Get the length of the string - int len = reply[0] | (reply[1] << 8) | (reply[2] << 16) | (reply[3] << 24); + int len = BitConverter.ToInt32(reply); // And get the string reply = new byte[len]; diff --git a/AdvancedSharpAdbClient/AdbSocket.cs b/AdvancedSharpAdbClient/AdbSocket.cs index 3e94037..57c3879 100644 --- a/AdvancedSharpAdbClient/AdbSocket.cs +++ b/AdvancedSharpAdbClient/AdbSocket.cs @@ -305,7 +305,7 @@ public virtual string ReadSyncString() } // Get the length of the string - int len = reply[0] | (reply[1] << 8) | (reply[2] << 16) | (reply[3] << 24); + int len = BitConverter.ToInt32(reply); // And get the string reply = new byte[len]; diff --git a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj index 664c6cc..dee57de 100644 --- a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj +++ b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj @@ -10,6 +10,7 @@ + True False $(NoWarn);NU1902;NU1903 net8.0;net10.0;netstandard1.3;netstandard2.0;netstandard2.1 @@ -38,7 +39,8 @@ System.Range; System.Runtime.CompilerServices.CallerArgumentExpressionAttribute; System.Runtime.CompilerServices.IsExternalInit; - System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute + System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute; + System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute @@ -51,7 +53,6 @@ - True 10.0.10240.0 diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs index 8794253..b49ec9a 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs @@ -220,15 +220,18 @@ public static Task StopAppAsync(this IAdbClient client, DeviceData device, strin /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. + /// V2 need Android 8 or above. public static async Task PullAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, Action? callback = null, + bool useV2 = false, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); - await service.PullAsync(remotePath, stream, callback, cancellationToken).ConfigureAwait(false); + await service.PullAsync(remotePath, stream, callback, useV2, cancellationToken).ConfigureAwait(false); } /// @@ -404,15 +407,18 @@ public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceDat /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. + /// V2 need Android 8 or above. public static async Task PullAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, IProgress? progress, + bool useV2 = false, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); - await service.PullAsync(remotePath, stream, progress.AsAction(), cancellationToken).ConfigureAwait(false); + await service.PullAsync(remotePath, stream, progress.AsAction(), useV2, cancellationToken).ConfigureAwait(false); } /// diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs index 362e805..eec0d0c 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs @@ -181,14 +181,17 @@ public static void StopApp(this IAdbClient client, DeviceData device, string pac /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. + /// V2 need Android 8 or above. public static void Pull(this IAdbClient client, DeviceData device, string remotePath, Stream stream, Action? callback = null, + bool useV2 = false, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); - service.Pull(remotePath, stream, callback, in isCancelled); + service.Pull(remotePath, stream, callback, useV2, in isCancelled); } /// @@ -225,6 +228,20 @@ public static FileStatistics Stat(this IAdbClient client, DeviceData device, str return service.Stat(path); } + /// + /// Gets the file statistics of a given file (v2). + /// + /// The to use when executing the command. + /// The device on which to look for the file. + /// The path to the file. + /// A object that represents the file. + /// Need Android 8 or above. + public static FileStatisticsV2 StatV2(this IAdbClient client, DeviceData device, string path) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + return service.StatV2(path); + } + /// /// Lists the contents of a directory on the device. /// @@ -241,6 +258,23 @@ public static IEnumerable GetDirectoryListing(this IAdbClient cl } } + /// + /// Lists the contents of a directory on the device. + /// + /// The to use when executing the command. + /// The device on which to list the directory. + /// The path to the directory on the device. + /// For each child item of the directory, a object with information of the item. + /// Need Android 11 or above. + public static IEnumerable GetDirectoryListingV2(this IAdbClient client, DeviceData device, string remotePath) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + foreach (FileStatisticsV2 fileStatistics in service.GetDirectoryListingV2(remotePath)) + { + yield return fileStatistics; + } + } + /// /// Gets the property of a device. /// @@ -337,14 +371,17 @@ public static void InstallMultiplePackage(this IAdbClient client, DeviceData dev /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. + /// V2 need Android 8 or above. public static void Pull(this IAdbClient client, DeviceData device, string remotePath, Stream stream, IProgress? progress = null, + bool useV2 = false, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); - service.Pull(remotePath, stream, progress.AsAction(), in isCancelled); + service.Pull(remotePath, stream, progress.AsAction(), useV2, in isCancelled); } /// diff --git a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs index e3782fe..d6e1105 100644 --- a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs +++ b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs @@ -29,9 +29,14 @@ public static AdbCommandLineStatus AdbCommandLineStatusCreator(params ReadOnlySp /// /// The data that feeds the struct. /// A new instance of struct. - public static ColorData ColorDataCreator(ReadOnlySpan values) => - new((uint)(values[0] | (values[1] << 8) | (values[2] << 16) | (values[3] << 24)), - (uint)(values[4] | (values[5] << 8) | (values[6] << 16) | (values[7] << 24))); + public static unsafe ColorData ColorDataCreator(ReadOnlySpan values) + { + fixed (byte* p = values) + { + ColorData* data = (ColorData*)p; + return *data; + } + } /// /// Build a struct. @@ -45,16 +50,41 @@ public static ColorData ColorDataCreator(ReadOnlySpan values) => /// /// The data that feeds the struct. /// A new instance of struct. - public static FileStatistics FileStatisticsCreator(ReadOnlySpan values) + public static FileStatistics FileStatisticsCreator(ReadOnlySpan values) => new(FileStatisticsDataCreator(values)); + + /// + /// Build a struct. + /// + /// The data that feeds the struct. + /// A new instance of struct. + public static unsafe FileStatisticsData FileStatisticsDataCreator(ReadOnlySpan values) { - int index = 0; - return new FileStatistics + fixed (byte* p = values) { - FileMode = (UnixFileStatus)ReadUInt32(values), - Size = ReadUInt32(values), - Time = DateTimeOffset.FromUnixTimeSeconds(ReadUInt32(values)) - }; - uint ReadUInt32(in ReadOnlySpan data) => unchecked((uint)(data[index++] | (data[index++] << 8) | (data[index++] << 16) | (data[index++] << 24))); + FileStatisticsData* data = (FileStatisticsData*)p; + return *data; + } + } + + /// + /// Build a struct. + /// + /// The data that feeds the struct. + /// A new instance of struct. + public static FileStatisticsV2 FileStatisticsV2Creator(ReadOnlySpan values) => new(FileStatisticsDataV2Creator(values)); + + /// + /// Build a struct. + /// + /// The data that feeds the struct. + /// A new instance of struct. + public static unsafe FileStatisticsDataV2 FileStatisticsDataV2Creator(ReadOnlySpan values) + { + fixed (byte* p = values) + { + FileStatisticsDataV2* data = (FileStatisticsDataV2*)p; + return *data; + } } /// @@ -62,8 +92,7 @@ public static FileStatistics FileStatisticsCreator(ReadOnlySpan values) /// /// The data that feeds the struct. /// A new instance of struct. - public static UnixFileStatus UnixFileStatusCreator(ReadOnlySpan values) => - (UnixFileStatus)(values[0] | (values[1] << 8) | (values[2] << 16) | (values[3] << 24)); + public static unsafe UnixFileStatus UnixFileStatusCreator(ReadOnlySpan values) => (UnixFileStatus)BitConverter.ToUInt32(values); /// /// Build a class. @@ -276,19 +305,19 @@ static void ReadLogEntry(BinaryReader reader, params ICollection parent) ushort? ReadUInt16(in ReadOnlySpan bytes) { ReadOnlySpan data = ReadBytesSafe(bytes, 2); - return data.IsEmpty ? null : (ushort)(data[0] | (data[1] << 8)); + return data.IsEmpty ? null : BitConverter.ToUInt16(data); } uint? ReadUInt32(in ReadOnlySpan bytes) { ReadOnlySpan data = ReadBytesSafe(bytes, 4); - return data.IsEmpty ? null : (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); + return data.IsEmpty ? null : BitConverter.ToUInt32(data); } int? ReadInt32(in ReadOnlySpan bytes) { ReadOnlySpan data = ReadBytesSafe(bytes, 4); - return data.Length != 4 ? null : data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + return data.Length != 4 ? null : BitConverter.ToInt32(data); } ReadOnlySpan ReadBytesSafe(in ReadOnlySpan bytes, int count) diff --git a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs index 77fcc2e..a5b13a3 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs @@ -29,7 +29,7 @@ public byte[] GetBytes() return [0, 0, 0, 0]; } - if (command is < SyncCommand.STAT or > SyncCommand.LST2) + if (command is < SyncCommand.STAT or > SyncCommand.DNT2) { throw new ArgumentOutOfRangeException(nameof(command), $"{command} is not a valid sync command"); } diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs index f2df095..af5d40b 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs @@ -32,10 +32,12 @@ public static Task PushAsync(this ISyncService service, Stream stream, string re /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PullAsync(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, CancellationToken cancellationToken = default) => - service.PullAsync(remotePath, stream, progress.AsAction(), cancellationToken); + /// V2 need Android 8 or above. + public static Task PullAsync(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PullAsync(remotePath, stream, progress.AsAction(), useV2, cancellationToken); #if HAS_WINRT /// @@ -59,10 +61,12 @@ public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stre /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PullAsync(this ISyncService.IWinRT service, string remotePath, IOutputStream stream, IProgress? progress = null, CancellationToken cancellationToken = default) => - service.PullAsync(remotePath, stream, progress.AsAction(), cancellationToken); + /// V2 need Android 8 or above. + public static Task PullAsync(this ISyncService.IWinRT service, string remotePath, IOutputStream stream, IProgress? progress = null, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PullAsync(remotePath, stream, progress.AsAction(), useV2, cancellationToken); #endif #if NET7_0_OR_GREATER diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs index c9b55e4..e87b8d7 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs @@ -33,9 +33,11 @@ public static void Push(this ISyncService service, Stream stream, string remoteP /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. - public static void Pull(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, in bool isCancelled = false) => - service.Pull(remotePath, stream, progress.AsAction(), isCancelled); + /// V2 need Android 8 or above. + public static void Pull(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, bool useV2 = false, in bool isCancelled = false) => + service.Pull(remotePath, stream, progress.AsAction(), useV2, isCancelled); #if NET7_0_OR_GREATER /// diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs index e41a39c..a6e13f2 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs @@ -30,9 +30,11 @@ public partial interface ISyncService /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - Task PullAsync(string remotePath, Stream stream, Action? callback, CancellationToken cancellationToken); + /// V2 need Android 8 or above. + Task PullAsync(string remotePath, Stream stream, Action? callback, bool useV2, CancellationToken cancellationToken); /// /// Asynchronously returns information about a file on the device. @@ -42,6 +44,15 @@ public partial interface ISyncService /// A which returns a object that contains information about the file. Task StatAsync(string remotePath, CancellationToken cancellationToken); + /// + /// Asynchronously returns information about a file on the device (v2). + /// + /// The path of the file on the device. + /// A that can be used to cancel the task. + /// A which returns a object that contains information about the file. + /// Need Android 8 or above. + Task StatV2Async(string remotePath, CancellationToken cancellationToken); + /// /// Asynchronously lists the contents of a directory on the device. /// @@ -50,6 +61,15 @@ public partial interface ISyncService /// A which returns for each child item of the directory, a object with information of the item. Task> GetDirectoryListingAsync(string remotePath, CancellationToken cancellationToken); + /// + /// Asynchronously lists the contents of a directory on the device (v2). + /// + /// The path to the directory on the device. + /// A that can be used to cancel the task. + /// A which returns for each child item of the directory, a object with information of the item. + /// Need Android 11 or above. + Task> GetDirectoryListingV2Async(string remotePath, CancellationToken cancellationToken); + #if COMP_NETSTANDARD2_1 /// /// Asynchronously lists the contents of a directory on the device. @@ -59,6 +79,16 @@ public partial interface ISyncService /// An which returns for each child item of the directory, a object with information of the item. IAsyncEnumerable GetDirectoryAsyncListing(string remotePath, CancellationToken cancellationToken) => GetDirectoryListingAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(cancellationToken); + + /// + /// Asynchronously lists the contents of a directory on the device (v2). + /// + /// The path to the directory on the device. + /// A that can be used to cancel the task. + /// An which returns for each child item of the directory, a object with information of the item. + /// Need Android 11 or above. + IAsyncEnumerable GetDirectoryAsyncListingV2(string remotePath, CancellationToken cancellationToken) => + GetDirectoryListingV2Async(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(cancellationToken); #endif /// @@ -99,9 +129,11 @@ public interface IWinRT /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - Task PullAsync(string remotePath, IOutputStream stream, Action? callback, CancellationToken cancellationToken); + /// V2 need Android 8 or above. + Task PullAsync(string remotePath, IOutputStream stream, Action? callback, bool useV2, CancellationToken cancellationToken); } #endif } diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs index 1945f64..9f94630 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs @@ -38,8 +38,10 @@ public partial interface ISyncService : IDisposable /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// if use ; use . /// A that can be used to cancel the task. - void Pull(string remotePath, Stream stream, Action? callback, in bool isCancelled); + /// V2 need Android 8 or above. + void Pull(string remotePath, Stream stream, Action? callback, bool useV2, in bool isCancelled); /// /// Returns information about a file on the device. @@ -48,6 +50,14 @@ public partial interface ISyncService : IDisposable /// A object that contains information about the file. FileStatistics Stat(string remotePath); + /// + /// Returns information about a file on the device (v2). + /// + /// The path of the file on the device. + /// A object that contains information about the file. + /// Need Android 8 or above. + FileStatisticsV2 StatV2(string remotePath); + /// /// Lists the contents of a directory on the device. /// @@ -55,6 +65,14 @@ public partial interface ISyncService : IDisposable /// For each child item of the directory, a object with information of the item. IEnumerable GetDirectoryListing(string remotePath); + /// + /// Lists the contents of a directory on the device. + /// + /// The path to the directory on the device. + /// For each child item of the directory, a object with information of the item. + /// Need Android 11 or above. + IEnumerable GetDirectoryListingV2(string remotePath); + /// /// Opens this connection. /// diff --git a/AdvancedSharpAdbClient/Logs/LogReader.Async.cs b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs index dd2047b..0bcf524 100644 --- a/AdvancedSharpAdbClient/Logs/LogReader.Async.cs +++ b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs @@ -191,7 +191,7 @@ public partial class LogReader private async Task ReadUInt16Async(CancellationToken cancellationToken = default) { byte[]? data = await ReadBytesSafeAsync(2, cancellationToken).ConfigureAwait(false); - return data == null ? null : (ushort)(data[0] | (data[1] << 8)); + return data == null ? null : BitConverter.ToUInt16(data); } /// @@ -202,7 +202,7 @@ public partial class LogReader private async Task ReadUInt32Async(CancellationToken cancellationToken = default) { byte[]? data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); - return data == null ? null : (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); + return data == null ? null : BitConverter.ToUInt32(data); } /// @@ -213,7 +213,7 @@ public partial class LogReader private async Task ReadInt32Async(CancellationToken cancellationToken = default) { byte[]? data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); - return data == null ? null : data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + return data == null ? null : BitConverter.ToInt32(data); } /// diff --git a/AdvancedSharpAdbClient/Logs/LogReader.cs b/AdvancedSharpAdbClient/Logs/LogReader.cs index 685b191..e5c32b5 100644 --- a/AdvancedSharpAdbClient/Logs/LogReader.cs +++ b/AdvancedSharpAdbClient/Logs/LogReader.cs @@ -234,7 +234,7 @@ protected static void ReadLogEntry(BinaryReader reader, params ICollection @@ -244,7 +244,7 @@ protected static void ReadLogEntry(BinaryReader reader, params ICollection @@ -254,7 +254,7 @@ protected static void ReadLogEntry(BinaryReader reader, params ICollection diff --git a/AdvancedSharpAdbClient/Models/ColorData.cs b/AdvancedSharpAdbClient/Models/ColorData.cs index 528b86e..c2d950e 100644 --- a/AdvancedSharpAdbClient/Models/ColorData.cs +++ b/AdvancedSharpAdbClient/Models/ColorData.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace AdvancedSharpAdbClient.Models { @@ -32,7 +33,7 @@ public readonly record struct ColorData(uint Offset, uint Length) : IReadOnlyLis /// /// The length of in bytes. /// - private const int count = 8; + public const int Size = 2 * sizeof(uint); /// /// Gets or sets the offset, in bits, within the byte array for a pixel, at which the @@ -48,11 +49,11 @@ public readonly record struct ColorData(uint Offset, uint Length) : IReadOnlyLis /// /// Gets the length of in bytes. /// - public readonly int Count => count; + readonly int IReadOnlyCollection.Count => Size; /// public readonly byte this[int index] => - index is < 0 or >= count + index is < 0 or >= Size ? throw new IndexOutOfRangeException("Index was out of range. Must be non-negative and less than the size of the collection.") : index switch { @@ -97,5 +98,42 @@ public readonly IEnumerator GetEnumerator() /// readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Returns a byte array containing the binary representation of this instance. + /// + /// A byte array that represents the contents of this instance. The length of the array is + /// equal to the size of the structure in bytes. + public byte[] ToArray() + { + ref readonly ColorData data = ref this; + unsafe + { + byte[] array = new byte[Size]; + fixed (ColorData* pData = &data) + { + Marshal.Copy((nint)pData, array, 0, Size); + return array; + } + } + } + +#if HAS_BUFFERS + /// + /// Returns a read-only span of bytes representing the contents of this instance. + /// + /// A that provides a read-only view of the bytes in this instance. + public ReadOnlySpan AsSpan() + { + ref readonly ColorData data = ref this; + unsafe + { + fixed (ColorData* pData = &data) + { + return new ReadOnlySpan(pData, Size); + } + } + } +#endif } } diff --git a/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs b/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs index 08c8a72..45a93df 100644 --- a/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs +++ b/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs @@ -62,11 +62,34 @@ public enum SyncCommand /// /// Stat a file v2. /// + /// Need Android 8 or above. STA2, /// /// Stat a list v2. /// - LST2 + /// Need Android 8 or above. + LST2, + + /// + /// List the files in a folder v2. + /// + /// Need Android 11 or above. + LIS2, + + /// + /// Retrieve a file from device v2. + /// + SND2, + + /// + /// Retrieve a file from device v2. + /// + RCV2, + + /// + /// A directory entry v2. + /// + DNT2 } } diff --git a/AdvancedSharpAdbClient/Models/Enums/UnixFileStatus.cs b/AdvancedSharpAdbClient/Models/Enums/UnixFileStatus.cs index 2f7d076..8a174fa 100644 --- a/AdvancedSharpAdbClient/Models/Enums/UnixFileStatus.cs +++ b/AdvancedSharpAdbClient/Models/Enums/UnixFileStatus.cs @@ -11,7 +11,7 @@ namespace AdvancedSharpAdbClient.Models /// /// [Flags] - public enum UnixFileStatus + public enum UnixFileStatus : uint { /// /// Empty property. diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs b/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs new file mode 100644 index 0000000..82697c4 --- /dev/null +++ b/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs @@ -0,0 +1,126 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace AdvancedSharpAdbClient.Models +{ + /// + /// Contains information about a file on the remote device (v2). + /// + /// +#if HAS_BUFFERS + [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsV2Creator))] +#endif + [DebuggerDisplay($"{{{nameof(GetType)}().{nameof(Type.ToString)}(),nq}} \\{{ {nameof(Path)} = {{{nameof(Path)}}}, {nameof(FileMode)} = {{{nameof(FileMode)}}}, {nameof(Size)} = {{{nameof(Size)}}}, {nameof(ModifiedTime)} = {{{nameof(ModifiedTime)}}} }}")] + public struct FileStatisticsV2(FileStatisticsDataV2 data) : IEquatable +#if NET7_0_OR_GREATER + , IEqualityOperators +#endif + { + /// + /// The data of the file. + /// + private readonly FileStatisticsDataV2 data = data; + + /// + /// Gets the path of the file. + /// + public string Path { get; set; } = string.Empty; + + /// + /// Gets the error code associated with the current operation. + /// + public readonly uint Error => data.Error; + + /// + /// Gets the device identifier associated with this instance. + /// + public readonly ulong Device => data.Device; + + /// + /// Gets the index node identifier associated with this entry. + /// + public readonly ulong IndexNode => data.IndexNode; + + /// + /// Gets the attributes of the file. + /// + public readonly UnixFileStatus FileMode => (UnixFileStatus)data.Mode; + + /// + /// Gets the number of hard links to the file or directory. + /// + public readonly uint LinkCount => data.LinkCount; + + /// + /// Gets the unique identifier of the user associated with this instance. + /// + public readonly uint UserId => data.UserId; + + /// + /// Gets the unique identifier for the group. + /// + public readonly uint GroupId => data.GroupId; + + /// + /// Gets the size, in bytes. + /// + public readonly ulong Size => data.Size; + + /// + /// Gets the last access time. + /// + public readonly DateTimeOffset AccessTime => DateTimeOffset.FromUnixTimeSeconds(data.AccessTime); + + /// + /// Gets the time indicating when the item was last modified. + /// + public readonly DateTimeOffset ModifiedTime => DateTimeOffset.FromUnixTimeSeconds(data.ModifiedTime); + + /// + /// Gets the time indicating when the last change occurred. + /// + public readonly DateTimeOffset ChangedTime => DateTimeOffset.FromUnixTimeSeconds(data.ChangedTime); + +#if HAS_BUFFERS + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator that can be used to iterate through the . + public readonly ReadOnlySpan.Enumerator GetEnumerator() => data.GetEnumerator(); +#endif + + /// + public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is FileStatisticsV2 other && Equals(other); + + /// + public readonly bool Equals(FileStatisticsV2 other) => Path == other.Path && data == other.data; + + /// + /// Tests whether two objects are equally. + /// + /// The structure that is to the left of the equality operator. + /// The structure that is to the right of the equality operator. + /// This operator returns if the two structures are equally; otherwise . + public static bool operator ==(FileStatisticsV2 left, FileStatisticsV2 right) => left.Equals(right); + + /// + /// Tests whether two objects are different. + /// + /// The structure that is to the left of the inequality operator. + /// The structure that is to the right of the inequality operator. + /// This operator returns if the two structures are unequally; otherwise . + public static bool operator !=(FileStatisticsV2 left, FileStatisticsV2 right) => !left.Equals(right); + + /// + public override readonly int GetHashCode() => HashCode.Combine(Path, data); + + /// + public override readonly string ToString() => string.Join('\t', FileMode.ToPermissionCode()!, Size, ModifiedTime, Path!); + } +} diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.cs b/AdvancedSharpAdbClient/Models/FileStatistics.cs index 3a212ba..970f434 100644 --- a/AdvancedSharpAdbClient/Models/FileStatistics.cs +++ b/AdvancedSharpAdbClient/Models/FileStatistics.cs @@ -13,74 +13,52 @@ namespace AdvancedSharpAdbClient.Models /// /// Contains information about a file on the remote device. /// - /// + /// The data of the file. #if HAS_BUFFERS [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsCreator))] #endif [DebuggerDisplay($"{{{nameof(GetType)}().{nameof(Type.ToString)}(),nq}} \\{{ {nameof(Path)} = {{{nameof(Path)}}}, {nameof(FileMode)} = {{{nameof(FileMode)}}}, {nameof(Size)} = {{{nameof(Size)}}}, {nameof(Time)} = {{{nameof(Time)}}} }}")] - public struct FileStatistics : IEquatable + public struct FileStatistics(FileStatisticsData data) : IEquatable #if NET7_0_OR_GREATER , IEqualityOperators #endif { /// - /// Initializes a new instance of the struct. + /// The data of the file. /// - public FileStatistics() { } + private readonly FileStatisticsData data = data; /// - /// Gets or sets the path of the file. + /// Gets the path of the file. /// public string Path { get; set; } = string.Empty; /// - /// Gets or sets the attributes of the file. + /// Gets the attributes of the file. /// - public UnixFileStatus FileMode { get; init; } + public readonly UnixFileStatus FileMode => (UnixFileStatus)data.Mode; /// - /// Gets or sets the total file size, in bytes. + /// Gets the total file size, in bytes. /// - public uint Size { get; init; } + public readonly uint Size => data.Size; /// - /// Gets or sets the time of last modification. + /// Gets the time of last modification. /// - public DateTimeOffset Time { get; init; } + public readonly DateTimeOffset Time => DateTimeOffset.FromUnixTimeSeconds(data.Time); /// /// Returns an enumerator that iterates through the . /// /// An enumerator that can be used to iterate through the . - public readonly IEnumerator GetEnumerator() - { - int mode = (int)FileMode; - yield return (byte)mode; - yield return (byte)(mode >> 8); - yield return (byte)(mode >> 16); - yield return (byte)(mode >> 24); - - yield return (byte)Size; - yield return (byte)(Size >> 8); - yield return (byte)(Size >> 16); - yield return (byte)(Size >> 24); - - long time = Time.ToUnixTimeSeconds(); - yield return (byte)time; - yield return (byte)(time >> 8); - yield return (byte)(time >> 16); - yield return (byte)(time >> 24); - } + public readonly IEnumerator GetEnumerator() => data.GetEnumerator(); /// public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is FileStatistics other && Equals(other); /// - public readonly bool Equals(FileStatistics other) => - Path == other.Path - && FileMode == other.FileMode - && Size == other.Size - && Time == other.Time; + public readonly bool Equals(FileStatistics other) => Path == other.Path && data == other.data; /// /// Tests whether two objects are equally. @@ -99,7 +77,7 @@ public readonly bool Equals(FileStatistics other) => public static bool operator !=(FileStatistics left, FileStatistics right) => !left.Equals(right); /// - public override readonly int GetHashCode() => HashCode.Combine(Path, FileMode, Size, Time); + public override readonly int GetHashCode() => HashCode.Combine(Path, data); /// public override readonly string ToString() => string.Join('\t', FileMode.ToPermissionCode()!, Size, Time, Path!); diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs new file mode 100644 index 0000000..fa11ea1 --- /dev/null +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs @@ -0,0 +1,143 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace AdvancedSharpAdbClient.Models +{ + /// + /// Contains information about a file on the remote device (v2). + /// + /// The error code associated with the current operation. + /// The device identifier associated with this instance. + /// The index node identifier associated with this entry. + /// The file mode value represented as an unsigned integer. + /// The number of hard links to the file or directory. + /// The unique identifier of the user associated with this instance. + /// The unique identifier for the group. + /// The size, in bytes, of the associated resource. + /// The last access time, in Unix timestamp format, for the associated resource. + /// The timestamp indicating when the item was last modified. + /// The timestamp indicating when the last change occurred. + /// +#if HAS_BUFFERS + [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsDataV2Creator))] +#endif + [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] + [StructLayout(LayoutKind.Explicit)] + public readonly record struct FileStatisticsDataV2(uint Error, ulong Device, ulong IndexNode, uint Mode, uint LinkCount, uint UserId, uint GroupId, ulong Size, long AccessTime, long ModifiedTime, long ChangedTime) +#if NET7_0_OR_GREATER + : IEqualityOperators +#endif + { + /// + /// The length of in bytes. + /// + public const int Length = 5 * sizeof(uint) + 3 * sizeof(ulong) + 3 * sizeof(long); + + /// + /// Gets the error code associated with the current operation. + /// + [field: FieldOffset(0)] + public uint Error { get; init; } = Error; + + /// + /// Gets the device identifier associated with this instance. + /// + [field: FieldOffset(sizeof(uint))] + public ulong Device { get; init; } = Device; + + /// + /// Gets the index node identifier associated with this entry. + /// + [field: FieldOffset(sizeof(uint) + sizeof(ulong))] + public ulong IndexNode { get; init; } = IndexNode; + + /// + /// Gets the file mode value represented as an unsigned integer. + /// + [field: FieldOffset(sizeof(uint) + 2 * sizeof(ulong))] + public uint Mode { get; init; } = Mode; + + /// + /// Gets the number of hard links to the file or directory. + /// + [field: FieldOffset(2 * sizeof(uint) + 2 * sizeof(ulong))] + public uint LinkCount { get; init; } = LinkCount; + + /// + /// Gets the unique identifier of the user associated with this instance. + /// + [field: FieldOffset(3 * sizeof(uint) + 2 * sizeof(ulong))] + public uint UserId { get; init; } = UserId; + + /// + /// Gets the unique identifier for the group. + /// + [field: FieldOffset(4 * sizeof(uint) + 2 * sizeof(ulong))] + public uint GroupId { get; init; } = GroupId; + + /// + /// Gets the size, in bytes. + /// + [field: FieldOffset(5 * sizeof(uint) + 2 * sizeof(ulong))] + public ulong Size { get; init; } = Size; + + /// + /// Gets the last access time, in Unix timestamp format. + /// + [field: FieldOffset(5 * sizeof(uint) + 3 * sizeof(ulong))] + public long AccessTime { get; init; } = AccessTime; + + /// + /// Gets the timestamp indicating when the item was last modified. + /// + [field: FieldOffset(5 * sizeof(uint) + 2 * sizeof(ulong) + sizeof(ulong))] + public long ModifiedTime { get; init; } = ModifiedTime; + + /// + /// Gets the timestamp indicating when the last change occurred. + /// + [field: FieldOffset(5 * sizeof(uint) + 2 * sizeof(ulong) + 2 * sizeof(ulong))] + public long ChangedTime { get; init; } = ChangedTime; + + /// + /// Returns a byte array containing the binary representation of this instance. + /// + /// A byte array that represents the contents of this instance. The length of the array is + /// equal to the size of the structure in bytes. + public unsafe byte[] ToArray() + { + byte[] array = new byte[Length]; + fixed (FileStatisticsDataV2* pData = &this) + { + Marshal.Copy((nint)pData, array, 0, Length); + return array; + } + } + +#if HAS_BUFFERS + /// + /// Returns a read-only span of bytes representing the contents of this instance. + /// + /// A that provides a read-only view of the bytes in this instance. + public unsafe ReadOnlySpan AsSpan() + { + fixed (FileStatisticsDataV2* pData = &this) + { + return new ReadOnlySpan(pData, Length); + } + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator that can be used to iterate through the . + public ReadOnlySpan.Enumerator GetEnumerator() => AsSpan().GetEnumerator(); +#endif + } +} diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs new file mode 100644 index 0000000..13d5db5 --- /dev/null +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs @@ -0,0 +1,143 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace AdvancedSharpAdbClient.Models +{ + /// + /// Contains information about a file on the remote device. + /// + /// The mode of the file. + /// The total file size, in bytes. + /// The time of last modification. + /// +#if HAS_BUFFERS + [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsDataCreator))] +#endif + [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] + public readonly record struct FileStatisticsData(uint Mode, uint Size, uint Time) : IReadOnlyList +#if NET7_0_OR_GREATER + , IEqualityOperators +#endif + { + /// + /// The length of in bytes. + /// + public const int Length = 3 * sizeof(uint); + + /// + /// Gets the mode of the file. + /// + public uint Mode { get; init; } = Mode; + + /// + /// Gets the total file size, in bytes. + /// + public uint Size { get; init; } = Size; + + /// + /// Gets the time of last modification. + /// + public uint Time { get; init; } = Time; + + /// + /// Gets the length of in bytes. + /// + readonly int IReadOnlyCollection.Count => Length; + + /// + /// Deconstruct the struct. + /// + /// The mode of the file. + /// The total file size, in bytes. + /// The time of last modification. + public readonly void Deconstruct(out uint mode, out uint size, out uint time) + { + mode = Mode; + size = Size; + time = Time; + } + + /// + public readonly byte this[int index] => + index is < 0 or >= Length + ? throw new IndexOutOfRangeException("Index was out of range. Must be non-negative and less than the size of the collection.") + : index switch + { + 0 => (byte)Mode, + 1 => (byte)(Mode >> 8), + 2 => (byte)(Mode >> 16), + 3 => (byte)(Mode >> 24), + + 4 => (byte)Size, + 5 => (byte)(Size >> 8), + 6 => (byte)(Size >> 16), + 7 => (byte)(Size >> 24), + + 8 => (byte)Time, + 9 => (byte)(Time >> 8), + 10 => (byte)(Time >> 16), + 11 => (byte)(Time >> 24), + + _ => throw new IndexOutOfRangeException("Index was out of range. Must be non-negative and less than the size of the collection.") + }; + + /// + public IEnumerator GetEnumerator() + { + yield return (byte)Mode; + yield return (byte)(Mode >> 8); + yield return (byte)(Mode >> 16); + yield return (byte)(Mode >> 24); + + yield return (byte)Size; + yield return (byte)(Size >> 8); + yield return (byte)(Size >> 16); + yield return (byte)(Size >> 24); + + yield return (byte)Time; + yield return (byte)(Time >> 8); + yield return (byte)(Time >> 16); + yield return (byte)(Time >> 24); + } + + /// + readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Returns a byte array containing the binary representation of this instance. + /// + /// A byte array that represents the contents of this instance. The length of the array is + /// equal to the size of the structure in bytes. + public unsafe byte[] ToArray() + { + byte[] array = new byte[Length]; + fixed (FileStatisticsData* pData = &this) + { + Marshal.Copy((nint)pData, array, 0, Length); + return array; + } + } + +#if HAS_BUFFERS + /// + /// Returns a read-only span of bytes representing the contents of this instance. + /// + /// A that provides a read-only view of the bytes in this instance. + public unsafe ReadOnlySpan AsSpan() + { + fixed (FileStatisticsData* pData = &this) + { + return new ReadOnlySpan(pData, Length); + } + } +#endif + } +} \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs index f3b9b43..d9164a4 100644 --- a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs +++ b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs @@ -21,6 +21,7 @@ namespace AdvancedSharpAdbClient.Models /// of the framebuffer, prefixed with a object that contains more /// information about the framebuffer. /// + /// As defined in #if HAS_BUFFERS [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FramebufferHeaderCreator))] #endif @@ -28,66 +29,48 @@ namespace AdvancedSharpAdbClient.Models public readonly struct FramebufferHeader : IReadOnlyList { /// - /// The length of the head when is . + /// The length of the head when is . /// - public const int MaxLength = 56; + public const int MinLength = 52; /// - /// The length of the head when is . + /// The length of the head when is . /// - public const int MiniLength = 52; + public const int MaxLength = 56; /// /// Initializes a new instance of the struct based on a byte array which contains the data. /// /// The data that feeds the struct. - /// As defined in public FramebufferHeader(byte[] data) { - if (data.Length is < MiniLength or > MaxLength) - { - throw new ArgumentOutOfRangeException(nameof(data), $"The length of {nameof(data)} must between {MiniLength} and {MaxLength}."); - } - - int index = 0; - - Version = ReadUInt32(data); - - if (Version > 2) + byte[] _data; + switch (data) { // Technically, 0 is not a supported version either; we assume version 0 indicates // an empty framebuffer. - throw new InvalidOperationException($"Framebuffer version {Version} is not supported"); + case { Length: < MinLength or > MaxLength }: + throw new ArgumentOutOfRangeException(nameof(data), $"The length of {nameof(data)} must be {MinLength} or {MaxLength}."); + case [> 2, ..]: + throw new InvalidOperationException($"Framebuffer version {Version} is not supported"); + case [2, ..]: + _data = data; + break; + case [< 2, ..]: + _data = new byte[MaxLength]; + Array.Copy(data, 0, _data, 0, 2 * sizeof(uint)); + Array.Copy(data, 2 * sizeof(uint), _data, (2 * sizeof(uint)) + 4, MinLength - (2 * sizeof(uint))); + break; } - Bpp = ReadUInt32(data); - - if (Version >= 2) + unsafe { - ColorSpace = ReadUInt32(data); + fixed (byte* p = _data) + { + FramebufferHeader* header = (FramebufferHeader*)p; + this = *header; + } } - - Size = ReadUInt32(data); - Width = ReadUInt32(data); - Height = ReadUInt32(data); - - Red = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - Blue = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - Green = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - Alpha = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - uint ReadUInt32(byte[] data) => (uint)(data[index++] | (data[index++] << 8) | (data[index++] << 16) | (data[index++] << 24)); } #if HAS_BUFFERS @@ -95,53 +78,27 @@ public FramebufferHeader(byte[] data) /// Initializes a new instance of the struct based on a byte array which contains the data. /// /// The data that feeds the struct. - /// As defined in + [OverloadResolutionPriority(1)] public FramebufferHeader(ReadOnlySpan data) { - if (data.Length is < MiniLength or > MaxLength) - { - throw new ArgumentOutOfRangeException(nameof(data), $"The length of {nameof(data)} must between {MiniLength} and {MaxLength}."); - } - - int index = 0; - - Version = ReadUInt32(data); - - if (Version > 2) + ReadOnlySpan _data = data switch { + { Length: < MinLength or > MaxLength } => throw new ArgumentOutOfRangeException(nameof(data), $"The length of {nameof(data)} must be {MinLength} or {MaxLength}."), // Technically, 0 is not a supported version either; we assume version 0 indicates // an empty framebuffer. - throw new InvalidOperationException($"Framebuffer version {Version} is not supported"); - } - - Bpp = ReadUInt32(data); + [> 2, ..] => throw new InvalidOperationException($"Framebuffer version {Version} is not supported"), + [2, ..] => data, + [< 2, ..] => [.. data[..(2 * sizeof(uint))], 0, 0, 0, 0, .. data[(2 * sizeof(uint))..]] + }; - if (Version >= 2) + unsafe { - ColorSpace = ReadUInt32(data); + fixed (byte* p = _data) + { + FramebufferHeader* header = (FramebufferHeader*)p; + this = *header; + } } - - Size = ReadUInt32(data); - Width = ReadUInt32(data); - Height = ReadUInt32(data); - - Red = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - Blue = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - Green = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - Alpha = new ColorData( - ReadUInt32(data), - ReadUInt32(data)); - - uint ReadUInt32(in ReadOnlySpan data) => (uint)(data[index++] | (data[index++] << 8) | (data[index++] << 16) | (data[index++] << 24)); } #endif @@ -198,7 +155,7 @@ public FramebufferHeader(ReadOnlySpan data) /// /// Gets the length of the head in bytes. /// - public int Count => Version < 2 ? MiniLength : MaxLength; + public int Count => Version < 2 ? MinLength : MaxLength; /// public byte this[int index] @@ -258,6 +215,7 @@ public byte this[int index] /// /// The data that feeds the struct. /// A new object. + [OverloadResolutionPriority(1)] public static FramebufferHeader Read(ReadOnlySpan data) => new(data); #endif diff --git a/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs b/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs index d62dd0e..0896278 100644 --- a/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs +++ b/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs @@ -11,19 +11,19 @@ namespace AdvancedSharpAdbClient.Models /// Provides data for the interface. /// [DebuggerDisplay($"{{{nameof(GetType)}().{nameof(Type.ToString)}(),nq}} \\{{ {nameof(ReceivedBytesSize)} = {{{nameof(ReceivedBytesSize)}}}, {nameof(TotalBytesToReceive)} = {{{nameof(TotalBytesToReceive)}}}, {nameof(ProgressPercentage)} = {{{nameof(ProgressPercentage)}}} }}")] - public sealed class SyncProgressChangedEventArgs(long current, long total) : EventArgs + public sealed class SyncProgressChangedEventArgs(ulong current, ulong total) : EventArgs { /// /// Gets the number of bytes sync to the local computer. /// - /// An representing the number of sync bytes. - public long ReceivedBytesSize => current; + /// An representing the number of sync bytes. + public ulong ReceivedBytesSize => current; /// /// Gets the total number of bytes for the sync operation. /// - /// An representing the total size of the download, in bytes. - public long TotalBytesToReceive => total; + /// An representing the total size of the download, in bytes. + public ulong TotalBytesToReceive => total; /// /// Gets the number of progress percentage (from to ) for the sync operation. diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/BitConverterExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/BitConverterExtensions.cs new file mode 100644 index 0000000..60eafe3 --- /dev/null +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/BitConverterExtensions.cs @@ -0,0 +1,43 @@ +#if !HAS_BUFFERS +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; + +namespace AdvancedSharpAdbClient.Polyfills +{ + /// + /// Provides extension methods for the class. + /// + internal static class BitConverterExtensions + { + /// + /// The extension for the class. + /// + extension(BitConverter) + { + /// + /// Returns a 32-bit signed integer converted from four bytes in a byte array. + /// + /// An array of bytes that includes the four bytes to convert. + /// A 32-bit signed integer representing the converted bytes. + public static int ToInt32(byte[] value) => BitConverter.ToInt32(value, 0); + + /// + /// Returns a 16-bit unsigned integer converted from two bytes in a byte array. + /// + /// The array of bytes that includes the two bytes to convert. + /// An 16-bit unsigned integer representing the converted bytes. + public static ushort ToUInt16(byte[] value) => BitConverter.ToUInt16(value, 0); + + /// + /// Returns a 32-bit unsigned integer converted from four bytes in a byte array. + /// + /// An array of bytes. + /// A 32-bit unsigned integer representing the converted bytes. + public static uint ToUInt32(byte[] value) => BitConverter.ToUInt32(value, 0); + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs index 51d0a56..18c53b4 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs @@ -29,6 +29,7 @@ public static Task ConnectAsync(this Socket socket, EndPoint remoteEP, Cancellat Task.Run(() => socket.Connect(remoteEP), cancellationToken); #endif +#if !HAS_BUFFERS /// /// Asynchronously receives data from a connected socket. /// @@ -40,6 +41,7 @@ public static Task ConnectAsync(this Socket socket, EndPoint remoteEP, Cancellat /// A which returns the number of bytes received. public static Task ReceiveAsync(this Socket socket, byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) => socket.ReceiveAsync(buffer, 0, buffer.Length, socketFlags, cancellationToken); +#endif /// /// Asynchronously receives data from a connected socket. @@ -70,7 +72,6 @@ public static Task ReceiveAsync(this Socket socket, byte[] buffer, int offs #if HAS_BUFFERS return socket.ReceiveAsync(buffer.AsMemory(offset, size), socketFlags, cancellationToken).AsTask(); #elif HAS_PROCESS - // Register a callback so that when a cancellation is requested, the socket is closed. // This will cause an ObjectDisposedException to bubble up via TrySetResult, which we can catch // and convert to a TaskCancelledException - which is the exception we expect. @@ -107,6 +108,7 @@ public static Task ReceiveAsync(this Socket socket, byte[] buffer, int offs #endif } +#if !HAS_BUFFERS /// /// Asynchronously sends data to a connected socket. /// @@ -118,6 +120,7 @@ public static Task ReceiveAsync(this Socket socket, byte[] buffer, int offs /// A which returns the number of bytes sent. public static Task SendAsync(this Socket socket, byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) => socket.SendAsync(buffer, 0, buffer.Length, socketFlags, cancellationToken); +#endif /// /// Asynchronously sends data to a connected socket. diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index 9345251..14e9de4 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -106,7 +106,7 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi await Socket.SendAsync(buffer, startPosition, read + dataBytes.Length + lengthBytes.Length, cancellationToken).ConfigureAwait(false); #endif // Let the caller know about our progress, if requested - callback?.Invoke(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); + callback?.Invoke(new SyncProgressChangedEventArgs((ulong)totalBytesRead, (ulong)totalBytesToProcess)); // check if we're canceled cancellationToken.ThrowIfCancellationRequested(); @@ -136,8 +136,10 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi } } + private Task GetFileSizeAsync(string remoteFilePath, bool useV2 = false, CancellationToken cancellationToken = default) => useV2 ? StatV2Async(remoteFilePath, cancellationToken).ContinueWith(x => x.Result.Size) : StatAsync(remoteFilePath, cancellationToken).ContinueWith(x => (ulong)x.Result.Size); + /// - public virtual async Task PullAsync(string remotePath, Stream stream, Action? callback = null, CancellationToken cancellationToken = default) + public virtual async Task PullAsync(string remotePath, Stream stream, Action? callback = null, bool useV2 = false, 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."); } @@ -147,9 +149,8 @@ public virtual async Task PullAsync(string remotePath, Stream stream, Action MaxBufferSize) { @@ -195,7 +196,7 @@ public virtual async Task PullAsync(string remotePath, Stream stream, Action - public virtual async Task PullAsync(string remotePath, IOutputStream stream, Action? progress = null, CancellationToken cancellationToken = default) + public virtual async Task PullAsync(string remotePath, IOutputStream stream, Action? progress = null, bool useV2 = false, 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."); } @@ -334,9 +335,8 @@ public virtual async Task PullAsync(string remotePath, IOutputStream stream, Act if (IsOutdate) { await ReopenAsync(cancellationToken).ConfigureAwait(false); } // Gets file information, including the file size, used to calculate the total amount of bytes to receive. - FileStatistics stat = await StatAsync(remotePath, cancellationToken).ConfigureAwait(false); - long totalBytesToProcess = stat.Size; - long totalBytesRead = 0; + ulong totalBytesToProcess = await GetFileSizeAsync(remotePath, useV2, cancellationToken).ConfigureAwait(false); + ulong totalBytesRead = 0; byte[] buffer = new byte[MaxBufferSize]; @@ -364,7 +364,7 @@ public virtual async Task PullAsync(string remotePath, IOutputStream stream, Act byte[] reply = new byte[4]; _ = await Socket.ReadAsync(reply, cancellationToken).ConfigureAwait(false); - int size = reply[0] | (reply[1] << 8) | (reply[2] << 16) | (reply[3] << 24); + int size = BitConverter.ToInt32(reply); if (size > MaxBufferSize) { @@ -417,6 +417,24 @@ public async Task StatAsync(string remotePath, CancellationToken return value; } + /// + public async Task StatV2Async(string remotePath, CancellationToken cancellationToken = default) + { + // create the stat request message. + await Socket.SendSyncRequestAsync(SyncCommand.STA2, remotePath, cancellationToken).ConfigureAwait(false); + + SyncCommand response = await Socket.ReadSyncResponseAsync(cancellationToken).ConfigureAwait(false); + if (response != SyncCommand.STA2) + { + throw new AdbException($"The server returned an invalid sync response {response}."); + } + + FileStatisticsV2 value = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); + value.Path = remotePath; + + return value; + } + /// public async Task> GetDirectoryListingAsync(string remotePath, CancellationToken cancellationToken = default) { @@ -467,6 +485,56 @@ public async Task> GetDirectoryListingAsync(string remotePa } } + /// + public async Task> GetDirectoryListingV2Async(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; + + start: + List value = []; + + try + { + // create the stat request message. + await Socket.SendSyncRequestAsync(SyncCommand.LIS2, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + + while (true) + { + SyncCommand response = await Socket.ReadSyncResponseAsync(cancellationToken).ConfigureAwait(false); + + switch (response) + { + case 0 when isLocked: + throw new AdbException("The server returned an empty sync response."); + case 0: + Reopen(); + isLocked = true; + goto start; + case SyncCommand.DONE: + goto finish; + case not SyncCommand.DNT2: + throw new AdbException($"The server returned an invalid sync response {response}."); + } + + FileStatisticsV2 entry = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); + entry.Path = await Socket.ReadSyncStringAsync(cancellationToken).ConfigureAwait(false); + + value.Add(entry); + } + + finish: + return value; + } + finally + { + IsOutdate = true; + IsProcessing = false; + } + } + #if COMP_NETSTANDARD2_1 /// public async IAsyncEnumerable GetDirectoryAsyncListing(string remotePath, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -516,6 +584,55 @@ public async IAsyncEnumerable GetDirectoryAsyncListing(string re IsProcessing = false; } } + + /// + public async IAsyncEnumerable GetDirectoryAsyncListingV2(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; + + try + { + start: + // create the stat request message. + await Socket.SendSyncRequestAsync(SyncCommand.LIS2, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + + while (true) + { + SyncCommand response = await Socket.ReadSyncResponseAsync(cancellationToken).ConfigureAwait(false); + + switch (response) + { + case 0 when isLocked: + throw new AdbException("The server returned an empty sync response."); + case 0: + Reopen(); + isLocked = true; + goto start; + case SyncCommand.DONE: + goto finish; + case not SyncCommand.DNT2: + throw new AdbException($"The server returned an invalid sync response {response}."); + } + + FileStatisticsV2 entry = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); + entry.Path = await Socket.ReadSyncStringAsync(cancellationToken).ConfigureAwait(false); + + yield return entry; + isLocked = true; + } + + finish: + yield break; + } + finally + { + IsOutdate = true; + IsProcessing = false; + } + } #endif /// @@ -526,20 +643,45 @@ public async IAsyncEnumerable GetDirectoryAsyncListing(string re protected async Task ReadStatisticsAsync(CancellationToken cancellationToken = default) { #if COMP_NETSTANDARD2_1 - Memory statResult = new byte[12]; + Memory statResult = new byte[FileStatisticsData.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); return EnumerableBuilder.FileStatisticsCreator(statResult.Span); #else - byte[] statResult = new byte[12]; + byte[] statResult = new byte[FileStatisticsData.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); - int index = 0; - return new FileStatistics + unsafe { - FileMode = (UnixFileStatus)ReadUInt32(statResult), - Size = ReadUInt32(statResult), - Time = DateTimeOffset.FromUnixTimeSeconds(ReadUInt32(statResult)) - }; - uint ReadUInt32(byte[] data) => unchecked((uint)(data[index++] | (data[index++] << 8) | (data[index++] << 16) | (data[index++] << 24))); + fixed (byte* p = statResult) + { + FileStatisticsData* data = (FileStatisticsData*)p; + return new FileStatistics(*data); + } + } +#endif + } + + /// + /// Asynchronously reads the statistics of a file from the socket. + /// + /// A that can be used to cancel the task. + /// A which returns a object that contains information about the file. + protected async Task ReadStatisticsV2Async(CancellationToken cancellationToken = default) + { +#if COMP_NETSTANDARD2_1 + Memory statResult = new byte[FileStatisticsDataV2.Length]; + _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); + return EnumerableBuilder.FileStatisticsV2Creator(statResult.Span); +#else + byte[] statResult = new byte[FileStatisticsDataV2.Length]; + _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); + unsafe + { + fixed (byte* p = statResult) + { + FileStatisticsDataV2* data = (FileStatisticsDataV2*)p; + return new FileStatisticsV2(*data); + } + } #endif } } diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index e9f1e23..d0d3b23 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -215,7 +215,7 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis Socket.Send(buffer, startPosition, read + dataBytes.Length + lengthBytes.Length); #endif // Let the caller know about our progress, if requested - callback?.Invoke(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); + callback?.Invoke(new SyncProgressChangedEventArgs((ulong)totalBytesRead, (ulong)totalBytesToProcess)); } // create the DONE message @@ -242,8 +242,10 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis } } + private ulong GetFileSize(string remoteFilePath, bool useV2 = false) => useV2 ? StatV2(remoteFilePath).Size : Stat(remoteFilePath).Size; + /// - public virtual void Pull(string remoteFilePath, Stream stream, Action? callback = null, in bool isCancelled = false) + public virtual void Pull(string remoteFilePath, Stream stream, Action? callback = null, bool useV2 = false, 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."); } @@ -253,9 +255,8 @@ public virtual void Pull(string remoteFilePath, Stream stream, Action MaxBufferSize) { @@ -301,7 +302,7 @@ public virtual void Pull(string remoteFilePath, Stream stream, Action + public FileStatisticsV2 StatV2(string remotePath) + { + // create the stat request message. + Socket.SendSyncRequest(SyncCommand.STA2, remotePath); + + SyncCommand response = Socket.ReadSyncResponse(); + if (response != SyncCommand.STA2) + { + throw new AdbException($"The server returned an invalid sync response {response}."); + } + + FileStatisticsV2 value = ReadStatisticsV2(); + value.Path = remotePath; + + return value; + } + /// public IEnumerable GetDirectoryListing(string remotePath) { @@ -385,6 +404,55 @@ public IEnumerable GetDirectoryListing(string remotePath) } } + /// + public IEnumerable GetDirectoryListingV2(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; + + try + { + start: + // create the stat request message. + Socket.SendSyncRequest(SyncCommand.LIS2, remotePath); + IsProcessing = true; + + while (true) + { + SyncCommand response = Socket.ReadSyncResponse(); + + switch (response) + { + case 0 when isLocked: + throw new AdbException("The server returned an empty sync response."); + case 0: + Reopen(); + isLocked = true; + goto start; + case SyncCommand.DONE: + goto finish; + case not SyncCommand.DNT2: + throw new AdbException($"The server returned an invalid sync response {response}."); + } + + FileStatisticsV2 entry = ReadStatisticsV2(); + entry.Path = Socket.ReadSyncString(); + + yield return entry; + isLocked = true; + } + + finish: + yield break; + } + finally + { + IsOutdate = true; + IsProcessing = false; + } + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -430,20 +498,44 @@ Socket is ICloneable cloneable protected FileStatistics ReadStatistics() { #if COMP_NETSTANDARD2_1 - Span statResult = stackalloc byte[12]; + Span statResult = stackalloc byte[FileStatisticsData.Length]; _ = Socket.Read(statResult); return EnumerableBuilder.FileStatisticsCreator(statResult); #else - byte[] statResult = new byte[12]; + byte[] statResult = new byte[FileStatisticsData.Length]; _ = Socket.Read(statResult); - int index = 0; - return new FileStatistics + unsafe { - FileMode = (UnixFileStatus)ReadUInt32(statResult), - Size = ReadUInt32(statResult), - Time = DateTimeOffset.FromUnixTimeSeconds(ReadUInt32(statResult)) - }; - uint ReadUInt32(byte[] data) => unchecked((uint)(data[index++] | (data[index++] << 8) | (data[index++] << 16) | (data[index++] << 24))); + fixed (byte* p = statResult) + { + FileStatisticsData* data = (FileStatisticsData*)p; + return new FileStatistics(*data); + } + } +#endif + } + + /// + /// Reads the statistics of a file from the socket (v2). + /// + /// A object that contains information about the file. + protected FileStatisticsV2 ReadStatisticsV2() + { +#if COMP_NETSTANDARD2_1 + Span statResult = stackalloc byte[FileStatisticsDataV2.Length]; + _ = Socket.Read(statResult); + return EnumerableBuilder.FileStatisticsV2Creator(statResult); +#else + byte[] statResult = new byte[FileStatisticsDataV2.Length]; + _ = Socket.Read(statResult); + unsafe + { + fixed (byte* p = statResult) + { + FileStatisticsDataV2* data = (FileStatisticsDataV2*)p; + return new FileStatisticsV2(*data); + } + } #endif } } diff --git a/AdvancedSharpAdbClient/TcpSocket.Async.cs b/AdvancedSharpAdbClient/TcpSocket.Async.cs index 5808f6d..34b6d4f 100644 --- a/AdvancedSharpAdbClient/TcpSocket.Async.cs +++ b/AdvancedSharpAdbClient/TcpSocket.Async.cs @@ -55,7 +55,7 @@ public Task ReconnectAsync(bool isForce, CancellationToken cancellationToken = d /// public Task SendAsync(byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) => -#if NET6_0_OR_GREATER +#if HAS_BUFFERS Socket.SendAsync(buffer, socketFlags, cancellationToken).AsTask(); #else Socket.SendAsync(buffer, socketFlags, cancellationToken); @@ -71,7 +71,7 @@ public Task SendAsync(byte[] buffer, int offset, int size, SocketFlags sock /// public Task ReceiveAsync(byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) => -#if NET6_0_OR_GREATER +#if HAS_BUFFERS Socket.ReceiveAsync(buffer, socketFlags, cancellationToken).AsTask(); #else Socket.ReceiveAsync(buffer, socketFlags, cancellationToken); From 64e398ade9eed8b98e52d0689f29a3ae0421068c Mon Sep 17 00:00:00 2001 From: where where Date: Fri, 9 Jan 2026 16:27:57 +0800 Subject: [PATCH 2/6] Set SyncCommand to their real value --- .../Extensions/SyncCommandConverterTests.cs | 8 --- .../Extensions/SyncCommandConverter.cs | 24 +------ .../Models/Enums/SyncCommand.cs | 68 +++++++++---------- 3 files changed, 37 insertions(+), 63 deletions(-) diff --git a/AdvancedSharpAdbClient.Tests/Extensions/SyncCommandConverterTests.cs b/AdvancedSharpAdbClient.Tests/Extensions/SyncCommandConverterTests.cs index d692fa6..e9ab963 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/SyncCommandConverterTests.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/SyncCommandConverterTests.cs @@ -16,14 +16,6 @@ public void GetCommandNullTest() => public void GetCommandInvalidNumberOfBytesTest() => _ = Assert.Throws(() => SyncCommandConverter.GetCommand([])); - [Fact] - public void GetCommandInvalidCommandTest() => - _ = Assert.Throws(() => SyncCommandConverter.GetCommand("QMTV"u8)); - - [Fact] - public void GetBytesInvalidCommandTest() => - _ = Assert.Throws(() => ((SyncCommand)99).GetBytes()); - [Fact] public void SyncCommandConverterTest() { diff --git a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs index a5b13a3..a323390 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs @@ -22,23 +22,7 @@ public static class SyncCommandConverter /// Gets the byte array that represents the . /// /// A byte array that represents the . - public byte[] GetBytes() - { - if (command == 0) - { - return [0, 0, 0, 0]; - } - - if (command is < SyncCommand.STAT or > SyncCommand.DNT2) - { - throw new ArgumentOutOfRangeException(nameof(command), $"{command} is not a valid sync command"); - } - - string commandText = command.ToString(); - byte[] commandBytes = AdbClient.Encoding.GetBytes(commandText); - - return commandBytes; - } + public byte[] GetBytes() => BitConverter.GetBytes((int)command); /// /// Returns an enumerator that iterates through the . @@ -60,8 +44,7 @@ public static SyncCommand GetCommand(byte[] value) throw new ArgumentOutOfRangeException(nameof(value)); } - string commandText = AdbClient.Encoding.GetString(value); - return commandText == "\0\0\0\0" ? 0 : Enum.TryParse(commandText, true, out SyncCommand result) ? result : throw new ArgumentOutOfRangeException(nameof(value), $"{commandText} is not a valid sync command"); + return (SyncCommand)BitConverter.ToInt32(value); } #if HAS_BUFFERS @@ -77,8 +60,7 @@ public static SyncCommand GetCommand(ReadOnlySpan value) throw new ArgumentOutOfRangeException(nameof(value)); } - string commandText = AdbClient.Encoding.GetString(value); - return commandText == "\0\0\0\0" ? 0 : Enum.TryParse(commandText, true, out SyncCommand result) ? result : throw new ArgumentOutOfRangeException(nameof(value), $"{commandText} is not a valid sync command"); + return (SyncCommand)BitConverter.ToInt32(value); } #endif } diff --git a/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs b/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs index 45a93df..b7fc8cb 100644 --- a/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs +++ b/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs @@ -12,84 +12,84 @@ public enum SyncCommand /// /// Stat a file. /// - STAT = 1, + STAT = 'S' | ('T' << 8) | ('A' << 16) | ('T' << 24), /// - /// List the files in a folder. + /// Stat a file v2. /// - LIST, + /// Need Android 8 or above. + STA2 = 'S' | ('T' << 8) | ('A' << 16) | ('2' << 24), /// - /// Send a file to device. + /// Stat a list v2. /// - SEND, + /// Need Android 8 or above. + LST2 = 'L' | ('S' << 8) | ('T' << 16) | ('2' << 24), /// - /// Retrieve a file from device. + /// List the files in a folder. /// - RECV, + LIST = 'L' | ('I' << 8) | ('S' << 16) | ('T' << 24), /// - /// A directory entry. + /// List the files in a folder v2. /// - DENT, + /// Need Android 11 or above. + LIS2 = 'L' | ('I' << 8) | ('S' << 16) | ('2' << 24), /// - /// The operation has completed. + /// A directory entry. /// - DONE, + DENT = 'D' | ('E' << 8) | ('N' << 16) | ('T' << 24), /// - /// Marks the start of a data packet. + /// A directory entry v2. /// - DATA, + DNT2 = 'D' | ('N' << 8) | ('T' << 16) | ('2' << 24), /// - /// The server has acknowledged the request. + /// Send a file to device. /// - OKAY, + SEND = 'S' | ('E' << 8) | ('N' << 16) | ('D' << 24), /// - /// The operation has failed. + /// Retrieve a file from device v2. /// - FAIL, + SND2 = 'S' | ('N' << 8) | ('D' << 16) | ('2' << 24), /// - /// The server has acknowledged the request. + /// Retrieve a file from device. /// - QUIT, + RECV = 'R' | ('E' << 8) | ('C' << 16) | ('V' << 24), /// - /// Stat a file v2. + /// Retrieve a file from device v2. /// - /// Need Android 8 or above. - STA2, + RCV2 = 'R' | ('C' << 8) | ('V' << 16) | ('2' << 24), /// - /// Stat a list v2. + /// The operation has completed. /// - /// Need Android 8 or above. - LST2, + DONE = 'D' | ('O' << 8) | ('N' << 16) | ('E' << 24), /// - /// List the files in a folder v2. + /// Marks the start of a data packet. /// - /// Need Android 11 or above. - LIS2, + DATA = 'D' | ('A' << 8) | ('T' << 16) | ('A' << 24), /// - /// Retrieve a file from device v2. + /// The server has acknowledged the request. /// - SND2, + OKAY = 'O' | ('K' << 8) | ('A' << 16) | ('Y' << 24), /// - /// Retrieve a file from device v2. + /// The operation has failed. /// - RCV2, + FAIL = 'F' | ('A' << 8) | ('I' << 16) | ('L' << 24), /// - /// A directory entry v2. + /// The server has acknowledged the request. /// - DNT2 + QUIT = 'Q' | ('U' << 8) | ('I' << 16) | ('T' << 24) } } From 8d23a476c609cdd337fd542f138b75308542d0aa Mon Sep 17 00:00:00 2001 From: where where Date: Sat, 10 Jan 2026 10:02:55 +0800 Subject: [PATCH 3/6] Add send v2 and recv v2 Update adb source link --- .../DeviceExtensionsTests.Async.cs | 6 +- .../DeviceCommands/DeviceExtensionsTests.cs | 4 +- .../Dummys/DummyAdbSocket.cs | 8 + .../Dummys/DummySyncService.cs | 14 +- .../SyncServiceTests.Async.cs | 53 +- .../SyncServiceTests.cs | 53 +- AdvancedSharpAdbClient/AdbClient.Async.cs | 2 +- AdvancedSharpAdbClient/AdbClient.cs | 2 +- AdvancedSharpAdbClient/AdbSocket.Async.cs | 23 +- AdvancedSharpAdbClient/AdbSocket.cs | 25 +- .../DeviceCommands/DeviceExtensions.Async.cs | 105 +++- .../DeviceCommands/DeviceExtensions.cs | 64 ++- .../DeviceCommands/PackageManager.Async.cs | 2 +- .../DeviceCommands/PackageManager.cs | 2 +- .../Extensions/EnumerableBuilder.cs | 18 +- .../Extensions/SyncServiceExtensions.Async.cs | 80 ++- .../Extensions/SyncServiceExtensions.cs | 56 +- .../Interfaces/IAdbClient.cs | 2 +- .../Interfaces/IAdbSocket.Async.cs | 14 +- .../Interfaces/IAdbSocket.cs | 12 +- .../Interfaces/IFileStatistics.cs | 34 ++ .../Interfaces/ISyncService.Async.cs | 24 +- .../Interfaces/ISyncService.cs | 16 +- .../Models/Enums/DeviceState.cs | 4 +- .../Models/Enums/SyncFlags.cs | 40 ++ .../Models/Enums/UnixErrorCode.cs | 513 ++++++++++++++++++ .../Models/FileStatistics.Base.cs | 97 ++++ .../Models/FileStatistics.V2.cs | 80 +-- .../Models/FileStatistics.cs | 53 +- .../Models/FileStatisticsData.V2.cs | 12 +- .../Models/FileStatisticsData.cs | 2 +- AdvancedSharpAdbClient/SyncService.Async.cs | 90 ++- AdvancedSharpAdbClient/SyncService.cs | 53 +- 33 files changed, 1320 insertions(+), 243 deletions(-) create mode 100644 AdvancedSharpAdbClient/Interfaces/IFileStatistics.cs create mode 100644 AdvancedSharpAdbClient/Models/Enums/SyncFlags.cs create mode 100644 AdvancedSharpAdbClient/Models/Enums/UnixErrorCode.cs create mode 100644 AdvancedSharpAdbClient/Models/FileStatistics.Base.cs diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs index 33cfb31..bb5d23c 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs @@ -98,7 +98,7 @@ public async Task ClickHomeButtonAsyncTest() public async Task StatAsyncTest() { const string remotePath = "/test"; - FileStatistics stats = new(); + FileStatistics stats = new(default); IAdbClient client = Substitute.For(); ISyncService mock = Substitute.For(); @@ -127,7 +127,7 @@ public async Task StatAsyncTest() public async Task GetDirectoryListingAsyncTest() { const string remotePath = "/test"; - List stats = [new()]; + List stats = [new(default)]; IAdbClient client = Substitute.For(); ISyncService mock = Substitute.For(); @@ -156,7 +156,7 @@ public async Task GetDirectoryListingAsyncTest() public async Task GetDirectoryAsyncListingTest() { const string remotePath = "/test"; - List stats = [new()]; + List stats = [new(default)]; IAdbClient client = Substitute.For(); ISyncService mock = Substitute.For(); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs index d435585..7741f26 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs @@ -98,7 +98,7 @@ public void ClickHomeButtonTest() public void StatTest() { const string remotePath = "/test"; - FileStatistics stats = new(); + FileStatistics stats = new(default); IAdbClient client = Substitute.For(); ISyncService mock = Substitute.For(); @@ -126,7 +126,7 @@ public void StatTest() public void GetDirectoryListingTest() { const string remotePath = "/test"; - IEnumerable stats = [new()]; + IEnumerable stats = [new(default)]; IAdbClient client = Substitute.For(); ISyncService mock = Substitute.For(); diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs index 0a7db30..ab7228e 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs @@ -64,6 +64,8 @@ internal partial class DummyAdbSocket : IDummyAdbSocket, ICloneable SyncRequests.Add((command, $"{path},{(int)permission}")); + public void SendSyncRequest(SyncCommand command, UnixFileStatus permission, SyncFlags flag) => SyncRequests.Add((command, $"{(int)permission}{(int)flag}")); + public void SendAdbRequest(string request) => Requests.Add(request); public void SendAdbRequest(DefaultInterpolatedStringHandler request) => Requests.Add(request.ToString()); @@ -202,6 +204,12 @@ public async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken ca Send(data.Span); } + public async Task SendSyncRequestAsync(SyncCommand command, UnixFileStatus permission, SyncFlags flags, CancellationToken cancellationToken = default) + { + await Task.Yield(); + SendSyncRequest(command, permission, flags); + } + public async Task SendSyncRequestAsync(SyncCommand command, string path, UnixFileStatus permission, CancellationToken cancellationToken = default) { await Task.Yield(); diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs index 8e78505..d0ee04a 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs @@ -42,7 +42,7 @@ public async Task PullAsync(string remotePath, Stream stream, Action callback = null, in bool isCancelled = false) + public void Push(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action callback = null, bool useV2 = false, in bool isCancelled = false) { for (uint i = 0; i <= 100; i++) { @@ -54,7 +54,7 @@ public void Push(Stream stream, string remotePath, UnixFileStatus permission, Da } } - public async Task PushAsync(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action callback = null, CancellationToken cancellationToken = default) + public async Task PushAsync(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action callback = null, bool useV2 = false, CancellationToken cancellationToken = default) { for (uint i = 0; i <= 100; i++) { @@ -79,23 +79,23 @@ public async Task ReopenAsync(CancellationToken cancellationToken = default) IAsyncEnumerable ISyncService.GetDirectoryAsyncListing(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); - IAsyncEnumerable ISyncService.GetDirectoryAsyncListingV2(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + IAsyncEnumerable ISyncService.GetDirectoryAsyncListingEx(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); IEnumerable ISyncService.GetDirectoryListing(string remotePath) => throw new NotImplementedException(); Task> ISyncService.GetDirectoryListingAsync(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); - IEnumerable ISyncService.GetDirectoryListingV2(string remotePath) => throw new NotImplementedException(); + IEnumerable ISyncService.GetDirectoryListingEx(string remotePath) => throw new NotImplementedException(); - Task> ISyncService.GetDirectoryListingV2Async(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + Task> ISyncService.GetDirectoryListingExAsync(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); FileStatistics ISyncService.Stat(string remotePath) => throw new NotImplementedException(); Task ISyncService.StatAsync(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); - FileStatisticsV2 ISyncService.StatV2(string remotePath) => throw new NotImplementedException(); + FileStatisticsEx ISyncService.StatEx(string remotePath) => throw new NotImplementedException(); - Task ISyncService.StatV2Async(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + Task ISyncService.StatExAsync(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); #endregion } diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index a2a5631..90a87ca 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -41,6 +41,57 @@ public async Task StatAsyncTest() Assert.Equal($"-rw-r-----\t597\t{value.Time}\t/fstab.donatello", value.ToString()); } + /// + /// Tests the method. + /// + [Fact] + public async Task StatExAsyncTest() + { + FileStatisticsEx value = await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [(SyncCommand.STA2, "/fstab.donatello")], + [SyncCommand.STA2], + [[ + 0, 0, 0, 0, + 167, 0, 0, 0, 0, 0, 0, 0, + 38, 240, 15, 0, 0, 0, 0, 0, + 160, 129, 0, 0, + 1, 0, 0, 0, + 146, 39, 0, 0, + 255, 3, 0, 0, + 85, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + ]], + null, + async () => + { + using SyncService service = new(Socket, Device); + FileStatisticsEx value = await service.StatExAsync("/fstab.donatello"); + Assert.False(service.IsProcessing); + Assert.False(service.IsOutdate); + return value; + }); + + Assert.Equal("/fstab.donatello", value.Path); + Assert.Equal(UnixErrorCode.Default, value.Error); + Assert.Equal(167u, value.Device); + Assert.Equal(1044518u, value.IndexNode); + Assert.Equal(UnixFileStatus.Regular, value.FileMode.GetFileType()); + Assert.Equal((UnixFileStatus)416, value.FileMode.GetPermissions()); + Assert.Equal(1u, value.LinkCount); + Assert.Equal(597u, value.Size); + Assert.Equal(10130u, value.UserId); + Assert.Equal(1023u, value.GroupId); + Assert.Equal(DateTimeExtensions.Epoch.ToLocalTime(), value.AccessTime); + Assert.Equal(DateTimeExtensions.Epoch.ToLocalTime(), value.ModifiedTime); + Assert.Equal(DateTimeExtensions.Epoch.ToLocalTime(), value.ChangedTime); + Assert.Equal($"-rw-r-----\t597\t{value.ModifiedTime}\t/fstab.donatello", value.ToString()); + } + /// /// Tests the method. /// @@ -201,7 +252,7 @@ await RunTestAsync( } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async Task PushAsyncTest() diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index b78f8de..4f834bc 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -49,6 +49,57 @@ public void StatTest() Assert.Equal($"-rw-r-----\t597\t{value.Time}\t/fstab.donatello", value.ToString()); } + /// + /// Tests the method. + /// + [Fact] + public void StatExTest() + { + FileStatisticsEx value = RunTest( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [(SyncCommand.STA2, "/fstab.donatello")], + [SyncCommand.STA2], + [[ + 0, 0, 0, 0, + 167, 0, 0, 0, 0, 0, 0, 0, + 38, 240, 15, 0, 0, 0, 0, 0, + 160, 129, 0, 0, + 1, 0, 0, 0, + 146, 39, 0, 0, + 255, 3, 0, 0, + 85, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + ]], + null, + () => + { + using SyncService service = new(Socket, Device); + FileStatisticsEx value = service.StatEx("/fstab.donatello"); + Assert.False(service.IsProcessing); + Assert.False(service.IsOutdate); + return value; + }); + + Assert.Equal("/fstab.donatello", value.Path); + Assert.Equal(UnixErrorCode.Default, value.Error); + Assert.Equal(167u, value.Device); + Assert.Equal(1044518u, value.IndexNode); + Assert.Equal(UnixFileStatus.Regular, value.FileMode.GetFileType()); + Assert.Equal((UnixFileStatus)416, value.FileMode.GetPermissions()); + Assert.Equal(1u, value.LinkCount); + Assert.Equal(597u, value.Size); + Assert.Equal(10130u, value.UserId); + Assert.Equal(1023u, value.GroupId); + Assert.Equal(DateTimeExtensions.Epoch.ToLocalTime(), value.AccessTime); + Assert.Equal(DateTimeExtensions.Epoch.ToLocalTime(), value.ModifiedTime); + Assert.Equal(DateTimeExtensions.Epoch.ToLocalTime(), value.ChangedTime); + Assert.Equal($"-rw-r-----\t597\t{value.ModifiedTime}\t/fstab.donatello", value.ToString()); + } + /// /// Tests the method. /// @@ -148,7 +199,7 @@ public void PullTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void PushTest() diff --git a/AdvancedSharpAdbClient/AdbClient.Async.cs b/AdvancedSharpAdbClient/AdbClient.Async.cs index f560b31..df0911b 100644 --- a/AdvancedSharpAdbClient/AdbClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbClient.Async.cs @@ -554,7 +554,7 @@ protected async Task RootAsync(string request, DeviceData device, CancellationTo Encoding.GetString(buffer, 0, read); #endif - // see https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/daemon/restart_service.cpp + // see https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/daemon/restart_service.cpp // for possible return strings if (!responseMessage.Contains("restarting", StringComparison.OrdinalIgnoreCase)) { diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 8b43062..914616b 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -728,7 +728,7 @@ protected void Root(string request, DeviceData device) Encoding.GetString(buffer, 0, read); #endif - // see https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/daemon/restart_service.cpp + // see https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/daemon/restart_service.cpp // for possible return strings if (!responseMessage.Contains("restarting", StringComparison.OrdinalIgnoreCase)) { diff --git a/AdvancedSharpAdbClient/AdbSocket.Async.cs b/AdvancedSharpAdbClient/AdbSocket.Async.cs index bcad140..296563e 100644 --- a/AdvancedSharpAdbClient/AdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/AdbSocket.Async.cs @@ -78,6 +78,25 @@ public virtual async Task SendAsync(byte[] data, int offset, int length, Cancell } } + /// + public async Task SendSyncRequestAsync(SyncCommand command, UnixFileStatus permissions, SyncFlags flags, CancellationToken cancellationToken = default) + { + byte[] commandBytes = command.GetBytes(); + byte[] modeBytes = BitConverter.GetBytes((uint)permissions.GetPermissions()); + byte[] flagsBytes = BitConverter.GetBytes((int)flags); + + if (!BitConverter.IsLittleEndian) + { + // Convert from big endian to little endian + Array.Reverse(modeBytes); + Array.Reverse(flagsBytes); + } + + _ = await WriteAsync(commandBytes, cancellationToken).ConfigureAwait(false); + _ = await WriteAsync(modeBytes, cancellationToken).ConfigureAwait(false); + _ = await WriteAsync(flagsBytes, cancellationToken).ConfigureAwait(false); + } + /// public Task SendSyncRequestAsync(SyncCommand command, string path, UnixFileStatus permissions, CancellationToken cancellationToken = default) => SendSyncRequestAsync(command, $"{path},{(int)permissions.GetPermissions()}", cancellationToken); @@ -92,7 +111,7 @@ public async Task SendSyncRequestAsync(SyncCommand command, string path, Cancell } /// - public async Task SendSyncRequestAsync(SyncCommand command, int length, CancellationToken cancellationToken = default) + public async Task SendSyncRequestAsync(SyncCommand command, int value, CancellationToken cancellationToken = default) { // The message structure is: // First four bytes: command @@ -100,7 +119,7 @@ public async Task SendSyncRequestAsync(SyncCommand command, int length, Cancella // Final bytes: path byte[] commandBytes = command.GetBytes(); - byte[] lengthBytes = BitConverter.GetBytes(length); + byte[] lengthBytes = BitConverter.GetBytes(value); if (!BitConverter.IsLittleEndian) { diff --git a/AdvancedSharpAdbClient/AdbSocket.cs b/AdvancedSharpAdbClient/AdbSocket.cs index 57c3879..3262cce 100644 --- a/AdvancedSharpAdbClient/AdbSocket.cs +++ b/AdvancedSharpAdbClient/AdbSocket.cs @@ -21,7 +21,7 @@ namespace AdvancedSharpAdbClient /// Bridge exposes, use the . /// For more information about the protocol that is implemented here, see chapter /// II Protocol Details, section 1. Client <->Server protocol at - /// . + /// . /// /// The at which the Android Debug Bridge is listening for clients. /// The logger to use when logging. @@ -154,6 +154,25 @@ public virtual void Send(byte[] data, int offset, int length) } } + /// + public void SendSyncRequest(SyncCommand command, UnixFileStatus permissions, SyncFlags flags) + { + byte[] commandBytes = command.GetBytes(); + byte[] modeBytes = BitConverter.GetBytes((uint)permissions.GetPermissions()); + byte[] flagsBytes = BitConverter.GetBytes((int)flags); + + if (!BitConverter.IsLittleEndian) + { + // Convert from big endian to little endian + Array.Reverse(modeBytes); + Array.Reverse(flagsBytes); + } + + _ = Write(commandBytes); + _ = Write(modeBytes); + _ = Write(flagsBytes); + } + /// public void SendSyncRequest(SyncCommand command, string path, UnixFileStatus permissions) => SendSyncRequest(command, $"{path},{(int)permissions.GetPermissions()}"); @@ -168,7 +187,7 @@ public void SendSyncRequest(SyncCommand command, string path) } /// - public void SendSyncRequest(SyncCommand command, int length) + public void SendSyncRequest(SyncCommand command, int value) { // The message structure is: // First four bytes: command @@ -176,7 +195,7 @@ public void SendSyncRequest(SyncCommand command, int length) // Final bytes: path byte[] commandBytes = command.GetBytes(); - byte[] lengthBytes = BitConverter.GetBytes(length); + byte[] lengthBytes = BitConverter.GetBytes(value); if (!BitConverter.IsLittleEndian) { diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs index b49ec9a..9134f91 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs @@ -220,7 +220,7 @@ public static Task StopAppAsync(this IAdbClient client, DeviceData device, strin /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. /// V2 need Android 8 or above. @@ -244,15 +244,17 @@ public static async Task PullAsync(this IAdbClient client, DeviceData device, /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. public static async Task PushAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileStatus permission, DateTimeOffset timestamp, Action? callback = null, + bool useV2 = false, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); - await service.PushAsync(stream, remotePath, permission, timestamp, callback, cancellationToken).ConfigureAwait(false); + await service.PushAsync(stream, remotePath, permission, timestamp, callback, useV2, cancellationToken).ConfigureAwait(false); } /// @@ -269,6 +271,32 @@ public static async Task StatAsync(this IAdbClient client, Devic return await service.StatAsync(path, cancellationToken).ConfigureAwait(false); } + /// + /// Asynchronously gets the file statistics of a given file (v2). + /// + /// The to use when executing the command. + /// The device on which to look for the file. + /// The path to the file. + /// A that can be used to cancel the task. + /// A which returns a object that contains information about the file. + public static async Task StatExAsync(this IAdbClient client, DeviceData device, string path, CancellationToken cancellationToken = default) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + return await service.StatExAsync(path, cancellationToken).ConfigureAwait(false); + } + + /// + /// Asynchronously gets the file statistics of a given file. + /// + /// The to use when executing the command. + /// The device on which to look for the file. + /// The path to the file. + /// to use ; otherwise, use . + /// A that can be used to cancel the task. + /// A which returns a object that contains information about the file. + public static Task StatAsync(this IAdbClient client, DeviceData device, string path, bool useV2, CancellationToken cancellationToken = default) => + useV2 ? client.StatExAsync(device, path, cancellationToken).ContinueWith(x => x.Result as IFileStatistics) : client.StatAsync(device, path, cancellationToken).ContinueWith(x => x.Result as IFileStatistics); + /// /// Asynchronously lists the contents of a directory on the device. /// @@ -283,6 +311,36 @@ public static async Task> GetDirectoryListingAsync(this IAd return await service.GetDirectoryListingAsync(remotePath, cancellationToken).ConfigureAwait(false); } + /// + /// Asynchronously lists the contents of a directory on the device (v2). + /// + /// The to use when executing the command. + /// The device on which to list the directory. + /// The path to the directory on the device. + /// A that can be used to cancel the task. + /// A which returns for each child item of the directory, a object with information of the item. + public static async Task> GetDirectoryListingExAsync(this IAdbClient client, DeviceData device, string remotePath, CancellationToken cancellationToken = default) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + return await service.GetDirectoryListingExAsync(remotePath, cancellationToken).ConfigureAwait(false); + } + + /// + /// Asynchronously lists the contents of a directory on the device. + /// + /// The to use when executing the command. + /// The device on which to list the directory. + /// The path to the directory on the device. + /// to use ; otherwise, use . + /// A that can be used to cancel the task. + /// A which returns for each child item of the directory, a object with information of the item. + public static Task> GetDirectoryListingAsync(this IAdbClient client, DeviceData device, string remotePath, bool useV2, CancellationToken cancellationToken = default) => +#if NETFRAMEWORK && !NET40_OR_GREATER + useV2 ? client.GetDirectoryListingExAsync(device, remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()) : client.GetDirectoryListingAsync(device, remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()); +#else + useV2 ? client.GetDirectoryListingExAsync(device, remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable) : client.GetDirectoryListingAsync(device, remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable); +#endif + #if COMP_NETSTANDARD2_1 /// /// Asynchronously lists the contents of a directory on the device. @@ -300,6 +358,35 @@ public static async IAsyncEnumerable GetDirectoryAsyncListing(th yield return file; } } + + /// + /// Asynchronously lists the contents of a directory on the device (v2). + /// + /// The to use when executing the command. + /// The device on which to list the directory. + /// The path to the directory on the device. + /// A that can be used to cancel the task. + /// An which returns for each child item of the directory, a object with information of the item. + public static async IAsyncEnumerable GetDirectoryAsyncListingEx(this IAdbClient client, DeviceData device, string remotePath, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + await foreach (FileStatisticsEx file in service.GetDirectoryAsyncListingEx(remotePath, cancellationToken).ConfigureAwait(false)) + { + yield return file; + } + } + + /// + /// Asynchronously lists the contents of a directory on the device. + /// + /// The to use when executing the command. + /// The device on which to list the directory. + /// The path to the directory on the device. + /// to use ; otherwise, use . + /// A that can be used to cancel the task. + /// An which returns for each child item of the directory, a object with information of the item. + public static IAsyncEnumerable GetDirectoryAsyncListing(this IAdbClient client, DeviceData device, string remotePath, bool useV2, CancellationToken cancellationToken = default) => + useV2 ? client.GetDirectoryAsyncListingEx(device, remotePath, cancellationToken) : client.GetDirectoryAsyncListing(device, remotePath, cancellationToken); #endif /// @@ -407,7 +494,7 @@ public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceDat /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. /// V2 need Android 8 or above. @@ -431,15 +518,17 @@ public static async Task PullAsync(this IAdbClient client, DeviceData device, /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. public static async Task PushAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileStatus permission, DateTimeOffset timestamp, IProgress? progress, + bool useV2 = false, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); - await service.PushAsync(stream, remotePath, permission, timestamp, progress.AsAction(), cancellationToken).ConfigureAwait(false); + await service.PushAsync(stream, remotePath, permission, timestamp, progress.AsAction(), useV2, cancellationToken).ConfigureAwait(false); } /// @@ -506,15 +595,17 @@ public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceDat /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. public static async Task PushAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileMode permission, DateTimeOffset timestamp, Action? callback = null, + bool useV2 = false, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); - await service.PushAsync(stream, remotePath, (UnixFileStatus)permission, timestamp, callback, cancellationToken).ConfigureAwait(false); + await service.PushAsync(stream, remotePath, (UnixFileStatus)permission, timestamp, callback, useV2, cancellationToken).ConfigureAwait(false); } /// @@ -527,15 +618,17 @@ public static async Task PushAsync(this IAdbClient client, DeviceData device, /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. public static async Task PushAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileMode permission, DateTimeOffset timestamp, IProgress? progress, + bool useV2 = false, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); - await service.PushAsync(stream, remotePath, (UnixFileStatus)permission, timestamp, progress.AsAction(), cancellationToken).ConfigureAwait(false); + await service.PushAsync(stream, remotePath, (UnixFileStatus)permission, timestamp, progress.AsAction(), useV2, cancellationToken).ConfigureAwait(false); } #endif #endif diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs index eec0d0c..72dc537 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs @@ -181,7 +181,7 @@ public static void StopApp(this IAdbClient client, DeviceData device, string pac /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// V2 need Android 8 or above. public static void Pull(this IAdbClient client, DeviceData device, @@ -204,15 +204,17 @@ public static void Pull(this IAdbClient client, DeviceData device, /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// The should coverts to a decimal number. For example, 644 should be 420 in decimal, &O644 in VB.NET and 0o644 in F# and Python. public static void Push(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileStatus permission, DateTimeOffset timestamp, Action? callback = null, + bool useV2 = false, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); - service.Push(stream, remotePath, permission, timestamp, callback, in isCancelled); + service.Push(stream, remotePath, permission, timestamp, callback, useV2, in isCancelled); } /// @@ -234,14 +236,26 @@ public static FileStatistics Stat(this IAdbClient client, DeviceData device, str /// The to use when executing the command. /// The device on which to look for the file. /// The path to the file. - /// A object that represents the file. + /// A object that represents the file. /// Need Android 8 or above. - public static FileStatisticsV2 StatV2(this IAdbClient client, DeviceData device, string path) + public static FileStatisticsEx StatEx(this IAdbClient client, DeviceData device, string path) { using ISyncService service = Factories.SyncServiceFactory(client, device); - return service.StatV2(path); + return service.StatEx(path); } + /// + /// Gets the file statistics of a given file. + /// + /// The to use when executing the command. + /// The device on which to look for the file. + /// The path to the file. + /// to use ; otherwise, use . + /// A object that represents the file. + /// V2 need Android 8 or above. + public static IFileStatistics Stat(this IAdbClient client, DeviceData device, string path, bool useV2) => + useV2 ? client.StatEx(device, path) : client.Stat(device, path); + /// /// Lists the contents of a directory on the device. /// @@ -259,22 +273,38 @@ public static IEnumerable GetDirectoryListing(this IAdbClient cl } /// - /// Lists the contents of a directory on the device. + /// Lists the contents of a directory on the device (v2). /// /// The to use when executing the command. /// The device on which to list the directory. /// The path to the directory on the device. - /// For each child item of the directory, a object with information of the item. + /// For each child item of the directory, a object with information of the item. /// Need Android 11 or above. - public static IEnumerable GetDirectoryListingV2(this IAdbClient client, DeviceData device, string remotePath) + public static IEnumerable GetDirectoryListingEx(this IAdbClient client, DeviceData device, string remotePath) { using ISyncService service = Factories.SyncServiceFactory(client, device); - foreach (FileStatisticsV2 fileStatistics in service.GetDirectoryListingV2(remotePath)) + foreach (FileStatisticsEx fileStatistics in service.GetDirectoryListingEx(remotePath)) { yield return fileStatistics; } } + /// + /// Lists the contents of a directory on the device. + /// + /// The to use when executing the command. + /// The device on which to list the directory. + /// The path to the directory on the device. + /// to use ; otherwise, use . + /// For each child item of the directory, a object with information of the item. + /// V2 need Android 11 or above. + public static IEnumerable GetDirectoryListing(this IAdbClient client, DeviceData device, string remotePath, bool useV2) => +#if NETFRAMEWORK && !NET40_OR_GREATER + useV2 ? client.GetDirectoryListingEx(device, remotePath).OfType() : client.GetDirectoryListing(device, remotePath).OfType(); +#else + useV2 ? client.GetDirectoryListingEx(device, remotePath) : client.GetDirectoryListing(device, remotePath); +#endif + /// /// Gets the property of a device. /// @@ -371,7 +401,7 @@ public static void InstallMultiplePackage(this IAdbClient client, DeviceData dev /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// V2 need Android 8 or above. public static void Pull(this IAdbClient client, DeviceData device, @@ -394,15 +424,17 @@ public static void Pull(this IAdbClient client, DeviceData device, /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// The should coverts to a decimal number. For example, 644 should be 420 in decimal, &O644 in VB.NET and 0o644 in F# and Python. public static void Push(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileStatus permission, DateTimeOffset timestamp, IProgress? progress = null, + bool useV2 = false, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); - service.Push(stream, remotePath, permission, timestamp, progress.AsAction(), in isCancelled); + service.Push(stream, remotePath, permission, timestamp, progress.AsAction(), useV2, in isCancelled); } /// @@ -463,14 +495,17 @@ public static void InstallMultiplePackage(this IAdbClient client, DeviceData dev /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. + /// V2 need Android 11 or above. public static void Push(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileMode permission, DateTimeOffset timestamp, Action? callback = null, + bool useV2 = false, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); - service.Push(stream, remotePath, (UnixFileStatus)permission, timestamp, callback, in isCancelled); + service.Push(stream, remotePath, (UnixFileStatus)permission, timestamp, callback, useV2, in isCancelled); } /// @@ -483,14 +518,17 @@ public static void Push(this IAdbClient client, DeviceData device, /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. + /// V2 need Android 11 or above. public static void Push(this IAdbClient client, DeviceData device, string remotePath, Stream stream, UnixFileMode permission, DateTimeOffset timestamp, IProgress? progress, + bool useV2 = false, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); - service.Push(stream, remotePath, (UnixFileStatus)permission, timestamp, progress.AsAction(), in isCancelled); + service.Push(stream, remotePath, (UnixFileStatus)permission, timestamp, progress.AsAction(), useV2, in isCancelled); } #endif #endif diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs index 5268df1..0a088d8 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs @@ -599,7 +599,7 @@ protected virtual async Task SyncPackageToDeviceAsync(string localFilePa Action? progress = callback == null ? null : args => callback.Invoke(localFilePath, args); // As C# can't use octal, the octal literal 666 (rw-Permission) is here converted to decimal (438) - await sync.PushAsync(stream, remoteFilePath, UnixFileStatus.DefaultFileMode, File.GetLastWriteTime(localFilePath), progress, cancellationToken).ConfigureAwait(false); + await sync.PushAsync(stream, remoteFilePath, UnixFileStatus.DefaultFileMode, File.GetLastWriteTime(localFilePath), progress, false, cancellationToken).ConfigureAwait(false); } return remoteFilePath; diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs index 97328bc..b3668cb 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs @@ -634,7 +634,7 @@ protected virtual string SyncPackageToDevice(string localFilePath, Action? progress = callback == null ? null : args => callback.Invoke(localFilePath, args); // As C# can't use octal, the octal literal 666 (rw-Permission) is here converted to decimal (438) - sync.Push(stream, remoteFilePath, UnixFileStatus.DefaultFileMode, File.GetLastWriteTime(localFilePath), progress, false); + sync.Push(stream, remoteFilePath, UnixFileStatus.DefaultFileMode, File.GetLastWriteTime(localFilePath), progress, false, false); } return remoteFilePath; diff --git a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs index d6e1105..bcad3e1 100644 --- a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs +++ b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs @@ -67,22 +67,22 @@ public static unsafe FileStatisticsData FileStatisticsDataCreator(ReadOnlySpan - /// Build a struct. + /// Build a struct. /// - /// The data that feeds the struct. - /// A new instance of struct. - public static FileStatisticsV2 FileStatisticsV2Creator(ReadOnlySpan values) => new(FileStatisticsDataV2Creator(values)); + /// The data that feeds the struct. + /// A new instance of struct. + public static FileStatisticsEx FileStatisticsV2Creator(ReadOnlySpan values) => new(FileStatisticsDataV2Creator(values)); /// - /// Build a struct. + /// Build a struct. /// - /// The data that feeds the struct. - /// A new instance of struct. - public static unsafe FileStatisticsDataV2 FileStatisticsDataV2Creator(ReadOnlySpan values) + /// The data that feeds the struct. + /// A new instance of struct. + public static unsafe FileStatisticsDataEx FileStatisticsDataV2Creator(ReadOnlySpan values) { fixed (byte* p = values) { - FileStatisticsDataV2* data = (FileStatisticsDataV2*)p; + FileStatisticsDataEx* data = (FileStatisticsDataEx*)p; return *data; } } diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs index af5d40b..78450d3 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs @@ -1,16 +1,59 @@ -#if (HAS_TASK && !NETFRAMEWORK) || NET40_OR_GREATER +#if HAS_TASK // // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; namespace AdvancedSharpAdbClient { public static partial class SyncServiceExtensions { + /// + /// Asynchronously returns information about a file on the device. + /// + /// An instance of a class that implements the interface. + /// The path of the file on the device. + /// to use ; otherwise, use . + /// A that can be used to cancel the task. + /// A which returns a object that contains information about the file. + /// V2 need Android 8 or above. + public static Task StatAsync(this ISyncService service, string remotePath, bool useV2 = false, CancellationToken cancellationToken = default) => + useV2 ? service.StatExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IFileStatistics) : service.StatAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IFileStatistics); + + /// + /// Asynchronously lists the contents of a directory on the device. + /// + /// An instance of a class that implements the interface. + /// The path to the directory on the device. + /// to use ; otherwise, use . + /// A that can be used to cancel the task. + /// A which returns for each child item of the directory, a object with information of the item. + public static Task> GetDirectoryListingAsync(this ISyncService service, string remotePath, bool useV2 = false, CancellationToken cancellationToken = default) => +#if NETFRAMEWORK && !NET40_OR_GREATER + useV2 ? service.GetDirectoryListingExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()) : service.GetDirectoryListingAsync(remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()); +#else + useV2 ? service.GetDirectoryListingExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable) : service.GetDirectoryListingAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable); +#endif + +#if COMP_NETSTANDARD2_1 + /// + /// Asynchronously lists the contents of a directory on the device. + /// + /// An instance of a class that implements the interface. + /// The path to the directory on the device. + /// to use ; otherwise, use . + /// A that can be used to cancel the task. + /// An which returns for each child item of the directory, a object with information of the item. + public static IAsyncEnumerable GetDirectoryAsyncListing(this ISyncService service, string remotePath, bool useV2 = false, CancellationToken cancellationToken = default) => + useV2 ? service.GetDirectoryAsyncListingEx(remotePath, cancellationToken) : service.GetDirectoryAsyncListing(remotePath, cancellationToken); +#endif + +#if !NETFRAMEWORK || NET40_OR_GREATER /// /// Asynchronously pushes (uploads) a file to the remote device. /// @@ -20,10 +63,11 @@ public static partial class SyncServiceExtensions /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PushAsync(this ISyncService service, Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, IProgress? progress = null, CancellationToken cancellationToken = default) => - service.PushAsync(stream, remotePath, permissions, timestamp, progress.AsAction(), cancellationToken); + public static Task PushAsync(this ISyncService service, Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, IProgress? progress = null, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, permissions, timestamp, progress.AsAction(), useV2, cancellationToken); /// /// Asynchronously pulls (downloads) a file from the remote device. @@ -32,7 +76,7 @@ public static Task PushAsync(this ISyncService service, Stream stream, string re /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. /// V2 need Android 8 or above. @@ -49,10 +93,11 @@ public static Task PullAsync(this ISyncService service, string remotePath, Strea /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, IProgress? progress = null, CancellationToken cancellationToken = default) => - service.PushAsync(stream, remotePath, permissions, timestamp, progress.AsAction(), cancellationToken); + public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, IProgress? progress = null, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, permissions, timestamp, progress.AsAction(), useV2, cancellationToken); /// /// Asynchronously pulls (downloads) a file from the remote device. @@ -61,7 +106,7 @@ public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stre /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. /// V2 need Android 8 or above. @@ -79,10 +124,11 @@ public static Task PullAsync(this ISyncService.IWinRT service, string remotePath /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PushAsync(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, Action? callback = null, CancellationToken cancellationToken = default) => - service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, callback, cancellationToken); + public static Task PushAsync(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, Action? callback = null, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, callback, useV2, cancellationToken); /// /// Asynchronously pushes (uploads) a file to the remote device. @@ -93,10 +139,11 @@ public static Task PushAsync(this ISyncService service, Stream stream, string re /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PushAsync(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, IProgress? progress, CancellationToken cancellationToken = default) => - service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, progress.AsAction(), cancellationToken); + public static Task PushAsync(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, IProgress? progress, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, progress.AsAction(), useV2, cancellationToken); #if WINDOWS10_0_17763_0_OR_GREATER /// @@ -108,10 +155,11 @@ public static Task PushAsync(this ISyncService service, Stream stream, string re /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, Action? callback = null, CancellationToken cancellationToken = default) => - service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, callback, cancellationToken); + public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, Action? callback = null, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, callback, useV2, cancellationToken); /// /// Asynchronously pushes (uploads) a file to the remote device. @@ -122,10 +170,12 @@ public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stre /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, IProgress? progress, CancellationToken cancellationToken = default) => - service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, progress.AsAction(), cancellationToken); + public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, IProgress? progress, bool useV2 = false, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, (UnixFileStatus)permissions, timestamp, progress.AsAction(), useV2, cancellationToken); +#endif #endif #endif } diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs index e87b8d7..780d955 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs @@ -1,10 +1,11 @@ -#if !NETFRAMEWORK || NET40_OR_GREATER -// +// // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // using System; +using System.Collections.Generic; using System.IO; +using System.Linq; namespace AdvancedSharpAdbClient { @@ -13,6 +14,33 @@ namespace AdvancedSharpAdbClient /// public static partial class SyncServiceExtensions { + /// + /// Returns information about a file on the device. + /// + /// An instance of a class that implements the interface. + /// The path of the file on the device. + /// to use ; otherwise, use . + /// A object that contains information about the file. + /// V2 need Android 8 or above. + public static IFileStatistics Stat(this ISyncService service, string remotePath, bool useV2 = false) => + useV2 ? service.StatEx(remotePath) : service.Stat(remotePath); + + /// + /// Lists the contents of a directory on the device. + /// + /// An instance of a class that implements the interface. + /// The path to the directory on the device. + /// to use ; otherwise, use . + /// For each child item of the directory, a object with information of the item. + /// V2 need Android 11 or above. + public static IEnumerable GetDirectoryListing(this ISyncService service, string remotePath, bool useV2 = false) => +#if NETFRAMEWORK && !NET40_OR_GREATER + useV2 ? service.GetDirectoryListingEx(remotePath).OfType() : service.GetDirectoryListing(remotePath).OfType(); +#else + useV2 ? service.GetDirectoryListingEx(remotePath) : service.GetDirectoryListing(remotePath); +#endif + +#if !NETFRAMEWORK || NET40_OR_GREATER /// /// Pushes (uploads) a file to the remote device. /// @@ -22,9 +50,11 @@ public static partial class SyncServiceExtensions /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. - public static void Push(this ISyncService service, Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, IProgress? progress = null, in bool isCancelled = false) => - service.Push(stream, remotePath, permissions, timestamp, progress.AsAction(), isCancelled); + /// V2 need Android 11 or above. + public static void Push(this ISyncService service, Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, IProgress? progress = null, bool useV2 = false, in bool isCancelled = false) => + service.Push(stream, remotePath, permissions, timestamp, progress.AsAction(), useV2, isCancelled); /// /// Pulls (downloads) a file from the remote device. @@ -33,7 +63,7 @@ public static void Push(this ISyncService service, Stream stream, string remoteP /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// V2 need Android 8 or above. public static void Pull(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, bool useV2 = false, in bool isCancelled = false) => @@ -49,9 +79,11 @@ public static void Pull(this ISyncService service, string remotePath, Stream str /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. - public static void Push(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, Action? callback = null, in bool isCancelled = false) => - service.Push(stream, remotePath, (UnixFileStatus)permissions, timestamp, callback, isCancelled); + /// V2 need Android 11 or above. + public static void Push(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, Action? callback = null, bool useV2 = false, in bool isCancelled = false) => + service.Push(stream, remotePath, (UnixFileStatus)permissions, timestamp, callback, useV2, isCancelled); /// /// Pushes (uploads) a file to the remote device. @@ -62,10 +94,12 @@ public static void Push(this ISyncService service, Stream stream, string remoteP /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. - public static void Push(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, IProgress? progress, in bool isCancelled = false) => - service.Push(stream, remotePath, (UnixFileStatus)permissions, timestamp, progress.AsAction(), isCancelled); + /// V2 need Android 11 or above. + public static void Push(this ISyncService service, Stream stream, string remotePath, UnixFileMode permissions, DateTimeOffset timestamp, IProgress? progress, bool useV2 = false, in bool isCancelled = false) => + service.Push(stream, remotePath, (UnixFileStatus)permissions, timestamp, progress.AsAction(), useV2, isCancelled); +#endif #endif } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs index 2e180cc..11982e3 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs @@ -29,7 +29,7 @@ public partial interface IAdbClient EndPoint EndPoint { get; } // The individual services are listed in the same order as - // https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT + // https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/docs/dev/services.md /// /// Ask the ADB server for its internal version number. diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs index 39ad689..74845a1 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs @@ -48,6 +48,16 @@ public partial interface IAdbSocket /// A that represents the asynchronous operation. Task SendAsync(byte[] data, int offset, int length, CancellationToken cancellationToken); + /// + /// Asynchronously sends a sync request to the device. + /// + /// The command to send. + /// If the command is a command, the permissions to assign to the newly created file. + /// If the command is a , the compression type to use. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + Task SendSyncRequestAsync(SyncCommand command, UnixFileStatus permissions, SyncFlags flags, CancellationToken cancellationToken); + /// /// Asynchronously sends a sync request to the device. /// @@ -71,10 +81,10 @@ public partial interface IAdbSocket /// Asynchronously sends a sync request to the device. /// /// The command to send. - /// The length of the data packet that follows. + /// A data or the length of the data packet that follows. /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - Task SendSyncRequestAsync(SyncCommand command, int length, CancellationToken cancellationToken); + Task SendSyncRequestAsync(SyncCommand command, int value, CancellationToken cancellationToken); /// /// Asynchronously sends a request to the Android Debug Bridge.To read the response, call diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.cs b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.cs index 66657b7..d062d0a 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.cs @@ -49,6 +49,14 @@ public partial interface IAdbSocket : IDisposable /// The number of bytes to send. void Send(byte[] data, int offset, int length); + /// + /// Sends a sync request to the device. + /// + /// The command to send. + /// If the command is a command, the permissions to assign to the newly created file. + /// If the command is a , the compression type to use. + void SendSyncRequest(SyncCommand command, UnixFileStatus permissions, SyncFlags flags); + /// /// Sends a sync request to the device. /// @@ -68,8 +76,8 @@ public partial interface IAdbSocket : IDisposable /// Sends a sync request to the device. /// /// The command to send. - /// The length of the data packet that follows. - void SendSyncRequest(SyncCommand command, int length); + /// A data or the length of the data packet that follows. + void SendSyncRequest(SyncCommand command, int value); /// /// Sends a request to the Android Debug Bridge.To read the response, call diff --git a/AdvancedSharpAdbClient/Interfaces/IFileStatistics.cs b/AdvancedSharpAdbClient/Interfaces/IFileStatistics.cs new file mode 100644 index 0000000..8c42617 --- /dev/null +++ b/AdvancedSharpAdbClient/Interfaces/IFileStatistics.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; + +namespace AdvancedSharpAdbClient +{ + /// + /// Contains information about a file on the remote device. + /// + public interface IFileStatistics + { + /// + /// Gets the path of the file. + /// + string Path { get; } + + /// + /// Gets the attributes of the file. + /// + UnixFileStatus FileMode { get; } + + /// + /// Gets the total file size, in bytes. + /// + ulong Size { get; } + + /// + /// Gets the time of last modification. + /// + DateTimeOffset Time { get; } + } +} diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs index a6e13f2..b1b97f1 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs @@ -20,9 +20,10 @@ public partial interface ISyncService /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - Task PushAsync(Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, Action? callback, CancellationToken cancellationToken); + Task PushAsync(Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, Action? callback, bool useV2, CancellationToken cancellationToken); /// /// Asynchronously pulls (downloads) a file from the remote device. @@ -30,7 +31,7 @@ public partial interface ISyncService /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. /// V2 need Android 8 or above. @@ -49,9 +50,9 @@ public partial interface ISyncService /// /// The path of the file on the device. /// A that can be used to cancel the task. - /// A which returns a object that contains information about the file. + /// A which returns a object that contains information about the file. /// Need Android 8 or above. - Task StatV2Async(string remotePath, CancellationToken cancellationToken); + Task StatExAsync(string remotePath, CancellationToken cancellationToken); /// /// Asynchronously lists the contents of a directory on the device. @@ -66,9 +67,9 @@ public partial interface ISyncService /// /// The path to the directory on the device. /// A that can be used to cancel the task. - /// A which returns for each child item of the directory, a object with information of the item. + /// A which returns for each child item of the directory, a object with information of the item. /// Need Android 11 or above. - Task> GetDirectoryListingV2Async(string remotePath, CancellationToken cancellationToken); + Task> GetDirectoryListingExAsync(string remotePath, CancellationToken cancellationToken); #if COMP_NETSTANDARD2_1 /// @@ -85,10 +86,10 @@ IAsyncEnumerable GetDirectoryAsyncListing(string remotePath, Can /// /// The path to the directory on the device. /// A that can be used to cancel the task. - /// An which returns for each child item of the directory, a object with information of the item. + /// An which returns for each child item of the directory, a object with information of the item. /// Need Android 11 or above. - IAsyncEnumerable GetDirectoryAsyncListingV2(string remotePath, CancellationToken cancellationToken) => - GetDirectoryListingV2Async(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(cancellationToken); + IAsyncEnumerable GetDirectoryAsyncListingEx(string remotePath, CancellationToken cancellationToken) => + GetDirectoryListingExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(cancellationToken); #endif /// @@ -119,9 +120,10 @@ public interface IWinRT /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - Task PushAsync(IInputStream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, Action? callback, CancellationToken cancellationToken); + Task PushAsync(IInputStream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, Action? callback, bool useV2, CancellationToken cancellationToken); /// /// Asynchronously pulls (downloads) a file from the remote device. @@ -129,7 +131,7 @@ public interface IWinRT /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. /// V2 need Android 8 or above. diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs index 9f94630..fcc44b6 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs @@ -29,8 +29,10 @@ public partial interface ISyncService : IDisposable /// The that contains the permissions of the newly created file on the device. /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// to use ; otherwise, use . /// A that can be used to cancel the task. - void Push(Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, Action? callback, in bool isCancelled); + /// V2 need Android 11 or above. + void Push(Stream stream, string remotePath, UnixFileStatus permissions, DateTimeOffset timestamp, Action? callback, bool useV2, in bool isCancelled); /// /// Pulls (downloads) a file from the remote device. @@ -38,7 +40,7 @@ public partial interface ISyncService : IDisposable /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. - /// if use ; use . + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// V2 need Android 8 or above. void Pull(string remotePath, Stream stream, Action? callback, bool useV2, in bool isCancelled); @@ -54,9 +56,9 @@ public partial interface ISyncService : IDisposable /// Returns information about a file on the device (v2). /// /// The path of the file on the device. - /// A object that contains information about the file. + /// A object that contains information about the file. /// Need Android 8 or above. - FileStatisticsV2 StatV2(string remotePath); + FileStatisticsEx StatEx(string remotePath); /// /// Lists the contents of a directory on the device. @@ -66,12 +68,12 @@ public partial interface ISyncService : IDisposable IEnumerable GetDirectoryListing(string remotePath); /// - /// Lists the contents of a directory on the device. + /// Lists the contents of a directory on the device (v2). /// /// The path to the directory on the device. - /// For each child item of the directory, a object with information of the item. + /// For each child item of the directory, a object with information of the item. /// Need Android 11 or above. - IEnumerable GetDirectoryListingV2(string remotePath); + IEnumerable GetDirectoryListingEx(string remotePath); /// /// Opens this connection. diff --git a/AdvancedSharpAdbClient/Models/Enums/DeviceState.cs b/AdvancedSharpAdbClient/Models/Enums/DeviceState.cs index 988d8fc..5d83fef 100644 --- a/AdvancedSharpAdbClient/Models/Enums/DeviceState.cs +++ b/AdvancedSharpAdbClient/Models/Enums/DeviceState.cs @@ -10,8 +10,8 @@ namespace AdvancedSharpAdbClient.Models /// Defines the state of an Android device connected to the Android Debug Bridge. /// /// - /// - /// + /// + /// /// [Flags] public enum DeviceState diff --git a/AdvancedSharpAdbClient/Models/Enums/SyncFlags.cs b/AdvancedSharpAdbClient/Models/Enums/SyncFlags.cs new file mode 100644 index 0000000..487db23 --- /dev/null +++ b/AdvancedSharpAdbClient/Models/Enums/SyncFlags.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; + +namespace AdvancedSharpAdbClient.Models +{ + /// + /// The sync flags for . + /// + [Flags] + public enum SyncFlags : uint + { + /// + /// No flags set. + /// + None = 0, + + /// + /// Use Brotli compression. + /// + Brotli = 1, + + /// + /// Use LZ4 compression. + /// + LZ4 = 2, + + /// + /// Use Zstd compression. + /// + Zstd = 4, + + /// + /// Perform a dry run. + /// + DryRun = 0x8000_0000U + } +} diff --git a/AdvancedSharpAdbClient/Models/Enums/UnixErrorCode.cs b/AdvancedSharpAdbClient/Models/Enums/UnixErrorCode.cs new file mode 100644 index 0000000..ff0b04e --- /dev/null +++ b/AdvancedSharpAdbClient/Models/Enums/UnixErrorCode.cs @@ -0,0 +1,513 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +namespace AdvancedSharpAdbClient.Models +{ + /// + /// Describes the standard Unix error codes. + /// + /// + public enum UnixErrorCode + { + /// + /// Specifies the default value. + /// + Default = 0, + + /// + /// Operation not permitted. + /// + EPERM = 1, + + /// + /// No such file or directory. + /// + ENOENT = 2, + + /// + /// No such process. + /// + ESRCH = 3, + + /// + /// Interrupted system call. + /// + EINTR = 4, + + /// + /// Input/output error. + /// + EIO = 5, + + /// + /// Device not configured. + /// + ENXIO = 6, + + /// + /// Argument list too long. + /// + E2BIG = 7, + + /// + /// Exec format error. + /// + ENOEXEC = 8, + + /// + /// Bad file descriptor. + /// + EBADF = 9, + + /// + /// No child processes. + /// + ECHILD = 10, + + /// + /// Resource deadlock avoided. + /// + EDEADLK = 11, + + /// + /// Cannot allocate memory. + /// + ENOMEM = 12, + + /// + /// Permission denied. + /// + EACCES = 13, + + /// + /// Bad address. + /// + EFAULT = 14, + + /// + /// Block device required. + /// + ENOTBLK = 15, + + /// + /// Device busy. + /// + EBUSY = 16, + + /// + /// File exists. + /// + EEXIST = 17, + + /// + /// Cross-device link. + /// + EXDEV = 18, + + /// + /// Operation not supported by device. + /// + ENODEV = 19, + + /// + /// Not a directory. + /// + ENOTDIR = 20, + + /// + /// Is a directory. + /// + EISDIR = 21, + + /// + /// Invalid argument. + /// + EINVAL = 22, + + /// + /// Too many open files in system. + /// + ENFILE = 23, + + /// + /// Too many open files. + /// + EMFILE = 24, + + /// + /// Inappropriate ioctl for device. + /// + ENOTTY = 25, + + /// + /// Text file busy. + /// + ETXTBSY = 26, + + /// + /// File too large. + /// + EFBIG = 27, + + /// + /// No space left on device. + /// + ENOSPC = 28, + + /// + /// Illegal seek. + /// + ESPIPE = 29, + + /// + /// Read-only file system. + /// + EROFS = 30, + + /// + /// Too many links. + /// + EMLINK = 31, + + /// + /// Broken pipe. + /// + EPIPE = 32, + + /// + /// Numerical argument out of domain. + /// + EDOM = 33, + + /// + /// Result too large. + /// + ERANGE = 34, + + /// + /// Resource temporarily unavailable. + /// + EAGAIN = 35, + + /// + /// Operation would block. + /// + EWOULDBLOCK = EAGAIN, + + /// + /// Operation now in progress. + /// + EINPROGRESS = 36, + + /// + /// Operation already in progress. + /// + EALREADY = 37, + + /// + /// Socket operation on non-socket. + /// + ENOTSOCK = 38, + + /// + /// Destination address required. + /// + EDESTADDRREQ = 39, + + /// + /// Message too long. + /// + EMSGSIZE = 40, + + /// + /// Protocol wrong type for socket. + /// + EPROTOTYPE = 41, + + /// + /// Protocol not available. + /// + ENOPROTOOPT = 42, + + /// + /// Protocol not supported. + /// + EPROTONOSUPPORT = 43, + + /// + /// Socket type not supported. + /// + ESOCKTNOSUPPORT = 44, + + /// + /// Operation not supported. + /// + EOPNOTSUPP = 45, + + /// + /// Protocol family not supported. + /// + EPFNOSUPPORT = 46, + + /// + /// Address family not supported by protocol family. + /// + EAFNOSUPPORT = 47, + + /// + /// Address already in use. + /// + EADDRINUSE = 48, + + /// + /// Can't assign requested address. + /// + EADDRNOTAVAIL = 49, + + /// + /// Network is down. + /// + ENETDOWN = 50, + + /// + /// Network is unreachable. + /// + ENETUNREACH = 51, + + /// + /// Network dropped connection on reset. + /// + ENETRESET = 52, + + /// + /// Software caused connection abort. + /// + ECONNABORTED = 53, + + /// + /// Connection reset by peer. + /// + ECONNRESET = 54, + + /// + /// No buffer space available. + /// + ENOBUFS = 55, + + /// + /// Socket is already connected. + /// + EISCONN = 56, + + /// + /// Socket is not connected. + /// + ENOTCONN = 57, + + /// + /// Can't send after socket shutdown. + /// + ESHUTDOWN = 58, + + /// + /// Too many references: can't splice. + /// + ETOOMANYREFS = 59, + + /// + /// Operation timed out. + /// + ETIMEDOUT = 60, + + /// + /// Connection refused. + /// + ECONNREFUSED = 61, + + /// + /// Too many levels of symbolic links. + /// + ELOOP = 62, + + /// + /// File name too long. + /// + ENAMETOOLONG = 63, + + /// + /// Host is down. + /// + EHOSTDOWN = 64, + + /// + /// No route to host. + /// + EHOSTUNREACH = 65, + + /// + /// Directory not empty. + /// + ENOTEMPTY = 66, + + /// + /// Too many processes. + /// + EPROCLIM = 67, + + /// + /// Too many users. + /// + EUSERS = 68, + + /// + /// Disk quota exceeded. + /// + EDQUOT = 69, + + /// + /// Stale NFS file handle. + /// + ESTALE = 70, + + /// + /// Too many levels of remote in path. + /// + EREMOTE = 71, + + /// + /// RPC struct is bad. + /// + EBADRPC = 72, + + /// + /// RPC version wrong. + /// + ERPCMISMATCH = 73, + + /// + /// RPC program not available. + /// + EPROGUNAVAIL = 74, + + /// + /// Program version wrong. + /// + EPROGMISMATCH = 75, + + /// + /// Bad procedure for program. + /// + EPROCUNAVAIL = 76, + + /// + /// No locks available. + /// + ENOLCK = 77, + + /// + /// Function not implemented. + /// + ENOSYS = 78, + + /// + /// Inappropriate file type or format. + /// + EFTYPE = 79, + + /// + /// Authentication error. + /// + EAUTH = 80, + + /// + /// Need authenticator. + /// + ENEEDAUTH = 81, + + /// + /// IPsec processing failure. + /// + EIPSEC = 82, + + /// + /// Attribute not found. + /// + ENOATTR = 83, + + /// + /// Illegal byte sequence. + /// + EILSEQ = 84, + + /// + /// No medium found. + /// + ENOMEDIUM = 85, + + /// + /// Wrong medium type. + /// + EMEDIUMTYPE = 86, + + /// + /// Value too large to be stored in data type. + /// + EOVERFLOW = 87, + + /// + /// Operation canceled. + /// + ECANCELED = 88, + + /// + /// Identifier removed. + /// + EIDRM = 89, + + /// + /// No message of desired type. + /// + ENOMSG = 90, + + /// + /// Not supported. + /// + ENOTSUP = 91, + + /// + /// Bad message. + /// + EBADMSG = 92, + + /// + /// State not recoverable. + /// + ENOTRECOVERABLE = 93, + + /// + /// Previous owner died. + /// + EOWNERDEAD = 94, + + /// + /// Protocol error. + /// + EPROTO = 95, + + /// + /// Must be equal largest errno. + /// + ELAST = EPROTO, + + /// + /// Restart syscall. + /// + ERESTART = -1, + + /// + /// Don't modify regs, just return. + /// + EJUSTRETURN = -2 + } +} diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.Base.cs b/AdvancedSharpAdbClient/Models/FileStatistics.Base.cs new file mode 100644 index 0000000..49d17ca --- /dev/null +++ b/AdvancedSharpAdbClient/Models/FileStatistics.Base.cs @@ -0,0 +1,97 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace AdvancedSharpAdbClient.Models +{ + /// + /// Contains information about a file on the remote device. + /// + /// The type of the data for file. + /// The type of the derived class. + /// The for the data of file. + public abstract class FileStatisticsBase(in T data) : IEquatable, IEquatable> +#if NET7_0_OR_GREATER + , IEqualityOperators, IEqualityOperators, bool> +#endif + where T : unmanaged, IEquatable +#if NET7_0_OR_GREATER + , IEqualityOperators +#endif + where TSelf : FileStatisticsBase + { + /// + /// The for the data of file. + /// + protected readonly T data = data; + + /// + /// Gets the path of the file. + /// + public string Path { get; set; } = string.Empty; + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is FileStatisticsBase other && Equals(other); + + /// + public bool Equals([NotNullWhen(true)] TSelf? other) => other is FileStatisticsBase obj && Equals(obj); + + /// + public bool Equals([NotNullWhen(true)] FileStatisticsBase? other) => + (object)this == other || + (other is not null + && Path == other.Path +#if NET7_0_OR_GREATER + && data == other.data); +#else + && data.Equals(other.data)); +#endif + + /// + /// Tests whether two objects are equally. + /// + /// The structure that is to the left of the equality operator. + /// The structure that is to the right of the equality operator. + /// This operator returns if the two structures are equally; otherwise . + public static bool operator ==(TSelf? left, FileStatisticsBase? right) => EqualityComparer?>.Default.Equals(left, right); + + /// + /// Tests whether two objects are equally. + /// + /// The structure that is to the left of the equality operator. + /// The structure that is to the right of the equality operator. + /// This operator returns if the two structures are equally; otherwise . + public static bool operator ==(FileStatisticsBase? left, FileStatisticsBase? right) => EqualityComparer?>.Default.Equals(left, right); + + /// + /// Tests whether two objects are different. + /// + /// The structure that is to the left of the inequality operator. + /// The structure that is to the right of the inequality operator. + /// This operator returns if the two structures are unequally; otherwise . + public static bool operator !=(TSelf? left, FileStatisticsBase? right) => !(left == right); + + /// + /// Tests whether two objects are different. + /// + /// The structure that is to the left of the inequality operator. + /// The structure that is to the right of the inequality operator. + /// This operator returns if the two structures are unequally; otherwise . + public static bool operator !=(FileStatisticsBase? left, FileStatisticsBase? right) => !(left == right); + +#if NET7_0_OR_GREATER + /// + static bool IEqualityOperators.operator ==(TSelf? left, TSelf? right) => EqualityComparer.Default.Equals(left, right); + + /// + static bool IEqualityOperators.operator !=(TSelf? left, TSelf? right) => !EqualityComparer.Default.Equals(left, right); +#endif + + /// + public override int GetHashCode() => HashCode.Combine(Path, data); + } +} diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs b/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs index 82697c4..a6264fb 100644 --- a/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs +++ b/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs @@ -5,122 +5,100 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace AdvancedSharpAdbClient.Models { /// /// Contains information about a file on the remote device (v2). /// - /// -#if HAS_BUFFERS - [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsV2Creator))] -#endif + /// [DebuggerDisplay($"{{{nameof(GetType)}().{nameof(Type.ToString)}(),nq}} \\{{ {nameof(Path)} = {{{nameof(Path)}}}, {nameof(FileMode)} = {{{nameof(FileMode)}}}, {nameof(Size)} = {{{nameof(Size)}}}, {nameof(ModifiedTime)} = {{{nameof(ModifiedTime)}}} }}")] - public struct FileStatisticsV2(FileStatisticsDataV2 data) : IEquatable + public class FileStatisticsEx(FileStatisticsDataEx data) : FileStatisticsBase(data), IFileStatistics #if NET7_0_OR_GREATER - , IEqualityOperators + , IEqualityOperators #endif { - /// - /// The data of the file. - /// - private readonly FileStatisticsDataV2 data = data; - - /// - /// Gets the path of the file. - /// - public string Path { get; set; } = string.Empty; - /// /// Gets the error code associated with the current operation. /// - public readonly uint Error => data.Error; + public UnixErrorCode Error => (UnixErrorCode)data.Error; /// /// Gets the device identifier associated with this instance. /// - public readonly ulong Device => data.Device; + public ulong Device => data.Device; /// /// Gets the index node identifier associated with this entry. /// - public readonly ulong IndexNode => data.IndexNode; + public ulong IndexNode => data.IndexNode; /// /// Gets the attributes of the file. /// - public readonly UnixFileStatus FileMode => (UnixFileStatus)data.Mode; + public UnixFileStatus FileMode => (UnixFileStatus)data.Mode; /// /// Gets the number of hard links to the file or directory. /// - public readonly uint LinkCount => data.LinkCount; + public uint LinkCount => data.LinkCount; /// /// Gets the unique identifier of the user associated with this instance. /// - public readonly uint UserId => data.UserId; + public uint UserId => data.UserId; /// /// Gets the unique identifier for the group. /// - public readonly uint GroupId => data.GroupId; + public uint GroupId => data.GroupId; /// /// Gets the size, in bytes. /// - public readonly ulong Size => data.Size; + public ulong Size => data.Size; /// /// Gets the last access time. /// - public readonly DateTimeOffset AccessTime => DateTimeOffset.FromUnixTimeSeconds(data.AccessTime); + public DateTimeOffset AccessTime => DateTimeOffset.FromUnixTimeSeconds(data.AccessTime); /// /// Gets the time indicating when the item was last modified. /// - public readonly DateTimeOffset ModifiedTime => DateTimeOffset.FromUnixTimeSeconds(data.ModifiedTime); + public DateTimeOffset ModifiedTime => DateTimeOffset.FromUnixTimeSeconds(data.ModifiedTime); /// /// Gets the time indicating when the last change occurred. /// - public readonly DateTimeOffset ChangedTime => DateTimeOffset.FromUnixTimeSeconds(data.ChangedTime); + public DateTimeOffset ChangedTime => DateTimeOffset.FromUnixTimeSeconds(data.ChangedTime); -#if HAS_BUFFERS - /// - /// Returns an enumerator that iterates through the . - /// - /// An enumerator that can be used to iterate through the . - public readonly ReadOnlySpan.Enumerator GetEnumerator() => data.GetEnumerator(); -#endif + /// + DateTimeOffset IFileStatistics.Time => ModifiedTime; /// - public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is FileStatisticsV2 other && Equals(other); + public override string ToString() => string.Join("\t", FileMode.ToPermissionCode()!, Size, ModifiedTime, Path!); /// - public readonly bool Equals(FileStatisticsV2 other) => Path == other.Path && data == other.data; + public override bool Equals([NotNullWhen(true)] object? obj) => base.Equals(obj); /// - /// Tests whether two objects are equally. + /// Tests whether two objects are equally. /// - /// The structure that is to the left of the equality operator. - /// The structure that is to the right of the equality operator. - /// This operator returns if the two structures are equally; otherwise . - public static bool operator ==(FileStatisticsV2 left, FileStatisticsV2 right) => left.Equals(right); + /// The structure that is to the left of the equality operator. + /// The structure that is to the right of the equality operator. + /// This operator returns if the two structures are equally; otherwise . + public static bool operator ==(FileStatisticsEx? left, FileStatisticsEx? right) => left == (right as FileStatisticsBase); /// - /// Tests whether two objects are different. + /// Tests whether two objects are different. /// - /// The structure that is to the left of the inequality operator. - /// The structure that is to the right of the inequality operator. - /// This operator returns if the two structures are unequally; otherwise . - public static bool operator !=(FileStatisticsV2 left, FileStatisticsV2 right) => !left.Equals(right); - - /// - public override readonly int GetHashCode() => HashCode.Combine(Path, data); + /// The structure that is to the left of the inequality operator. + /// The structure that is to the right of the inequality operator. + /// This operator returns if the two structures are unequally; otherwise . + public static bool operator !=(FileStatisticsEx? left, FileStatisticsEx? right) => !(left == right); /// - public override readonly string ToString() => string.Join('\t', FileMode.ToPermissionCode()!, Size, ModifiedTime, Path!); + public override int GetHashCode() => base.GetHashCode(); } } diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.cs b/AdvancedSharpAdbClient/Models/FileStatistics.cs index 970f434..121c3aa 100644 --- a/AdvancedSharpAdbClient/Models/FileStatistics.cs +++ b/AdvancedSharpAdbClient/Models/FileStatistics.cs @@ -3,10 +3,8 @@ // using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace AdvancedSharpAdbClient.Models { @@ -14,72 +12,53 @@ namespace AdvancedSharpAdbClient.Models /// Contains information about a file on the remote device. /// /// The data of the file. -#if HAS_BUFFERS - [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsCreator))] -#endif [DebuggerDisplay($"{{{nameof(GetType)}().{nameof(Type.ToString)}(),nq}} \\{{ {nameof(Path)} = {{{nameof(Path)}}}, {nameof(FileMode)} = {{{nameof(FileMode)}}}, {nameof(Size)} = {{{nameof(Size)}}}, {nameof(Time)} = {{{nameof(Time)}}} }}")] - public struct FileStatistics(FileStatisticsData data) : IEquatable + public sealed class FileStatistics(in FileStatisticsData data) : FileStatisticsBase(data), IFileStatistics #if NET7_0_OR_GREATER , IEqualityOperators #endif { - /// - /// The data of the file. - /// - private readonly FileStatisticsData data = data; - - /// - /// Gets the path of the file. - /// - public string Path { get; set; } = string.Empty; - /// /// Gets the attributes of the file. /// - public readonly UnixFileStatus FileMode => (UnixFileStatus)data.Mode; + public UnixFileStatus FileMode => (UnixFileStatus)data.Mode; /// /// Gets the total file size, in bytes. /// - public readonly uint Size => data.Size; + public uint Size => data.Size; /// /// Gets the time of last modification. /// - public readonly DateTimeOffset Time => DateTimeOffset.FromUnixTimeSeconds(data.Time); - - /// - /// Returns an enumerator that iterates through the . - /// - /// An enumerator that can be used to iterate through the . - public readonly IEnumerator GetEnumerator() => data.GetEnumerator(); + public DateTimeOffset Time => DateTimeOffset.FromUnixTimeSeconds(data.Time); /// - public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is FileStatistics other && Equals(other); + ulong IFileStatistics.Size => Size; /// - public readonly bool Equals(FileStatistics other) => Path == other.Path && data == other.data; + public override bool Equals([NotNullWhen(true)] object? obj) => base.Equals(obj); /// - /// Tests whether two objects are equally. + /// Tests whether two objects are equally. /// - /// The structure that is to the left of the equality operator. - /// The structure that is to the right of the equality operator. + /// The structure that is to the left of the equality operator. + /// The structure that is to the right of the equality operator. /// This operator returns if the two structures are equally; otherwise . - public static bool operator ==(FileStatistics left, FileStatistics right) => left.Equals(right); + public static bool operator ==(FileStatistics? left, FileStatistics? right) => left == (right as FileStatisticsBase); /// - /// Tests whether two objects are different. + /// Tests whether two objects are different. /// - /// The structure that is to the left of the inequality operator. - /// The structure that is to the right of the inequality operator. + /// The structure that is to the left of the inequality operator. + /// The structure that is to the right of the inequality operator. /// This operator returns if the two structures are unequally; otherwise . - public static bool operator !=(FileStatistics left, FileStatistics right) => !left.Equals(right); + public static bool operator !=(FileStatistics? left, FileStatistics? right) => !(left == right); /// - public override readonly int GetHashCode() => HashCode.Combine(Path, data); + public override int GetHashCode() => base.GetHashCode(); /// - public override readonly string ToString() => string.Join('\t', FileMode.ToPermissionCode()!, Size, Time, Path!); + public override string ToString() => string.Join("\t", FileMode.ToPermissionCode()!, Size, Time, Path!); } } diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs index fa11ea1..bb1905e 100644 --- a/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs @@ -23,19 +23,19 @@ namespace AdvancedSharpAdbClient.Models /// The last access time, in Unix timestamp format, for the associated resource. /// The timestamp indicating when the item was last modified. /// The timestamp indicating when the last change occurred. - /// + /// #if HAS_BUFFERS [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsDataV2Creator))] #endif [DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")] [StructLayout(LayoutKind.Explicit)] - public readonly record struct FileStatisticsDataV2(uint Error, ulong Device, ulong IndexNode, uint Mode, uint LinkCount, uint UserId, uint GroupId, ulong Size, long AccessTime, long ModifiedTime, long ChangedTime) + public readonly record struct FileStatisticsDataEx(uint Error, ulong Device, ulong IndexNode, uint Mode, uint LinkCount, uint UserId, uint GroupId, ulong Size, long AccessTime, long ModifiedTime, long ChangedTime) #if NET7_0_OR_GREATER - : IEqualityOperators + : IEqualityOperators #endif { /// - /// The length of in bytes. + /// The length of in bytes. /// public const int Length = 5 * sizeof(uint) + 3 * sizeof(ulong) + 3 * sizeof(long); @@ -113,7 +113,7 @@ public readonly record struct FileStatisticsDataV2(uint Error, ulong Device, ulo public unsafe byte[] ToArray() { byte[] array = new byte[Length]; - fixed (FileStatisticsDataV2* pData = &this) + fixed (FileStatisticsDataEx* pData = &this) { Marshal.Copy((nint)pData, array, 0, Length); return array; @@ -127,7 +127,7 @@ public unsafe byte[] ToArray() /// A that provides a read-only view of the bytes in this instance. public unsafe ReadOnlySpan AsSpan() { - fixed (FileStatisticsDataV2* pData = &this) + fixed (FileStatisticsDataEx* pData = &this) { return new ReadOnlySpan(pData, Length); } diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs index 13d5db5..0350a0e 100644 --- a/AdvancedSharpAdbClient/Models/FileStatisticsData.cs +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs @@ -17,7 +17,7 @@ namespace AdvancedSharpAdbClient.Models /// The mode of the file. /// The total file size, in bytes. /// The time of last modification. - /// + /// #if HAS_BUFFERS [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsDataCreator))] #endif diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index 14e9de4..7b29cf3 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -36,7 +36,7 @@ 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) + public virtual async Task PushAsync(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action? callback = null, bool useV2 = false, 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."); } @@ -52,8 +52,17 @@ public virtual async Task PushAsync(Stream stream, string remotePath, UnixFileSt try { - await Socket.SendSyncRequestAsync(SyncCommand.SEND, remotePath, permission, cancellationToken).ConfigureAwait(false); - IsProcessing = true; + if (useV2) + { + await Socket.SendSyncRequestAsync(SyncCommand.SND2, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + await Socket.SendSyncRequestAsync(SyncCommand.SND2, permission, SyncFlags.None, cancellationToken).ConfigureAwait(false); + } + else + { + await Socket.SendSyncRequestAsync(SyncCommand.SEND, remotePath, permission, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + } // create the buffer used to read. // we read max SYNC_DATA_MAX. @@ -135,9 +144,7 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi IsProcessing = false; } } - - private Task GetFileSizeAsync(string remoteFilePath, bool useV2 = false, CancellationToken cancellationToken = default) => useV2 ? StatV2Async(remoteFilePath, cancellationToken).ContinueWith(x => x.Result.Size) : StatAsync(remoteFilePath, cancellationToken).ContinueWith(x => (ulong)x.Result.Size); - + /// public virtual async Task PullAsync(string remotePath, Stream stream, Action? callback = null, bool useV2 = false, CancellationToken cancellationToken = default) { @@ -149,15 +156,24 @@ public virtual async Task PullAsync(string remotePath, Stream stream, Action x.Result.Size).ConfigureAwait(false); ulong totalBytesRead = 0; byte[] buffer = new byte[MaxBufferSize]; try { - await Socket.SendSyncRequestAsync(SyncCommand.RECV, remotePath, cancellationToken).ConfigureAwait(false); - IsProcessing = true; + if (useV2) + { + await Socket.SendSyncRequestAsync(SyncCommand.RCV2, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + await Socket.SendSyncRequestAsync(SyncCommand.RCV2, (int)SyncFlags.None, cancellationToken).ConfigureAwait(false); + } + else + { + await Socket.SendSyncRequestAsync(SyncCommand.RECV, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + } while (true) { @@ -216,7 +232,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) + public virtual async Task PushAsync(IInputStream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action? progress = null, bool useV2 = false, 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."); } @@ -232,8 +248,17 @@ public virtual async Task PushAsync(IInputStream stream, string remotePath, Unix try { - await Socket.SendSyncRequestAsync(SyncCommand.SEND, remotePath, permission, cancellationToken).ConfigureAwait(false); - IsProcessing = true; + if (useV2) + { + await Socket.SendSyncRequestAsync(SyncCommand.SND2, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + await Socket.SendSyncRequestAsync(SyncCommand.SND2, permission, SyncFlags.None, cancellationToken).ConfigureAwait(false); + } + else + { + await Socket.SendSyncRequestAsync(SyncCommand.SEND, remotePath, permission, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + } // create the buffer used to read. // we read max SYNC_DATA_MAX. @@ -335,15 +360,24 @@ public virtual async Task PullAsync(string remotePath, IOutputStream stream, Act if (IsOutdate) { await ReopenAsync(cancellationToken).ConfigureAwait(false); } // Gets file information, including the file size, used to calculate the total amount of bytes to receive. - ulong totalBytesToProcess = await GetFileSizeAsync(remotePath, useV2, cancellationToken).ConfigureAwait(false); + ulong totalBytesToProcess = await this.StatAsync(remotePath, useV2, cancellationToken).ContinueWith(x => x.Result.Size).ConfigureAwait(false); ulong totalBytesRead = 0; byte[] buffer = new byte[MaxBufferSize]; try { - await Socket.SendSyncRequestAsync(SyncCommand.RECV, remotePath, cancellationToken).ConfigureAwait(false); - IsProcessing = true; + if (useV2) + { + await Socket.SendSyncRequestAsync(SyncCommand.RCV2, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + await Socket.SendSyncRequestAsync(SyncCommand.RCV2, (int)SyncFlags.None, cancellationToken).ConfigureAwait(false); + } + else + { + await Socket.SendSyncRequestAsync(SyncCommand.RECV, remotePath, cancellationToken).ConfigureAwait(false); + IsProcessing = true; + } while (true) { @@ -418,7 +452,7 @@ public async Task StatAsync(string remotePath, CancellationToken } /// - public async Task StatV2Async(string remotePath, CancellationToken cancellationToken = default) + public async Task StatExAsync(string remotePath, CancellationToken cancellationToken = default) { // create the stat request message. await Socket.SendSyncRequestAsync(SyncCommand.STA2, remotePath, cancellationToken).ConfigureAwait(false); @@ -429,7 +463,7 @@ public async Task StatV2Async(string remotePath, CancellationT throw new AdbException($"The server returned an invalid sync response {response}."); } - FileStatisticsV2 value = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); + FileStatisticsEx value = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); value.Path = remotePath; return value; @@ -486,14 +520,14 @@ public async Task> GetDirectoryListingAsync(string remotePa } /// - public async Task> GetDirectoryListingV2Async(string remotePath, CancellationToken cancellationToken = default) + public async Task> GetDirectoryListingExAsync(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; start: - List value = []; + List value = []; try { @@ -519,7 +553,7 @@ public async Task> GetDirectoryListingV2Async(string remo throw new AdbException($"The server returned an invalid sync response {response}."); } - FileStatisticsV2 entry = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); + FileStatisticsEx entry = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); entry.Path = await Socket.ReadSyncStringAsync(cancellationToken).ConfigureAwait(false); value.Add(entry); @@ -586,7 +620,7 @@ public async IAsyncEnumerable GetDirectoryAsyncListing(string re } /// - public async IAsyncEnumerable GetDirectoryAsyncListingV2(string remotePath, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public async IAsyncEnumerable GetDirectoryAsyncListingEx(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); } @@ -617,7 +651,7 @@ public async IAsyncEnumerable GetDirectoryAsyncListingV2(strin throw new AdbException($"The server returned an invalid sync response {response}."); } - FileStatisticsV2 entry = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); + FileStatisticsEx entry = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); entry.Path = await Socket.ReadSyncStringAsync(cancellationToken).ConfigureAwait(false); yield return entry; @@ -664,22 +698,22 @@ protected async Task ReadStatisticsAsync(CancellationToken cance /// Asynchronously reads the statistics of a file from the socket. /// /// A that can be used to cancel the task. - /// A which returns a object that contains information about the file. - protected async Task ReadStatisticsV2Async(CancellationToken cancellationToken = default) + /// A which returns a object that contains information about the file. + protected async Task ReadStatisticsV2Async(CancellationToken cancellationToken = default) { #if COMP_NETSTANDARD2_1 - Memory statResult = new byte[FileStatisticsDataV2.Length]; + Memory statResult = new byte[FileStatisticsDataEx.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); return EnumerableBuilder.FileStatisticsV2Creator(statResult.Span); #else - byte[] statResult = new byte[FileStatisticsDataV2.Length]; + byte[] statResult = new byte[FileStatisticsDataEx.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); unsafe { fixed (byte* p = statResult) { - FileStatisticsDataV2* data = (FileStatisticsDataV2*)p; - return new FileStatisticsV2(*data); + FileStatisticsDataEx* data = (FileStatisticsDataEx*)p; + return new FileStatisticsEx(*data); } } #endif diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index d0d3b23..18dfc18 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Net; +using System.Security; namespace AdvancedSharpAdbClient { @@ -145,7 +146,7 @@ public void Reopen() } /// - public virtual void Push(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action? callback = null, in bool isCancelled = false) + public virtual void Push(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, Action? callback = null, bool useV2 = false, 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."); } @@ -161,8 +162,17 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis try { - Socket.SendSyncRequest(SyncCommand.SEND, remotePath, permission); - IsProcessing = true; + if (useV2) + { + Socket.SendSyncRequest(SyncCommand.SND2, remotePath); + IsProcessing = true; + Socket.SendSyncRequest(SyncCommand.SND2, permission, SyncFlags.None); + } + else + { + Socket.SendSyncRequest(SyncCommand.SEND, remotePath, permission); + IsProcessing = true; + } // create the buffer used to read. // we read max SYNC_DATA_MAX. @@ -242,8 +252,6 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis } } - private ulong GetFileSize(string remoteFilePath, bool useV2 = false) => useV2 ? StatV2(remoteFilePath).Size : Stat(remoteFilePath).Size; - /// public virtual void Pull(string remoteFilePath, Stream stream, Action? callback = null, bool useV2 = false, in bool isCancelled = false) { @@ -255,15 +263,24 @@ public virtual void Pull(string remoteFilePath, Stream stream, Action - public FileStatisticsV2 StatV2(string remotePath) + public FileStatisticsEx StatEx(string remotePath) { // create the stat request message. Socket.SendSyncRequest(SyncCommand.STA2, remotePath); @@ -349,7 +366,7 @@ public FileStatisticsV2 StatV2(string remotePath) throw new AdbException($"The server returned an invalid sync response {response}."); } - FileStatisticsV2 value = ReadStatisticsV2(); + FileStatisticsEx value = ReadStatisticsV2(); value.Path = remotePath; return value; @@ -405,7 +422,7 @@ public IEnumerable GetDirectoryListing(string remotePath) } /// - public IEnumerable GetDirectoryListingV2(string remotePath) + public IEnumerable GetDirectoryListingEx(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(); } @@ -436,7 +453,7 @@ public IEnumerable GetDirectoryListingV2(string remotePath) throw new AdbException($"The server returned an invalid sync response {response}."); } - FileStatisticsV2 entry = ReadStatisticsV2(); + FileStatisticsEx entry = ReadStatisticsV2(); entry.Path = Socket.ReadSyncString(); yield return entry; @@ -518,22 +535,22 @@ protected FileStatistics ReadStatistics() /// /// Reads the statistics of a file from the socket (v2). /// - /// A object that contains information about the file. - protected FileStatisticsV2 ReadStatisticsV2() + /// A object that contains information about the file. + protected FileStatisticsEx ReadStatisticsV2() { #if COMP_NETSTANDARD2_1 - Span statResult = stackalloc byte[FileStatisticsDataV2.Length]; + Span statResult = stackalloc byte[FileStatisticsDataEx.Length]; _ = Socket.Read(statResult); return EnumerableBuilder.FileStatisticsV2Creator(statResult); #else - byte[] statResult = new byte[FileStatisticsDataV2.Length]; + byte[] statResult = new byte[FileStatisticsDataEx.Length]; _ = Socket.Read(statResult); unsafe { fixed (byte* p = statResult) { - FileStatisticsDataV2* data = (FileStatisticsDataV2*)p; - return new FileStatisticsV2(*data); + FileStatisticsDataEx* data = (FileStatisticsDataEx*)p; + return new FileStatisticsEx(*data); } } #endif From 5ec1ea6d7cfd7b199ffe1e1f87895d414afe19fd Mon Sep 17 00:00:00 2001 From: where where Date: Sun, 11 Jan 2026 11:47:51 +0800 Subject: [PATCH 4/6] Use Unsafe.As to convert ptr Add ColorDataTest Upgrade to xunit v3 --- .../AdbClientTests.Async.cs | 228 +++++++++++------- .../AdbCommandLineClientTests.Async.cs | 8 +- .../AdbServerTests.Async.cs | 26 +- .../AdbSocketTests.Async.cs | 46 ++-- .../AdvancedSharpAdbClient.Tests.csproj | 2 +- .../DeviceCommands/DeviceClientTexts.Async.cs | 62 ++--- .../DeviceExtensionsTests.Async.cs | 34 +-- .../PackageManagerTests.Async.cs | 27 ++- .../DeviceMonitorTests.Async.cs | 30 ++- .../AdbClientExtensionsTests.Async.cs | 44 ++-- .../Extensions/EnumerableExtensionsTests.cs | 4 +- AdvancedSharpAdbClient.Tests/Logs/LogTests.cs | 12 +- .../Models/FramebufferHeaderTests.cs | 126 ++++++++-- .../Models/FramebufferTests.cs | 6 +- .../Models/ShellStreamTests.cs | 12 +- .../SocketBasedTests.cs | 51 ++-- .../SyncServiceTests.Async.cs | 91 +++---- .../TcpSocketTests.Async.cs | 20 +- .../Extensions/EnumerableBuilder.cs | 43 +--- AdvancedSharpAdbClient/Models/ColorData.cs | 12 +- .../Models/FileStatistics.V2.cs | 2 +- .../Models/FileStatisticsData.V2.cs | 8 +- .../Models/FileStatisticsData.cs | 8 +- .../Models/FramebufferHeader.cs | 24 +- AdvancedSharpAdbClient/Polyfills/Unsafe.cs | 52 ++++ AdvancedSharpAdbClient/SyncService.Async.cs | 26 +- AdvancedSharpAdbClient/SyncService.cs | 27 +-- 27 files changed, 589 insertions(+), 442 deletions(-) create mode 100644 AdvancedSharpAdbClient/Polyfills/Unsafe.cs diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs index ee23851..364d5f7 100644 --- a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs @@ -26,7 +26,8 @@ public async Task GetAdbVersionAsyncTest() OkResponse, responseMessages, requests, - () => TestClient.GetAdbVersionAsync()); + ctx => TestClient.GetAdbVersionAsync(ctx), + TestContext.Current.CancellationToken); // Make sure and the correct value is returned. Assert.Equal(32, version); @@ -44,7 +45,8 @@ await RunTestAsync( NoResponses, NoResponseMessages, requests, - () => TestClient.KillAdbAsync()); + ctx => TestClient.KillAdbAsync(ctx), + TestContext.Current.CancellationToken); } /// @@ -60,7 +62,8 @@ public async Task GetDevicesAsyncTest() OkResponse, responseMessages, requests, - async () => await TestClient.GetDevicesAsync().ToArrayAsync()); + ctx => TestClient.GetDevicesAsync(ctx).ToArrayAsync(), + TestContext.Current.CancellationToken); // Make sure and the correct value is returned. Assert.NotNull(devices); @@ -80,8 +83,9 @@ public async Task GetDevicesAsyncTest() [Fact] public async Task CreateForwardAsyncTest() => await RunCreateForwardAsyncTest( - device => TestClient.CreateForwardAsync(device, "tcp:1", "tcp:2", true), - "tcp:1;tcp:2"); + (device, ctx) => TestClient.CreateForwardAsync(device, "tcp:1", "tcp:2", true, ctx), + "tcp:1;tcp:2", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -89,8 +93,9 @@ await RunCreateForwardAsyncTest( [Fact] public async Task CreateReverseAsyncTest() => await RunCreateReverseAsyncTest( - device => TestClient.CreateReverseForwardAsync(device, "tcp:1", "tcp:2", true), - "tcp:1;tcp:2"); + (device, ctx) => TestClient.CreateReverseForwardAsync(device, "tcp:1", "tcp:2", true, ctx), + "tcp:1;tcp:2", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -98,8 +103,9 @@ await RunCreateReverseAsyncTest( [Fact] public async Task CreateTcpForwardAsyncTest() => await RunCreateForwardAsyncTest( - device => TestClient.CreateForwardAsync(device, 3, 4), - "tcp:3;tcp:4"); + (device, ctx) => TestClient.CreateForwardAsync(device, 3, 4, cancellationToken: ctx), + "tcp:3;tcp:4", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -107,8 +113,9 @@ await RunCreateForwardAsyncTest( [Fact] public async Task CreateSocketForwardAsyncTest() => await RunCreateForwardAsyncTest( - device => TestClient.CreateForwardAsync(device, 5, "/socket/1"), - "tcp:5;local:/socket/1"); + (device, ctx) => TestClient.CreateForwardAsync(device, 5, "/socket/1", cancellationToken: ctx), + "tcp:5;local:/socket/1", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -124,7 +131,8 @@ public async Task CreateDuplicateForwardAsyncTest() responses, NoResponseMessages, requests, - () => TestClient.CreateForwardAsync(Device, "tcp:1", "tcp:2", false))); + ctx => TestClient.CreateForwardAsync(Device, "tcp:1", "tcp:2", false, ctx), + TestContext.Current.CancellationToken)); } /// @@ -139,7 +147,8 @@ await RunTestAsync( OkResponse, NoResponseMessages, requests, - () => TestClient.RemoveForwardAsync(Device, 1)); + ctx => TestClient.RemoveForwardAsync(Device, 1, ctx), + TestContext.Current.CancellationToken); } /// @@ -158,7 +167,8 @@ await RunTestAsync( OkResponses(2), NoResponseMessages, requests, - () => TestClient.RemoveReverseForwardAsync(Device, "localabstract:test")); + ctx => TestClient.RemoveReverseForwardAsync(Device, "localabstract:test", ctx), + TestContext.Current.CancellationToken); } /// @@ -173,7 +183,8 @@ await RunTestAsync( OkResponse, NoResponseMessages, requests, - () => TestClient.RemoveAllForwardsAsync(Device)); + ctx => TestClient.RemoveAllForwardsAsync(Device, ctx), + TestContext.Current.CancellationToken); } /// @@ -192,7 +203,8 @@ await RunTestAsync( OkResponses(2), NoResponseMessages, requests, - () => TestClient.RemoveAllReverseForwardsAsync(Device)); + ctx => TestClient.RemoveAllReverseForwardsAsync(Device, ctx), + TestContext.Current.CancellationToken); } /// @@ -208,7 +220,8 @@ public async Task ListForwardAsyncTest() OkResponse, responseMessages, requests, - async () => await TestClient.ListForwardAsync(Device).ToArrayAsync()); + ctx => TestClient.ListForwardAsync(Device, ctx).ToArrayAsync(), + TestContext.Current.CancellationToken); Assert.NotNull(forwards); Assert.Equal(3, forwards.Length); @@ -235,7 +248,8 @@ public async Task ListReverseForwardAsyncTest() OkResponses(2), responseMessages, requests, - async () => await TestClient.ListReverseForwardAsync(Device).ToArrayAsync()); + ctx => TestClient.ListReverseForwardAsync(Device, ctx).ToArrayAsync(), + TestContext.Current.CancellationToken); Assert.NotNull(forwards); Assert.Equal(3, forwards.Length); @@ -262,7 +276,8 @@ await RunTestAsync( NoResponseMessages, requests, [shellStream], - () => TestClient.ExecuteServerCommandAsync("host", "version", receiver, AdbClient.Encoding)); + ctx => TestClient.ExecuteServerCommandAsync("host", "version", receiver, AdbClient.Encoding, ctx), + TestContext.Current.CancellationToken); string version = receiver.ToString(); Assert.Equal("0020\r\n", receiver.ToString(), ignoreLineEndingDifferences: true); @@ -291,7 +306,8 @@ await RunTestAsync( NoResponseMessages, requests, [shellStream], - () => TestClient.ExecuteRemoteCommandAsync("echo Hello, World", Device, receiver, AdbClient.Encoding)); + ctx => TestClient.ExecuteRemoteCommandAsync("echo Hello, World", Device, receiver, AdbClient.Encoding, ctx), + TestContext.Current.CancellationToken); Assert.Equal("Hello, World\r\n", receiver.ToString(), ignoreLineEndingDifferences: true); } @@ -316,7 +332,8 @@ public async Task ExecuteRemoteCommandAsyncUnresponsiveTest() NoResponseMessages, requests, null, - () => TestClient.ExecuteRemoteCommandAsync("echo Hello, World", Device, receiver, AdbClient.Encoding))); + ctx => TestClient.ExecuteRemoteCommandAsync("echo Hello, World", Device, receiver, AdbClient.Encoding, ctx), + TestContext.Current.CancellationToken)); } /// @@ -338,11 +355,12 @@ public async Task GetFrameBufferAsyncTest() NoSyncRequests, NoSyncResponses, [ - await File.ReadAllBytesAsync("Assets/FramebufferHeader.bin"), - await File.ReadAllBytesAsync("Assets/Framebuffer.bin") + await File.ReadAllBytesAsync("Assets/FramebufferHeader.bin", TestContext.Current.CancellationToken), + await File.ReadAllBytesAsync("Assets/Framebuffer.bin", TestContext.Current.CancellationToken) ], null, - () => TestClient.GetFrameBufferAsync(Device)); + ctx => TestClient.GetFrameBufferAsync(Device, ctx), + TestContext.Current.CancellationToken); Assert.NotNull(framebuffer); Assert.Equal(Device, framebuffer.Device); @@ -409,7 +427,8 @@ await RunTestAsync( NoResponseMessages, requests, [shellStream], - () => TestClient.RunLogServiceAsync(Device, sink, default, LogId.System)); + ctx => TestClient.RunLogServiceAsync(Device, sink, ctx, LogId.System), + TestContext.Current.CancellationToken); Assert.Equal(3, logs.Count); } @@ -436,7 +455,8 @@ public async Task RunLogServiceEnumerableAsyncTest() NoResponseMessages, requests, [shellStream], - async () => await TestClient.RunLogServiceAsync(Device, default, LogId.System).ToListAsync()); + ctx => TestClient.RunLogServiceAsync(Device, ctx, LogId.System).ToListAsync(cancellationToken: ctx).AsTask(), + TestContext.Current.CancellationToken); Assert.Equal(3, logs.Count); } @@ -457,7 +477,8 @@ await RunTestAsync( OkResponses(2), NoResponseMessages, requests, - () => TestClient.RebootAsync(Device)); + ctx => TestClient.RebootAsync(Device, cancellationToken: ctx), + TestContext.Current.CancellationToken); } /// @@ -466,9 +487,10 @@ await RunTestAsync( [Fact] public async Task PairAsyncIPAddressTest() => await RunPairAsyncTest( - () => TestClient.PairAsync(IPAddress.Loopback, "114514"), + ctx => TestClient.PairAsync(IPAddress.Loopback, "114514", cancellationToken: ctx), "127.0.0.1:5555", - "114514"); + "114514", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -476,9 +498,10 @@ await RunPairAsyncTest( [Fact] public async Task PairAsyncDnsEndpointTest() => await RunPairAsyncTest( - () => TestClient.PairAsync(new DnsEndPoint("localhost", 1234), "114514"), + ctx => TestClient.PairAsync(new DnsEndPoint("localhost", 1234), "114514", cancellationToken: ctx), "localhost:1234", - "114514"); + "114514", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -486,9 +509,10 @@ await RunPairAsyncTest( [Fact] public async Task PairAsyncIPEndpointTest() => await RunPairAsyncTest( - () => TestClient.PairAsync(new IPEndPoint(IPAddress.Loopback, 4321), "114514"), + ctx => TestClient.PairAsync(new IPEndPoint(IPAddress.Loopback, 4321), "114514", cancellationToken: ctx), "127.0.0.1:4321", - "114514"); + "114514", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -496,37 +520,38 @@ await RunPairAsyncTest( [Fact] public async Task PairAsyncHostEndpointTest() => await RunPairAsyncTest( - () => TestClient.PairAsync("localhost:9926", "114514"), + ctx => TestClient.PairAsync("localhost:9926", "114514", cancellationToken: ctx), "localhost:9926", - "114514"); + "114514", + TestContext.Current.CancellationToken); /// /// Tests the method. /// [Fact] public async Task PairAsyncIPAddressNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPAddress)null, "114514")); + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPAddress)null, "114514", cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. /// [Fact] public async Task PairAsyncDnsEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((DnsEndPoint)null, "114514")); + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((DnsEndPoint)null, "114514", cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. /// [Fact] public async Task PairAsyncIPEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPEndPoint)null, "114514")); + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPEndPoint)null, "114514", cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. /// [Fact] public async Task PairAsyncHostEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((string)null, "114514")); + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((string)null, "114514", cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. @@ -534,8 +559,9 @@ public async Task PairAsyncHostEndpointNullTest() => [Fact] public async Task ConnectAsyncIPAddressTest() => await RunConnectAsyncTest( - () => TestClient.ConnectAsync(IPAddress.Loopback), - "127.0.0.1:5555"); + ctx => TestClient.ConnectAsync(IPAddress.Loopback, cancellationToken: ctx), + "127.0.0.1:5555", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -543,8 +569,9 @@ await RunConnectAsyncTest( [Fact] public async Task ConnectAsyncDnsEndpointTest() => await RunConnectAsyncTest( - () => TestClient.ConnectAsync(new DnsEndPoint("localhost", 1234)), - "localhost:1234"); + ctx => TestClient.ConnectAsync(new DnsEndPoint("localhost", 1234), cancellationToken: ctx), + "localhost:1234", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -552,8 +579,9 @@ await RunConnectAsyncTest( [Fact] public async Task ConnectAsyncIPEndpointTest() => await RunConnectAsyncTest( - () => TestClient.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 4321)), - "127.0.0.1:4321"); + ctx => TestClient.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 4321), cancellationToken: ctx), + "127.0.0.1:4321", + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -561,36 +589,37 @@ await RunConnectAsyncTest( [Fact] public async Task ConnectAsyncHostEndpointTest() => await RunConnectAsyncTest( - () => TestClient.ConnectAsync("localhost:9926"), - "localhost:9926"); + ctx => TestClient.ConnectAsync("localhost:9926", cancellationToken: ctx), + "localhost:9926", + TestContext.Current.CancellationToken); /// /// Tests the method. /// [Fact] public async Task ConnectAsyncIPAddressNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPAddress)null)); + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPAddress)null, cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. /// [Fact] public async Task ConnectAsyncDnsEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((DnsEndPoint)null)); + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((DnsEndPoint)null, cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. /// [Fact] public async Task ConnectAsyncIPEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPEndPoint)null)); + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPEndPoint)null, cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. /// [Fact] public async Task ConnectAsyncHostEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync(null)); + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync(null, cancellationToken: TestContext.Current.CancellationToken)); /// /// Tests the method. @@ -605,7 +634,8 @@ await RunTestAsync( OkResponse, responseMessages, requests, - () => TestClient.DisconnectAsync(new DnsEndPoint("localhost", 5555))); + ctx => TestClient.DisconnectAsync(new DnsEndPoint("localhost", 5555), cancellationToken: ctx), + TestContext.Current.CancellationToken); } /// @@ -632,7 +662,8 @@ public async Task RootAsyncTest() NoSyncResponses, [expectedData], null, - () => TestClient.RootAsync(Device))); + ctx => TestClient.RootAsync(Device, ctx), + TestContext.Current.CancellationToken)); } /// @@ -659,7 +690,8 @@ public async Task UnrootAsyncTest() NoSyncResponses, [expectedData], null, - () => TestClient.UnrootAsync(Device))); + ctx => TestClient.UnrootAsync(Device, ctx), + TestContext.Current.CancellationToken)); } /// @@ -676,7 +708,7 @@ public async Task InstallAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -701,12 +733,13 @@ await RunTestAsync( NoSyncResponses, [response], applicationDataChunks, - () => TestClient.InstallAsync(Device, stream, + ctx => TestClient.InstallAsync(Device, stream, new InstallProgress( PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, - PackageInstallProgressState.Finished))); + PackageInstallProgressState.Finished), ctx), + TestContext.Current.CancellationToken); } } @@ -724,7 +757,7 @@ public async Task InstallMultipleAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -760,13 +793,14 @@ await RunTestAsync( responses, applicationDataChunks, [sessionStream, commitStream], - () => TestClient.InstallMultipleAsync(Device, [abiStream], "com.google.android.gms", + ctx => TestClient.InstallMultipleAsync(Device, [abiStream], "com.google.android.gms", new InstallProgress( PackageInstallProgressState.Preparing, PackageInstallProgressState.CreateSession, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, - PackageInstallProgressState.Finished))); + PackageInstallProgressState.Finished), ctx), + TestContext.Current.CancellationToken); } /// @@ -783,7 +817,7 @@ public async Task InstallMultipleWithBaseAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -795,7 +829,7 @@ public async Task InstallMultipleWithBaseAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -835,13 +869,14 @@ await RunTestAsync( responses, applicationDataChunks, [sessionStream, commitStream], - () => TestClient.InstallMultipleAsync(Device, baseStream, [abiStream], + ctx => TestClient.InstallMultipleAsync(Device, baseStream, [abiStream], new InstallProgress( PackageInstallProgressState.Preparing, PackageInstallProgressState.CreateSession, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, - PackageInstallProgressState.Finished))); + PackageInstallProgressState.Finished), ctx), + TestContext.Current.CancellationToken); } /// @@ -864,7 +899,8 @@ public async Task InstallCreateAsyncTest() NoResponseMessages, requests, [shellStream], - () => TestClient.InstallCreateAsync(Device, "com.google.android.gms", default)); + ctx => TestClient.InstallCreateAsync(Device, "com.google.android.gms", ctx), + TestContext.Current.CancellationToken); Assert.Equal("936013062", session); } @@ -883,7 +919,7 @@ public async Task InstallWriteAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -908,7 +944,8 @@ await RunTestAsync( NoSyncResponses, [response], applicationDataChunks, - () => TestClient.InstallWriteAsync(Device, stream, "base", "936013062", new InstallProgress())); + ctx => TestClient.InstallWriteAsync(Device, stream, "base", "936013062", new InstallProgress(), cancellationToken: ctx), + TestContext.Current.CancellationToken); } } @@ -932,7 +969,8 @@ await RunTestAsync( NoResponseMessages, requests, [shellStream], - () => TestClient.InstallCommitAsync(Device, "936013062")); + ctx => TestClient.InstallCommitAsync(Device, "936013062", ctx), + TestContext.Current.CancellationToken); } #if WINDOWS10_0_17763_0_OR_GREATER @@ -952,7 +990,7 @@ public async Task InstallWinRTAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -978,12 +1016,13 @@ await RunTestAsync( NoSyncResponses, [response], applicationDataChunks, - () => TestClient.InstallAsync(Device, stream, + ctx => TestClient.InstallAsync(Device, stream, new InstallProgress( PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, - PackageInstallProgressState.Finished))); + PackageInstallProgressState.Finished), ctx), + TestContext.Current.CancellationToken); } } @@ -1003,7 +1042,7 @@ public async Task InstallMultipleWinRTAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -1040,13 +1079,14 @@ await RunTestAsync( responses, applicationDataChunks, [sessionStream, commitStream], - () => TestClient.InstallMultipleAsync(Device, [abiStream], "com.google.android.gms", + ctx => TestClient.InstallMultipleAsync(Device, [abiStream], "com.google.android.gms", new InstallProgress( PackageInstallProgressState.Preparing, PackageInstallProgressState.CreateSession, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, - PackageInstallProgressState.Finished))); + PackageInstallProgressState.Finished), ctx), + TestContext.Current.CancellationToken); } /// @@ -1065,7 +1105,7 @@ public async Task InstallMultipleWinRTWithBaseAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -1077,7 +1117,7 @@ public async Task InstallMultipleWinRTWithBaseAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -1119,13 +1159,14 @@ await RunTestAsync( responses, applicationDataChunks, [sessionStream, commitStream], - () => TestClient.InstallMultipleAsync(Device, baseStream, [abiStream], + ctx => TestClient.InstallMultipleAsync(Device, baseStream, [abiStream], new InstallProgress( PackageInstallProgressState.Preparing, PackageInstallProgressState.CreateSession, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, - PackageInstallProgressState.Finished))); + PackageInstallProgressState.Finished), ctx), + TestContext.Current.CancellationToken); } /// @@ -1144,7 +1185,7 @@ public async Task InstallWriteWinRTAsyncTest() byte[] buffer = new byte[32 * 1024]; int read = 0; - while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), TestContext.Current.CancellationToken)) > 0) { byte[] array = buffer.AsSpan(0, read).ToArray(); applicationDataChunks.Add(array); @@ -1178,7 +1219,8 @@ await RunTestAsync( NoSyncResponses, [response], applicationDataChunks, - () => TestClient.InstallWriteAsync(Device, stream, "base", "936013062", progress)); + ctx => TestClient.InstallWriteAsync(Device, stream, "base", "936013062", progress, cancellationToken: ctx), + TestContext.Current.CancellationToken); } } #endif @@ -1203,7 +1245,8 @@ await RunTestAsync( NoResponseMessages, requests, [shellStream], - () => TestClient.UninstallAsync(Device, "com.android.gallery3d")); + ctx => TestClient.UninstallAsync(Device, "com.android.gallery3d", ctx), + TestContext.Current.CancellationToken); } /// @@ -1219,14 +1262,15 @@ public async Task GetFeatureSetAsyncTest() OkResponse, responses, requests, - async () => await TestClient.GetFeatureSetAsync(Device).ToArrayAsync()); + ctx => TestClient.GetFeatureSetAsync(Device, ctx).ToArrayAsync(), + TestContext.Current.CancellationToken); Assert.Equal(12, features.Length); Assert.Equal("sendrecv_v2_brotli", features.FirstOrDefault()); Assert.Equal("stat_v2", features.LastOrDefault()); } - private Task RunConnectAsyncTest(Func test, string connectString) + private Task RunConnectAsyncTest(Func test, string connectString, CancellationToken cancellationToken = default) { string[] requests = [$"host:connect:{connectString}"]; string[] responseMessages = [$"connected to {connectString}"]; @@ -1235,10 +1279,11 @@ private Task RunConnectAsyncTest(Func test, string connectString) OkResponse, responseMessages, requests, - test); + test, + cancellationToken); } - private Task RunPairAsyncTest(Func test, string connectString, string code) + private Task RunPairAsyncTest(Func test, string connectString, string code, CancellationToken cancellationToken = default) { string[] requests = [$"host:pair:{code}:{connectString}"]; string[] responseMessages = [$"Successfully paired to {connectString} [guid=adb-996198a3-xPRwsQ]"]; @@ -1247,10 +1292,11 @@ private Task RunPairAsyncTest(Func test, string connectString, string code OkResponse, responseMessages, requests, - test); + test, + cancellationToken); } - private Task RunCreateReverseAsyncTest(Func test, string reverseString) + private Task RunCreateReverseAsyncTest(Func test, string reverseString, CancellationToken cancellationToken = default) { string[] requests = [ @@ -1262,10 +1308,11 @@ private Task RunCreateReverseAsyncTest(Func test, string rever OkResponses(3), [null], requests, - () => test(Device)); + ctx => test(Device, ctx), + cancellationToken); } - private Task RunCreateForwardAsyncTest(Func test, string forwardString) + private Task RunCreateForwardAsyncTest(Func test, string forwardString, CancellationToken cancellationToken = default) { string[] requests = [$"host-serial:169.254.109.177:5555:forward:{forwardString}"]; @@ -1273,7 +1320,8 @@ private Task RunCreateForwardAsyncTest(Func test, string forwa OkResponses(2), [null], requests, - () => test(Device)); + ctx => test(Device, ctx), + cancellationToken); } } } diff --git a/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.Async.cs index 4b18526..d26e89c 100644 --- a/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.Async.cs @@ -20,7 +20,7 @@ public async Task GetVersionAsyncTest() { Version = new AdbCommandLineStatus(adbVersion, fileVersion, filePath) }; - AdbCommandLineStatus status = await commandLine.GetVersionAsync(); + AdbCommandLineStatus status = await commandLine.GetVersionAsync(TestContext.Current.CancellationToken); Assert.Equal(adbVersion, status.AdbVersion); Assert.Equal(fileVersion, status.FileVersion); Assert.Equal(filePath, status.FilePath); @@ -36,7 +36,7 @@ public async Task GetVersionAsyncNullTest() { Version = default }; - _ = await Assert.ThrowsAsync(() => commandLine.GetVersionAsync()); + _ = await Assert.ThrowsAsync(() => commandLine.GetVersionAsync(TestContext.Current.CancellationToken)); } /// @@ -49,7 +49,7 @@ public async Task GetOutdatedVersionAsyncTest() { Version = AdbCommandLineStatus.GetVersionFromOutput(["Android Debug Bridge version 1.0.1"]) }; - _ = await Assert.ThrowsAsync(() => commandLine.GetVersionAsync()); + _ = await Assert.ThrowsAsync(() => commandLine.GetVersionAsync(TestContext.Current.CancellationToken)); } /// @@ -60,7 +60,7 @@ public async Task StartServerAsyncTest() { DummyAdbCommandLineClient commandLine = new(); Assert.False(commandLine.ServerStarted); - await commandLine.StartServerAsync(); + await commandLine.StartServerAsync(TestContext.Current.CancellationToken); Assert.True(commandLine.ServerStarted); } } diff --git a/AdvancedSharpAdbClient.Tests/AdbServerTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbServerTests.Async.cs index e8a1c22..0a52901 100644 --- a/AdvancedSharpAdbClient.Tests/AdbServerTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/AdbServerTests.Async.cs @@ -21,7 +21,7 @@ public async Task GetStatusAsyncNotRunningTest() AdbServer adbServer = new(endPoint => adbSocketMock, adbCommandLineClientFactory); - AdbServerStatus status = await adbServer.GetStatusAsync(); + AdbServerStatus status = await adbServer.GetStatusAsync(TestContext.Current.CancellationToken); Assert.False(status.IsRunning); Assert.Null(status.Version); } @@ -35,7 +35,7 @@ public async Task GetStatusAsyncRunningTest() socket.Responses.Enqueue(AdbResponse.OK); socket.ResponseMessages.Enqueue("0020"); - AdbServerStatus status = await adbServer.GetStatusAsync(); + AdbServerStatus status = await adbServer.GetStatusAsync(TestContext.Current.CancellationToken); Assert.Empty(socket.Responses); Assert.Empty(socket.ResponseMessages); @@ -56,7 +56,7 @@ public async Task GetStatusAsyncOtherSocketExceptionTest() adbServer = new AdbServer(adbSocketFactory, adbCommandLineClientFactory); - _ = await Assert.ThrowsAsync(async () => await adbServer.GetStatusAsync()); + _ = await Assert.ThrowsAsync(() => adbServer.GetStatusAsync(TestContext.Current.CancellationToken)); } /// @@ -69,7 +69,7 @@ public async Task GetStatusAsyncOtherExceptionTest() adbServer = new AdbServer(adbSocketFactory, adbCommandLineClientFactory); - _ = await Assert.ThrowsAsync(async () => await adbServer.GetStatusAsync()); + _ = await Assert.ThrowsAsync(() => adbServer.GetStatusAsync(TestContext.Current.CancellationToken)); } /// @@ -82,7 +82,7 @@ public async Task StartServerAsyncAlreadyRunningTest() socket.Responses.Enqueue(AdbResponse.OK); socket.ResponseMessages.Enqueue("0020"); - StartServerResult result = await adbServer.StartServerAsync(null, false); + StartServerResult result = await adbServer.StartServerAsync(null, false, TestContext.Current.CancellationToken); Assert.Equal(StartServerResult.AlreadyRunning, result); @@ -99,7 +99,7 @@ public async Task StartServerAsyncOutdatedRunningNoExecutableTest() socket.Responses.Enqueue(AdbResponse.OK); socket.ResponseMessages.Enqueue("0010"); - AggregateException exception = await Assert.ThrowsAsync(async () => await adbServer.StartServerAsync(null, false)); + AggregateException exception = await Assert.ThrowsAsync(() => adbServer.StartServerAsync(null, false, TestContext.Current.CancellationToken)); Assert.IsType(exception.InnerException); } @@ -113,7 +113,7 @@ public async Task StartServerAsyncNotRunningNoExecutableTest() adbServer = new AdbServer(adbSocketFactory, adbCommandLineClientFactory); - AggregateException exception = await Assert.ThrowsAsync(async () => await adbServer.StartServerAsync(null, false)); + AggregateException exception = await Assert.ThrowsAsync(() => adbServer.StartServerAsync(null, false, TestContext.Current.CancellationToken)); Assert.IsType(exception.InnerException); } @@ -129,7 +129,7 @@ public async Task StartServerAsyncOutdatedRunningTest() commandLineClient.Version = AdbCommandLineStatus.GetVersionFromOutput(["Android Debug Bridge version 1.0.32"]); Assert.False(commandLineClient.ServerStarted); - _ = await adbServer.StartServerAsync(ServerName, false); + _ = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken); Assert.True(commandLineClient.ServerStarted); @@ -152,7 +152,7 @@ public async Task StartServerAsyncNotRunningTest() Assert.False(commandLineClient.ServerStarted); - StartServerResult result = await adbServer.StartServerAsync(ServerName, false); + StartServerResult result = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken); Assert.True(commandLineClient.ServerStarted); } @@ -169,7 +169,7 @@ public async Task StartServerAsyncIntermediateRestartRequestedRunningTest() commandLineClient.Version = AdbCommandLineStatus.GetVersionFromOutput(["Android Debug Bridge version 1.0.32"]); Assert.False(commandLineClient.ServerStarted); - _ = await adbServer.StartServerAsync(ServerName, true); + _ = await adbServer.StartServerAsync(ServerName, true, TestContext.Current.CancellationToken); Assert.True(commandLineClient.ServerStarted); @@ -190,7 +190,7 @@ public async Task StartServerAsyncIntermediateRestartNotRequestedRunningTest() commandLineClient.Version = AdbCommandLineStatus.GetVersionFromOutput(["Android Debug Bridge version 1.0.32"]); Assert.False(commandLineClient.ServerStarted); - _ = await adbServer.StartServerAsync(ServerName, false); + _ = await adbServer.StartServerAsync(ServerName, false, TestContext.Current.CancellationToken); Assert.False(commandLineClient.ServerStarted); @@ -210,7 +210,7 @@ public async Task RestartServerAsyncTest() commandLineClient.Version = AdbCommandLineStatus.GetVersionFromOutput(["Android Debug Bridge version 1.0.32"]); Assert.False(commandLineClient.ServerStarted); - _ = await adbServer.RestartServerAsync(ServerName); + _ = await adbServer.RestartServerAsync(ServerName, TestContext.Current.CancellationToken); Assert.True(commandLineClient.ServerStarted); @@ -225,7 +225,7 @@ public async Task RestartServerAsyncTest() [Fact] public async Task StopServerAsyncTest() { - await adbServer.StopServerAsync(); + await adbServer.StopServerAsync(TestContext.Current.CancellationToken); Assert.Single(socket.Requests); Assert.Equal("host:kill", socket.Requests[0]); diff --git a/AdvancedSharpAdbClient.Tests/AdbSocketTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbSocketTests.Async.cs index c7e07d3..6211bfd 100644 --- a/AdvancedSharpAdbClient.Tests/AdbSocketTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/AdbSocketTests.Async.cs @@ -15,8 +15,9 @@ public partial class AdbSocketTests [Fact] public async Task SendSyncDATARequestAsyncTest() => await RunTestAsync( - socket => socket.SendSyncRequestAsync(SyncCommand.DATA, 2, default), - [(byte)'D', (byte)'A', (byte)'T', (byte)'A', 2, 0, 0, 0]); + (socket, ctx) => socket.SendSyncRequestAsync(SyncCommand.DATA, 2, ctx), + [(byte)'D', (byte)'A', (byte)'T', (byte)'A', 2, 0, 0, 0], + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -24,8 +25,9 @@ await RunTestAsync( [Fact] public async Task SendSyncSENDRequestAsyncTest() => await RunTestAsync( - socket => socket.SendSyncRequestAsync(SyncCommand.SEND, "/test", UnixFileStatus.GroupMask | UnixFileStatus.StickyBit | UnixFileStatus.UserExecute | UnixFileStatus.OtherExecute, default), - [(byte)'S', (byte)'E', (byte)'N', (byte)'D', 9, 0, 0, 0, (byte)'/', (byte)'t', (byte)'e', (byte)'s', (byte)'t', (byte)',', (byte)'6', (byte)'3', (byte)'3']); + (socket, ctx) => socket.SendSyncRequestAsync(SyncCommand.SEND, "/test", UnixFileStatus.GroupMask | UnixFileStatus.StickyBit | UnixFileStatus.UserExecute | UnixFileStatus.OtherExecute, ctx), + [(byte)'S', (byte)'E', (byte)'N', (byte)'D', 9, 0, 0, 0, (byte)'/', (byte)'t', (byte)'e', (byte)'s', (byte)'t', (byte)',', (byte)'6', (byte)'3', (byte)'3'], + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -33,8 +35,9 @@ await RunTestAsync( [Fact] public async Task SendSyncDENTRequestAsyncTest() => await RunTestAsync( - socket => socket.SendSyncRequestAsync(SyncCommand.DENT, "/data", default), - [(byte)'D', (byte)'E', (byte)'N', (byte)'T', 5, 0, 0, 0, (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a']); + (socket, ctx) => socket.SendSyncRequestAsync(SyncCommand.DENT, "/data", ctx), + [(byte)'D', (byte)'E', (byte)'N', (byte)'T', 5, 0, 0, 0, (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a'], + TestContext.Current.CancellationToken); /// /// Tests the method. @@ -42,7 +45,7 @@ await RunTestAsync( [Fact] public async Task SendSyncNullRequestAsyncTest() => _ = await Assert.ThrowsAsync(() => - RunTestAsync(socket => socket.SendSyncRequestAsync(SyncCommand.DATA, null, default), [])); + RunTestAsync((socket, ctx) => socket.SendSyncRequestAsync(SyncCommand.DATA, null, ctx), [], TestContext.Current.CancellationToken)); /// /// Tests the method. @@ -60,7 +63,7 @@ public async Task ReadSyncResponseAsync() tcpSocket.InputStream.Position = 0; - Assert.Equal(SyncCommand.DENT, await socket.ReadSyncResponseAsync()); + Assert.Equal(SyncCommand.DENT, await socket.ReadSyncResponseAsync(TestContext.Current.CancellationToken)); } /// @@ -81,7 +84,7 @@ public async Task ReadStringAsyncTest() tcpSocket.InputStream.Position = 0; - Assert.Equal("Hello", await socket.ReadStringAsync()); + Assert.Equal("Hello", await socket.ReadStringAsync(TestContext.Current.CancellationToken)); } /// @@ -103,7 +106,7 @@ public async Task ReadFailStringAsyncTest() tcpSocket.InputStream.Position = 0; - Assert.Equal("Hello", await socket.ReadStringAsync()); + Assert.Equal("Hello", await socket.ReadStringAsync(TestContext.Current.CancellationToken)); } /// @@ -124,7 +127,7 @@ public async Task ReadSyncStringAsyncTest() tcpSocket.InputStream.Position = 0; - Assert.Equal("Hello", await socket.ReadSyncStringAsync()); + Assert.Equal("Hello", await socket.ReadSyncStringAsync(TestContext.Current.CancellationToken)); } /// @@ -143,7 +146,7 @@ public async Task ReadAdbOkayResponseAsyncTest() tcpSocket.InputStream.Position = 0; - AdbResponse response = await socket.ReadAdbResponseAsync(); + AdbResponse response = await socket.ReadAdbResponseAsync(TestContext.Current.CancellationToken); Assert.True(response.IOSuccess); Assert.Equal(string.Empty, response.Message); Assert.True(response.Okay); @@ -168,7 +171,7 @@ public async Task ReadAdbFailResponseAsyncTest() tcpSocket.InputStream.Position = 0; - _ = await Assert.ThrowsAsync(() => socket.ReadAdbResponseAsync()); + _ = await Assert.ThrowsAsync(() => socket.ReadAdbResponseAsync(TestContext.Current.CancellationToken)); } /// @@ -187,13 +190,13 @@ public async Task ReadAsyncTest() data[i] = (byte)i; } - await tcpSocket.InputStream.WriteAsync(data); + await tcpSocket.InputStream.WriteAsync(data, TestContext.Current.CancellationToken); tcpSocket.InputStream.Position = 0; // Buffer has a capacity of 101, but we'll only want to read 100 bytes byte[] received = new byte[101]; - await socket.ReadAsync(received, 100); + await socket.ReadAsync(received, 100, TestContext.Current.CancellationToken); for (int i = 0; i < 100; i++) { @@ -219,13 +222,13 @@ public async Task ReadAsyncMemoryTest() data[i] = (byte)i; } - await tcpSocket.InputStream.WriteAsync(data); + await tcpSocket.InputStream.WriteAsync(data, TestContext.Current.CancellationToken); tcpSocket.InputStream.Position = 0; // Buffer has a capacity of 101, but we'll only want to read 100 bytes byte[] received = new byte[101]; - await socket.ReadAsync(received.AsMemory(0, 100)); + await socket.ReadAsync(received.AsMemory(0, 100), TestContext.Current.CancellationToken); for (int i = 0; i < 100; i++) { @@ -241,16 +244,17 @@ public async Task ReadAsyncMemoryTest() [Fact] public async Task SendAdbRequestAsyncTest() => await RunTestAsync( - socket => socket.SendAdbRequestAsync("Test", default), - "0004Test"u8.ToArray()); + (socket, ctx) => socket.SendAdbRequestAsync("Test", ctx), + "0004Test"u8.ToArray(), + TestContext.Current.CancellationToken); - private static async Task RunTestAsync(Func test, byte[] expectedDataSent) + private static async Task RunTestAsync(Func test, byte[] expectedDataSent, CancellationToken cancellationToken = default) { using DummyTcpSocket tcpSocket = new(); using AdbSocket socket = new(tcpSocket); // Run the test. - await test(socket); + await test(socket, cancellationToken); // Validate the data that was sent over the wire. Assert.Equal(expectedDataSent, tcpSocket.GetBytesSent()); diff --git a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj index dd74243..869f801 100644 --- a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj +++ b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj @@ -25,10 +25,10 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs index 598da93..6be5d9d 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs @@ -18,13 +18,13 @@ public partial class DeviceExtensionsTests [Fact] public async Task DumpScreenStringAsyncTest() { - string dump = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt"); - string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + string dump = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt", TestContext.Current.CancellationToken); + string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml", TestContext.Current.CancellationToken); DummyAdbClient client = new(); client.Commands["shell:uiautomator dump /dev/tty"] = dump; - string xml = await new DeviceClient(client, Device).DumpScreenStringAsync(); + string xml = await new DeviceClient(client, Device).DumpScreenStringAsync(TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); @@ -38,13 +38,13 @@ public async Task DumpScreenStringAsyncTest() [Fact] public async Task DumpScreenStringMIUIAsyncTest() { - string miuidump = await File.ReadAllTextAsync(@"Assets/DumpScreen.MIUI.txt"); - string cleanMIUIDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + string miuidump = await File.ReadAllTextAsync(@"Assets/DumpScreen.MIUI.txt", TestContext.Current.CancellationToken); + string cleanMIUIDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml", TestContext.Current.CancellationToken); DummyAdbClient client = new(); client.Commands["shell:uiautomator dump /dev/tty"] = miuidump; - string miuiXml = await new DeviceClient(client, Device).DumpScreenStringAsync(); + string miuiXml = await new DeviceClient(client, Device).DumpScreenStringAsync(TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); @@ -61,7 +61,7 @@ public async Task DumpScreenStringEmptyAsyncTest() DummyAdbClient client = new(); client.Commands["shell:uiautomator dump /dev/tty"] = string.Empty; - string emptyXml = await new DeviceClient(client, Device).DumpScreenStringAsync(); + string emptyXml = await new DeviceClient(client, Device).DumpScreenStringAsync(TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); @@ -76,9 +76,9 @@ public async Task DumpScreenStringEmptyAsyncTest() public async Task DumpScreenStringErrorAsyncTest() { DummyAdbClient client = new(); - client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.Error.txt"); + client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.Error.txt", TestContext.Current.CancellationToken); - _ = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).DumpScreenStringAsync()); + _ = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).DumpScreenStringAsync(TestContext.Current.CancellationToken)); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); @@ -91,14 +91,14 @@ public async Task DumpScreenStringErrorAsyncTest() public async Task DumpScreenAsyncTest() { DummyAdbClient client = new(); - client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt"); + client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt", TestContext.Current.CancellationToken); - XmlDocument xml = await new DeviceClient(client, Device).DumpScreenAsync(); + XmlDocument xml = await new DeviceClient(client, Device).DumpScreenAsync(TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); - string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml", TestContext.Current.CancellationToken); XmlDocument doc = new(); doc.LoadXml(cleanDump); @@ -115,14 +115,14 @@ public async Task DumpScreenWinRTAsyncTest() if (!OperatingSystem.IsWindowsVersionAtLeast(10)) { return; } DummyAdbClient client = new(); - client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt"); + client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt", TestContext.Current.CancellationToken); - Windows.Data.Xml.Dom.XmlDocument xml = await new DeviceClient(client, Device).DumpScreenWinRTAsync(); + Windows.Data.Xml.Dom.XmlDocument xml = await new DeviceClient(client, Device).DumpScreenWinRTAsync(TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); - string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml", TestContext.Current.CancellationToken); Windows.Data.Xml.Dom.XmlDocument doc = new(); doc.LoadXml(cleanDump); @@ -164,7 +164,7 @@ at android.os.Binder.execTransactInternal(Binder.java:1165) at android.os.Binder.execTransact(Binder.java:1134) """; - JavaException exception = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).ClickAsync(100, 100)); + JavaException exception = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).ClickAsync(100, 100, TestContext.Current.CancellationToken)); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input tap 100 100", client.ReceivedCommands[0]); @@ -206,7 +206,7 @@ public async Task ClickCordsAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input tap 100 100"] = "Error: Injecting to another application requires INJECT_EVENTS permission\r\n"; - _ = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).ClickAsync(new Point(100, 100))); + _ = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).ClickAsync(new Point(100, 100), TestContext.Current.CancellationToken)); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input tap 100 100", client.ReceivedCommands[0]); @@ -221,7 +221,7 @@ public async Task SwipeAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; - await new DeviceClient(client, Device).SwipeAsync(100, 200, 300, 400, 500); + await new DeviceClient(client, Device).SwipeAsync(100, 200, 300, 400, 500, TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); @@ -236,7 +236,7 @@ public async Task SwipePointAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; - await new DeviceClient(client, Device).SwipeAsync(new Point(100, 200), new Point(300, 400), 500); + await new DeviceClient(client, Device).SwipeAsync(new Point(100, 200), new Point(300, 400), 500, TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); @@ -251,7 +251,7 @@ public async Task SwipeElementAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; - await new DeviceClient(client, Device).SwipeAsync(new Element(client, Device, new Rectangle(0, 0, 200, 400)), new Element(client, Device, new Rectangle(0, 0, 600, 800)), 500); + await new DeviceClient(client, Device).SwipeAsync(new Element(client, Device, new Rectangle(0, 0, 200, 400)), new Element(client, Device, new Rectangle(0, 0, 600, 800)), 500, TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); @@ -273,7 +273,7 @@ public async Task IsAppRunningAsyncTest(string response, bool expected) DummyAdbClient client = new(); client.Commands["shell:pidof com.google.android.gms"] = response; - bool result = await new DeviceClient(client, Device).IsAppRunningAsync("com.google.android.gms"); + bool result = await new DeviceClient(client, Device).IsAppRunningAsync("com.google.android.gms", TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:pidof com.google.android.gms", client.ReceivedCommands[0]); @@ -297,7 +297,7 @@ public async Task IsAppInForegroundAsyncTest(string packageName, bool expected) mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5} """; - bool result = await new DeviceClient(client, Device).IsAppInForegroundAsync(packageName); + bool result = await new DeviceClient(client, Device).IsAppInForegroundAsync(packageName, TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); @@ -321,7 +321,7 @@ public async Task GetAppStatusAsyncTest(string packageName, string response, App """; client.Commands[$"shell:pidof {packageName}"] = response; - AppStatus result = await new DeviceClient(client, Device).GetAppStatusAsync(packageName); + AppStatus result = await new DeviceClient(client, Device).GetAppStatusAsync(packageName, TestContext.Current.CancellationToken); Assert.Equal(2, client.ReceivedCommands.Count); Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); @@ -345,7 +345,7 @@ public async Task GetAppStatusForegroundAsyncTest(string packageName, AppStatus mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5} """; - AppStatus result = await new DeviceClient(client, Device).GetAppStatusAsync(packageName); + AppStatus result = await new DeviceClient(client, Device).GetAppStatusAsync(packageName, TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); @@ -362,7 +362,7 @@ public async Task FindElementAsyncTest() DummyAdbClient client = new(); client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); - Element element = await new DeviceClient(client, Device).FindElementAsync(); + Element element = await new DeviceClient(client, Device).FindElementAsync(cancellationToken: TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); @@ -387,7 +387,7 @@ public async Task FindElementsAsyncTest() DummyAdbClient client = new(); client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); - Element[] elements = (await new DeviceClient(client, Device).FindElementsAsync()).ToArray(); + Element[] elements = await new DeviceClient(client, Device).FindElementsAsync(cancellationToken: TestContext.Current.CancellationToken).ToArrayAsync(); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); @@ -409,7 +409,7 @@ public async Task FindAsyncElementsTest() DummyAdbClient client = new(); client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); - List elements = await new DeviceClient(client, Device).FindAsyncElements().ToListAsync(); + List elements = await new DeviceClient(client, Device).FindAsyncElements(cancellationToken: TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); @@ -431,7 +431,7 @@ public async Task SendKeyEventAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input keyevent KEYCODE_MOVE_END"] = string.Empty; - await new DeviceClient(client, Device).SendKeyEventAsync("KEYCODE_MOVE_END"); + await new DeviceClient(client, Device).SendKeyEventAsync("KEYCODE_MOVE_END", TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input keyevent KEYCODE_MOVE_END", client.ReceivedCommands[0]); @@ -446,7 +446,7 @@ public async Task SendTextAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input text Hello, World"] = string.Empty; - await new DeviceClient(client, Device).SendTextAsync("Hello, World"); + await new DeviceClient(client, Device).SendTextAsync("Hello, World", TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input text Hello, World", client.ReceivedCommands[0]); @@ -460,7 +460,7 @@ public async Task StartAppAsyncTest() { DummyAdbClient client = new(); - await new DeviceClient(client, Device).StartAppAsync("com.android.settings"); + await new DeviceClient(client, Device).StartAppAsync("com.android.settings", TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:monkey -p com.android.settings 1", client.ReceivedCommands[0]); @@ -474,7 +474,7 @@ public async Task StopAppAsyncTest() { DummyAdbClient client = new(); - await new DeviceClient(client, Device).StopAppAsync("com.android.settings"); + await new DeviceClient(client, Device).StopAppAsync("com.android.settings", TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:am force-stop com.android.settings", client.ReceivedCommands[0]); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs index bb5d23c..2518209 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs @@ -25,7 +25,7 @@ public async Task ExecuteServerCommandAsyncTest() { Assert.Equal(command, x.ArgAt(0)); Assert.Equal(Device, x.ArgAt(1)); - Assert.Equal(default, x.ArgAt(2)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(2)); return Task.CompletedTask; }); _ = client.ExecuteRemoteCommandAsync(Arg.Any(), Device, Arg.Any(), Arg.Any(), Arg.Any()) @@ -35,13 +35,13 @@ public async Task ExecuteServerCommandAsyncTest() Assert.Equal(Device, x.ArgAt(1)); Assert.Equal(receiver, x.ArgAt(2)); Assert.Equal(AdbClient.Encoding, x.ArgAt(3)); - Assert.Equal(default, x.ArgAt(4)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(4)); return Task.CompletedTask; }); - await client.ExecuteShellCommandAsync(Device, command); - await client.ExecuteShellCommandAsync(Device, command, receiver); - await client.ExecuteShellCommandAsync(Device, command, predicate); + await client.ExecuteShellCommandAsync(Device, command, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteShellCommandAsync(Device, command, receiver, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteShellCommandAsync(Device, command, predicate, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -54,7 +54,7 @@ public async Task ClearInputAsyncTest() client.Commands["shell:input keyevent KEYCODE_MOVE_END"] = string.Empty; client.Commands["shell:input keyevent KEYCODE_DEL KEYCODE_DEL KEYCODE_DEL"] = string.Empty; - await client.ClearInputAsync(Device, 3); + await client.ClearInputAsync(Device, 3, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(2, client.ReceivedCommands.Count); Assert.Equal("shell:input keyevent KEYCODE_MOVE_END", client.ReceivedCommands[0]); @@ -70,7 +70,7 @@ public async Task ClickBackButtonAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input keyevent KEYCODE_BACK"] = string.Empty; - await client.ClickBackButtonAsync(Device); + await client.ClickBackButtonAsync(Device, cancellationToken: TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input keyevent KEYCODE_BACK", client.ReceivedCommands[0]); @@ -85,7 +85,7 @@ public async Task ClickHomeButtonAsyncTest() DummyAdbClient client = new(); client.Commands["shell:input keyevent KEYCODE_HOME"] = string.Empty; - await client.ClickHomeButtonAsync(Device); + await client.ClickHomeButtonAsync(Device, cancellationToken: TestContext.Current.CancellationToken); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:input keyevent KEYCODE_HOME", client.ReceivedCommands[0]); @@ -106,7 +106,7 @@ public async Task StatAsyncTest() .Returns(x => { Assert.Equal(remotePath, x.ArgAt(0)); - Assert.Equal(default, x.ArgAt(1)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(1)); return stats; }); @@ -117,7 +117,7 @@ public async Task StatAsyncTest() return mock; }; - Assert.Equal(stats, await client.StatAsync(Device, remotePath)); + Assert.Equal(stats, await client.StatAsync(Device, remotePath, cancellationToken: TestContext.Current.CancellationToken)); } /// @@ -135,7 +135,7 @@ public async Task GetDirectoryListingAsyncTest() .Returns(x => { Assert.Equal(remotePath, x.ArgAt(0)); - Assert.Equal(default, x.ArgAt(1)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(1)); return stats; }); @@ -146,7 +146,7 @@ public async Task GetDirectoryListingAsyncTest() return mock; }; - Assert.Equal(stats, await client.GetDirectoryListingAsync(Device, remotePath)); + Assert.Equal(stats, await client.GetDirectoryListingAsync(Device, remotePath, cancellationToken: TestContext.Current.CancellationToken)); } /// @@ -164,7 +164,7 @@ public async Task GetDirectoryAsyncListingTest() .Returns(x => { Assert.Equal(remotePath, x.ArgAt(0)); - Assert.Equal(default, x.ArgAt(1)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(1)); return stats.ToAsyncEnumerable(x.ArgAt(1)); }); @@ -175,7 +175,7 @@ public async Task GetDirectoryAsyncListingTest() return mock; }; - Assert.Equal(stats, await client.GetDirectoryAsyncListing(Device, remotePath).ToListAsync()); + Assert.Equal(stats, await client.GetDirectoryAsyncListing(Device, remotePath, cancellationToken: TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); } /// @@ -188,7 +188,7 @@ public async Task GetEnvironmentVariablesAsyncTest() adbClient.Commands[$"shell:{EnvironmentVariablesReceiver.PrintEnvCommand}"] = "a=b"; - Dictionary variables = await adbClient.GetEnvironmentVariablesAsync(Device); + Dictionary variables = await adbClient.GetEnvironmentVariablesAsync(Device, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(variables); Assert.Single(variables.Keys); Assert.True(variables.ContainsKey("a")); @@ -456,7 +456,7 @@ public async Task GetPackageVersionAsyncTest(string command, int versionCode, st adbClient.Commands[$"shell:dumpsys package {packageName}"] = command; - VersionInfo version = await adbClient.GetPackageVersionAsync(Device, packageName); + VersionInfo version = await adbClient.GetPackageVersionAsync(Device, packageName, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(versionCode, version.VersionCode); Assert.Equal(versionName, version.VersionName); @@ -496,7 +496,7 @@ public async Task ListProcessesAsyncTest() 3 (ksoftirqd/0) S 2 0 0 0 -1 69238848 0 0 0 0 0 23 0 0 20 0 1 0 7 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579284070 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 """; - List processes = await adbClient.ListProcessesAsync(Device); + List processes = await adbClient.ListProcessesAsync(Device, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(3, processes.Count); Assert.Equal("init", processes[0].Name); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs index b93e39a..82bfe86 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs @@ -17,14 +17,14 @@ public async Task InstallRemotePackageAsyncTest() PackageManager manager = new(adbClient, Device); - await manager.InstallRemotePackageAsync("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing)); + await manager.InstallRemotePackageAsync("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing), TestContext.Current.CancellationToken); Assert.Equal(2, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install \"/data/base.apk\"", adbClient.ReceivedCommands[1]); adbClient.ReceivedCommands.Clear(); - await manager.InstallRemotePackageAsync("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing), default, "-r", "-t"); + await manager.InstallRemotePackageAsync("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing), TestContext.Current.CancellationToken, new string[] { "-r", "-t" }); Assert.Single(adbClient.ReceivedCommands); Assert.Equal("shell:pm install -r -t \"/data/base.apk\"", adbClient.ReceivedCommands[0]); @@ -49,7 +49,8 @@ await manager.InstallPackageAsync("Assets/TestApp/base.apk", PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, PackageInstallProgressState.PostInstall, - PackageInstallProgressState.Finished)); + PackageInstallProgressState.Finished), + TestContext.Current.CancellationToken); Assert.Equal(3, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[1]); @@ -80,7 +81,8 @@ await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split_ new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, - PackageInstallProgressState.Installing)); + PackageInstallProgressState.Installing), + TestContext.Current.CancellationToken); Assert.Equal(6, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create", adbClient.ReceivedCommands[1]); @@ -95,7 +97,7 @@ await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split_ new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, - PackageInstallProgressState.Installing), default, "-r", "-t"); + PackageInstallProgressState.Installing), TestContext.Current.CancellationToken, "-r", "-t"); Assert.Equal(5, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -r -t", adbClient.ReceivedCommands[0]); @@ -110,7 +112,8 @@ await manager.InstallMultipleRemotePackageAsync(["/data/split_config.arm64_v8a.a new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, - PackageInstallProgressState.Installing)); + PackageInstallProgressState.Installing), + TestContext.Current.CancellationToken); Assert.Equal(4, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[0]); @@ -124,7 +127,7 @@ await manager.InstallMultipleRemotePackageAsync(["/data/split_config.arm64_v8a.a new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, - PackageInstallProgressState.Installing), default, "-r", "-t"); + PackageInstallProgressState.Installing), TestContext.Current.CancellationToken, "-r", "-t"); Assert.Equal(4, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms -r -t", adbClient.ReceivedCommands[0]); @@ -161,7 +164,8 @@ await manager.InstallMultiplePackageAsync("Assets/TestApp/base.apk", ["Assets/Te PackageInstallProgressState.WriteSession, PackageInstallProgressState.Installing, PackageInstallProgressState.PostInstall, - PackageInstallProgressState.Finished)); + PackageInstallProgressState.Finished), + TestContext.Current.CancellationToken); Assert.Equal(9, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create", adbClient.ReceivedCommands[1]); @@ -189,7 +193,8 @@ await manager.InstallMultiplePackageAsync(["Assets/TestApp/split_config.arm64_v8 PackageInstallProgressState.WriteSession, PackageInstallProgressState.Installing, PackageInstallProgressState.PostInstall, - PackageInstallProgressState.Finished)); + PackageInstallProgressState.Finished), + TestContext.Current.CancellationToken); Assert.Equal(6, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[0]); @@ -214,7 +219,7 @@ public async Task UninstallPackageAsyncTest() // Command should execute correctly; if the wrong command is passed an exception // would be thrown. - await manager.UninstallPackageAsync("com.android.gallery3d"); + await manager.UninstallPackageAsync("com.android.gallery3d", TestContext.Current.CancellationToken); } [Fact] @@ -224,7 +229,7 @@ public async Task GetPackageVersionInfoAsyncTest() client.Commands["shell:dumpsys package com.google.android.gms"] = File.ReadAllText("Assets/DumpSys.GApps.txt"); PackageManager manager = new(client, Device, skipInit: true); - VersionInfo versionInfo = await manager.GetVersionInfoAsync("com.google.android.gms"); + VersionInfo versionInfo = await manager.GetVersionInfoAsync("com.google.android.gms", TestContext.Current.CancellationToken); Assert.Equal(11062448, versionInfo.VersionCode); Assert.Equal("11.0.62 (448-160311229)", versionInfo.VersionName); } diff --git a/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.Async.cs index 7e7d646..d20f674 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.Async.cs @@ -22,7 +22,8 @@ await RunTestAsync( OkResponse, ["169.254.109.177:5555\tdevice\n"], ["host:track-devices"], - () => monitor.StartAsync()); + ctx => monitor.StartAsync(ctx), + TestContext.Current.CancellationToken); Assert.Single(monitor.Devices); Assert.Single(sink.ConnectedEvents); @@ -44,7 +45,8 @@ await RunTestAsync( NoResponses, [string.Empty], NoRequests, - () => Task.Run(() => eventWaiter.WaitOne(1000))); + ctx => Task.Run(() => eventWaiter.WaitOne(1000), ctx), + TestContext.Current.CancellationToken); Assert.Empty(monitor.Devices); Assert.Empty(sink.ConnectedEvents); @@ -72,7 +74,8 @@ await RunTestAsync( OkResponse, [string.Empty], ["host:track-devices"], - () => monitor.StartAsync()); + ctx => monitor.StartAsync(ctx), + TestContext.Current.CancellationToken); Assert.Empty(monitor.Devices); Assert.Empty(sink.ConnectedEvents); @@ -94,7 +97,8 @@ await RunTestAsync( NoResponses, ["169.254.109.177:5555\tdevice\n"], NoRequests, - () => Task.Run(() => eventWaiter.WaitOne(1000))); + ctx => Task.Run(() => eventWaiter.WaitOne(1000), ctx), + TestContext.Current.CancellationToken); Assert.Single(monitor.Devices); Assert.Single(sink.ConnectedEvents); @@ -124,7 +128,8 @@ await RunTestAsync( OkResponse, ["169.254.109.177:5555\tdevice\n"], ["host:track-devices"], - () => monitor.StartAsync()); + ctx => monitor.StartAsync(ctx), + TestContext.Current.CancellationToken); Assert.Single(monitor.Devices); Assert.Equal("169.254.109.177:5555", monitor.Devices[0].Serial); @@ -153,7 +158,8 @@ await RunTestAsync( OkResponse, ["169.254.109.177:5555\toffline\n"], ["host:track-devices"], - () => monitor.StartAsync()); + ctx => monitor.StartAsync(ctx), + TestContext.Current.CancellationToken); Assert.Single(monitor.Devices); Assert.Equal(DeviceState.Offline, monitor.Devices[0].State); @@ -176,7 +182,8 @@ await RunTestAsync( NoResponses, ["169.254.109.177:5555\tdevice\n"], NoRequests, - () => Task.Run(() => eventWaiter.WaitOne(1000))); + ctx => Task.Run(() => eventWaiter.WaitOne(1000), ctx), + TestContext.Current.CancellationToken); Assert.Single(monitor.Devices); Assert.Equal(DeviceState.Online, monitor.Devices[0].State); @@ -206,7 +213,8 @@ await RunTestAsync( OkResponse, ["169.254.109.177:5555\toffline\n"], ["host:track-devices"], - () => monitor.StartAsync()); + ctx => monitor.StartAsync(ctx), + TestContext.Current.CancellationToken); Assert.Single(monitor.Devices); Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); @@ -229,7 +237,8 @@ await RunTestAsync( NoResponses, ["169.254.109.177:5555\toffline\n"], NoRequests, - () => Task.Run(() => eventWaiter.WaitOne(1000))); + ctx => Task.Run(() => eventWaiter.WaitOne(1000), ctx), + TestContext.Current.CancellationToken); Assert.Single(monitor.Devices); Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); @@ -257,7 +266,8 @@ await RunTestAsync( OkResponses(2), [DummyAdbSocket.ServerDisconnected, string.Empty], ["host:track-devices", "host:track-devices"], - () => monitor.StartAsync()); + ctx => monitor.StartAsync(ctx), + TestContext.Current.CancellationToken); Assert.True(Socket.DidReconnect); Assert.True(dummyAdbServer.WasRestarted); diff --git a/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs index 2f55954..53143c7 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs @@ -30,7 +30,7 @@ public async Task ExecuteServerCommandAsyncTest() Assert.Equal(command, x.ArgAt(1)); Assert.Equal(receiver, x.ArgAt(2)); Assert.Equal(encoding, x.ArgAt(3)); - Assert.Equal(default, x.ArgAt(4)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(4)); return Task.CompletedTask; }); _ = client.ExecuteServerCommandAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) @@ -41,7 +41,7 @@ public async Task ExecuteServerCommandAsyncTest() Assert.Equal(socket, x.ArgAt(2)); Assert.Equal(receiver, x.ArgAt(3)); Assert.Equal(encoding, x.ArgAt(4)); - Assert.Equal(default, x.ArgAt(5)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(5)); return Task.CompletedTask; }); _ = client.ExecuteServerEnumerableAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) @@ -50,7 +50,7 @@ public async Task ExecuteServerCommandAsyncTest() Assert.Equal(target, x.ArgAt(0)); Assert.Equal(command, x.ArgAt(1)); Assert.Equal(encoding, x.ArgAt(2)); - Assert.Equal(default, x.ArgAt(3)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(3)); return result.ToAsyncEnumerable(x.ArgAt(3)); }); _ = client.ExecuteServerEnumerableAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) @@ -60,18 +60,18 @@ public async Task ExecuteServerCommandAsyncTest() Assert.Equal(command, x.ArgAt(1)); Assert.Equal(socket, x.ArgAt(2)); Assert.Equal(encoding, x.ArgAt(3)); - Assert.Equal(default, x.ArgAt(4)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(4)); return result.ToAsyncEnumerable(x.ArgAt(4)); }); - await client.ExecuteServerCommandAsync(target, command, receiver); - await client.ExecuteServerCommandAsync(target, command, socket, receiver); - await client.ExecuteServerCommandAsync(target, command, predicate); - await client.ExecuteServerCommandAsync(target, command, socket, predicate); - await client.ExecuteServerCommandAsync(target, command, predicate, encoding); - await client.ExecuteServerCommandAsync(target, command, socket, predicate, encoding); - Assert.Equal(result, await client.ExecuteServerEnumerableAsync(target, command).ToListAsync()); - Assert.Equal(result, await client.ExecuteServerEnumerableAsync(target, command, socket).ToListAsync()); + await client.ExecuteServerCommandAsync(target, command, receiver, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteServerCommandAsync(target, command, socket, receiver, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteServerCommandAsync(target, command, predicate, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteServerCommandAsync(target, command, socket, predicate, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteServerCommandAsync(target, command, predicate, encoding, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteServerCommandAsync(target, command, socket, predicate, encoding, cancellationToken: TestContext.Current.CancellationToken); + Assert.Equal(result, await client.ExecuteServerEnumerableAsync(target, command, cancellationToken: TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); + Assert.Equal(result, await client.ExecuteServerEnumerableAsync(target, command, socket, cancellationToken: TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); } [Fact] @@ -92,7 +92,7 @@ public async Task ExecuteRemoteCommandAsyncTest() Assert.Equal(device, x.ArgAt(1)); Assert.Equal(receiver, x.ArgAt(2)); Assert.Equal(encoding, x.ArgAt(3)); - Assert.Equal(default, x.ArgAt(4)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(4)); return Task.CompletedTask; }); _ = client.ExecuteRemoteEnumerableAsync(Arg.Any(), device, Arg.Any(), Arg.Any()) @@ -101,14 +101,14 @@ public async Task ExecuteRemoteCommandAsyncTest() Assert.Equal(command, x.ArgAt(0)); Assert.Equal(device, x.ArgAt(1)); Assert.Equal(encoding, x.ArgAt(2)); - Assert.Equal(default, x.ArgAt(3)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(3)); return result.ToAsyncEnumerable(x.ArgAt(3)); }); - await client.ExecuteRemoteCommandAsync(command, device, receiver); - await client.ExecuteRemoteCommandAsync(command, device, predicate); - await client.ExecuteRemoteCommandAsync(command, device, predicate, encoding); - Assert.Equal(result, await client.ExecuteRemoteEnumerableAsync(command, device).ToListAsync()); + await client.ExecuteRemoteCommandAsync(command, device, receiver, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteRemoteCommandAsync(command, device, predicate, cancellationToken: TestContext.Current.CancellationToken); + await client.ExecuteRemoteCommandAsync(command, device, predicate, encoding, cancellationToken: TestContext.Current.CancellationToken); + Assert.Equal(result, await client.ExecuteRemoteEnumerableAsync(command, device, cancellationToken: TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); } [Fact] @@ -119,19 +119,23 @@ public async Task RunLogServiceAsyncTest() Action messageSink = progress.Report; LogId[] logNames = Enumerable.Range((int)LogId.Min, (int)(LogId.Max - LogId.Min + 1)).Select(x => (LogId)x).ToArray(); + CancellationToken token = TestContext.Current.CancellationToken; IAdbClient client = Substitute.For(); _ = client.RunLogServiceAsync(device, Arg.Any>(), Arg.Any(), Arg.Any()) .Returns(x => { Assert.Equal(device, x.ArgAt(0)); Assert.Equal(messageSink, x.ArgAt>(1)); - Assert.Equal(default, x.ArgAt(2)); + Assert.Equal(token, x.ArgAt(2)); Assert.Equal(logNames, x.ArgAt(3)); return Task.CompletedTask; }); + await client.RunLogServiceAsync(device, messageSink, token, logNames); + await client.RunLogServiceAsync(device, progress, token, logNames); + + token = default; await client.RunLogServiceAsync(device, messageSink, logNames); - await client.RunLogServiceAsync(device, progress, default, logNames); await client.RunLogServiceAsync(device, progress, logNames); } } diff --git a/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs index d52c4de..1d3a43b 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs @@ -42,8 +42,8 @@ public void AddRangeTest() public async Task TaskToArrayTest() { int[] array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - Task> arrayTask = Task.Delay(10).ContinueWith(_ => array.Select(x => x)); - IEnumerable> taskArray = array.Select(x => Task.Delay(x).ContinueWith(_ => x)); + Task> arrayTask = Task.Delay(10, TestContext.Current.CancellationToken).ContinueWith(_ => array.Select(x => x)); + IEnumerable> taskArray = array.Select(x => Task.Delay(x, TestContext.Current.CancellationToken).ContinueWith(_ => x)); Assert.Equal(array, await taskArray.ToArrayAsync()); Assert.Equal(array, await arrayTask.ToArrayAsync()); } diff --git a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs index 8aab49f..b36553f 100644 --- a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs +++ b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs @@ -49,7 +49,7 @@ public async Task ReadLogAsyncTest() // This stream contains 3 log entries. Read & validate the first one, // read the next two ones (to be sure all data is read correctly). - LogEntry log = await reader.ReadEntryAsync(); + LogEntry log = await reader.ReadEntryAsync(TestContext.Current.CancellationToken); Assert.IsType(log); @@ -66,8 +66,8 @@ public async Task ReadLogAsyncTest() Assert.Equal("Start proc com.google.android.gm for broadcast com.google.android.gm/.widget.GmailWidgetProvider: pid=7026 uid=10066 gids={50066, 9997, 3003, 1028, 1015} abi=x86", androidLog.Message); Assert.Equal($"{log.TimeStamp.LocalDateTime:yy-MM-dd HH:mm:ss.fff} 707 707 I ActivityManager: Start proc com.google.android.gm for broadcast com.google.android.gm/.widget.GmailWidgetProvider: pid=7026 uid=10066 gids={{50066, 9997, 3003, 1028, 1015}} abi=x86", androidLog.ToString()); - Assert.NotNull(await reader.ReadEntryAsync()); - Assert.NotNull(await reader.ReadEntryAsync()); + Assert.NotNull(await reader.ReadEntryAsync(TestContext.Current.CancellationToken)); + Assert.NotNull(await reader.ReadEntryAsync(TestContext.Current.CancellationToken)); } [Fact] @@ -112,7 +112,7 @@ public async Task ReadEventLogAsyncTest() // has already taken place. await using FileStream stream = File.OpenRead(@"Assets/LogcatEvents.bin"); LogReader reader = new(stream); - LogEntry entry = await reader.ReadEntryAsync(); + LogEntry entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken); Assert.IsType(entry); Assert.Equal(707, entry.ProcessId); @@ -136,8 +136,8 @@ public async Task ReadEventLogAsyncTest() Assert.Equal(19512, list[1]); Assert.Equal("com.amazon.kindle", list[2]); - entry = await reader.ReadEntryAsync(); - entry = await reader.ReadEntryAsync(); + entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken); + entry = await reader.ReadEntryAsync(TestContext.Current.CancellationToken); } } } diff --git a/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs b/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs index 8886933..d184a74 100644 --- a/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using Xunit; namespace AdvancedSharpAdbClient.Models.Tests @@ -9,36 +10,71 @@ namespace AdvancedSharpAdbClient.Models.Tests /// public class FramebufferHeaderTests { + /// + /// Tests the method. + /// [Fact] public void ReadFramebufferTest() { byte[] data = File.ReadAllBytes("Assets/FramebufferHeader.V1.bin"); - FramebufferHeader header = [.. data]; + FramebufferHeader header = Read(default, data); - Assert.Equal(8u, header.Alpha.Length); - Assert.Equal(24u, header.Alpha.Offset); - Assert.Equal(8u, header.Green.Length); - Assert.Equal(8u, header.Green.Offset); - Assert.Equal(8u, header.Red.Length); - Assert.Equal(0u, header.Red.Offset); - Assert.Equal(8u, header.Blue.Length); - Assert.Equal(16u, header.Blue.Offset); + Assert.Equal(1u, header.Version); Assert.Equal(32u, header.Bpp); + Assert.Equal(0u, header.ColorSpace); Assert.Equal(14745600u, header.Size); - Assert.Equal(2560u, header.Height); Assert.Equal(1440u, header.Width); + Assert.Equal(2560u, header.Height); + + ColorDataTest(header.Red, 0u, 8u, data.AsSpan(5 * sizeof(uint), ColorData.Size)); + ColorDataTest(header.Blue, 16u, 8u, data.AsSpan((5 * sizeof(uint)) + ColorData.Size, ColorData.Size)); + ColorDataTest(header.Green, 8u, 8u, data.AsSpan((5 * sizeof(uint)) + (2 * ColorData.Size), ColorData.Size)); + ColorDataTest(header.Alpha, 24u, 8u, data.AsSpan((5 * sizeof(uint)) + (3 * ColorData.Size), ColorData.Size)); + + Assert.Equal(FramebufferHeader.MinLength, header.Count); + for (int i = 0; i < header.Count; i++) + { + Assert.Equal(data[i], header[i]); + } + + Assert.Equal(data.AsSpan(), [.. header]); + } + + /// + /// Tests the method. + /// + [Fact] + public void ReadFramebufferBySpanTest() + { + Span data = File.ReadAllBytes("Assets/FramebufferHeader.V1.bin"); + + FramebufferHeader header = [.. data]; + Assert.Equal(1u, header.Version); + Assert.Equal(32u, header.Bpp); Assert.Equal(0u, header.ColorSpace); + Assert.Equal(14745600u, header.Size); + Assert.Equal(1440u, header.Width); + Assert.Equal(2560u, header.Height); + ColorDataTest(header.Red, 0u, 8u, data.Slice(5 * sizeof(uint), ColorData.Size)); + ColorDataTest(header.Blue, 16u, 8u, data.Slice((5 * sizeof(uint)) + ColorData.Size, ColorData.Size)); + ColorDataTest(header.Green, 8u, 8u, data.Slice((5 * sizeof(uint)) + (2 * ColorData.Size), ColorData.Size)); + ColorDataTest(header.Alpha, 24u, 8u, data.Slice((5 * sizeof(uint)) + (3 * ColorData.Size), ColorData.Size)); + + Assert.Equal(FramebufferHeader.MinLength, header.Count); for (int i = 0; i < header.Count; i++) { Assert.Equal(data[i], header[i]); } - Assert.Equal(data.AsSpan(), [.. header]); + Assert.Equal(data, [.. header]); } + /// + /// Tests the method. + /// [Fact] public void ReadFramebufferV2Test() { @@ -46,27 +82,56 @@ public void ReadFramebufferV2Test() FramebufferHeader header = [.. data]; - Assert.Equal(8u, header.Alpha.Length); - Assert.Equal(24u, header.Alpha.Offset); - Assert.Equal(8u, header.Green.Length); - Assert.Equal(8u, header.Green.Offset); - Assert.Equal(8u, header.Red.Length); - Assert.Equal(0u, header.Red.Offset); - Assert.Equal(8u, header.Blue.Length); - Assert.Equal(16u, header.Blue.Offset); + Assert.Equal(2u, header.Version); Assert.Equal(32u, header.Bpp); + Assert.Equal(0u, header.ColorSpace); Assert.Equal(8294400u, header.Size); - Assert.Equal(1920u, header.Height); Assert.Equal(1080u, header.Width); + Assert.Equal(1920u, header.Height); + + ColorDataTest(header.Red, 0u, 8u, data.AsSpan(6 * sizeof(uint), ColorData.Size)); + ColorDataTest(header.Blue, 16u, 8u, data.AsSpan((6 * sizeof(uint)) + ColorData.Size, ColorData.Size)); + ColorDataTest(header.Green, 8u, 8u, data.AsSpan((6 * sizeof(uint)) + (2 * ColorData.Size), ColorData.Size)); + ColorDataTest(header.Alpha, 24u, 8u, data.AsSpan((6 * sizeof(uint)) + (3 * ColorData.Size), ColorData.Size)); + + Assert.Equal(FramebufferHeader.MaxLength, header.Count); + for (int i = 0; i < header.Count; i++) + { + Assert.Equal(data[i], header[i]); + } + + Assert.Equal(data.AsSpan(), [.. header]); + } + + /// + /// Tests the method. + /// + [Fact] + public void ReadFramebufferV2BySpanTest() + { + Span data = File.ReadAllBytes("Assets/FramebufferHeader.V2.bin"); + + FramebufferHeader header = [.. data]; + Assert.Equal(2u, header.Version); + Assert.Equal(32u, header.Bpp); Assert.Equal(0u, header.ColorSpace); + Assert.Equal(8294400u, header.Size); + Assert.Equal(1080u, header.Width); + Assert.Equal(1920u, header.Height); + + ColorDataTest(header.Red, 0u, 8u, data.Slice(6 * sizeof(uint), ColorData.Size)); + ColorDataTest(header.Blue, 16u, 8u, data.Slice((6 * sizeof(uint)) + ColorData.Size, ColorData.Size)); + ColorDataTest(header.Green, 8u, 8u, data.Slice((6 * sizeof(uint)) + (2 * ColorData.Size), ColorData.Size)); + ColorDataTest(header.Alpha, 24u, 8u, data.Slice((6 * sizeof(uint)) + (3 * ColorData.Size), ColorData.Size)); + Assert.Equal(FramebufferHeader.MaxLength, header.Count); for (int i = 0; i < header.Count; i++) { Assert.Equal(data[i], header[i]); } - Assert.Equal(data.AsSpan(), [.. header]); + Assert.Equal(data, [.. header]); } #if HAS_IMAGING @@ -106,5 +171,24 @@ public void ToImageEmptyTest() Assert.Null(image); } #endif + + private static void ColorDataTest(ColorData actual, uint offset, uint length, Span data) + { + Assert.Equal(offset, actual.Offset); + Assert.Equal(length, actual.Length); + for (int i = 0; i < ColorData.Size; i++) + { + Assert.Equal(data[i], actual[i]); + } + Assert.Equal(data, [.. actual]); + Assert.Equal(data.ToArray(), actual.ToArray()); + Assert.Equal(data, actual.AsSpan()); + (uint o, uint l) = actual; + Assert.Equal(offset, o); + Assert.Equal(length, l); + } + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Read")] + private static extern FramebufferHeader Read(FramebufferHeader header, byte[] data); } } diff --git a/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs b/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs index 3d66a5b..56d5293 100644 --- a/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs @@ -102,12 +102,12 @@ public async Task RefreshAsyncTest() socket.Requests.Add("host:transport:169.254.109.177:5555"); socket.Requests.Add("framebuffer:"); - socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/FramebufferHeader.bin")); - socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/Framebuffer.bin")); + socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/FramebufferHeader.bin", TestContext.Current.CancellationToken)); + socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/Framebuffer.bin", TestContext.Current.CancellationToken)); using Framebuffer framebuffer = new(device, endPoint => socket); - await framebuffer.RefreshAsync(); + await framebuffer.RefreshAsync(cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(framebuffer); Assert.Equal(device, framebuffer.Device); diff --git a/AdvancedSharpAdbClient.Tests/Models/ShellStreamTests.cs b/AdvancedSharpAdbClient.Tests/Models/ShellStreamTests.cs index 8761eb1..e916523 100644 --- a/AdvancedSharpAdbClient.Tests/Models/ShellStreamTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/ShellStreamTests.cs @@ -113,7 +113,7 @@ public async Task CRLFAtStartAsyncTest() stream.Position = 0; byte[] buffer = new byte[2]; - int read = await shellStream.ReadAsync(buffer.AsMemory(0, 2)); + int read = await shellStream.ReadAsync(buffer.AsMemory(0, 2), TestContext.Current.CancellationToken); Assert.Equal(2, read); Assert.Equal((byte)'\n', buffer[0]); Assert.Equal((byte)'H', buffer[1]); @@ -132,7 +132,7 @@ public async Task MultipleCRLFInStringAsyncTest() stream.Position = 0; byte[] buffer = new byte[100]; - int read = await shellStream.ReadAsync(buffer.AsMemory(0, 100)); + int read = await shellStream.ReadAsync(buffer.AsMemory(0, 100), TestContext.Current.CancellationToken); string actual = Encoding.ASCII.GetString(buffer, 0, read); Assert.Equal("\n1\n2\n3\n4\n5", actual); @@ -150,19 +150,19 @@ public async Task PendingByteAsyncTest() await using MemoryStream stream = GetStream("\r\nH\ra"); await using ShellStream shellStream = new(stream, false); byte[] buffer = new byte[1]; - int read = await shellStream.ReadAsync(buffer.AsMemory(0, 1)); + int read = await shellStream.ReadAsync(buffer.AsMemory(0, 1), TestContext.Current.CancellationToken); Assert.Equal(1, read); Assert.Equal((byte)'\n', buffer[0]); - read = await shellStream.ReadAsync(buffer.AsMemory(0, 1)); + read = await shellStream.ReadAsync(buffer.AsMemory(0, 1), TestContext.Current.CancellationToken); Assert.Equal(1, read); Assert.Equal((byte)'H', buffer[0]); - read = await shellStream.ReadAsync(buffer.AsMemory(0, 1)); + read = await shellStream.ReadAsync(buffer.AsMemory(0, 1), TestContext.Current.CancellationToken); Assert.Equal(1, read); Assert.Equal((byte)'\r', buffer[0]); - read = await shellStream.ReadAsync(buffer.AsMemory(0, 1)); + read = await shellStream.ReadAsync(buffer.AsMemory(0, 1), TestContext.Current.CancellationToken); Assert.Equal(1, read); Assert.Equal((byte)'a', buffer[0]); } diff --git a/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs b/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs index bd8f263..444ac6e 100644 --- a/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs +++ b/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -663,13 +664,15 @@ protected TResult RunTest( /// The messages that should follow the . /// The requests the client should send. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. protected Task RunTestAsync( IEnumerable responses, IEnumerable responseMessages, IEnumerable requests, - Func test) => - RunTestAsync(responses, responseMessages, requests, null, null, null, null, null, test); + Func test, + CancellationToken cancellationToken = default) => + RunTestAsync(responses, responseMessages, requests, null, null, null, null, null, test, cancellationToken); /// /// @@ -693,14 +696,16 @@ protected Task RunTestAsync( /// The requests the client should send. /// The of which the should use. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. protected Task RunTestAsync( IEnumerable responses, IEnumerable responseMessages, IEnumerable requests, IEnumerable shellStreams, - Func test) => - RunTestAsync(responses, responseMessages, requests, null, null, null, null, shellStreams, test); + Func test, + CancellationToken cancellationToken = default) => + RunTestAsync(responses, responseMessages, requests, null, null, null, null, shellStreams, test, cancellationToken); /// /// @@ -727,6 +732,7 @@ protected Task RunTestAsync( /// The of data which the ADB sever should send. /// The of data which the client should send. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. protected Task RunTestAsync( IEnumerable responses, @@ -736,7 +742,8 @@ protected Task RunTestAsync( IEnumerable syncResponses, IEnumerable syncDataReceived, IEnumerable syncDataSent, - Func test) => + Func test, + CancellationToken cancellationToken = default) => RunTestAsync( responses, responseMessages, @@ -746,7 +753,8 @@ protected Task RunTestAsync( syncDataReceived, syncDataSent, null, - test); + test, + cancellationToken); /// /// @@ -774,6 +782,7 @@ protected Task RunTestAsync( /// The of data which the client should send. /// The of which the should use. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. protected async Task RunTestAsync( IEnumerable responses, @@ -784,7 +793,8 @@ protected async Task RunTestAsync( IEnumerable syncDataReceived, IEnumerable syncDataSent, IEnumerable shellStreams, - Func test) + Func test, + CancellationToken cancellationToken = default) { // If we are running unit tests, we need to mock all the responses // that are sent by the device. Do that now. @@ -829,7 +839,7 @@ protected async Task RunTestAsync( try { - await test(); + await test(cancellationToken); } catch (AggregateException ex) { @@ -959,13 +969,15 @@ protected async Task RunTestAsync( /// The messages that should follow the . /// The requests the client should send. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which return the result of . protected Task RunTestAsync( IEnumerable responses, IEnumerable responseMessages, IEnumerable requests, - Func> test) => - RunTestAsync(responses, responseMessages, requests, null, null, null, null, null, test); + Func> test, + CancellationToken cancellationToken = default) => + RunTestAsync(responses, responseMessages, requests, null, null, null, null, null, test, cancellationToken); /// /// @@ -989,14 +1001,16 @@ protected Task RunTestAsync( /// The requests the client should send. /// The of which the should use. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which return the result of . protected Task RunTestAsync( IEnumerable responses, IEnumerable responseMessages, IEnumerable requests, IEnumerable shellStreams, - Func> test) => - RunTestAsync(responses, responseMessages, requests, null, null, null, null, shellStreams, test); + Func> test, + CancellationToken cancellationToken = default) => + RunTestAsync(responses, responseMessages, requests, null, null, null, null, shellStreams, test, cancellationToken); /// /// @@ -1023,6 +1037,7 @@ protected Task RunTestAsync( /// The of data which the ADB sever should send. /// The of data which the client should send. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which return the result of . protected Task RunTestAsync( IEnumerable responses, @@ -1032,7 +1047,8 @@ protected Task RunTestAsync( IEnumerable syncResponses, IEnumerable syncDataReceived, IEnumerable syncDataSent, - Func> test) => + Func> test, + CancellationToken cancellationToken = default) => RunTestAsync( responses, responseMessages, @@ -1042,7 +1058,8 @@ protected Task RunTestAsync( syncDataReceived, syncDataSent, null, - test); + test, + cancellationToken); /// /// @@ -1070,6 +1087,7 @@ protected Task RunTestAsync( /// The of data which the client should send. /// The of which the should use. /// The test to run. + /// A which can be used to cancel the asynchronous operation. /// A which return the result of . protected async Task RunTestAsync( IEnumerable responses, @@ -1080,7 +1098,8 @@ protected async Task RunTestAsync( IEnumerable syncDataReceived, IEnumerable syncDataSent, IEnumerable shellStreams, - Func> test) + Func> test, + CancellationToken cancellationToken = default) { // If we are running unit tests, we need to mock all the responses // that are sent by the device. Do that now. @@ -1126,7 +1145,7 @@ protected async Task RunTestAsync( try { - result = await test(); + result = await test(cancellationToken); } catch (AggregateException ex) { diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index 90a87ca..faed6f0 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -24,14 +24,15 @@ public async Task StatAsyncTest() [SyncCommand.STAT], [[160, 129, 0, 0, 85, 2, 0, 0, 0, 0, 0, 0]], null, - async () => + async (ctx) => { using SyncService service = new(Socket, Device); - FileStatistics value = await service.StatAsync("/fstab.donatello"); + FileStatistics value = await service.StatAsync("/fstab.donatello", ctx); Assert.False(service.IsProcessing); Assert.False(service.IsOutdate); return value; - }); + }, + TestContext.Current.CancellationToken); Assert.Equal("/fstab.donatello", value.Path); Assert.Equal(UnixFileStatus.Regular, value.FileMode.GetFileType()); @@ -67,14 +68,15 @@ public async Task StatExAsyncTest() 0, 0, 0, 0, 0, 0, 0, 0 ]], null, - async () => + async (ctx) => { using SyncService service = new(Socket, Device); - FileStatisticsEx value = await service.StatExAsync("/fstab.donatello"); + FileStatisticsEx value = await service.StatExAsync("/fstab.donatello", ctx); Assert.False(service.IsProcessing); Assert.False(service.IsOutdate); return value; - }); + }, + TestContext.Current.CancellationToken); Assert.Equal("/fstab.donatello", value.Path); Assert.Equal(UnixErrorCode.Default, value.Error); @@ -111,14 +113,15 @@ public async Task GetListingAsyncTest() [109, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86] ], null, - async () => + async (ctx) => { using SyncService service = new(Socket, Device); - List value = await service.GetDirectoryListingAsync("/storage"); + List value = await service.GetDirectoryListingAsync("/storage", ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); return value; - }); + }, + TestContext.Current.CancellationToken); Assert.Equal(4, value.Count); @@ -172,14 +175,15 @@ public async Task GetAsyncListingTest() [109, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86] ], null, - async () => + async (ctx) => { using SyncService service = new(Socket, Device); - List value = await service.GetDirectoryAsyncListing("/storage").ToListAsync(); + List value = await service.GetDirectoryAsyncListing("/storage", ctx).ToListAsync(); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); return value; - }); + }, + TestContext.Current.CancellationToken); Assert.Equal(4, value.Count); @@ -221,7 +225,7 @@ public async Task GetAsyncListingTest() public async Task PullAsyncTest() { await using MemoryStream stream = new(); - byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); byte[] contentLength = BitConverter.GetBytes(content.Length); await RunTestAsync( @@ -239,13 +243,14 @@ await RunTestAsync( content ], null, - async () => + async (ctx) => { using SyncService service = new(Socket, Device); - await service.PullAsync("/fstab.donatello", stream, null); + await service.PullAsync("/fstab.donatello", stream, null, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); - }); + }, + TestContext.Current.CancellationToken); // Make sure the data that has been sent to the stream is the expected data Assert.Equal(content, stream.ToArray()); @@ -258,7 +263,7 @@ await RunTestAsync( public async Task PushAsyncTest() { FileStream stream = File.OpenRead("Assets/Fstab.bin"); - byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); byte[] contentMessage = [ .. SyncCommand.DATA.GetBytes(), @@ -277,13 +282,14 @@ await RunTestAsync( [SyncCommand.OKAY], null, [contentMessage], - async () => + async (ctx) => { 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); + await service.PushAsync(stream, "/sdcard/test", UnixFileStatus.StickyBit | UnixFileStatus.UserWrite | UnixFileStatus.OtherRead, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), null, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); - }); + }, + TestContext.Current.CancellationToken); } /// @@ -305,30 +311,31 @@ await RunTestAsync( [109, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86] ], null, - async () => + async (ctx) => { using SyncService service = new(Socket, Device); - await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage")) + await foreach (FileStatistics stat in service.GetDirectoryAsyncListing("/storage", ctx)) { 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_18362_0_OR_GREATER - _ = await Assert.ThrowsAsync(() => service.PushAsync((IInputStream)null, null, default, default)); - _ = await Assert.ThrowsAsync(() => service.PullAsync(null, (IOutputStream)null)); + _ = await Assert.ThrowsAsync(() => service.PushAsync((Stream)null, null, default, default, cancellationToken: ctx)); + _ = await Assert.ThrowsAsync(() => service.PullAsync(null, (Stream)null, cancellationToken: ctx)); +#if WINDOWS10_0_17763_0_OR_GREATER + _ = await Assert.ThrowsAsync(() => service.PushAsync((IInputStream)null, null, default, default, cancellationToken: ctx)); + _ = await Assert.ThrowsAsync(() => service.PullAsync(null, (IOutputStream)null, cancellationToken: ctx)); #endif - _ = await Assert.ThrowsAsync(() => service.GetDirectoryListingAsync(null)); - _ = await Assert.ThrowsAsync(() => service.GetDirectoryAsyncListing(null).ToListAsync().AsTask()); + _ = await Assert.ThrowsAsync(() => service.GetDirectoryListingAsync(null, ctx)); + _ = await Assert.ThrowsAsync(() => service.GetDirectoryAsyncListing(null, ctx).ToListAsync(cancellationToken: ctx).AsTask()); } Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); - }); + }, + TestContext.Current.CancellationToken); } -#if WINDOWS10_0_18362_0_OR_GREATER +#if WINDOWS10_0_17763_0_OR_GREATER /// - /// Tests the method. + /// Tests the method. /// [Fact] public async Task PullWinRTAsyncTest() @@ -336,7 +343,7 @@ public async Task PullWinRTAsyncTest() if (!OperatingSystem.IsWindowsVersionAtLeast(10)) { return; } using InMemoryRandomAccessStream stream = new(); - byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); byte[] contentLength = BitConverter.GetBytes(content.Length); await RunTestAsync( @@ -354,13 +361,14 @@ await RunTestAsync( content ], null, - async () => + async (ctx) => { using SyncService service = new(Socket, Device); - await service.PullAsync("/fstab.donatello", stream, null); + await service.PullAsync("/fstab.donatello", stream, null, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); - }); + }, + TestContext.Current.CancellationToken); IBuffer buffer = await stream.GetInputStreamAt(0).ReadAsync(new byte[(int)stream.Size].AsBuffer(), (uint)stream.Size, InputStreamOptions.None); // Make sure the data that has been sent to the stream is the expected data @@ -368,7 +376,7 @@ await RunTestAsync( } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async Task PushWinRTAsyncTest() @@ -377,7 +385,7 @@ public async Task PushWinRTAsyncTest() StorageFile storageFile = await StorageFile.GetFileFromPathAsync(Path.GetFullPath("Assets/Fstab.bin")); using IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync(); - byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); byte[] contentMessage = [ .. SyncCommand.DATA.GetBytes(), @@ -396,13 +404,14 @@ await RunTestAsync( [SyncCommand.OKAY], null, [contentMessage], - async () => + async (ctx) => { 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); + await service.PushAsync(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), null, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); - }); + }, + TestContext.Current.CancellationToken); } #endif } diff --git a/AdvancedSharpAdbClient.Tests/TcpSocketTests.Async.cs b/AdvancedSharpAdbClient.Tests/TcpSocketTests.Async.cs index f436ae0..5894a88 100644 --- a/AdvancedSharpAdbClient.Tests/TcpSocketTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/TcpSocketTests.Async.cs @@ -16,15 +16,14 @@ public async Task LifecycleAsyncTest() using TcpSocket socket = new(); Assert.False(socket.Connected); - await socket.ConnectAsync(new DnsEndPoint("www.bing.com", 80)); + await socket.ConnectAsync(new DnsEndPoint("www.bing.com", 80), TestContext.Current.CancellationToken); Assert.True(socket.Connected); byte[] data = "GET / HTTP/1.1\n\n"u8.ToArray(); - await socket.SendAsync(data, data.Length, SocketFlags.None); + await socket.SendAsync(data, data.Length, SocketFlags.None, TestContext.Current.CancellationToken); byte[] responseData = new byte[128]; - await socket.ReceiveAsync(responseData, responseData.Length, SocketFlags.None); - + await socket.ReceiveAsync(responseData, responseData.Length, SocketFlags.None, TestContext.Current.CancellationToken); _ = Encoding.ASCII.GetString(responseData); } @@ -34,14 +33,13 @@ public async Task LifecycleAsyncMemoryTest() using TcpSocket socket = new(); Assert.False(socket.Connected); - await socket.ConnectAsync(new DnsEndPoint("www.bing.com", 80)); + await socket.ConnectAsync(new DnsEndPoint("www.bing.com", 80), TestContext.Current.CancellationToken); Assert.True(socket.Connected); ReadOnlyMemory data = "GET / HTTP/1.1\n\n"u8.ToArray(); - await socket.SendAsync(data, SocketFlags.None); - + await socket.SendAsync(data, SocketFlags.None, TestContext.Current.CancellationToken); byte[] responseData = new byte[128]; - await socket.ReceiveAsync(responseData.AsMemory(), SocketFlags.None); + await socket.ReceiveAsync(responseData.AsMemory(), SocketFlags.None, TestContext.Current.CancellationToken); _ = Encoding.ASCII.GetString(responseData); } @@ -55,13 +53,13 @@ public async Task ReconnectAsyncTest() using TcpSocket socket = new(); Assert.False(socket.Connected); - await socket.ConnectAsync(new DnsEndPoint("www.bing.com", 80)); + await socket.ConnectAsync(new DnsEndPoint("www.bing.com", 80), TestContext.Current.CancellationToken); Assert.True(socket.Connected); socket.Dispose(); Assert.False(socket.Connected); - await socket.ReconnectAsync(); + await socket.ReconnectAsync(TestContext.Current.CancellationToken); Assert.True(socket.Connected); } @@ -72,7 +70,7 @@ public async Task ReconnectAsyncTest() public async Task CreateUnsupportedSocketAsyncTest() { using TcpSocket socket = new(); - _ = await Assert.ThrowsAsync(() => socket.ConnectAsync(new CustomEndPoint())); + _ = await Assert.ThrowsAsync(() => socket.ConnectAsync(new CustomEndPoint(), TestContext.Current.CancellationToken)); } } } diff --git a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs index bcad3e1..d6e934d 100644 --- a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs +++ b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace AdvancedSharpAdbClient { @@ -29,14 +31,7 @@ public static AdbCommandLineStatus AdbCommandLineStatusCreator(params ReadOnlySp /// /// The data that feeds the struct. /// A new instance of struct. - public static unsafe ColorData ColorDataCreator(ReadOnlySpan values) - { - fixed (byte* p = values) - { - ColorData* data = (ColorData*)p; - return *data; - } - } + public static unsafe ColorData ColorDataCreator(ReadOnlySpan values) => Unsafe.As(ref MemoryMarshal.GetReference(values)); /// /// Build a struct. @@ -45,47 +40,19 @@ public static unsafe ColorData ColorDataCreator(ReadOnlySpan values) /// A new instance of struct. public static FramebufferHeader FramebufferHeaderCreator(ReadOnlySpan values) => new(values); - /// - /// Build a struct. - /// - /// The data that feeds the struct. - /// A new instance of struct. - public static FileStatistics FileStatisticsCreator(ReadOnlySpan values) => new(FileStatisticsDataCreator(values)); - /// /// Build a struct. /// /// The data that feeds the struct. /// A new instance of struct. - public static unsafe FileStatisticsData FileStatisticsDataCreator(ReadOnlySpan values) - { - fixed (byte* p = values) - { - FileStatisticsData* data = (FileStatisticsData*)p; - return *data; - } - } - - /// - /// Build a struct. - /// - /// The data that feeds the struct. - /// A new instance of struct. - public static FileStatisticsEx FileStatisticsV2Creator(ReadOnlySpan values) => new(FileStatisticsDataV2Creator(values)); + public static unsafe FileStatisticsData FileStatisticsDataCreator(ReadOnlySpan values) => Unsafe.As(ref MemoryMarshal.GetReference(values)); /// /// Build a struct. /// /// The data that feeds the struct. /// A new instance of struct. - public static unsafe FileStatisticsDataEx FileStatisticsDataV2Creator(ReadOnlySpan values) - { - fixed (byte* p = values) - { - FileStatisticsDataEx* data = (FileStatisticsDataEx*)p; - return *data; - } - } + public static unsafe FileStatisticsDataEx FileStatisticsDataV2Creator(ReadOnlySpan values) => Unsafe.As(ref MemoryMarshal.GetReference(values)); /// /// Build a enum. diff --git a/AdvancedSharpAdbClient/Models/ColorData.cs b/AdvancedSharpAdbClient/Models/ColorData.cs index c2d950e..22eef9b 100644 --- a/AdvancedSharpAdbClient/Models/ColorData.cs +++ b/AdvancedSharpAdbClient/Models/ColorData.cs @@ -123,17 +123,7 @@ public byte[] ToArray() /// Returns a read-only span of bytes representing the contents of this instance. /// /// A that provides a read-only view of the bytes in this instance. - public ReadOnlySpan AsSpan() - { - ref readonly ColorData data = ref this; - unsafe - { - fixed (ColorData* pData = &data) - { - return new ReadOnlySpan(pData, Size); - } - } - } + public ReadOnlySpan AsSpan() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Size); #endif } } diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs b/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs index a6264fb..7f603f6 100644 --- a/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs +++ b/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs @@ -13,7 +13,7 @@ namespace AdvancedSharpAdbClient.Models /// /// [DebuggerDisplay($"{{{nameof(GetType)}().{nameof(Type.ToString)}(),nq}} \\{{ {nameof(Path)} = {{{nameof(Path)}}}, {nameof(FileMode)} = {{{nameof(FileMode)}}}, {nameof(Size)} = {{{nameof(Size)}}}, {nameof(ModifiedTime)} = {{{nameof(ModifiedTime)}}} }}")] - public class FileStatisticsEx(FileStatisticsDataEx data) : FileStatisticsBase(data), IFileStatistics + public class FileStatisticsEx(in FileStatisticsDataEx data) : FileStatisticsBase(data), IFileStatistics #if NET7_0_OR_GREATER , IEqualityOperators #endif diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs index bb1905e..2302e0b 100644 --- a/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs @@ -125,13 +125,7 @@ public unsafe byte[] ToArray() /// Returns a read-only span of bytes representing the contents of this instance. /// /// A that provides a read-only view of the bytes in this instance. - public unsafe ReadOnlySpan AsSpan() - { - fixed (FileStatisticsDataEx* pData = &this) - { - return new ReadOnlySpan(pData, Length); - } - } + public unsafe ReadOnlySpan AsSpan() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Length); /// /// Returns an enumerator that iterates through the . diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs index 0350a0e..bf8448f 100644 --- a/AdvancedSharpAdbClient/Models/FileStatisticsData.cs +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs @@ -131,13 +131,7 @@ public unsafe byte[] ToArray() /// Returns a read-only span of bytes representing the contents of this instance. /// /// A that provides a read-only view of the bytes in this instance. - public unsafe ReadOnlySpan AsSpan() - { - fixed (FileStatisticsData* pData = &this) - { - return new ReadOnlySpan(pData, Length); - } - } + public ReadOnlySpan AsSpan() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Length); #endif } } \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs index d9164a4..abf663e 100644 --- a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs +++ b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs @@ -54,23 +54,18 @@ public FramebufferHeader(byte[] data) case [> 2, ..]: throw new InvalidOperationException($"Framebuffer version {Version} is not supported"); case [2, ..]: - _data = data; - break; + goto default; case [< 2, ..]: _data = new byte[MaxLength]; Array.Copy(data, 0, _data, 0, 2 * sizeof(uint)); Array.Copy(data, 2 * sizeof(uint), _data, (2 * sizeof(uint)) + 4, MinLength - (2 * sizeof(uint))); break; + default: + _data = data; + break; } - unsafe - { - fixed (byte* p = _data) - { - FramebufferHeader* header = (FramebufferHeader*)p; - this = *header; - } - } + this = Unsafe.As(ref _data[0]); } #if HAS_BUFFERS @@ -91,14 +86,7 @@ public FramebufferHeader(ReadOnlySpan data) [< 2, ..] => [.. data[..(2 * sizeof(uint))], 0, 0, 0, 0, .. data[(2 * sizeof(uint))..]] }; - unsafe - { - fixed (byte* p = _data) - { - FramebufferHeader* header = (FramebufferHeader*)p; - this = *header; - } - } + this = Unsafe.As(ref MemoryMarshal.GetReference(_data)); } #endif diff --git a/AdvancedSharpAdbClient/Polyfills/Unsafe.cs b/AdvancedSharpAdbClient/Polyfills/Unsafe.cs new file mode 100644 index 0000000..eaecc59 --- /dev/null +++ b/AdvancedSharpAdbClient/Polyfills/Unsafe.cs @@ -0,0 +1,52 @@ +#if NETCOREAPP3_0_OR_GREATER +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.Unsafe))] +#else +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +namespace System.Runtime.CompilerServices +{ + /// + /// Contains generic, low-level functionality for manipulating managed and unmanaged pointers. + /// + public static class Unsafe + { + /// + /// Reinterprets the given managed pointer as a new managed pointer to a value of type . + /// + /// The type of managed pointer to reinterpret. + /// The desired type of the managed pointer. + /// The managed pointer to reinterpret. + /// A managed pointer to a value of type . + [MethodImpl((MethodImplOptions)0x100)] + public unsafe static ref TTo As(ref TFrom source) + where TFrom : unmanaged + where TTo : unmanaged + { + fixed (TFrom* ptr = &source) + { + return ref *(TTo*)ptr; + } + } + +#if HAS_BUFFERS + /// + /// Reinterprets the given location as a reference to a value of type . + /// + /// The elemental type of the managed pointer. + /// The read-only reference to reinterpret. + /// A mutable reference to a value of type . + /// The lifetime of the reference will not be validated when using this API. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe static ref T AsRef(scoped ref readonly T source) where T : unmanaged + { + fixed (T* ptr = &source) + { + return ref *ptr; + } + } +#endif + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index 7b29cf3..db04263 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -679,18 +679,13 @@ protected async Task ReadStatisticsAsync(CancellationToken cance #if COMP_NETSTANDARD2_1 Memory statResult = new byte[FileStatisticsData.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); - return EnumerableBuilder.FileStatisticsCreator(statResult.Span); + FileStatisticsData data = EnumerableBuilder.FileStatisticsDataCreator(statResult.Span); + return new FileStatistics(data); #else byte[] statResult = new byte[FileStatisticsData.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); - unsafe - { - fixed (byte* p = statResult) - { - FileStatisticsData* data = (FileStatisticsData*)p; - return new FileStatistics(*data); - } - } + ref FileStatisticsData data = ref Unsafe.As(ref statResult[0]); + return new FileStatistics(data); #endif } @@ -704,18 +699,13 @@ protected async Task ReadStatisticsV2Async(CancellationToken c #if COMP_NETSTANDARD2_1 Memory statResult = new byte[FileStatisticsDataEx.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); - return EnumerableBuilder.FileStatisticsV2Creator(statResult.Span); + FileStatisticsDataEx data = EnumerableBuilder.FileStatisticsDataV2Creator(statResult.Span); + return new FileStatisticsEx(data); #else byte[] statResult = new byte[FileStatisticsDataEx.Length]; _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); - unsafe - { - fixed (byte* p = statResult) - { - FileStatisticsDataEx* data = (FileStatisticsDataEx*)p; - return new FileStatisticsEx(*data); - } - } + ref FileStatisticsDataEx data = ref Unsafe.As(ref statResult[0]); + return new FileStatisticsEx(data); #endif } } diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index 18dfc18..f72557f 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Net; +using System.Runtime.CompilerServices; using System.Security; namespace AdvancedSharpAdbClient @@ -517,18 +518,13 @@ protected FileStatistics ReadStatistics() #if COMP_NETSTANDARD2_1 Span statResult = stackalloc byte[FileStatisticsData.Length]; _ = Socket.Read(statResult); - return EnumerableBuilder.FileStatisticsCreator(statResult); + FileStatisticsData data = EnumerableBuilder.FileStatisticsDataCreator(statResult); + return new FileStatistics(data); #else byte[] statResult = new byte[FileStatisticsData.Length]; _ = Socket.Read(statResult); - unsafe - { - fixed (byte* p = statResult) - { - FileStatisticsData* data = (FileStatisticsData*)p; - return new FileStatistics(*data); - } - } + ref FileStatisticsData data = ref Unsafe.As(ref statResult[0]); + return new FileStatistics(data); #endif } @@ -541,18 +537,13 @@ protected FileStatisticsEx ReadStatisticsV2() #if COMP_NETSTANDARD2_1 Span statResult = stackalloc byte[FileStatisticsDataEx.Length]; _ = Socket.Read(statResult); - return EnumerableBuilder.FileStatisticsV2Creator(statResult); + FileStatisticsDataEx data = EnumerableBuilder.FileStatisticsDataV2Creator(statResult); + return new FileStatisticsEx(data); #else byte[] statResult = new byte[FileStatisticsDataEx.Length]; _ = Socket.Read(statResult); - unsafe - { - fixed (byte* p = statResult) - { - FileStatisticsDataEx* data = (FileStatisticsDataEx*)p; - return new FileStatisticsEx(*data); - } - } + ref FileStatisticsDataEx data = ref Unsafe.As(ref statResult[0]); + return new FileStatisticsEx(data); #endif } } From 75964a3f6526bba191b89e96f098810ce16f36f1 Mon Sep 17 00:00:00 2001 From: where where Date: Mon, 12 Jan 2026 10:12:32 +0800 Subject: [PATCH 5/6] Add sync v2 tests --- .../AdbClientTests.Async.cs | 22 +- .../DeviceCommands/DeviceClientTexts.Async.cs | 6 +- .../DeviceExtensionsTests.Async.cs | 113 +++- .../DeviceCommands/DeviceExtensionsTests.cs | 68 ++- .../AdbClientExtensionsTests.Async.cs | 6 +- .../Extensions/EnumerableExtensions.cs | 34 ++ .../Extensions/EnumerableExtensionsTests.cs | 6 +- .../UnixFileStatusExtensionsTests.cs | 2 +- AdvancedSharpAdbClient.Tests/Logs/LogTests.cs | 4 +- .../Models/FramebufferTests.cs | 44 +- .../SyncServiceTests.Async.cs | 482 +++++++++++++++++- .../SyncServiceTests.cs | 261 +++++++++- AdvancedSharpAdbClient/AdbClient.cs | 2 +- .../Exceptions/JavaException.cs | 1 - .../Extensions/SyncCommandConverter.cs | 14 +- .../Extensions/UnixFileStatusExtensions.cs | 424 ++++++++------- .../Interfaces/IAdbClient.Async.cs | 9 +- .../Interfaces/IAdbSocket.Async.cs | 4 +- .../Interfaces/ISyncService.Async.cs | 4 +- .../Interfaces/ITcpSocket.Async.cs | 4 +- AdvancedSharpAdbClient/Models/DeviceData.cs | 10 +- .../Models/FileStatisticsData.V2.cs | 18 +- .../Polyfills/AsyncEnumerable.cs | 97 ++++ .../Extensions/EnumerableExtensions.cs | 69 +-- AdvancedSharpAdbClient/Polyfills/Unsafe.cs | 4 +- AdvancedSharpAdbClient/SyncService.Async.cs | 2 +- AdvancedSharpAdbClient/SyncService.cs | 1 - 27 files changed, 1326 insertions(+), 385 deletions(-) create mode 100644 AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensions.cs create mode 100644 AdvancedSharpAdbClient/Polyfills/AsyncEnumerable.cs diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs index 364d5f7..b870fcd 100644 --- a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs @@ -58,11 +58,11 @@ public async Task GetDevicesAsyncTest() string[] responseMessages = ["169.254.109.177:5555 device product:VS Emulator 5\" KitKat (4.4) XXHDPI Phone model:5__KitKat__4_4__XXHDPI_Phone device:donatello\n"]; string[] requests = ["host:devices-l"]; - DeviceData[] devices = await RunTestAsync( + List devices = await RunTestAsync( OkResponse, responseMessages, requests, - ctx => TestClient.GetDevicesAsync(ctx).ToArrayAsync(), + ctx => TestClient.GetDevicesAsync(ctx).ToListAsync(), TestContext.Current.CancellationToken); // Make sure and the correct value is returned. @@ -216,15 +216,15 @@ public async Task ListForwardAsyncTest() string[] responseMessages = ["169.254.109.177:5555 tcp:1 tcp:2\n169.254.109.177:5555 tcp:3 tcp:4\n169.254.109.177:5555 tcp:5 local:/socket/1\n"]; string[] requests = ["host-serial:169.254.109.177:5555:list-forward"]; - ForwardData[] forwards = await RunTestAsync( + List forwards = await RunTestAsync( OkResponse, responseMessages, requests, - ctx => TestClient.ListForwardAsync(Device, ctx).ToArrayAsync(), + ctx => TestClient.ListForwardAsync(Device, ctx).ToListAsync(), TestContext.Current.CancellationToken); Assert.NotNull(forwards); - Assert.Equal(3, forwards.Length); + Assert.Equal(3, forwards.Count); Assert.Equal("169.254.109.177:5555", forwards[0].SerialNumber); Assert.Equal("tcp:1", forwards[0].Local); Assert.Equal("tcp:2", forwards[0].Remote); @@ -244,15 +244,15 @@ public async Task ListReverseForwardAsyncTest() "reverse:list-forward" ]; - ForwardData[] forwards = await RunTestAsync( + List forwards = await RunTestAsync( OkResponses(2), responseMessages, requests, - ctx => TestClient.ListReverseForwardAsync(Device, ctx).ToArrayAsync(), + ctx => TestClient.ListReverseForwardAsync(Device, ctx).ToListAsync(), TestContext.Current.CancellationToken); Assert.NotNull(forwards); - Assert.Equal(3, forwards.Length); + Assert.Equal(3, forwards.Count); Assert.Equal("(reverse)", forwards[0].SerialNumber); Assert.Equal("localabstract:scrcpy", forwards[0].Local); Assert.Equal("tcp:100", forwards[0].Remote); @@ -1258,14 +1258,14 @@ public async Task GetFeatureSetAsyncTest() string[] requests = ["host-serial:169.254.109.177:5555:features"]; string[] responses = ["sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir,fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2\r\n"]; - string[] features = await RunTestAsync( + List features = await RunTestAsync( OkResponse, responses, requests, - ctx => TestClient.GetFeatureSetAsync(Device, ctx).ToArrayAsync(), + ctx => TestClient.GetFeatureSetAsync(Device, ctx).ToListAsync(), TestContext.Current.CancellationToken); - Assert.Equal(12, features.Length); + Assert.Equal(12, features.Count); Assert.Equal("sendrecv_v2_brotli", features.FirstOrDefault()); Assert.Equal("stat_v2", features.LastOrDefault()); } diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs index 6be5d9d..9d101eb 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs @@ -387,13 +387,13 @@ public async Task FindElementsAsyncTest() DummyAdbClient client = new(); client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); - Element[] elements = await new DeviceClient(client, Device).FindElementsAsync(cancellationToken: TestContext.Current.CancellationToken).ToArrayAsync(); + List elements = await new DeviceClient(client, Device).FindElementsAsync(cancellationToken: TestContext.Current.CancellationToken).ToListAsync(); Assert.Single(client.ReceivedCommands); Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); - int childCount = elements.Length; - Array.ForEach(elements, x => childCount += x.GetChildCount()); + int childCount = elements.Count; + elements.ForEach(x => childCount += x.GetChildCount()); Assert.Equal(145, childCount); Element element = elements[0][0][0][0][0][0][0][0][0][2][1][0][0]; Assert.Equal("where-where", element.Text); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs index 2518209..1455774 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs @@ -112,12 +112,45 @@ public async Task StatAsyncTest() Factories.SyncServiceFactory = (c, d) => { - Factories.Reset(); Assert.Equal(d, Device); return mock; }; - Assert.Equal(stats, await client.StatAsync(Device, remotePath, cancellationToken: TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.StatAsync(Device, remotePath, TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.StatAsync(Device, remotePath, false, TestContext.Current.CancellationToken)); + + Factories.Reset(); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task StatExAsyncTest() + { + const string remotePath = "/test"; + FileStatisticsEx stats = new(default); + + IAdbClient client = Substitute.For(); + ISyncService mock = Substitute.For(); + mock.StatExAsync(Arg.Any(), Arg.Any()) + .Returns(x => + { + Assert.Equal(remotePath, x.ArgAt(0)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(1)); + return stats; + }); + + Factories.SyncServiceFactory = (c, d) => + { + Assert.Equal(d, Device); + return mock; + }; + + Assert.Equal(stats, await client.StatExAsync(Device, remotePath, TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.StatAsync(Device, remotePath, true, TestContext.Current.CancellationToken)); + + Factories.Reset(); } /// @@ -141,12 +174,45 @@ public async Task GetDirectoryListingAsyncTest() Factories.SyncServiceFactory = (c, d) => { - Factories.Reset(); Assert.Equal(d, Device); return mock; }; - Assert.Equal(stats, await client.GetDirectoryListingAsync(Device, remotePath, cancellationToken: TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.GetDirectoryListingAsync(Device, remotePath, TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.GetDirectoryListingAsync(Device, remotePath, false, TestContext.Current.CancellationToken)); + + Factories.Reset(); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task GetDirectoryListingExAsyncTest() + { + const string remotePath = "/test"; + List stats = [new(default)]; + + IAdbClient client = Substitute.For(); + ISyncService mock = Substitute.For(); + mock.GetDirectoryListingExAsync(Arg.Any(), Arg.Any()) + .Returns(x => + { + Assert.Equal(remotePath, x.ArgAt(0)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(1)); + return stats; + }); + + Factories.SyncServiceFactory = (c, d) => + { + Assert.Equal(d, Device); + return mock; + }; + + Assert.Equal(stats, await client.GetDirectoryListingExAsync(Device, remotePath, TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.GetDirectoryListingAsync(Device, remotePath, true, TestContext.Current.CancellationToken)); + + Factories.Reset(); } /// @@ -165,17 +231,50 @@ public async Task GetDirectoryAsyncListingTest() { Assert.Equal(remotePath, x.ArgAt(0)); Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(1)); - return stats.ToAsyncEnumerable(x.ArgAt(1)); + return stats.ToAsyncEnumerable(); }); Factories.SyncServiceFactory = (c, d) => { - Factories.Reset(); Assert.Equal(d, Device); return mock; }; - Assert.Equal(stats, await client.GetDirectoryAsyncListing(Device, remotePath, cancellationToken: TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.GetDirectoryAsyncListing(Device, remotePath, TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.GetDirectoryAsyncListing(Device, remotePath, false, TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); + + Factories.Reset(); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task GetDirectoryAsyncListingExTest() + { + const string remotePath = "/test"; + List stats = [new(default)]; + + IAdbClient client = Substitute.For(); + ISyncService mock = Substitute.For(); + mock.GetDirectoryAsyncListingEx(Arg.Any(), Arg.Any()) + .Returns(x => + { + Assert.Equal(remotePath, x.ArgAt(0)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(1)); + return stats.ToAsyncEnumerable(); + }); + + Factories.SyncServiceFactory = (c, d) => + { + Assert.Equal(d, Device); + return mock; + }; + + Assert.Equal(stats, await client.GetDirectoryAsyncListingEx(Device, remotePath, TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); + Assert.Equal(stats, await client.GetDirectoryAsyncListing(Device, remotePath, true, TestContext.Current.CancellationToken).ToListAsync(cancellationToken: TestContext.Current.CancellationToken)); + + Factories.Reset(); } /// diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs index 7741f26..24ba2ae 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs @@ -111,12 +111,44 @@ public void StatTest() Factories.SyncServiceFactory = (c, d) => { - Factories.Reset(); Assert.Equal(d, Device); return mock; }; Assert.Equal(stats, client.Stat(Device, remotePath)); + Assert.Equal(stats, client.Stat(Device, remotePath, false)); + + Factories.Reset(); + } + + /// + /// Tests the method. + /// + [Fact] + public void StatExTest() + { + const string remotePath = "/test"; + FileStatisticsEx stats = new(default); + + IAdbClient client = Substitute.For(); + ISyncService mock = Substitute.For(); + mock.StatEx(Arg.Any()) + .Returns(x => + { + Assert.Equal(remotePath, x.ArgAt(0)); + return stats; + }); + + Factories.SyncServiceFactory = (c, d) => + { + Assert.Equal(d, Device); + return mock; + }; + + Assert.Equal(stats, client.StatEx(Device, remotePath)); + Assert.Equal(stats, client.Stat(Device, remotePath, true)); + + Factories.Reset(); } /// @@ -139,12 +171,44 @@ public void GetDirectoryListingTest() Factories.SyncServiceFactory = (c, d) => { - Factories.Reset(); Assert.Equal(d, Device); return mock; }; Assert.Equal(stats, client.GetDirectoryListing(Device, remotePath)); + Assert.Equal(stats, client.GetDirectoryListing(Device, remotePath, false)); + + Factories.Reset(); + } + + /// + /// Tests the method. + /// + [Fact] + public void GetDirectoryListingExTest() + { + const string remotePath = "/test"; + IEnumerable stats = [new(default)]; + + IAdbClient client = Substitute.For(); + ISyncService mock = Substitute.For(); + mock.GetDirectoryListingEx(Arg.Any()) + .Returns(x => + { + Assert.Equal(remotePath, x.ArgAt(0)); + return stats; + }); + + Factories.SyncServiceFactory = (c, d) => + { + Assert.Equal(d, Device); + return mock; + }; + + Assert.Equal(stats, client.GetDirectoryListingEx(Device, remotePath)); + Assert.Equal(stats, client.GetDirectoryListing(Device, remotePath, true)); + + Factories.Reset(); } /// diff --git a/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs index 53143c7..f8f8f4c 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs @@ -51,7 +51,7 @@ public async Task ExecuteServerCommandAsyncTest() Assert.Equal(command, x.ArgAt(1)); Assert.Equal(encoding, x.ArgAt(2)); Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(3)); - return result.ToAsyncEnumerable(x.ArgAt(3)); + return result.ToAsyncEnumerable(); }); _ = client.ExecuteServerEnumerableAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(x => @@ -61,7 +61,7 @@ public async Task ExecuteServerCommandAsyncTest() Assert.Equal(socket, x.ArgAt(2)); Assert.Equal(encoding, x.ArgAt(3)); Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(4)); - return result.ToAsyncEnumerable(x.ArgAt(4)); + return result.ToAsyncEnumerable(); }); await client.ExecuteServerCommandAsync(target, command, receiver, cancellationToken: TestContext.Current.CancellationToken); @@ -102,7 +102,7 @@ public async Task ExecuteRemoteCommandAsyncTest() Assert.Equal(device, x.ArgAt(1)); Assert.Equal(encoding, x.ArgAt(2)); Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(3)); - return result.ToAsyncEnumerable(x.ArgAt(3)); + return result.ToAsyncEnumerable(); }); await client.ExecuteRemoteCommandAsync(command, device, receiver, cancellationToken: TestContext.Current.CancellationToken); diff --git a/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensions.cs b/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..a410066 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensions.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AdvancedSharpAdbClient.Tests +{ + /// + /// Provides extension methods for the class. + /// + public static class EnumerableExtensions + { + /// + /// Asynchronously creates an list from a of . + /// + /// The type of the elements of . + /// An of to create an list from. + /// A which returns an list that contains the elements from the input sequence. + public static Task> ToListAsync(this Task> source) => + source.ContinueWith(x => x.Result.ToList()); + + /// + /// Asynchronously creates an array from a of . + /// + /// The type of the elements of . + /// An of to create an array from. + /// A which returns an array that contains the elements from the input sequence. + public static Task ToArrayAsync(this IEnumerable> source) => + source.WhenAll(); + } +} diff --git a/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs index 1d3a43b..0bd8075 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/EnumerableExtensionsTests.cs @@ -20,7 +20,7 @@ public void AddRangeTest() int[] numbs = [6, 7, 8, 9, 10]; List list = [1, 2, 3, 4, 5]; - list.AddRange(numbs); + EnumerableExtensions.AddRange(list, numbs); Assert.Equal(10, list.Count); Assert.Equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], list); @@ -36,7 +36,7 @@ public void AddRangeTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async Task TaskToArrayTest() @@ -45,7 +45,7 @@ public async Task TaskToArrayTest() Task> arrayTask = Task.Delay(10, TestContext.Current.CancellationToken).ContinueWith(_ => array.Select(x => x)); IEnumerable> taskArray = array.Select(x => Task.Delay(x, TestContext.Current.CancellationToken).ContinueWith(_ => x)); Assert.Equal(array, await taskArray.ToArrayAsync()); - Assert.Equal(array, await arrayTask.ToArrayAsync()); + Assert.Equal(array, await arrayTask.ToListAsync()); } } } diff --git a/AdvancedSharpAdbClient.Tests/Extensions/UnixFileStatusExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/Extensions/UnixFileStatusExtensionsTests.cs index cc26c3e..c1ce8fa 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/UnixFileStatusExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/UnixFileStatusExtensionsTests.cs @@ -165,7 +165,7 @@ public class UnixFileStatusExtensionsTests [InlineData("s-w--w--w-", (UnixFileStatus)0xC092)] [InlineData("?--x--x--x", (UnixFileStatus)0x0049)] [InlineData("7777", (UnixFileStatus)0x0FFF)] - public void FromPermissionCodeTest(string code, UnixFileStatus mode) => Assert.Equal(mode, UnixFileStatusExtensions.FromPermissionCode(code)); + public void FromPermissionCodeTest(string code, UnixFileStatus mode) => Assert.Equal(mode, UnixFileStatus.FromPermissionCode(code)); [Theory] [InlineData((UnixFileStatus)0x0FFF, "\0rwsrwsrwt")] diff --git a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs index b36553f..a698afe 100644 --- a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs +++ b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs @@ -1,7 +1,5 @@ -using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; -using System; +using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Xunit; diff --git a/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs b/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs index 56d5293..fdd9e81 100644 --- a/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs @@ -52,20 +52,20 @@ public void RefreshTest() FramebufferHeader header = framebuffer.Header; - Assert.Equal(8u, header.Alpha.Length); - Assert.Equal(24u, header.Alpha.Offset); - Assert.Equal(8u, header.Green.Length); - Assert.Equal(8u, header.Green.Offset); - Assert.Equal(8u, header.Red.Length); - Assert.Equal(0u, header.Red.Offset); - Assert.Equal(8u, header.Blue.Length); - Assert.Equal(16u, header.Blue.Offset); + Assert.Equal(1u, header.Version); Assert.Equal(32u, header.Bpp); + Assert.Equal(0u, header.ColorSpace); Assert.Equal(4u, header.Size); - Assert.Equal(1u, header.Height); Assert.Equal(1u, header.Width); - Assert.Equal(1u, header.Version); - Assert.Equal(0u, header.ColorSpace); + Assert.Equal(1u, header.Height); + Assert.Equal(0u, header.Red.Offset); + Assert.Equal(8u, header.Red.Length); + Assert.Equal(16u, header.Blue.Offset); + Assert.Equal(8u, header.Blue.Length); + Assert.Equal(8u, header.Green.Offset); + Assert.Equal(8u, header.Green.Length); + Assert.Equal(24u, header.Alpha.Offset); + Assert.Equal(8u, header.Alpha.Length); #if HAS_IMAGING if (!OperatingSystem.IsWindows()) { return; } @@ -115,20 +115,20 @@ public async Task RefreshAsyncTest() FramebufferHeader header = framebuffer.Header; - Assert.Equal(8u, header.Alpha.Length); - Assert.Equal(24u, header.Alpha.Offset); - Assert.Equal(8u, header.Green.Length); - Assert.Equal(8u, header.Green.Offset); - Assert.Equal(8u, header.Red.Length); - Assert.Equal(0u, header.Red.Offset); - Assert.Equal(8u, header.Blue.Length); - Assert.Equal(16u, header.Blue.Offset); + Assert.Equal(1u, header.Version); Assert.Equal(32u, header.Bpp); + Assert.Equal(0u, header.ColorSpace); Assert.Equal(4u, header.Size); - Assert.Equal(1u, header.Height); Assert.Equal(1u, header.Width); - Assert.Equal(1u, header.Version); - Assert.Equal(0u, header.ColorSpace); + Assert.Equal(1u, header.Height); + Assert.Equal(0u, header.Red.Offset); + Assert.Equal(8u, header.Red.Length); + Assert.Equal(16u, header.Blue.Offset); + Assert.Equal(8u, header.Blue.Length); + Assert.Equal(8u, header.Green.Offset); + Assert.Equal(8u, header.Green.Length); + Assert.Equal(24u, header.Alpha.Offset); + Assert.Equal(8u, header.Alpha.Length); #if HAS_IMAGING if (!OperatingSystem.IsWindows()) { return; } diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index faed6f0..b973817 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -139,21 +139,163 @@ public async Task GetListingAsyncTest() Assert.Equal((UnixFileStatus)16877, parentDir.FileMode); Assert.Equal(0u, parentDir.Size); Assert.Equal(time, parentDir.Time); - Assert.Equal($"drwxr-xr-x\t0\t{dir.Time}\t..", parentDir.ToString()); + Assert.Equal($"drwxr-xr-x\t0\t{parentDir.Time}\t..", parentDir.ToString()); FileStatistics sdcard0 = value[2]; Assert.Equal("sdcard0", sdcard0.Path); Assert.Equal((UnixFileStatus)41471, sdcard0.FileMode); Assert.Equal(24u, sdcard0.Size); Assert.Equal(time, sdcard0.Time); - Assert.Equal($"lrwxrwxrwx\t24\t{dir.Time}\tsdcard0", sdcard0.ToString()); + Assert.Equal($"lrwxrwxrwx\t24\t{sdcard0.Time}\tsdcard0", sdcard0.ToString()); FileStatistics emulated = value[3]; Assert.Equal("emulated", emulated.Path); Assert.Equal((UnixFileStatus)16749, emulated.FileMode); Assert.Equal(0u, emulated.Size); Assert.Equal(time, emulated.Time); - Assert.Equal($"dr-xr-xr-x\t0\t{dir.Time}\temulated", emulated.ToString()); + Assert.Equal($"dr-xr-xr-x\t0\t{emulated.Time}\temulated", emulated.ToString()); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task GetListingExAsyncTest() + { + List value = await RunTestAsync( + OkResponses(2), + [".", "..", "sdcard0", "emulated"], + ["host:transport:169.254.109.177:5555", "sync:"], + [(SyncCommand.LIS2, "/storage")], + [SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DONE], + [ + [ + 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, + 83, 14, 0, 0, 0, 0, 0, 0, + 233, 65, 0, 0, + 4, 0, 0, 0, + 208, 7, 0, 0, + 13, 39, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 5, 3, 1, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + 237, 65, 0, 0, + 27, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 153, 0, 0, 0, 0, 0, 0, 0, + 1, 192, 48, 0, 0, 0, 0, 0, + 255, 161, 0, 0, + 5, 0, 0, 0, + 0, 0, 0, 0, + 13, 39, 0, 0, + 24, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, + 84, 14, 0, 0, 0, 0, 0, 0, + 109, 65, 0, 0, + 2, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ] + ], + null, + async (ctx) => + { + using SyncService service = new(Socket, Device); + List value = await service.GetDirectoryListingExAsync("/storage", ctx); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + return value; + }, + TestContext.Current.CancellationToken); + + Assert.Equal(4, value.Count); + + DateTime time = new(2015, 11, 3, 9, 47, 4, DateTimeKind.Utc); + + FileStatisticsEx dir = value[0]; + Assert.Equal(".", dir.Path); + Assert.Equal(UnixErrorCode.Default, dir.Error); + Assert.Equal(19u, dir.Device); + Assert.Equal(3667u, dir.IndexNode); + Assert.Equal((UnixFileStatus)16873, dir.FileMode); + Assert.Equal(4u, dir.LinkCount); + Assert.Equal(0u, dir.Size); + Assert.Equal(2000u, dir.UserId); + Assert.Equal(9997u, dir.GroupId); + Assert.Equal(time, dir.AccessTime); + Assert.Equal(time, dir.ModifiedTime); + Assert.Equal(time, dir.ChangedTime); + Assert.Equal($"drwxr-x--x\t0\t{dir.ModifiedTime}\t.", dir.ToString()); + + FileStatisticsEx parentDir = value[1]; + Assert.Equal("..", parentDir.Path); + Assert.Equal(UnixErrorCode.Default, parentDir.Error); + Assert.Equal(66309u, parentDir.Device); + Assert.Equal(2u, parentDir.IndexNode); + Assert.Equal((UnixFileStatus)16877, parentDir.FileMode); + Assert.Equal(27u, parentDir.LinkCount); + Assert.Equal(0u, parentDir.Size); + Assert.Equal(0u, parentDir.UserId); + Assert.Equal(0u, parentDir.GroupId); + Assert.Equal(time, parentDir.AccessTime); + Assert.Equal(time, parentDir.ModifiedTime); + Assert.Equal(time, parentDir.ChangedTime); + Assert.Equal($"drwxr-xr-x\t0\t{parentDir.ModifiedTime}\t..", parentDir.ToString()); + + FileStatisticsEx sdcard0 = value[2]; + Assert.Equal("sdcard0", sdcard0.Path); + Assert.Equal(UnixErrorCode.Default, sdcard0.Error); + Assert.Equal(153u, sdcard0.Device); + Assert.Equal(3194881u, sdcard0.IndexNode); + Assert.Equal((UnixFileStatus)41471, sdcard0.FileMode); + Assert.Equal(5u, sdcard0.LinkCount); + Assert.Equal(24u, sdcard0.Size); + Assert.Equal(0u, sdcard0.UserId); + Assert.Equal(9997u, sdcard0.GroupId); + Assert.Equal(time, sdcard0.AccessTime); + Assert.Equal(time, sdcard0.ModifiedTime); + Assert.Equal(time, sdcard0.ChangedTime); + Assert.Equal($"lrwxrwxrwx\t24\t{sdcard0.ModifiedTime}\tsdcard0", sdcard0.ToString()); + + FileStatisticsEx emulated = value[3]; + Assert.Equal("emulated", emulated.Path); + Assert.Equal(UnixErrorCode.Default, emulated.Error); + Assert.Equal(19u, emulated.Device); + Assert.Equal(3668u, emulated.IndexNode); + Assert.Equal((UnixFileStatus)16749, emulated.FileMode); + Assert.Equal(2u, emulated.LinkCount); + Assert.Equal(0u, emulated.Size); + Assert.Equal(0u, emulated.UserId); + Assert.Equal(0u, emulated.GroupId); + Assert.Equal(time, emulated.AccessTime); + Assert.Equal(time, emulated.ModifiedTime); + Assert.Equal(time, emulated.ChangedTime); + Assert.Equal($"dr-xr-xr-x\t0\t{emulated.ModifiedTime}\temulated", emulated.ToString()); } /// @@ -178,7 +320,7 @@ public async Task GetAsyncListingTest() async (ctx) => { using SyncService service = new(Socket, Device); - List value = await service.GetDirectoryAsyncListing("/storage", ctx).ToListAsync(); + List value = await service.GetDirectoryAsyncListing("/storage", ctx).ToListAsync(cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); return value; @@ -218,6 +360,148 @@ public async Task GetAsyncListingTest() Assert.Equal($"dr-xr-xr-x\t0\t{dir.Time}\temulated", emulated.ToString()); } + /// + /// Tests the method. + /// + [Fact] + public async Task GetAsyncListingExTest() + { + List value = await RunTestAsync( + OkResponses(2), + [".", "..", "sdcard0", "emulated"], + ["host:transport:169.254.109.177:5555", "sync:"], + [(SyncCommand.LIS2, "/storage")], + [SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DONE], + [ + [ + 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, + 83, 14, 0, 0, 0, 0, 0, 0, + 233, 65, 0, 0, + 4, 0, 0, 0, + 208, 7, 0, 0, + 13, 39, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 5, 3, 1, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + 237, 65, 0, 0, + 27, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 153, 0, 0, 0, 0, 0, 0, 0, + 1, 192, 48, 0, 0, 0, 0, 0, + 255, 161, 0, 0, + 5, 0, 0, 0, + 0, 0, 0, 0, + 13, 39, 0, 0, + 24, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, + 84, 14, 0, 0, 0, 0, 0, 0, + 109, 65, 0, 0, + 2, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ] + ], + null, + async (ctx) => + { + using SyncService service = new(Socket, Device); + List value = await service.GetDirectoryAsyncListingEx("/storage", ctx).ToListAsync(cancellationToken: ctx); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + return value; + }, + TestContext.Current.CancellationToken); + + Assert.Equal(4, value.Count); + + DateTime time = new(2015, 11, 3, 9, 47, 4, DateTimeKind.Utc); + + FileStatisticsEx dir = value[0]; + Assert.Equal(".", dir.Path); + Assert.Equal(UnixErrorCode.Default, dir.Error); + Assert.Equal(19u, dir.Device); + Assert.Equal(3667u, dir.IndexNode); + Assert.Equal((UnixFileStatus)16873, dir.FileMode); + Assert.Equal(4u, dir.LinkCount); + Assert.Equal(0u, dir.Size); + Assert.Equal(2000u, dir.UserId); + Assert.Equal(9997u, dir.GroupId); + Assert.Equal(time, dir.AccessTime); + Assert.Equal(time, dir.ModifiedTime); + Assert.Equal(time, dir.ChangedTime); + Assert.Equal($"drwxr-x--x\t0\t{dir.ModifiedTime}\t.", dir.ToString()); + + FileStatisticsEx parentDir = value[1]; + Assert.Equal("..", parentDir.Path); + Assert.Equal(UnixErrorCode.Default, parentDir.Error); + Assert.Equal(66309u, parentDir.Device); + Assert.Equal(2u, parentDir.IndexNode); + Assert.Equal((UnixFileStatus)16877, parentDir.FileMode); + Assert.Equal(27u, parentDir.LinkCount); + Assert.Equal(0u, parentDir.Size); + Assert.Equal(0u, parentDir.UserId); + Assert.Equal(0u, parentDir.GroupId); + Assert.Equal(time, parentDir.AccessTime); + Assert.Equal(time, parentDir.ModifiedTime); + Assert.Equal(time, parentDir.ChangedTime); + Assert.Equal($"drwxr-xr-x\t0\t{parentDir.ModifiedTime}\t..", parentDir.ToString()); + + FileStatisticsEx sdcard0 = value[2]; + Assert.Equal("sdcard0", sdcard0.Path); + Assert.Equal(UnixErrorCode.Default, sdcard0.Error); + Assert.Equal(153u, sdcard0.Device); + Assert.Equal(3194881u, sdcard0.IndexNode); + Assert.Equal((UnixFileStatus)41471, sdcard0.FileMode); + Assert.Equal(5u, sdcard0.LinkCount); + Assert.Equal(24u, sdcard0.Size); + Assert.Equal(0u, sdcard0.UserId); + Assert.Equal(9997u, sdcard0.GroupId); + Assert.Equal(time, sdcard0.AccessTime); + Assert.Equal(time, sdcard0.ModifiedTime); + Assert.Equal(time, sdcard0.ChangedTime); + Assert.Equal($"lrwxrwxrwx\t24\t{sdcard0.ModifiedTime}\tsdcard0", sdcard0.ToString()); + + FileStatisticsEx emulated = value[3]; + Assert.Equal("emulated", emulated.Path); + Assert.Equal(UnixErrorCode.Default, emulated.Error); + Assert.Equal(19u, emulated.Device); + Assert.Equal(3668u, emulated.IndexNode); + Assert.Equal((UnixFileStatus)16749, emulated.FileMode); + Assert.Equal(2u, emulated.LinkCount); + Assert.Equal(0u, emulated.Size); + Assert.Equal(0u, emulated.UserId); + Assert.Equal(0u, emulated.GroupId); + Assert.Equal(time, emulated.AccessTime); + Assert.Equal(time, emulated.ModifiedTime); + Assert.Equal(time, emulated.ChangedTime); + Assert.Equal($"dr-xr-xr-x\t0\t{emulated.ModifiedTime}\temulated", emulated.ToString()); + } + /// /// Tests the method. /// @@ -246,7 +530,58 @@ await RunTestAsync( async (ctx) => { using SyncService service = new(Socket, Device); - await service.PullAsync("/fstab.donatello", stream, null, cancellationToken: ctx); + await service.PullAsync("/fstab.donatello", stream, cancellationToken: ctx); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }, + TestContext.Current.CancellationToken); + + // Make sure the data that has been sent to the stream is the expected data + Assert.Equal(content, stream.ToArray()); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task PullExAsyncTest() + { + await using MemoryStream stream = new(); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); + byte[] contentLength = BitConverter.GetBytes(content.Length); + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.STA2, "/fstab.donatello"), + (SyncCommand.RCV2, "/fstab.donatello"), + (SyncCommand.RCV2, "0") + ], + [SyncCommand.STA2, SyncCommand.DATA, SyncCommand.DONE], + [ + [ + 0, 0, 0, 0, + 167, 0, 0, 0, 0, 0, 0, 0, + 38, 240, 15, 0, 0, 0, 0, 0, + 160, 129, 0, 0, + 1, 0, 0, 0, + 146, 39, 0, 0, + 255, 3, 0, 0, + 85, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + ], + contentLength, + content + ], + null, + async (ctx) => + { + using SyncService service = new(Socket, Device); + await service.PullAsync("/fstab.donatello", stream, null, useV2: true, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); }, @@ -285,7 +620,44 @@ await RunTestAsync( async (ctx) => { 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, cancellationToken: ctx); + await service.PushAsync(stream, "/sdcard/test", UnixFileStatus.StickyBit | UnixFileStatus.UserWrite | UnixFileStatus.OtherRead, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), cancellationToken: ctx); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }, + TestContext.Current.CancellationToken); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task PushExAsyncTest() + { + FileStream stream = File.OpenRead("Assets/Fstab.bin"); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); + byte[] contentMessage = + [ + .. SyncCommand.DATA.GetBytes(), + .. BitConverter.GetBytes(content.Length), + .. content, + ]; + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.SND2, "/sdcard/test"), + (SyncCommand.SND2, "6440"), + (SyncCommand.DONE, "1446505200") + ], + [SyncCommand.OKAY], + null, + [contentMessage], + async (ctx) => + { + 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), useV2: true, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); }, @@ -364,7 +736,61 @@ await RunTestAsync( async (ctx) => { using SyncService service = new(Socket, Device); - await service.PullAsync("/fstab.donatello", stream, null, cancellationToken: ctx); + await service.PullAsync("/fstab.donatello", stream, cancellationToken: ctx); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }, + TestContext.Current.CancellationToken); + + IBuffer buffer = await stream.GetInputStreamAt(0).ReadAsync(new byte[(int)stream.Size].AsBuffer(), (uint)stream.Size, InputStreamOptions.None); + // Make sure the data that has been sent to the stream is the expected data + Assert.Equal(content, buffer.ToArray()); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task PullWinRTExAsyncTest() + { + if (!OperatingSystem.IsWindowsVersionAtLeast(10)) { return; } + + using InMemoryRandomAccessStream stream = new(); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); + byte[] contentLength = BitConverter.GetBytes(content.Length); + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.STA2, "/fstab.donatello"), + (SyncCommand.RCV2, "/fstab.donatello"), + (SyncCommand.RCV2, "0") + ], + [SyncCommand.STA2, SyncCommand.DATA, SyncCommand.DONE], + [ + [ + 0, 0, 0, 0, + 167, 0, 0, 0, 0, 0, 0, 0, + 38, 240, 15, 0, 0, 0, 0, 0, + 160, 129, 0, 0, + 1, 0, 0, 0, + 146, 39, 0, 0, + 255, 3, 0, 0, + 85, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + ], + contentLength, + content + ], + null, + async (ctx) => + { + using SyncService service = new(Socket, Device); + await service.PullAsync("/fstab.donatello", stream, useV2: true, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); }, @@ -407,7 +833,47 @@ await RunTestAsync( async (ctx) => { 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, cancellationToken: ctx); + await service.PushAsync(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), cancellationToken: ctx); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }, + TestContext.Current.CancellationToken); + } + + /// + /// Tests the method. + /// + [Fact] + public async Task PushWinRTExAsyncTest() + { + if (!OperatingSystem.IsWindowsVersionAtLeast(10)) { return; } + + StorageFile storageFile = await StorageFile.GetFileFromPathAsync(Path.GetFullPath("Assets/Fstab.bin")); + using IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync(); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin", TestContext.Current.CancellationToken); + byte[] contentMessage = + [ + .. SyncCommand.DATA.GetBytes(), + .. BitConverter.GetBytes(content.Length), + .. content, + ]; + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.SND2, "/sdcard/test"), + (SyncCommand.SND2, "6440"), + (SyncCommand.DONE, "1446505200") + ], + [SyncCommand.OKAY], + null, + [contentMessage], + async (ctx) => + { + using SyncService service = new(Socket, Device); + await service.PushAsync(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), useV2: true, cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); }, diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index 4f834bc..82926c8 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -61,19 +61,21 @@ public void StatExTest() ["host:transport:169.254.109.177:5555", "sync:"], [(SyncCommand.STA2, "/fstab.donatello")], [SyncCommand.STA2], - [[ - 0, 0, 0, 0, - 167, 0, 0, 0, 0, 0, 0, 0, - 38, 240, 15, 0, 0, 0, 0, 0, - 160, 129, 0, 0, - 1, 0, 0, 0, - 146, 39, 0, 0, - 255, 3, 0, 0, - 85, 2, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ]], + [ + [ + 0, 0, 0, 0, + 167, 0, 0, 0, 0, 0, 0, 0, + 38, 240, 15, 0, 0, 0, 0, 0, + 160, 129, 0, 0, + 1, 0, 0, 0, + 146, 39, 0, 0, + 255, 3, 0, 0, + 85, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + ] + ], null, () => { @@ -144,21 +146,162 @@ public void GetListingTest() Assert.Equal((UnixFileStatus)16877, parentDir.FileMode); Assert.Equal(0u, parentDir.Size); Assert.Equal(time, parentDir.Time); - Assert.Equal($"drwxr-xr-x\t0\t{dir.Time}\t..", parentDir.ToString()); + Assert.Equal($"drwxr-xr-x\t0\t{parentDir.Time}\t..", parentDir.ToString()); FileStatistics sdcard0 = value[2]; Assert.Equal("sdcard0", sdcard0.Path); Assert.Equal((UnixFileStatus)41471, sdcard0.FileMode); Assert.Equal(24u, sdcard0.Size); Assert.Equal(time, sdcard0.Time); - Assert.Equal($"lrwxrwxrwx\t24\t{dir.Time}\tsdcard0", sdcard0.ToString()); + Assert.Equal($"lrwxrwxrwx\t24\t{sdcard0.Time}\tsdcard0", sdcard0.ToString()); FileStatistics emulated = value[3]; Assert.Equal("emulated", emulated.Path); Assert.Equal((UnixFileStatus)16749, emulated.FileMode); Assert.Equal(0u, emulated.Size); Assert.Equal(time, emulated.Time); - Assert.Equal($"dr-xr-xr-x\t0\t{dir.Time}\temulated", emulated.ToString()); + Assert.Equal($"dr-xr-xr-x\t0\t{emulated.Time}\temulated", emulated.ToString()); + } + + /// + /// Tests the method. + /// + [Fact] + public void GetListingExTest() + { + FileStatisticsEx[] value = RunTest( + OkResponses(2), + [".", "..", "sdcard0", "emulated"], + ["host:transport:169.254.109.177:5555", "sync:"], + [(SyncCommand.LIS2, "/storage")], + [SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DNT2, SyncCommand.DONE], + [ + [ + 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, + 83, 14, 0, 0, 0, 0, 0, 0, + 233, 65, 0, 0, + 4, 0, 0, 0, + 208, 7, 0, 0, + 13, 39, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 5, 3, 1, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + 237, 65, 0, 0, + 27, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 153, 0, 0, 0, 0, 0, 0, 0, + 1, 192, 48, 0, 0, 0, 0, 0, + 255, 161, 0, 0, + 5, 0, 0, 0, + 0, 0, 0, 0, + 13, 39, 0, 0, + 24, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ], + [ + 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, + 84, 14, 0, 0, 0, 0, 0, 0, + 109, 65, 0, 0, + 2, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0, + 152, 130, 56, 86, 0, 0, 0, 0 + ] + ], + null, + () => + { + using SyncService service = new(Socket, Device); + FileStatisticsEx[] value = [.. service.GetDirectoryListingEx("/storage")]; + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + return value; + }); + + Assert.Equal(4, value.Length); + + DateTime time = new(2015, 11, 3, 9, 47, 4, DateTimeKind.Utc); + + FileStatisticsEx dir = value[0]; + Assert.Equal(".", dir.Path); + Assert.Equal(UnixErrorCode.Default, dir.Error); + Assert.Equal(19u, dir.Device); + Assert.Equal(3667u, dir.IndexNode); + Assert.Equal((UnixFileStatus)16873, dir.FileMode); + Assert.Equal(4u, dir.LinkCount); + Assert.Equal(0u, dir.Size); + Assert.Equal(2000u, dir.UserId); + Assert.Equal(9997u, dir.GroupId); + Assert.Equal(time, dir.AccessTime); + Assert.Equal(time, dir.ModifiedTime); + Assert.Equal(time, dir.ChangedTime); + Assert.Equal($"drwxr-x--x\t0\t{dir.ModifiedTime}\t.", dir.ToString()); + + FileStatisticsEx parentDir = value[1]; + Assert.Equal("..", parentDir.Path); + Assert.Equal(UnixErrorCode.Default, parentDir.Error); + Assert.Equal(66309u, parentDir.Device); + Assert.Equal(2u, parentDir.IndexNode); + Assert.Equal((UnixFileStatus)16877, parentDir.FileMode); + Assert.Equal(27u, parentDir.LinkCount); + Assert.Equal(0u, parentDir.Size); + Assert.Equal(0u, parentDir.UserId); + Assert.Equal(0u, parentDir.GroupId); + Assert.Equal(time, parentDir.AccessTime); + Assert.Equal(time, parentDir.ModifiedTime); + Assert.Equal(time, parentDir.ChangedTime); + Assert.Equal($"drwxr-xr-x\t0\t{parentDir.ModifiedTime}\t..", parentDir.ToString()); + + FileStatisticsEx sdcard0 = value[2]; + Assert.Equal("sdcard0", sdcard0.Path); + Assert.Equal(UnixErrorCode.Default, sdcard0.Error); + Assert.Equal(153u, sdcard0.Device); + Assert.Equal(3194881u, sdcard0.IndexNode); + Assert.Equal((UnixFileStatus)41471, sdcard0.FileMode); + Assert.Equal(5u, sdcard0.LinkCount); + Assert.Equal(24u, sdcard0.Size); + Assert.Equal(0u, sdcard0.UserId); + Assert.Equal(9997u, sdcard0.GroupId); + Assert.Equal(time, sdcard0.AccessTime); + Assert.Equal(time, sdcard0.ModifiedTime); + Assert.Equal(time, sdcard0.ChangedTime); + Assert.Equal($"lrwxrwxrwx\t24\t{sdcard0.ModifiedTime}\tsdcard0", sdcard0.ToString()); + + FileStatisticsEx emulated = value[3]; + Assert.Equal("emulated", emulated.Path); + Assert.Equal(UnixErrorCode.Default, emulated.Error); + Assert.Equal(19u, emulated.Device); + Assert.Equal(3668u, emulated.IndexNode); + Assert.Equal((UnixFileStatus)16749, emulated.FileMode); + Assert.Equal(2u, emulated.LinkCount); + Assert.Equal(0u, emulated.Size); + Assert.Equal(0u, emulated.UserId); + Assert.Equal(0u, emulated.GroupId); + Assert.Equal(time, emulated.AccessTime); + Assert.Equal(time, emulated.ModifiedTime); + Assert.Equal(time, emulated.ChangedTime); + Assert.Equal($"dr-xr-xr-x\t0\t{emulated.ModifiedTime}\temulated", emulated.ToString()); } /// @@ -198,6 +341,56 @@ public void PullTest() Assert.Equal(content, stream.ToArray()); } + /// + /// Tests the method. + /// + [Fact] + public void PullExTest() + { + using MemoryStream stream = new(); + byte[] content = File.ReadAllBytes("Assets/Fstab.bin"); + byte[] contentLength = BitConverter.GetBytes(content.Length); + + RunTest( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.STA2, "/fstab.donatello"), + (SyncCommand.RCV2, "/fstab.donatello"), + (SyncCommand.RCV2, "0") + ], + [SyncCommand.STA2, SyncCommand.DATA, SyncCommand.DONE], + [ + [ + 0, 0, 0, 0, + 167, 0, 0, 0, 0, 0, 0, 0, + 38, 240, 15, 0, 0, 0, 0, 0, + 160, 129, 0, 0, + 1, 0, 0, 0, + 146, 39, 0, 0, + 255, 3, 0, 0, + 85, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + ], + contentLength, + content + ], + null, + () => + { + using SyncService service = new(Socket, Device); + service.Pull("/fstab.donatello", stream, useV2: true); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }); + + // Make sure the data that has been sent to the stream is the expected data + Assert.Equal(content, stream.ToArray()); + } + /// /// Tests the method. /// @@ -233,6 +426,42 @@ .. BitConverter.GetBytes(content.Length), }); } + /// + /// Tests the method. + /// + [Fact] + public void PushExTest() + { + FileStream stream = File.OpenRead("Assets/Fstab.bin"); + byte[] content = File.ReadAllBytes("Assets/Fstab.bin"); + byte[] contentMessage = + [ + .. SyncCommand.DATA.GetBytes(), + .. BitConverter.GetBytes(content.Length), + .. content, + ]; + + RunTest( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.SND2, "/sdcard/test"), + (SyncCommand.SND2, "6440"), + (SyncCommand.DONE, "1446505200") + ], + [SyncCommand.OKAY], + null, + [contentMessage], + () => + { + using SyncService service = new(Socket, Device); + service.Push(stream, "/sdcard/test", (UnixFileStatus)644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), useV2: true); + Assert.False(service.IsProcessing); + Assert.True(service.IsOutdate); + }); + } + /// /// Tests the field. /// diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 914616b..44dbcc7 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -219,7 +219,7 @@ public static byte[] CreateAdbForwardRequest(ReadOnlySpan address, int por } #endif - /// + /// public int GetAdbVersion() { using IAdbSocket socket = CreateAdbSocket(); diff --git a/AdvancedSharpAdbClient/Exceptions/JavaException.cs b/AdvancedSharpAdbClient/Exceptions/JavaException.cs index d45547f..8600301 100644 --- a/AdvancedSharpAdbClient/Exceptions/JavaException.cs +++ b/AdvancedSharpAdbClient/Exceptions/JavaException.cs @@ -119,7 +119,6 @@ public static JavaException Parse(params IEnumerable lines) if (m.Success) { exception = m.Groups[1].Value; - message = m.Groups[2].Value; message = string.IsNullOrWhiteSpace(message) ? UnknownError : message; } diff --git a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs index a323390..95a8480 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs @@ -39,12 +39,7 @@ public static SyncCommand GetCommand(byte[] value) { ArgumentNullException.ThrowIfNull(value); - if (value.Length != 4) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - return (SyncCommand)BitConverter.ToInt32(value); + return value.Length != 4 ? throw new ArgumentOutOfRangeException(nameof(value)) : (SyncCommand)BitConverter.ToInt32(value); } #if HAS_BUFFERS @@ -55,12 +50,7 @@ public static SyncCommand GetCommand(byte[] value) /// The corresponding . public static SyncCommand GetCommand(ReadOnlySpan value) { - if (value.Length != 4) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - return (SyncCommand)BitConverter.ToInt32(value); + return value.Length != 4 ? throw new ArgumentOutOfRangeException(nameof(value)) : (SyncCommand)BitConverter.ToInt32(value); } #endif } diff --git a/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs b/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs index 2e6bcc0..6199f4d 100644 --- a/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/UnixFileStatusExtensions.cs @@ -14,255 +14,249 @@ namespace AdvancedSharpAdbClient.Models public static class UnixFileStatusExtensions { /// - /// Gets the type of the given file status. + /// The extension for the enum. /// /// File status to process. - /// The type of the file status. - public static UnixFileStatus GetFileType(this UnixFileStatus mode) => mode & UnixFileStatus.TypeMask; - - /// - /// Gets the permissions of the given file status. - /// - /// File status to process. - /// The permissions of the given file status. - public static UnixFileStatus GetPermissions(this UnixFileStatus mode) => mode & UnixFileStatus.AllPermissions; + extension(UnixFileStatus mode) + { + /// + /// Gets the type of the given file status. + /// + /// The type of the file status. + public UnixFileStatus GetFileType() => mode & UnixFileStatus.TypeMask; - /// - /// Gets the access permissions of the given file status. - /// - /// File status to process. - /// The access permissions of the given file status. - public static UnixFileStatus GetAccessPermissions(this UnixFileStatus mode) => mode & UnixFileStatus.AccessPermissions; + /// + /// Gets the permissions of the given file status. + /// + /// The permissions of the given file status. + public UnixFileStatus GetPermissions() => mode & UnixFileStatus.AllPermissions; - /// - /// Checks if the given file status corresponds to a directory, as if determined by . - /// - /// File status to check. - /// if the type indicated refers to a directory, otherwise . - public static bool IsDirectory(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Directory; + /// + /// Gets the access permissions of the given file status. + /// + /// The access permissions of the given file status. + public UnixFileStatus GetAccessPermissions() => mode & UnixFileStatus.AccessPermissions; - /// - /// Checks if the given file status or path corresponds to a character special file, as if determined by . - /// Examples of character special files are character devices such as /dev/null, /dev/tty, /dev/audio, or /dev/nvram on Linux. - /// - /// File status to check. - /// if the type indicated refers to a character device, otherwise . - public static bool IsCharacterFile(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Character; + /// + /// Checks if the given file status corresponds to a directory, as if determined by . + /// + /// if the type indicated refers to a directory, otherwise . + public bool IsDirectory() => mode.GetFileType() == UnixFileStatus.Directory; - /// - /// Checks if the given file status corresponds to a block special file, as if determined by . - /// Examples of block special files are block devices such as /dev/sda or /dev/loop0 on Linux. - /// - /// File status to check. - /// if the type indicated refers to a block device, otherwise . - public static bool IsBlockFile(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Block; + /// + /// Checks if the given file status or path corresponds to a character special file, as if determined by . + /// Examples of character special files are character devices such as /dev/null, /dev/tty, /dev/audio, or /dev/nvram on Linux. + /// + /// if the type indicated refers to a character device, otherwise . + public bool IsCharacterFile() => mode.GetFileType() == UnixFileStatus.Character; - /// - /// Checks if the given file status corresponds to a regular file, as if determined by . - /// - /// File status to check. - /// if the type indicated by refers to a regular file, otherwise . - public static bool IsRegularFile(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Regular; + /// + /// Checks if the given file status corresponds to a block special file, as if determined by . + /// Examples of block special files are block devices such as /dev/sda or /dev/loop0 on Linux. + /// + /// if the type indicated refers to a block device, otherwise . + public bool IsBlockFile() => mode.GetFileType() == UnixFileStatus.Block; - /// - /// Checks if the given file status corresponds to a FIFO or pipe file as if determined by . - /// - /// File status to check. - /// if the type indicated refers to a FIFO pipe, otherwise . - public static bool IsFIFO(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.FIFO; + /// + /// Checks if the given file status corresponds to a regular file, as if determined by . + /// + /// if the type indicated by refers to a regular file, otherwise . + public bool IsRegularFile() => mode.GetFileType() == UnixFileStatus.Regular; - /// - /// Checks if the given file status corresponds to a symbolic link, as if determined by . - /// - /// File status to check. - /// if the type indicated refers to a symbolic link, otherwise . - public static bool IsSymbolicLink(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.SymbolicLink; + /// + /// Checks if the given file status corresponds to a FIFO or pipe file as if determined by . + /// + /// if the type indicated refers to a FIFO pipe, otherwise . + public bool IsFIFO() => mode.GetFileType() == UnixFileStatus.FIFO; - /// - /// Checks if the given file status or path corresponds to a named IPC socket, as if determined by . - /// - /// File status to check. - /// if the type indicated refers to a named socket, otherwise . - public static bool IsSocket(this UnixFileStatus mode) => mode.GetFileType() == UnixFileStatus.Socket; + /// + /// Checks if the given file status corresponds to a symbolic link, as if determined by . + /// + /// if the type indicated refers to a symbolic link, otherwise . + public bool IsSymbolicLink() => mode.GetFileType() == UnixFileStatus.SymbolicLink; - /// - /// Checks if the given file status corresponds to a file of type other type. That is, the file exists, but is neither regular file, nor directory nor a symlink. - /// - /// File status to check. - /// if the type indicated refers to a file that is not regular file, directory, or a symlink, otherwise . - public static bool IsOther(this UnixFileStatus mode) => mode.GetFileType() is not (UnixFileStatus.Regular or UnixFileStatus.Directory or UnixFileStatus.SymbolicLink); + /// + /// Checks if the given file status or path corresponds to a named IPC socket, as if determined by . + /// + /// if the type indicated refers to a named socket, otherwise . + public bool IsSocket() => mode.GetFileType() == UnixFileStatus.Socket; - /// - /// Checks if the given file type is known, equivalent to mode.GetFileType() != . - /// - /// File type to check. - /// if the given file type is a known file type, otherwise . - public static bool IsTypeKnown(this UnixFileStatus mode) => mode.GetFileType() != UnixFileStatus.None; + /// + /// Checks if the given file status corresponds to a file of type other type. That is, the file exists, but is neither regular file, nor directory nor a symlink. + /// + /// if the type indicated refers to a file that is not regular file, directory, or a symlink, otherwise . + public bool IsOther() => mode.GetFileType() is not (UnixFileStatus.Regular or UnixFileStatus.Directory or UnixFileStatus.SymbolicLink); - /// - /// Parses a Unix permission code into a . - /// - /// The permission code to parse. - /// A representing the parsed permission code. - public static UnixFileStatus FromPermissionCode(string code) - { - ArgumentNullException.ThrowIfNull(code); + /// + /// Checks if the given file type is known, equivalent to mode.GetFileType() != . + /// + /// if the given file type is a known file type, otherwise . + public bool IsTypeKnown() => mode.GetFileType() != UnixFileStatus.None; - if (code.Length is not (9 or 10)) + /// + /// Parses a Unix permission code into a . + /// + /// The permission code to parse. + /// A representing the parsed permission code. + public static UnixFileStatus FromPermissionCode(string code) { - try - { - return (UnixFileStatus)Convert.ToInt32(code, 8); - } - catch + ArgumentNullException.ThrowIfNull(code); + + if (code.Length is not (9 or 10)) { - throw new ArgumentOutOfRangeException(nameof(code), $"The length of {nameof(code)} should be 9 or 10, but it is {code.Length}."); + try + { + return (UnixFileStatus)Convert.ToInt32(code, 8); + } + catch + { + throw new ArgumentOutOfRangeException(nameof(code), $"The length of {nameof(code)} should be 9 or 10, but it is {code.Length}."); + } } - } - UnixFileStatus mode = UnixFileStatus.None; - int index = code.Length; - - mode |= code[--index] switch - { - 'x' => UnixFileStatus.OtherExecute, - 't' => UnixFileStatus.StickyBit | UnixFileStatus.OtherExecute, - 'T' => UnixFileStatus.StickyBit, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 't', 'T' or '-', but it is {code[index]}.") - }; - mode |= code[--index] switch - { - 'w' => UnixFileStatus.OtherWrite, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.") - }; - mode |= code[--index] switch - { - 'r' => UnixFileStatus.OtherRead, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.") - }; + UnixFileStatus result = UnixFileStatus.None; + int index = code.Length; - mode |= code[--index] switch - { - 'x' => UnixFileStatus.GroupExecute, - 's' => UnixFileStatus.SetGroup | UnixFileStatus.GroupExecute, - 'S' => UnixFileStatus.SetGroup, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.") - }; - mode |= code[--index] switch - { - 'w' => UnixFileStatus.GroupWrite, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.") - }; - mode |= code[--index] switch - { - 'r' => UnixFileStatus.GroupRead, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.") - }; + result |= code[--index] switch + { + 'x' => UnixFileStatus.OtherExecute, + 't' => UnixFileStatus.StickyBit | UnixFileStatus.OtherExecute, + 'T' => UnixFileStatus.StickyBit, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 't', 'T' or '-', but it is {code[index]}.") + }; + result |= code[--index] switch + { + 'w' => UnixFileStatus.OtherWrite, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.") + }; + result |= code[--index] switch + { + 'r' => UnixFileStatus.OtherRead, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.") + }; - mode |= code[--index] switch - { - 'x' => UnixFileStatus.UserExecute, - 's' => UnixFileStatus.SetUser | UnixFileStatus.UserExecute, - 'S' => UnixFileStatus.SetUser, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.") - }; - mode |= code[--index] switch - { - 'w' => UnixFileStatus.UserWrite, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.") - }; - mode |= code[--index] switch - { - 'r' => UnixFileStatus.UserRead, - '-' => UnixFileStatus.None, - _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.") - }; + result |= code[--index] switch + { + 'x' => UnixFileStatus.GroupExecute, + 's' => UnixFileStatus.SetGroup | UnixFileStatus.GroupExecute, + 'S' => UnixFileStatus.SetGroup, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.") + }; + result |= code[--index] switch + { + 'w' => UnixFileStatus.GroupWrite, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.") + }; + result |= code[--index] switch + { + 'r' => UnixFileStatus.GroupRead, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.") + }; - if (index == 1) - { - mode |= code[0] switch + result |= code[--index] switch { - 'p' => UnixFileStatus.FIFO, - 'c' => UnixFileStatus.Character, - 'd' => UnixFileStatus.Directory, - 'b' => UnixFileStatus.Block, - '-' => UnixFileStatus.Regular, - 'l' => UnixFileStatus.SymbolicLink, - 's' => UnixFileStatus.Socket, - _ => UnixFileStatus.None + 'x' => UnixFileStatus.UserExecute, + 's' => UnixFileStatus.SetUser | UnixFileStatus.UserExecute, + 'S' => UnixFileStatus.SetUser, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'x', 's', 'S' or '-', but it is {code[index]}.") + }; + result |= code[--index] switch + { + 'w' => UnixFileStatus.UserWrite, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'w' or '-', but it is {code[index]}.") + }; + result |= code[--index] switch + { + 'r' => UnixFileStatus.UserRead, + '-' => UnixFileStatus.None, + _ => throw new ArgumentOutOfRangeException(nameof(code), $"The char of index {index} should be 'r' or '-', but it is {code[index]}.") }; - } - return mode; - } + if (index == 1) + { + result |= code[0] switch + { + 'p' => UnixFileStatus.FIFO, + 'c' => UnixFileStatus.Character, + 'd' => UnixFileStatus.Directory, + 'b' => UnixFileStatus.Block, + '-' => UnixFileStatus.Regular, + 'l' => UnixFileStatus.SymbolicLink, + 's' => UnixFileStatus.Socket, + _ => UnixFileStatus.None + }; + } - /// - /// Provides a string representation of the given . - /// - /// The to process. - /// A string representation of the given . - public static string ToPermissionCode(this UnixFileStatus mode) - { + return result; + } + + /// + /// Provides a string representation of the given . + /// + /// A string representation of the given . + public string ToPermissionCode() + { #if HAS_BUFFERS - Span code = stackalloc char[10]; + Span code = stackalloc char[10]; #else - char[] code = new char[10]; + char[] code = new char[10]; #endif - BitArray array = new([(int)mode]); + BitArray array = new([(int)mode]); - code[9] = array[0] - ? array[9] ? 't' : 'x' - : array[9] ? 'T' : '-'; - code[8] = array[1] ? 'w' : '-'; - code[7] = array[2] ? 'r' : '-'; + code[9] = array[0] + ? array[9] ? 't' : 'x' + : array[9] ? 'T' : '-'; + code[8] = array[1] ? 'w' : '-'; + code[7] = array[2] ? 'r' : '-'; - code[6] = array[3] - ? array[10] ? 's' : 'x' - : array[10] ? 'S' : '-'; - code[5] = array[4] ? 'w' : '-'; - code[4] = array[5] ? 'r' : '-'; + code[6] = array[3] + ? array[10] ? 's' : 'x' + : array[10] ? 'S' : '-'; + code[5] = array[4] ? 'w' : '-'; + code[4] = array[5] ? 'r' : '-'; - code[3] = array[6] - ? array[11] ? 's' : 'x' - : array[11] ? 'S' : '-'; - code[2] = array[7] ? 'w' : '-'; - code[1] = array[8] ? 'r' : '-'; + code[3] = array[6] + ? array[11] ? 's' : 'x' + : array[11] ? 'S' : '-'; + code[2] = array[7] ? 'w' : '-'; + code[1] = array[8] ? 'r' : '-'; - code[0] = mode.GetFileType() switch - { - UnixFileStatus.FIFO => 'p', - UnixFileStatus.Character => 'c', - UnixFileStatus.Directory => 'd', - UnixFileStatus.Block => 'b', - UnixFileStatus.Regular => '-', - UnixFileStatus.SymbolicLink => 'l', - UnixFileStatus.Socket => 's', - UnixFileStatus.None => '\0', - _ => '?' - }; + code[0] = mode.GetFileType() switch + { + UnixFileStatus.FIFO => 'p', + UnixFileStatus.Character => 'c', + UnixFileStatus.Directory => 'd', + UnixFileStatus.Block => 'b', + UnixFileStatus.Regular => '-', + UnixFileStatus.SymbolicLink => 'l', + UnixFileStatus.Socket => 's', + UnixFileStatus.None => '\0', + _ => '?' + }; - return new string(code); - } + return new string(code); + } - /// - /// Returns an enumerator that iterates through the . - /// - /// An enumerator that can be used to iterate through the . - public static IEnumerator GetEnumerator(this UnixFileStatus mode) - { - int num = (int)mode; - yield return (byte)num; - yield return (byte)(num >> 8); - yield return (byte)(num >> 16); - yield return (byte)(num >> 24); + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator that can be used to iterate through the . + public IEnumerator GetEnumerator() + { + int num = (int)mode; + yield return (byte)num; + yield return (byte)(num >> 8); + yield return (byte)(num >> 16); + yield return (byte)(num >> 24); + } } } } diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs index f32435e..94a8a0d 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading; @@ -238,7 +239,7 @@ public partial interface IAdbClient /// A which can be used to cancel the asynchronous operation. /// A of strings, each representing a line of output from the command. IAsyncEnumerable ExecuteServerEnumerableAsync(string target, string command, Encoding encoding, CancellationToken cancellationToken) => - ExecuteServerEnumerable(target, command, encoding).ToAsyncEnumerable(cancellationToken); + ExecuteServerEnumerable(target, command, encoding).ToAsyncEnumerable(); /// /// Asynchronously executes a command on the adb server and returns the output. @@ -251,7 +252,7 @@ IAsyncEnumerable ExecuteServerEnumerableAsync(string target, string comm /// A which can be used to cancel the asynchronous operation. /// A of strings, each representing a line of output from the command. IAsyncEnumerable ExecuteServerEnumerableAsync(string target, string command, IAdbSocket socket, Encoding encoding, CancellationToken cancellationToken) => - ExecuteServerEnumerable(target, command, socket, encoding).ToAsyncEnumerable(cancellationToken); + ExecuteServerEnumerable(target, command, socket, encoding).ToAsyncEnumerable(); /// /// Asynchronously executes a command on the device and returns the output. @@ -262,7 +263,7 @@ IAsyncEnumerable ExecuteServerEnumerableAsync(string target, string comm /// A which can be used to cancel the asynchronous operation. /// A of strings, each representing a line of output from the command. IAsyncEnumerable ExecuteRemoteEnumerableAsync(string command, DeviceData device, Encoding encoding, CancellationToken cancellationToken) => - ExecuteRemoteEnumerable(command, device, encoding).ToAsyncEnumerable(cancellationToken); + ExecuteRemoteEnumerable(command, device, encoding).ToAsyncEnumerable(); /// /// Asynchronously runs the event log service on a device and returns it. @@ -272,7 +273,7 @@ IAsyncEnumerable ExecuteRemoteEnumerableAsync(string command, DeviceData /// Optionally, the names of the logs to receive. /// A which contains the log entries. IAsyncEnumerable RunLogServiceAsync(DeviceData device, CancellationToken cancellationToken, params LogId[] logNames) => - RunLogService(device, logNames).ToAsyncEnumerable(cancellationToken); + RunLogService(device, logNames).ToAsyncEnumerable(); #endif /// diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs index 74845a1..fac9d44 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs @@ -151,7 +151,7 @@ public partial interface IAdbSocket /// A array that acts as a buffer, containing the data to send. /// A that can be used to cancel the task. /// A that represents the asynchronous operation. - public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default) => new(SendAsync(data.ToArray(), data.Length, cancellationToken)); + ValueTask SendAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default) => new(SendAsync(data.ToArray(), data.Length, cancellationToken)); /// /// Asynchronously receives data from a into a receive buffer. @@ -160,7 +160,7 @@ public partial interface IAdbSocket /// A that can be used to cancel the task. /// Cancelling the task will also close the socket. /// A that represents the asynchronous operation. The result value of the task contains the number of bytes received. - public ValueTask ReadAsync(Memory data, CancellationToken cancellationToken) + ValueTask ReadAsync(Memory data, CancellationToken cancellationToken) { byte[] bytes = new byte[data.Length]; return new(ReadAsync(bytes, bytes.Length, cancellationToken).ContinueWith(x => diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs index b1b97f1..09f920c 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs @@ -79,7 +79,7 @@ public partial interface ISyncService /// A that can be used to cancel the task. /// An which returns for each child item of the directory, a object with information of the item. IAsyncEnumerable GetDirectoryAsyncListing(string remotePath, CancellationToken cancellationToken) => - GetDirectoryListingAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(cancellationToken); + GetDirectoryListingAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(); /// /// Asynchronously lists the contents of a directory on the device (v2). @@ -89,7 +89,7 @@ IAsyncEnumerable GetDirectoryAsyncListing(string remotePath, Can /// An which returns for each child item of the directory, a object with information of the item. /// Need Android 11 or above. IAsyncEnumerable GetDirectoryAsyncListingEx(string remotePath, CancellationToken cancellationToken) => - GetDirectoryListingExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(cancellationToken); + GetDirectoryListingExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(); #endif /// diff --git a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs index b3edd70..72295d5 100644 --- a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs @@ -108,7 +108,7 @@ public partial interface ITcpSocket /// A bitwise combination of the SocketFlags values. /// A which can be used to cancel the asynchronous task. /// A which returns the number of bytes sent to the Socket. - public ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketFlags, CancellationToken cancellationToken) + ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketFlags, CancellationToken cancellationToken) => new(ReceiveAsync(buffer.ToArray(), buffer.Length, socketFlags, cancellationToken)); /// @@ -120,7 +120,7 @@ public ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketF /// A which can be used to cancel the asynchronous task. /// Cancelling the task will also close the socket. /// A which returns the number of bytes received. - public ValueTask ReceiveAsync(Memory buffer, SocketFlags socketFlags, CancellationToken cancellationToken) + ValueTask ReceiveAsync(Memory buffer, SocketFlags socketFlags, CancellationToken cancellationToken) { byte[] bytes = new byte[buffer.Length]; return new(ReceiveAsync(bytes, bytes.Length, socketFlags, cancellationToken).ContinueWith(x => diff --git a/AdvancedSharpAdbClient/Models/DeviceData.cs b/AdvancedSharpAdbClient/Models/DeviceData.cs index c38df1f..9fc3dfa 100644 --- a/AdvancedSharpAdbClient/Models/DeviceData.cs +++ b/AdvancedSharpAdbClient/Models/DeviceData.cs @@ -217,7 +217,7 @@ public override string ToString() builder.AppendLiteral(Serial); builder.AppendFormatted('\t'); - switch(State) + switch (State) { case DeviceState.Online: builder.AppendLiteral("device"); @@ -299,11 +299,9 @@ or DeviceState.Authorizing public static DeviceData EnsureDevice([NotNull] DeviceData? device, [CallerArgumentExpression(nameof(device))] string? paramName = "device") { ArgumentNullException.ThrowIfNull(device, paramName); - if (device.IsEmpty) - { - throw new ArgumentOutOfRangeException(paramName, "You must specific a transport ID or serial number for the device"); - } - return device; + return device.IsEmpty + ? throw new ArgumentOutOfRangeException(paramName, "You must specific a transport ID or serial number for the device") + : device; } /// diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs index 2302e0b..f74f57d 100644 --- a/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs @@ -37,7 +37,7 @@ public readonly record struct FileStatisticsDataEx(uint Error, ulong Device, ulo /// /// The length of in bytes. /// - public const int Length = 5 * sizeof(uint) + 3 * sizeof(ulong) + 3 * sizeof(long); + public const int Length = (5 * sizeof(uint)) + (3 * sizeof(ulong)) + (3 * sizeof(long)); /// /// Gets the error code associated with the current operation. @@ -60,49 +60,49 @@ public readonly record struct FileStatisticsDataEx(uint Error, ulong Device, ulo /// /// Gets the file mode value represented as an unsigned integer. /// - [field: FieldOffset(sizeof(uint) + 2 * sizeof(ulong))] + [field: FieldOffset(sizeof(uint) + (2 * sizeof(ulong)))] public uint Mode { get; init; } = Mode; /// /// Gets the number of hard links to the file or directory. /// - [field: FieldOffset(2 * sizeof(uint) + 2 * sizeof(ulong))] + [field: FieldOffset((2 * sizeof(uint)) + (2 * sizeof(ulong)))] public uint LinkCount { get; init; } = LinkCount; /// /// Gets the unique identifier of the user associated with this instance. /// - [field: FieldOffset(3 * sizeof(uint) + 2 * sizeof(ulong))] + [field: FieldOffset((3 * sizeof(uint)) + (2 * sizeof(ulong)))] public uint UserId { get; init; } = UserId; /// /// Gets the unique identifier for the group. /// - [field: FieldOffset(4 * sizeof(uint) + 2 * sizeof(ulong))] + [field: FieldOffset((4 * sizeof(uint)) + (2 * sizeof(ulong)))] public uint GroupId { get; init; } = GroupId; /// /// Gets the size, in bytes. /// - [field: FieldOffset(5 * sizeof(uint) + 2 * sizeof(ulong))] + [field: FieldOffset((5 * sizeof(uint)) + (2 * sizeof(ulong)))] public ulong Size { get; init; } = Size; /// /// Gets the last access time, in Unix timestamp format. /// - [field: FieldOffset(5 * sizeof(uint) + 3 * sizeof(ulong))] + [field: FieldOffset((5 * sizeof(uint)) + (3 * sizeof(ulong)))] public long AccessTime { get; init; } = AccessTime; /// /// Gets the timestamp indicating when the item was last modified. /// - [field: FieldOffset(5 * sizeof(uint) + 2 * sizeof(ulong) + sizeof(ulong))] + [field: FieldOffset((5 * sizeof(uint)) + (2 * sizeof(ulong)) + sizeof(ulong))] public long ModifiedTime { get; init; } = ModifiedTime; /// /// Gets the timestamp indicating when the last change occurred. /// - [field: FieldOffset(5 * sizeof(uint) + 2 * sizeof(ulong) + 2 * sizeof(ulong))] + [field: FieldOffset((5 * sizeof(uint)) + (2 * sizeof(ulong)) + (2 * sizeof(ulong)))] public long ChangedTime { get; init; } = ChangedTime; /// diff --git a/AdvancedSharpAdbClient/Polyfills/AsyncEnumerable.cs b/AdvancedSharpAdbClient/Polyfills/AsyncEnumerable.cs new file mode 100644 index 0000000..a213ac4 --- /dev/null +++ b/AdvancedSharpAdbClient/Polyfills/AsyncEnumerable.cs @@ -0,0 +1,97 @@ +#if COMP_NETSTANDARD2_1 && !NET10_0_OR_GREATER +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Threading; + +namespace System.Linq +{ + /// + /// Provides a set of static methods for querying objects that implement . + /// + internal static class AsyncEnumerable + { + /// + /// Creates a new that iterates through . + /// + /// The type of the elements of . + /// An of the elements to enumerate. + /// An containing the sequence of elements from . + /// is . + /// Each iteration through the resulting will iterate through the . + public static IAsyncEnumerable ToAsyncEnumerable(this IEnumerable source) + { + ArgumentNullException.ThrowIfNull(source); + + return source switch + { + TSource[] array => array.Length == 0 ? Empty() : FromArray(array), + List list => FromList(list), + IList list => FromIList(list), + _ when source == Enumerable.Empty() => Empty(), + _ => FromIterator(source), + }; + + static async IAsyncEnumerable FromArray(TSource[] source) + { + for (int i = 0; ; i++) + { + int localI = i; + TSource[] localSource = source; + if ((uint)localI >= (uint)localSource.Length) + { + break; + } + yield return localSource[localI]; + } + } + + static async IAsyncEnumerable FromList(List source) + { + for (int i = 0; i < source.Count; i++) + { + yield return source[i]; + } + } + + static async IAsyncEnumerable FromIList(IList source) + { + int count = source.Count; + for (int i = 0; i < count; i++) + { + yield return source[i]; + } + } + + static async IAsyncEnumerable FromIterator(IEnumerable source) + { + foreach (TSource element in source) + { + yield return element; + } + } + } + + /// + /// Returns an empty that has the specified type argument. + /// + /// The type of the elements of the sequence. + /// An empty whose type argument is . + public static IAsyncEnumerable Empty() => EmptyAsyncEnumerable.Instance; + + private sealed class EmptyAsyncEnumerable : IAsyncEnumerable, IAsyncEnumerator + { + public static readonly EmptyAsyncEnumerable Instance = new(); + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => this; + + public ValueTask MoveNextAsync() => default; + + public TResult Current => default!; + + public ValueTask DisposeAsync() => default; + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs index 6b81a70..3ae9e26 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs @@ -6,8 +6,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; namespace AdvancedSharpAdbClient.Polyfills { @@ -50,60 +48,35 @@ public static void AddRange(this ICollection source, params IE } } -#if HAS_TASK - /// - /// Asynchronously creates an array from a of . - /// - /// The type of the elements of . - /// An of to create an array from. - /// A which returns an array that contains the elements from the input sequence. - public static Task ToArrayAsync(this Task> source) => - source.ContinueWith(x => x.Result.ToArray()); - - /// - /// Asynchronously creates an array from a of . - /// - /// The type of the elements of . - /// An of to create an array from. - /// A which returns an array that contains the elements from the input sequence. - public static Task ToArrayAsync(this IEnumerable> source) => - source.WhenAll(); - #if COMP_NETSTANDARD2_1 /// - /// Returns the input typed as . - /// - /// The type of the elements of . - /// The sequence to type as . - /// A which can be used to cancel the asynchronous operation. - /// The input sequence typed as . - public static async IAsyncEnumerable ToAsyncEnumerable(this IEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using IEnumerator enumerator = source.GetEnumerator(); - while (!cancellationToken.IsCancellationRequested && enumerator.MoveNext()) - { - await Task.Yield(); - yield return enumerator.Current; - } - } - - /// - /// Returns the input typed as . + /// Creates a new that iterates through . /// /// The type of the elements of . - /// The sequence to type as . - /// A which can be used to cancel the asynchronous operation. - /// The input sequence typed as . - public static async IAsyncEnumerable ToAsyncEnumerable(this Task> source, [EnumeratorCancellation] CancellationToken cancellationToken = default) + /// An of the of the elements to enumerate. + /// An containing the sequence of elements from . + public static async IAsyncEnumerable ToAsyncEnumerable(this Task> source) { - using IEnumerator enumerator = await source.ContinueWith(x => x.Result.GetEnumerator()).ConfigureAwait(false); - while (!cancellationToken.IsCancellationRequested && enumerator.MoveNext()) + ArgumentNullException.ThrowIfNull(source); + IEnumerable enumerable = await source.ConfigureAwait(false); + switch (enumerable) { - await Task.Yield(); - yield return enumerator.Current; + case null: + throw new NullReferenceException("The source task completed to a null enumerable."); + case TSource[] array: + foreach (TSource item in array) + { + yield return item; + } + yield break; + default: + foreach (TSource item in enumerable) + { + yield return item; + } + yield break; } } -#endif #endif } } diff --git a/AdvancedSharpAdbClient/Polyfills/Unsafe.cs b/AdvancedSharpAdbClient/Polyfills/Unsafe.cs index eaecc59..487d9dc 100644 --- a/AdvancedSharpAdbClient/Polyfills/Unsafe.cs +++ b/AdvancedSharpAdbClient/Polyfills/Unsafe.cs @@ -20,7 +20,7 @@ public static class Unsafe /// The managed pointer to reinterpret. /// A managed pointer to a value of type . [MethodImpl((MethodImplOptions)0x100)] - public unsafe static ref TTo As(ref TFrom source) + public static unsafe ref TTo As(ref TFrom source) where TFrom : unmanaged where TTo : unmanaged { @@ -39,7 +39,7 @@ public unsafe static ref TTo As(ref TFrom source) /// A mutable reference to a value of type . /// The lifetime of the reference will not be validated when using this API. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe static ref T AsRef(scoped ref readonly T source) where T : unmanaged + public static unsafe ref T AsRef(scoped ref readonly T source) where T : unmanaged { fixed (T* ptr = &source) { diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index db04263..853b15a 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -144,7 +144,7 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi IsProcessing = false; } } - + /// public virtual async Task PullAsync(string remotePath, Stream stream, Action? callback = null, bool useV2 = false, CancellationToken cancellationToken = default) { diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index f72557f..283fa4c 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -8,7 +8,6 @@ using System.IO; using System.Net; using System.Runtime.CompilerServices; -using System.Security; namespace AdvancedSharpAdbClient { From 7797553bda23e0276ca71a7009c07b4570530f68 Mon Sep 17 00:00:00 2001 From: where where Date: Tue, 13 Jan 2026 11:31:44 +0800 Subject: [PATCH 6/6] Clarify file size limits and V2 requirements in docs Updated XML documentation comments to specify that file sizes above 4GB require V2 protocol, and V2 requires Android 11 or above in most cases. Added remarks about 4 GiB cutoff due to 32-bit unsigned integer usage in file statistics. These clarifications affect device, sync service, and model classes, as well as issue templates. --- .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/feature_request.yaml | 1 + .../DeviceCommands/DeviceExtensions.Async.cs | 10 ++++++++-- .../DeviceCommands/DeviceExtensions.cs | 7 ++++--- .../Extensions/SyncServiceExtensions.Async.cs | 8 +++++--- .../Extensions/SyncServiceExtensions.cs | 4 ++-- .../Interfaces/ISyncService.Async.cs | 4 ++-- AdvancedSharpAdbClient/Interfaces/ISyncService.cs | 3 ++- AdvancedSharpAdbClient/Models/FileStatistics.cs | 1 + AdvancedSharpAdbClient/Models/FileStatisticsData.cs | 1 + 10 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index d6a50ce..84d211c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -2,6 +2,7 @@ name: Bug report description: Create a report to help us improve title: Bug title labels: [bug] +type: Bug body: - type: textarea validations: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 968fc77..639c368 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -2,6 +2,7 @@ name: Feature request description: Suggest an idea for this project title: Feature title labels: [enhancement] +type: Feature body: - type: textarea validations: diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs index 9134f91..babc4cd 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs @@ -223,7 +223,7 @@ public static Task StopAppAsync(this IAdbClient client, DeviceData device, strin /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static async Task PullAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, Action? callback = null, @@ -265,6 +265,7 @@ public static async Task PushAsync(this IAdbClient client, DeviceData device, /// The path to the file. /// A that can be used to cancel the task. /// A which returns a object that contains information about the file. + /// The file size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. public static async Task StatAsync(this IAdbClient client, DeviceData device, string path, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); @@ -294,6 +295,7 @@ public static async Task StatExAsync(this IAdbClient client, D /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which returns a object that contains information about the file. + /// The file size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. public static Task StatAsync(this IAdbClient client, DeviceData device, string path, bool useV2, CancellationToken cancellationToken = default) => useV2 ? client.StatExAsync(device, path, cancellationToken).ContinueWith(x => x.Result as IFileStatistics) : client.StatAsync(device, path, cancellationToken).ContinueWith(x => x.Result as IFileStatistics); @@ -305,6 +307,7 @@ public static Task StatAsync(this IAdbClient client, DeviceData /// The path to the directory on the device. /// A that can be used to cancel the task. /// A which returns for each child item of the directory, a object with information of the item. + /// The file size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. public static async Task> GetDirectoryListingAsync(this IAdbClient client, DeviceData device, string remotePath, CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); @@ -334,6 +337,7 @@ public static async Task> GetDirectoryListingExAsync(this /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which returns for each child item of the directory, a object with information of the item. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static Task> GetDirectoryListingAsync(this IAdbClient client, DeviceData device, string remotePath, bool useV2, CancellationToken cancellationToken = default) => #if NETFRAMEWORK && !NET40_OR_GREATER useV2 ? client.GetDirectoryListingExAsync(device, remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()) : client.GetDirectoryListingAsync(device, remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()); @@ -350,6 +354,7 @@ public static Task> GetDirectoryListingAsync(this I /// The path to the directory on the device. /// A that can be used to cancel the task. /// An which returns for each child item of the directory, a object with information of the item. + /// The file size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. public static async IAsyncEnumerable GetDirectoryAsyncListing(this IAdbClient client, DeviceData device, string remotePath, [EnumeratorCancellation] CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); @@ -385,6 +390,7 @@ public static async IAsyncEnumerable GetDirectoryAsyncListingE /// to use ; otherwise, use . /// A that can be used to cancel the task. /// An which returns for each child item of the directory, a object with information of the item. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static IAsyncEnumerable GetDirectoryAsyncListing(this IAdbClient client, DeviceData device, string remotePath, bool useV2, CancellationToken cancellationToken = default) => useV2 ? client.GetDirectoryAsyncListingEx(device, remotePath, cancellationToken) : client.GetDirectoryAsyncListing(device, remotePath, cancellationToken); #endif @@ -497,7 +503,7 @@ public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceDat /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static async Task PullAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, IProgress? progress, diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs index 72dc537..6761601 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs @@ -183,7 +183,7 @@ public static void StopApp(this IAdbClient client, DeviceData device, string pac /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. /// to use and ; otherwise, use and . /// A that can be used to cancel the task. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static void Pull(this IAdbClient client, DeviceData device, string remotePath, Stream stream, Action? callback = null, @@ -224,6 +224,7 @@ public static void Push(this IAdbClient client, DeviceData device, /// The device on which to look for the file. /// The path to the file. /// A object that represents the file. + /// The file size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. public static FileStatistics Stat(this IAdbClient client, DeviceData device, string path) { using ISyncService service = Factories.SyncServiceFactory(client, device); @@ -252,7 +253,7 @@ public static FileStatisticsEx StatEx(this IAdbClient client, DeviceData device, /// The path to the file. /// to use ; otherwise, use . /// A object that represents the file. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 8 or above. public static IFileStatistics Stat(this IAdbClient client, DeviceData device, string path, bool useV2) => useV2 ? client.StatEx(device, path) : client.Stat(device, path); @@ -403,7 +404,7 @@ public static void InstallMultiplePackage(this IAdbClient client, DeviceData dev /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the file which has been transferred. /// to use and ; otherwise, use and . /// A that can be used to cancel the task. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static void Pull(this IAdbClient client, DeviceData device, string remotePath, Stream stream, IProgress? progress = null, diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs index 78450d3..e46c136 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs @@ -21,7 +21,7 @@ public static partial class SyncServiceExtensions /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which returns a object that contains information about the file. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 8 or above. public static Task StatAsync(this ISyncService service, string remotePath, bool useV2 = false, CancellationToken cancellationToken = default) => useV2 ? service.StatExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IFileStatistics) : service.StatAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IFileStatistics); @@ -33,6 +33,7 @@ public static Task StatAsync(this ISyncService service, string /// to use ; otherwise, use . /// A that can be used to cancel the task. /// A which returns for each child item of the directory, a object with information of the item. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static Task> GetDirectoryListingAsync(this ISyncService service, string remotePath, bool useV2 = false, CancellationToken cancellationToken = default) => #if NETFRAMEWORK && !NET40_OR_GREATER useV2 ? service.GetDirectoryListingExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()) : service.GetDirectoryListingAsync(remotePath, cancellationToken).ContinueWith(x => x.Result.OfType()); @@ -49,6 +50,7 @@ public static Task> GetDirectoryListingAsync(this I /// to use ; otherwise, use . /// A that can be used to cancel the task. /// An which returns for each child item of the directory, a object with information of the item. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static IAsyncEnumerable GetDirectoryAsyncListing(this ISyncService service, string remotePath, bool useV2 = false, CancellationToken cancellationToken = default) => useV2 ? service.GetDirectoryAsyncListingEx(remotePath, cancellationToken) : service.GetDirectoryAsyncListing(remotePath, cancellationToken); #endif @@ -79,7 +81,7 @@ public static Task PushAsync(this ISyncService service, Stream stream, string re /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static Task PullAsync(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, bool useV2 = false, CancellationToken cancellationToken = default) => service.PullAsync(remotePath, stream, progress.AsAction(), useV2, cancellationToken); @@ -109,7 +111,7 @@ public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stre /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static Task PullAsync(this ISyncService.IWinRT service, string remotePath, IOutputStream stream, IProgress? progress = null, bool useV2 = false, CancellationToken cancellationToken = default) => service.PullAsync(remotePath, stream, progress.AsAction(), useV2, cancellationToken); #endif diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs index 780d955..48e1f9d 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs @@ -21,7 +21,7 @@ public static partial class SyncServiceExtensions /// The path of the file on the device. /// to use ; otherwise, use . /// A object that contains information about the file. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 8 or above. public static IFileStatistics Stat(this ISyncService service, string remotePath, bool useV2 = false) => useV2 ? service.StatEx(remotePath) : service.Stat(remotePath); @@ -65,7 +65,7 @@ public static void Push(this ISyncService service, Stream stream, string remoteP /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. /// to use and ; otherwise, use and . /// A that can be used to cancel the task. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. public static void Pull(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, bool useV2 = false, in bool isCancelled = false) => service.Pull(remotePath, stream, progress.AsAction(), useV2, isCancelled); diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs index 09f920c..55cd575 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs @@ -34,7 +34,7 @@ public partial interface ISyncService /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. Task PullAsync(string remotePath, Stream stream, Action? callback, bool useV2, CancellationToken cancellationToken); /// @@ -134,7 +134,7 @@ public interface IWinRT /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. Task PullAsync(string remotePath, IOutputStream stream, Action? callback, bool useV2, CancellationToken cancellationToken); } #endif diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs index fcc44b6..03e6919 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs @@ -42,7 +42,7 @@ public partial interface ISyncService : IDisposable /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. /// to use and ; otherwise, use and . /// A that can be used to cancel the task. - /// V2 need Android 8 or above. + /// File size bigger than 4GB need V2, and V2 need Android 11 or above. void Pull(string remotePath, Stream stream, Action? callback, bool useV2, in bool isCancelled); /// @@ -50,6 +50,7 @@ public partial interface ISyncService : IDisposable /// /// The path of the file on the device. /// A object that contains information about the file. + /// The file size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. FileStatistics Stat(string remotePath); /// diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.cs b/AdvancedSharpAdbClient/Models/FileStatistics.cs index 121c3aa..d213a62 100644 --- a/AdvancedSharpAdbClient/Models/FileStatistics.cs +++ b/AdvancedSharpAdbClient/Models/FileStatistics.cs @@ -26,6 +26,7 @@ public sealed class FileStatistics(in FileStatisticsData data) : FileStatisticsB /// /// Gets the total file size, in bytes. /// + /// The size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. public uint Size => data.Size; /// diff --git a/AdvancedSharpAdbClient/Models/FileStatisticsData.cs b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs index bf8448f..3490978 100644 --- a/AdvancedSharpAdbClient/Models/FileStatisticsData.cs +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs @@ -40,6 +40,7 @@ public readonly record struct FileStatisticsData(uint Mode, uint Size, uint Time /// /// Gets the total file size, in bytes. /// + /// The size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. public uint Size { get; init; } = Size; ///