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.Tests/AdbClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs index ee23851..b870fcd 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); } /// @@ -56,11 +58,12 @@ 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, - async () => await TestClient.GetDevicesAsync().ToArrayAsync()); + ctx => TestClient.GetDevicesAsync(ctx).ToListAsync(), + 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); } /// @@ -204,14 +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, - async () => await TestClient.ListForwardAsync(Device).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); @@ -231,14 +244,15 @@ public async Task ListReverseForwardAsyncTest() "reverse:list-forward" ]; - ForwardData[] forwards = await RunTestAsync( + List forwards = await RunTestAsync( OkResponses(2), responseMessages, requests, - async () => await TestClient.ListReverseForwardAsync(Device).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); @@ -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); } /// @@ -1215,18 +1258,19 @@ 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, - async () => await TestClient.GetFeatureSetAsync(Device).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()); } - 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..9d101eb 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,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()).ToArray(); + 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); @@ -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 33cfb31..1455774 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]); @@ -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(); @@ -106,18 +106,51 @@ 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; }); Factories.SyncServiceFactory = (c, d) => { - Factories.Reset(); Assert.Equal(d, Device); return mock; }; - Assert.Equal(stats, await client.StatAsync(Device, remotePath)); + 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(); } /// @@ -127,7 +160,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(); @@ -135,18 +168,51 @@ 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; }); Factories.SyncServiceFactory = (c, d) => { - Factories.Reset(); Assert.Equal(d, Device); return mock; }; - Assert.Equal(stats, await client.GetDirectoryListingAsync(Device, remotePath)); + 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(); } /// @@ -156,7 +222,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(); @@ -164,18 +230,51 @@ public async Task GetDirectoryAsyncListingTest() .Returns(x => { Assert.Equal(remotePath, x.ArgAt(0)); - Assert.Equal(default, x.ArgAt(1)); - return stats.ToAsyncEnumerable(x.ArgAt(1)); + Assert.Equal(TestContext.Current.CancellationToken, 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).ToListAsync()); + 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(); } /// @@ -188,7 +287,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 +555,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 +595,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/DeviceExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs index d435585..24ba2ae 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(); @@ -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(); } /// @@ -126,7 +158,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(); @@ -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/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/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 f5008ad..d0ee04a 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs @@ -25,26 +25,26 @@ 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)); } } - public void Push(Stream stream, string remotePath, UnixFileStatus permission, DateTimeOffset timestamp, 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 (int i = 0; i <= 100; i++) + for (uint i = 0; i <= 100; i++) { if (i == 100) { @@ -54,9 +54,9 @@ 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 (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.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.GetDirectoryListingEx(string remotePath) => 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(); + FileStatisticsEx ISyncService.StatEx(string remotePath) => throw new NotImplementedException(); + + Task ISyncService.StatExAsync(string remotePath, CancellationToken cancellationToken) => throw new NotImplementedException(); + #endregion } } diff --git a/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/Extensions/AdbClientExtensionsTests.Async.cs index 2f55954..f8f8f4c 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,8 +50,8 @@ 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)); - return result.ToAsyncEnumerable(x.ArgAt(3)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(3)); + return result.ToAsyncEnumerable(); }); _ = client.ExecuteServerEnumerableAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(x => @@ -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)); - return result.ToAsyncEnumerable(x.ArgAt(4)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(4)); + return result.ToAsyncEnumerable(); }); - 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)); - return result.ToAsyncEnumerable(x.ArgAt(3)); + Assert.Equal(TestContext.Current.CancellationToken, x.ArgAt(3)); + return result.ToAsyncEnumerable(); }); - 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/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 d52c4de..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,16 +36,16 @@ public void AddRangeTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] 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()); + Assert.Equal(array, await arrayTask.ToListAsync()); } } } 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.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 8aab49f..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; @@ -49,7 +47,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 +64,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 +110,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 +134,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..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; } @@ -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); @@ -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/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 1f3c337..b973817 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()); @@ -41,6 +42,58 @@ 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 (ctx) => + { + using SyncService service = new(Socket, Device); + 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); + 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. /// @@ -60,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); @@ -85,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()); } /// @@ -121,14 +317,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(cancellationToken: ctx); Assert.False(service.IsProcessing); Assert.True(service.IsOutdate); return value; - }); + }, + TestContext.Current.CancellationToken); Assert.Equal(4, value.Count); @@ -164,13 +361,155 @@ public async Task GetAsyncListingTest() } /// - /// Tests the method. + /// 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. /// [Fact] 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( @@ -188,26 +527,78 @@ 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, 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. + /// 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); + }, + 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 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(), @@ -226,13 +617,51 @@ 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), 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); + }, + TestContext.Current.CancellationToken); } /// @@ -254,30 +683,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() @@ -285,7 +715,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( @@ -303,13 +733,68 @@ await RunTestAsync( content ], null, - async () => + async (ctx) => + { + using SyncService service = new(Socket, Device); + 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, null); + await service.PullAsync("/fstab.donatello", stream, useV2: true, 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 @@ -317,7 +802,7 @@ await RunTestAsync( } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async Task PushWinRTAsyncTest() @@ -326,7 +811,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(), @@ -345,13 +830,54 @@ 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), 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), null); + 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); - }); + }, + TestContext.Current.CancellationToken); } #endif } diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index f5e99e4..82926c8 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -49,6 +49,59 @@ 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. /// @@ -93,25 +146,166 @@ 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()); } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void PullTest() @@ -148,7 +342,57 @@ public void PullTest() } /// - /// Tests the method. + /// 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. /// [Fact] public void PushTest() @@ -182,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.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/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..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(); @@ -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 f91fa73..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) { @@ -221,7 +240,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..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) { @@ -305,7 +324,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..babc4cd 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. + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. + /// 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, + 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); } /// @@ -241,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); } /// @@ -260,12 +265,40 @@ 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); 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. + /// 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); + /// /// Asynchronously lists the contents of a directory on the device. /// @@ -274,12 +307,44 @@ public static async Task StatAsync(this IAdbClient client, Devic /// 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); 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. + /// 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()); +#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. @@ -289,6 +354,7 @@ public static async Task> GetDirectoryListingAsync(this IAd /// 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); @@ -297,6 +363,36 @@ 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. + /// 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 /// @@ -404,15 +500,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. + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. /// A which represents the asynchronous operation. + /// 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, + 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); } /// @@ -425,15 +524,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); } /// @@ -500,15 +601,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); } /// @@ -521,15 +624,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 362e805..6761601 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. + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. + /// 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, + 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); } /// @@ -201,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); } /// @@ -219,12 +224,39 @@ 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); 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 FileStatisticsEx StatEx(this IAdbClient client, DeviceData device, string path) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + 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. + /// 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); + /// /// Lists the contents of a directory on the device. /// @@ -241,6 +273,39 @@ public static IEnumerable GetDirectoryListing(this IAdbClient cl } } + /// + /// 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. + /// Need Android 11 or above. + public static IEnumerable GetDirectoryListingEx(this IAdbClient client, DeviceData device, string remotePath) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + 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. /// @@ -337,14 +402,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. + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. + /// 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, + 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); } /// @@ -357,15 +425,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); } /// @@ -426,14 +496,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); } /// @@ -446,14 +519,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/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/EnumerableBuilder.cs b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs index e3782fe..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,9 +31,7 @@ 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) => Unsafe.As(ref MemoryMarshal.GetReference(values)); /// /// Build a struct. @@ -41,29 +41,25 @@ public static ColorData ColorDataCreator(ReadOnlySpan values) => public static FramebufferHeader FramebufferHeaderCreator(ReadOnlySpan values) => new(values); /// - /// Build a struct. + /// Build a struct. /// - /// The data that feeds the struct. - /// A new instance of struct. - public static FileStatistics FileStatisticsCreator(ReadOnlySpan values) - { - int index = 0; - return new FileStatistics - { - 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))); - } + /// The data that feeds the struct. + /// A new instance of struct. + 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) => Unsafe.As(ref MemoryMarshal.GetReference(values)); /// /// Build a enum. /// /// 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 +272,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..95a8480 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.LST2) - { - 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 . @@ -55,13 +39,7 @@ public static SyncCommand GetCommand(byte[] value) { ArgumentNullException.ThrowIfNull(value); - if (value.Length != 4) - { - 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 value.Length != 4 ? throw new ArgumentOutOfRangeException(nameof(value)) : (SyncCommand)BitConverter.ToInt32(value); } #if HAS_BUFFERS @@ -72,13 +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)); - } - - 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 value.Length != 4 ? throw new ArgumentOutOfRangeException(nameof(value)) : (SyncCommand)BitConverter.ToInt32(value); } #endif } diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs index f2df095..e46c136 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs @@ -1,16 +1,61 @@ -#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. + /// 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); + + /// + /// 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. + /// 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()); +#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. + /// 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 + +#if !NETFRAMEWORK || NET40_OR_GREATER /// /// Asynchronously pushes (uploads) a file to the remote device. /// @@ -20,10 +65,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,10 +78,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. + /// to use and ; otherwise, use and . /// 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); + /// 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); #if HAS_WINRT /// @@ -47,10 +95,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. @@ -59,10 +108,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. + /// to use and ; otherwise, use and . /// 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); + /// 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 #if NET7_0_OR_GREATER @@ -75,10 +126,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. @@ -89,10 +141,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 /// @@ -104,10 +157,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. @@ -118,10 +172,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 c9b55e4..48e1f9d 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. + /// 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); + + /// + /// 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,9 +63,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. + /// to use and ; otherwise, use and . /// 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); + /// 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); #if NET7_0_OR_GREATER /// @@ -47,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. @@ -60,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/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/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..fac9d44 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 @@ -141,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. @@ -150,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/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 e41a39c..55cd575 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,9 +31,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. + /// to use and ; otherwise, use and . /// 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); + /// 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); /// /// Asynchronously returns information about a file on the device. @@ -42,6 +45,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 StatExAsync(string remotePath, CancellationToken cancellationToken); + /// /// Asynchronously lists the contents of a directory on the device. /// @@ -50,6 +62,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> GetDirectoryListingExAsync(string remotePath, CancellationToken cancellationToken); + #if COMP_NETSTANDARD2_1 /// /// Asynchronously lists the contents of a directory on the device. @@ -58,7 +79,17 @@ 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). + /// + /// 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 GetDirectoryAsyncListingEx(string remotePath, CancellationToken cancellationToken) => + GetDirectoryListingExAsync(remotePath, cancellationToken).ContinueWith(x => x.Result as IEnumerable).ToAsyncEnumerable(); #endif /// @@ -89,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. @@ -99,9 +131,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. + /// to use and ; otherwise, use and . /// 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); + /// 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 1945f64..03e6919 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,16 +40,27 @@ 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. + /// to use and ; otherwise, use and . /// A that can be used to cancel the task. - void Pull(string remotePath, Stream stream, Action? callback, in bool isCancelled); + /// 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); /// /// Returns information about a file on the device. /// /// 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); + /// + /// 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. + FileStatisticsEx StatEx(string remotePath); + /// /// Lists the contents of a directory on the device. /// @@ -55,6 +68,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 (v2). + /// + /// 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 GetDirectoryListingEx(string remotePath); + /// /// Opens this connection. /// 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/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..22eef9b 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,32 @@ 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() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Size); +#endif } } 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/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/SyncCommand.cs b/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs index 08c8a72..b7fc8cb 100644 --- a/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs +++ b/AdvancedSharpAdbClient/Models/Enums/SyncCommand.cs @@ -12,61 +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), + + /// + /// List the files in a folder v2. + /// + /// Need Android 11 or above. + LIS2 = 'L' | ('I' << 8) | ('S' << 16) | ('2' << 24), /// /// A directory entry. /// - DENT, + DENT = 'D' | ('E' << 8) | ('N' << 16) | ('T' << 24), /// - /// The operation has completed. + /// A directory entry v2. /// - DONE, + DNT2 = 'D' | ('N' << 8) | ('T' << 16) | ('2' << 24), /// - /// Marks the start of a data packet. + /// Send a file to device. /// - DATA, + SEND = 'S' | ('E' << 8) | ('N' << 16) | ('D' << 24), /// - /// The server has acknowledged the request. + /// Retrieve a file from device v2. /// - OKAY, + SND2 = 'S' | ('N' << 8) | ('D' << 16) | ('2' << 24), /// - /// The operation has failed. + /// Retrieve a file from device. /// - FAIL, + RECV = 'R' | ('E' << 8) | ('C' << 16) | ('V' << 24), + + /// + /// Retrieve a file from device v2. + /// + RCV2 = 'R' | ('C' << 8) | ('V' << 16) | ('2' << 24), + + /// + /// The operation has completed. + /// + DONE = 'D' | ('O' << 8) | ('N' << 16) | ('E' << 24), + + /// + /// Marks the start of a data packet. + /// + DATA = 'D' | ('A' << 8) | ('T' << 16) | ('A' << 24), /// /// The server has acknowledged the request. /// - QUIT, + OKAY = 'O' | ('K' << 8) | ('A' << 16) | ('Y' << 24), /// - /// Stat a file v2. + /// The operation has failed. /// - STA2, + FAIL = 'F' | ('A' << 8) | ('I' << 16) | ('L' << 24), /// - /// Stat a list v2. + /// The server has acknowledged the request. /// - LST2 + QUIT = 'Q' | ('U' << 8) | ('I' << 16) | ('T' << 24) } } 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/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.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 new file mode 100644 index 0000000..7f603f6 --- /dev/null +++ b/AdvancedSharpAdbClient/Models/FileStatistics.V2.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace AdvancedSharpAdbClient.Models +{ + /// + /// Contains information about a file on the remote device (v2). + /// + /// + [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(in FileStatisticsDataEx data) : FileStatisticsBase(data), IFileStatistics +#if NET7_0_OR_GREATER + , IEqualityOperators +#endif + { + /// + /// Gets the error code associated with the current operation. + /// + public UnixErrorCode Error => (UnixErrorCode)data.Error; + + /// + /// Gets the device identifier associated with this instance. + /// + public ulong Device => data.Device; + + /// + /// Gets the index node identifier associated with this entry. + /// + public ulong IndexNode => data.IndexNode; + + /// + /// Gets the attributes of the file. + /// + public UnixFileStatus FileMode => (UnixFileStatus)data.Mode; + + /// + /// Gets the number of hard links to the file or directory. + /// + public uint LinkCount => data.LinkCount; + + /// + /// Gets the unique identifier of the user associated with this instance. + /// + public uint UserId => data.UserId; + + /// + /// Gets the unique identifier for the group. + /// + public uint GroupId => data.GroupId; + + /// + /// Gets the size, in bytes. + /// + public ulong Size => data.Size; + + /// + /// Gets the last access time. + /// + public DateTimeOffset AccessTime => DateTimeOffset.FromUnixTimeSeconds(data.AccessTime); + + /// + /// Gets the time indicating when the item was last modified. + /// + public DateTimeOffset ModifiedTime => DateTimeOffset.FromUnixTimeSeconds(data.ModifiedTime); + + /// + /// Gets the time indicating when the last change occurred. + /// + public DateTimeOffset ChangedTime => DateTimeOffset.FromUnixTimeSeconds(data.ChangedTime); + + /// + DateTimeOffset IFileStatistics.Time => ModifiedTime; + + /// + public override string ToString() => string.Join("\t", FileMode.ToPermissionCode()!, Size, ModifiedTime, Path!); + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => base.Equals(obj); + + /// + /// 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 ==(FileStatisticsEx? left, FileStatisticsEx? right) => left == (right as FileStatisticsBase); + + /// + /// 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 !=(FileStatisticsEx? left, FileStatisticsEx? right) => !(left == right); + + /// + public override int GetHashCode() => base.GetHashCode(); + } +} diff --git a/AdvancedSharpAdbClient/Models/FileStatistics.cs b/AdvancedSharpAdbClient/Models/FileStatistics.cs index 3a212ba..d213a62 100644 --- a/AdvancedSharpAdbClient/Models/FileStatistics.cs +++ b/AdvancedSharpAdbClient/Models/FileStatistics.cs @@ -3,105 +3,63 @@ // using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace AdvancedSharpAdbClient.Models { /// /// Contains information about a file on the remote device. /// - /// -#if HAS_BUFFERS - [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.FileStatisticsCreator))] -#endif + /// The data of the file. [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 sealed class FileStatistics(in FileStatisticsData data) : FileStatisticsBase(data), IFileStatistics #if NET7_0_OR_GREATER , IEqualityOperators #endif { /// - /// Initializes a new instance of the struct. - /// - public FileStatistics() { } - - /// - /// Gets or sets the path of the file. - /// - public string Path { get; set; } = string.Empty; - - /// - /// Gets or sets the attributes of the file. - /// - public UnixFileStatus FileMode { get; init; } - - /// - /// Gets or sets the total file size, in bytes. + /// Gets the attributes of the file. /// - public uint Size { get; init; } + public UnixFileStatus FileMode => (UnixFileStatus)data.Mode; /// - /// Gets or sets the time of last modification. + /// Gets the total file size, in bytes. /// - public DateTimeOffset Time { get; init; } + /// The size will be cut off at 4 GiB due to the use of a 32-bit unsigned integer. + public uint Size => data.Size; /// - /// Returns an enumerator that iterates through the . + /// Gets the time of last modification. /// - /// 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 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 - && FileMode == other.FileMode - && Size == other.Size - && Time == other.Time; + 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, FileMode, Size, Time); + 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 new file mode 100644 index 0000000..f74f57d --- /dev/null +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.V2.cs @@ -0,0 +1,137 @@ +// +// 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 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 +#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 (FileStatisticsDataEx* 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() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), 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..3490978 --- /dev/null +++ b/AdvancedSharpAdbClient/Models/FileStatisticsData.cs @@ -0,0 +1,138 @@ +// +// 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. + /// + /// 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; + + /// + /// 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 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 f3b9b43..abf663e 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,43 @@ 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, ..]: + 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; } - Bpp = ReadUInt32(data); - - if (Version >= 2) - { - ColorSpace = ReadUInt32(data); - } - - 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)); + this = Unsafe.As(ref _data[0]); } #if HAS_BUFFERS @@ -95,53 +73,20 @@ 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); - - if (Version >= 2) - { - ColorSpace = ReadUInt32(data); - } - - 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)); + [> 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))..]] + }; - uint ReadUInt32(in ReadOnlySpan data) => (uint)(data[index++] | (data[index++] << 8) | (data[index++] << 16) | (data[index++] << 24)); + this = Unsafe.As(ref MemoryMarshal.GetReference(_data)); } #endif @@ -198,7 +143,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 +203,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/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/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/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/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/Polyfills/Unsafe.cs b/AdvancedSharpAdbClient/Polyfills/Unsafe.cs new file mode 100644 index 0000000..487d9dc --- /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 static unsafe 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 static unsafe 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 9345251..853b15a 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. @@ -106,7 +115,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(); @@ -137,7 +146,7 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi } /// - 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,16 +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) { @@ -177,7 +194,7 @@ public virtual async Task PullAsync(string remotePath, Stream stream, Action MaxBufferSize) { @@ -195,7 +212,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."); } @@ -231,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. @@ -293,7 +319,7 @@ public virtual async Task PushAsync(IInputStream stream, string remotePath, Unix await Socket.SendAsync(buffer, startPosition, (int)(read + dataBytes.Length + lengthBytes.Length), cancellationToken).ConfigureAwait(false); #endif // Let the caller know about our progress, if requested - progress?.Invoke(new SyncProgressChangedEventArgs((long)totalBytesRead, (long)totalBytesToProcess)); + progress?.Invoke(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); // check if we're canceled cancellationToken.ThrowIfCancellationRequested(); @@ -324,7 +350,7 @@ public virtual async Task PushAsync(IInputStream stream, string remotePath, Unix } /// - 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,16 +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. - FileStatistics stat = await StatAsync(remotePath, cancellationToken).ConfigureAwait(false); - long totalBytesToProcess = stat.Size; - long totalBytesRead = 0; + 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) { @@ -364,7 +398,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 +451,24 @@ public async Task StatAsync(string remotePath, CancellationToken return value; } + /// + public async Task StatExAsync(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}."); + } + + FileStatisticsEx value = await ReadStatisticsV2Async(cancellationToken).ConfigureAwait(false); + value.Path = remotePath; + + return value; + } + /// public async Task> GetDirectoryListingAsync(string remotePath, CancellationToken cancellationToken = default) { @@ -467,6 +519,56 @@ public async Task> GetDirectoryListingAsync(string remotePa } } + /// + 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 = []; + + 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}."); + } + + FileStatisticsEx 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 +618,55 @@ public async IAsyncEnumerable GetDirectoryAsyncListing(string re IsProcessing = false; } } + + /// + 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); } + 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}."); + } + + FileStatisticsEx 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 +677,35 @@ 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); + FileStatisticsData data = EnumerableBuilder.FileStatisticsDataCreator(statResult.Span); + return new FileStatistics(data); #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 - { - 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))); + ref FileStatisticsData data = ref Unsafe.As(ref statResult[0]); + 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[FileStatisticsDataEx.Length]; + _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); + FileStatisticsDataEx data = EnumerableBuilder.FileStatisticsDataV2Creator(statResult.Span); + return new FileStatisticsEx(data); +#else + byte[] statResult = new byte[FileStatisticsDataEx.Length]; + _ = await Socket.ReadAsync(statResult, cancellationToken).ConfigureAwait(false); + 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 e9f1e23..283fa4c 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; 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. @@ -215,7 +225,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 @@ -243,7 +253,7 @@ public virtual void Push(Stream stream, string remotePath, UnixFileStatus permis } /// - 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,16 +263,24 @@ public virtual void Pull(string remoteFilePath, Stream stream, Action MaxBufferSize) { @@ -301,7 +319,7 @@ public virtual void Pull(string remoteFilePath, Stream stream, Action + public FileStatisticsEx StatEx(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}."); + } + + FileStatisticsEx value = ReadStatisticsV2(); + value.Path = remotePath; + + return value; + } + /// public IEnumerable GetDirectoryListing(string remotePath) { @@ -385,6 +421,55 @@ public IEnumerable GetDirectoryListing(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(); } + 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}."); + } + + FileStatisticsEx 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 +515,34 @@ 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); + FileStatisticsData data = EnumerableBuilder.FileStatisticsDataCreator(statResult); + return new FileStatistics(data); #else - byte[] statResult = new byte[12]; + byte[] statResult = new byte[FileStatisticsData.Length]; _ = Socket.Read(statResult); - int index = 0; - return new FileStatistics - { - 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))); + ref FileStatisticsData data = ref Unsafe.As(ref statResult[0]); + return new FileStatistics(data); +#endif + } + + /// + /// Reads the statistics of a file from the socket (v2). + /// + /// A object that contains information about the file. + protected FileStatisticsEx ReadStatisticsV2() + { +#if COMP_NETSTANDARD2_1 + Span statResult = stackalloc byte[FileStatisticsDataEx.Length]; + _ = Socket.Read(statResult); + FileStatisticsDataEx data = EnumerableBuilder.FileStatisticsDataV2Creator(statResult); + return new FileStatisticsEx(data); +#else + byte[] statResult = new byte[FileStatisticsDataEx.Length]; + _ = Socket.Read(statResult); + ref FileStatisticsDataEx data = ref Unsafe.As(ref statResult[0]); + return new FileStatisticsEx(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);