mirror of https://github.com/dahall/Vanara.git
Corrected problem with test code for DoSvc (#465).
parent
7837934295
commit
f652be90ab
|
@ -2,51 +2,110 @@
|
||||||
using NUnit.Framework.Internal;
|
using NUnit.Framework.Internal;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Vanara.Utilities;
|
using Vanara.Utilities;
|
||||||
using static Vanara.PInvoke.DOSvc;
|
using static Vanara.PInvoke.DOSvc;
|
||||||
|
using static Vanara.PInvoke.Ole32;
|
||||||
|
|
||||||
namespace Vanara.PInvoke.Tests;
|
namespace Vanara.PInvoke.Tests;
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class DOSvcTests
|
public class DOSvcTests
|
||||||
{
|
{
|
||||||
[Test]
|
private const string name = "Test download";
|
||||||
public async void Test()
|
private readonly string uri = "https://github.com/EWSoftware/SHFB/releases/download/2023.7.8.0/SHFBInstaller_2023.7.8.0.zip";
|
||||||
{
|
|
||||||
const string name = "Test download";
|
|
||||||
string id = Guid.NewGuid().ToString("N");
|
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void _Setup() => CoInitializeSecurity(PSECURITY_DESCRIPTOR.NULL, -1, null, default, Rpc.RPC_C_AUTHN_LEVEL.RPC_C_AUTHN_LEVEL_DEFAULT,
|
||||||
|
Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE, dwCapabilities: EOLE_AUTHENTICATION_CAPABILITIES.EOAC_STATIC_CLOAKING).ThrowIfFailed();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestAsync()
|
||||||
|
{
|
||||||
using TemporaryDirectory tempRoot = new();
|
using TemporaryDirectory tempRoot = new();
|
||||||
string uri = "https://github.com/EWSoftware/SHFB/releases/download/2023.7.8.0/SHFBInstaller_2023.7.8.0.zip";
|
|
||||||
string dest = tempRoot.RandomTxtFileFullPath;
|
string dest = tempRoot.RandomTxtFileFullPath;
|
||||||
|
|
||||||
|
var tw = TestContext.Out;
|
||||||
Progress<DO_DOWNLOAD_STATUS> progress = new();
|
Progress<DO_DOWNLOAD_STATUS> progress = new();
|
||||||
progress.ProgressChanged += (s, e) => TestContext.WriteLine(e.State);
|
progress.ProgressChanged += (s, e) => tw.WriteLine($"{e.State} - {100*e.BytesTransferred/e.BytesTotal}%");
|
||||||
CancellationTokenSource cancel = new();
|
CancellationTokenSource cancel = new();
|
||||||
await Downloader.DownloadAsync(uri, dest, progress, cancel.Token, true, name, id);
|
var ret = await Downloader.DownloadAsync(uri, dest, true, name, true, null, progress, null, cancel.Token);
|
||||||
Assert.That(File.Exists(dest), Is.True);
|
Assert.That(File.Exists(dest), Is.True);
|
||||||
|
Assert.That(ret, Is.Not.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
//IDOManager mgr = new();
|
[Test]
|
||||||
//IDODownload dnld = mgr.CreateDownload();
|
public void TestAsyncCancel()
|
||||||
//CoSetProxyBlanket(dnld, dwImpLevel: Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE/*, dwCapabilities: EOLE_AUTHENTICATION_CAPABILITIES.EOAC_STATIC_CLOAKING*/).ThrowIfFailed();
|
{
|
||||||
|
using TemporaryDirectory tempRoot = new();
|
||||||
|
string dest = tempRoot.RandomTxtFileFullPath;
|
||||||
|
|
||||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_Uri, uri), Throws.Nothing);
|
var tw = TestContext.Out;
|
||||||
//Assert.That((string)dnld.GetProperty(DODownloadProperty.DODownloadProperty_Uri), Is.EqualTo(uri));
|
Progress<DO_DOWNLOAD_STATUS> progress = new();
|
||||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority, true), Throws.Nothing);
|
progress.ProgressChanged += (s, e) => tw.WriteLine($"{e.State} - {100*e.BytesTransferred/e.BytesTotal}%");
|
||||||
//Assert.That((bool)dnld.GetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority), Is.True);
|
CancellationTokenSource cancel = new();
|
||||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_LocalPath, dest), Throws.Nothing);
|
var task = Downloader.DownloadAsync(uri, dest, false, name, true, null, progress, null, cancel.Token);
|
||||||
//Assert.That((string)dnld.GetProperty(DODownloadProperty.DODownloadProperty_LocalPath), Is.EqualTo(dest));
|
cancel.Cancel();
|
||||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_DisplayName, name), Throws.Nothing);
|
Assert.That(task.IsCanceled, Is.True);
|
||||||
//Assert.That((string)dnld.GetProperty(DODownloadProperty.DODownloadProperty_DisplayName), Is.EqualTo(name));
|
}
|
||||||
|
|
||||||
//AutoResetEvent done = new(false);
|
[Test]
|
||||||
//Callback callback = new();
|
public void Test()
|
||||||
//callback.StatusChange += (s) => { if (s.State == DODownloadState.DODownloadState_Finalized) done.Set(); else if (s.Error.Failed) throw s.Error.GetException()!; };
|
{
|
||||||
//object wrp = new UnknownWrapper(callback);
|
SetupDownload(out var dnld, out var tempRoot, out var dest, out var done);
|
||||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_CallbackInterface, wrp), Throws.Nothing);
|
|
||||||
|
|
||||||
//dnld.Start();
|
dnld.Start();
|
||||||
//done.WaitOne();
|
Assert.That(done.WaitOne(TimeSpan.FromMinutes(1)), Is.True);
|
||||||
//dnld.Finalize();
|
dnld.Finalize();
|
||||||
|
Thread.Sleep(250);
|
||||||
|
Assert.That(File.Exists(dest), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAbort()
|
||||||
|
{
|
||||||
|
SetupDownload(out var dnld, out var tempRoot, out var dest, out var done);
|
||||||
|
|
||||||
|
dnld.Start();
|
||||||
|
dnld.Abort();
|
||||||
|
Assert.That(dnld.GetStatus().State, Is.EqualTo(DODownloadState.DODownloadState_Aborted));
|
||||||
|
Assert.That(() => dnld.Finalize(), Throws.Exception);
|
||||||
|
Assert.That(File.Exists(dest), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupDownload(out IDODownload dnld, out TemporaryDirectory tempRoot, out string dest, out AutoResetEvent doneEvent)
|
||||||
|
{
|
||||||
|
IDOManager mgr = new();
|
||||||
|
IDODownload download = dnld = mgr.CreateDownload();
|
||||||
|
CoSetProxyBlanket(download, dwImpLevel: Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE/*, dwCapabilities: EOLE_AUTHENTICATION_CAPABILITIES.EOAC_STATIC_CLOAKING*/).ThrowIfFailed();
|
||||||
|
|
||||||
|
var done = doneEvent = new(false);
|
||||||
|
Callback callback = new();
|
||||||
|
var tw = TestContext.Out;
|
||||||
|
callback.StatusChange += (s) => { tw.WriteLine("========"); tw.WriteLine(s.GetStringVal()); if (s.State is DODownloadState.DODownloadState_Transferred or DODownloadState.DODownloadState_Finalized) done.Set(); else if (s.Error.Failed) throw s.Error.GetException()!; };
|
||||||
|
Assert.That(() => download.SetProperty(DODownloadProperty.DODownloadProperty_CallbackInterface, callback), Throws.Nothing);
|
||||||
|
|
||||||
|
tempRoot = new();
|
||||||
|
string destination = dest = tempRoot.RandomTxtFileFullPath;
|
||||||
|
Assert.That(() => download.SetProperty(DODownloadProperty.DODownloadProperty_Uri, uri), Throws.Nothing);
|
||||||
|
Assert.That((string)download.GetProperty(DODownloadProperty.DODownloadProperty_Uri), Is.EqualTo(uri));
|
||||||
|
Assert.That(() => download.SetProperty(DODownloadProperty.DODownloadProperty_LocalPath, destination), Throws.Nothing);
|
||||||
|
Assert.That((string)download.GetProperty(DODownloadProperty.DODownloadProperty_LocalPath), Is.EqualTo(dest));
|
||||||
|
Assert.That(() => download.SetProperty(DODownloadProperty.DODownloadProperty_DisplayName, name), Throws.Nothing);
|
||||||
|
Assert.That((string)download.GetProperty(DODownloadProperty.DODownloadProperty_DisplayName), Is.EqualTo(name));
|
||||||
|
Assert.That(() => download.SetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority, true), Throws.Nothing);
|
||||||
|
Assert.That((bool)download.GetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComVisible(true), Guid("90AFD61C-C21C-4627-8A9A-E3268BC89051")]
|
||||||
|
public class Callback : IDODownloadStatusCallback
|
||||||
|
{
|
||||||
|
public event Action<DO_DOWNLOAD_STATUS>? StatusChange;
|
||||||
|
|
||||||
|
HRESULT IDODownloadStatusCallback.OnStatusChange(IDODownload download, in DO_DOWNLOAD_STATUS status)
|
||||||
|
{
|
||||||
|
StatusChange?.Invoke(status);
|
||||||
|
return HRESULT.S_OK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,127 +7,99 @@ using Vanara.PInvoke;
|
||||||
using static Vanara.PInvoke.DOSvc;
|
using static Vanara.PInvoke.DOSvc;
|
||||||
using static Vanara.PInvoke.Ole32;
|
using static Vanara.PInvoke.Ole32;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
namespace Vanara.Utilities
|
namespace Vanara.Utilities
|
||||||
{
|
{
|
||||||
public static class Downloader
|
public static class Downloader
|
||||||
{
|
{
|
||||||
public static async Task<byte[]> DownloadAsync(string url, string destPath, IProgress<DO_DOWNLOAD_STATUS>? progress = null, CancellationToken cancellationToken = default, bool computeHash = true, string? displayName = null, string? contentId = null)
|
/// <summary>Downloads the asynchronous.</summary>
|
||||||
|
/// <param name="url">The remote URI path of the resource to download.</param>
|
||||||
|
/// <param name="destPath">The local path name to save the download file. If the path does not exist, Delivery Optimization will attempt to create it under
|
||||||
|
/// the caller's privileges.</param>
|
||||||
|
/// <param name="computeHash">if set to <see langword="true" /> [compute hash].</param>
|
||||||
|
/// <param name="displayName">Optional. Use this property to set the download display name.</param>
|
||||||
|
/// <param name="foregroundPriority">Optional. Use this property to set or get the current download priority. <see langword="true" /> value will bring the download to
|
||||||
|
/// the foreground with higher priority. The default is background priority.</param>
|
||||||
|
/// <param name="ranges">Optional. An array of <c>DO_DOWNLOAD_RANGE</c> structures (to download only specific ranges of the file). Pass <see langword="null" /> to download the entire file.</param>
|
||||||
|
/// <param name="progress">Optional. A <see cref="IProgress{T}" /> instance to receive status information.</param>
|
||||||
|
/// <param name="timeout">Optional. A timeout for the download. If the timeout expires, the download will be aborted.</param>
|
||||||
|
/// <param name="cancellationToken">Optional. A cancellation token.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// If <paramref name="computeHash" /> is <see langword="true" />, a <see cref="byte" /> array with a SHA256 hash of the resulting file.
|
||||||
|
/// </returns>
|
||||||
|
public static Task<byte[]> DownloadAsync(string url, string destPath, bool computeHash = true, string? displayName = null,
|
||||||
|
bool foregroundPriority = false, DO_DOWNLOAD_RANGE[]? ranges = null, IProgress<DO_DOWNLOAD_STATUS>? progress = null,
|
||||||
|
TimeSpan? timeout = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
File.Delete(destPath);
|
// Get interfaces and set security
|
||||||
|
CoInitializeSecurity(PSECURITY_DESCRIPTOR.NULL, -1, null, default, Rpc.RPC_C_AUTHN_LEVEL.RPC_C_AUTHN_LEVEL_DEFAULT,
|
||||||
|
Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE, dwCapabilities: EOLE_AUTHENTICATION_CAPABILITIES.EOAC_STATIC_CLOAKING);
|
||||||
IDOManager mgr = new();
|
IDOManager mgr = new();
|
||||||
IDODownload dnld = mgr.CreateDownload();
|
IDODownload dnld = mgr.CreateDownload();
|
||||||
CoSetProxyBlanket(dnld, dwImpLevel: Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE).ThrowIfFailed();
|
CoSetProxyBlanket(dnld, dwImpLevel: Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE).ThrowIfFailed();
|
||||||
|
|
||||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_Uri, url);
|
// Create a task completion source
|
||||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_LocalPath, destPath);
|
TaskCompletionSource<byte[]> tcs = new();
|
||||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority, true);
|
|
||||||
DownloadStatusCallback callback = new(progress, cancellationToken);
|
// Set properties
|
||||||
|
DownloadStatusCallback callback = new(Callback_StatusChange);
|
||||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_CallbackInterface, callback);
|
dnld.SetProperty(DODownloadProperty.DODownloadProperty_CallbackInterface, callback);
|
||||||
if (!string.IsNullOrEmpty(displayName))
|
if (!string.IsNullOrEmpty(displayName))
|
||||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_DisplayName, displayName!);
|
dnld.SetProperty(DODownloadProperty.DODownloadProperty_DisplayName, displayName!);
|
||||||
if (!string.IsNullOrEmpty(contentId))
|
if (foregroundPriority)
|
||||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_ContentId, contentId!);
|
dnld.SetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority, true);
|
||||||
|
if (timeout.HasValue)
|
||||||
|
dnld.SetProperty(DODownloadProperty.DODownloadProperty_NoProgressTimeoutSeconds, (uint)timeout.Value.TotalSeconds);
|
||||||
|
dnld.SetProperty(DODownloadProperty.DODownloadProperty_Uri, url);
|
||||||
|
dnld.SetProperty(DODownloadProperty.DODownloadProperty_LocalPath, destPath);
|
||||||
|
|
||||||
return await Task.Factory.StartNew(() =>
|
// Start download
|
||||||
|
cancellationToken.Register(() => { dnld.Abort(); tcs.TrySetCanceled(cancellationToken); }, false);
|
||||||
|
dnld.Start(ranges);
|
||||||
|
|
||||||
|
// Wait for completion or failure
|
||||||
|
return tcs.Task;
|
||||||
|
|
||||||
|
// Process status change
|
||||||
|
void Callback_StatusChange(DO_DOWNLOAD_STATUS currentStatus)
|
||||||
{
|
{
|
||||||
dnld.Start();
|
if (currentStatus.Error.Failed)
|
||||||
cancellationToken.Register(dnld.Abort);
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
dnld.Abort();
|
tcs.TrySetException(currentStatus.Error.GetException()!);
|
||||||
return new byte[0];
|
return;
|
||||||
}
|
}
|
||||||
if (callback.Wait())
|
Report();
|
||||||
|
switch (currentStatus.State)
|
||||||
{
|
{
|
||||||
dnld.Finalize();
|
case DODownloadState.DODownloadState_Transferred:
|
||||||
if (computeHash)
|
dnld.Finalize();
|
||||||
return SHA256.Create().ComputeHash(File.ReadAllBytes(destPath));
|
tcs.TrySetResult(computeHash && File.Exists(destPath) ? SHA256.Create().ComputeHash(File.ReadAllBytes(destPath)) : []);
|
||||||
}
|
break;
|
||||||
return new byte[0];
|
|
||||||
}, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<IDODownload> EnumDownloads(DODownloadProperty? category = null) => new IDOManager().EnumDownloads(category);
|
case DODownloadState.DODownloadState_Aborted:
|
||||||
}
|
case DODownloadState.DODownloadState_Transferring:
|
||||||
|
case DODownloadState.DODownloadState_Created:
|
||||||
[ComVisible(true), Guid("90AFD61C-C21C-4627-8A9A-E3268BC89051"), ClassInterface(ClassInterfaceType.None)]
|
case DODownloadState.DODownloadState_Paused:
|
||||||
public class DownloadStatusCallback : IDODownloadStatusCallback
|
case DODownloadState.DODownloadState_Finalized:
|
||||||
{
|
break;
|
||||||
private readonly CancellationToken cancel;
|
|
||||||
private readonly object lockObj = new();
|
|
||||||
private readonly IProgress<DO_DOWNLOAD_STATUS>? progress;
|
|
||||||
private DO_DOWNLOAD_STATUS currentStatus = default;
|
|
||||||
|
|
||||||
public DownloadStatusCallback(IProgress<DO_DOWNLOAD_STATUS>? progress, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
this.progress = progress;
|
|
||||||
cancel = cancellationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(5);
|
|
||||||
|
|
||||||
public bool Wait()
|
|
||||||
{
|
|
||||||
lock (lockObj)
|
|
||||||
{
|
|
||||||
bool transferChange = false;
|
|
||||||
ulong? initialTransferAmount = null;
|
|
||||||
while (!cancel.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
if (!transferChange)
|
|
||||||
{
|
|
||||||
if (cancel.WaitHandle.WaitOne(Timeout) == false)
|
|
||||||
throw new TimeoutException();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cancel.WaitHandle.WaitOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancel.IsCancellationRequested)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (currentStatus.Error.Failed)
|
|
||||||
throw currentStatus.Error.GetException()!;
|
|
||||||
|
|
||||||
switch (currentStatus.State)
|
|
||||||
{
|
|
||||||
case DODownloadState.DODownloadState_Created:
|
|
||||||
case DODownloadState.DODownloadState_Paused:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DODownloadState.DODownloadState_Transferring:
|
|
||||||
if (currentStatus.BytesTransferred > 0 || currentStatus.BytesTotal > 0)
|
|
||||||
progress?.Report(currentStatus);
|
|
||||||
if (!initialTransferAmount.HasValue)
|
|
||||||
initialTransferAmount = currentStatus.BytesTransferred;
|
|
||||||
else if (currentStatus.BytesTransferred != initialTransferAmount.Value)
|
|
||||||
transferChange = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DODownloadState.DODownloadState_Transferred:
|
|
||||||
case DODownloadState.DODownloadState_Finalized:
|
|
||||||
if (currentStatus.BytesTransferred > 0 || currentStatus.BytesTotal > 0)
|
|
||||||
progress?.Report(currentStatus);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case DODownloadState.DODownloadState_Aborted:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
void Report() { if (currentStatus.BytesTransferred > 0 || currentStatus.BytesTotal > 0) progress?.Report(currentStatus); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT IDODownloadStatusCallback.OnStatusChange(IDODownload download, in DO_DOWNLOAD_STATUS status)
|
/// <summary>Enumerates the existing downloads.</summary>
|
||||||
|
/// <param name="category">The property name to be used as a category to enumerate. Passing <see langword="null"/> will retrieve all existing downloads</param>
|
||||||
|
/// <returns>An enumeration of the existing downloads.</returns>
|
||||||
|
public static IEnumerable<IDODownload> EnumDownloads(DODownloadProperty? category = null) => new IDOManager().EnumDownloads(category);
|
||||||
|
|
||||||
|
[ComVisible(true), Guid("90AFD61C-C21C-4627-8A9A-E3268BC89051")]
|
||||||
|
internal class DownloadStatusCallback(Action<DO_DOWNLOAD_STATUS>? StatusChange) : IDODownloadStatusCallback
|
||||||
{
|
{
|
||||||
lock (lockObj)
|
HRESULT IDODownloadStatusCallback.OnStatusChange(IDODownload download, in DO_DOWNLOAD_STATUS status)
|
||||||
currentStatus = status;
|
{
|
||||||
progress?.Report(currentStatus);
|
StatusChange?.Invoke(status);
|
||||||
return HRESULT.S_OK;
|
return HRESULT.S_OK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue