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 System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.Utilities;
|
||||
using static Vanara.PInvoke.DOSvc;
|
||||
using static Vanara.PInvoke.Ole32;
|
||||
|
||||
namespace Vanara.PInvoke.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class DOSvcTests
|
||||
{
|
||||
[Test]
|
||||
public async void Test()
|
||||
{
|
||||
const string name = "Test download";
|
||||
string id = Guid.NewGuid().ToString("N");
|
||||
private const string name = "Test download";
|
||||
private readonly string uri = "https://github.com/EWSoftware/SHFB/releases/download/2023.7.8.0/SHFBInstaller_2023.7.8.0.zip";
|
||||
|
||||
[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();
|
||||
string uri = "https://github.com/EWSoftware/SHFB/releases/download/2023.7.8.0/SHFBInstaller_2023.7.8.0.zip";
|
||||
string dest = tempRoot.RandomTxtFileFullPath;
|
||||
|
||||
var tw = TestContext.Out;
|
||||
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();
|
||||
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(ret, Is.Not.Empty);
|
||||
}
|
||||
|
||||
//IDOManager mgr = new();
|
||||
//IDODownload dnld = mgr.CreateDownload();
|
||||
//CoSetProxyBlanket(dnld, dwImpLevel: Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE/*, dwCapabilities: EOLE_AUTHENTICATION_CAPABILITIES.EOAC_STATIC_CLOAKING*/).ThrowIfFailed();
|
||||
[Test]
|
||||
public void TestAsyncCancel()
|
||||
{
|
||||
using TemporaryDirectory tempRoot = new();
|
||||
string dest = tempRoot.RandomTxtFileFullPath;
|
||||
|
||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_Uri, uri), Throws.Nothing);
|
||||
//Assert.That((string)dnld.GetProperty(DODownloadProperty.DODownloadProperty_Uri), Is.EqualTo(uri));
|
||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority, true), Throws.Nothing);
|
||||
//Assert.That((bool)dnld.GetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority), Is.True);
|
||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_LocalPath, dest), Throws.Nothing);
|
||||
//Assert.That((string)dnld.GetProperty(DODownloadProperty.DODownloadProperty_LocalPath), Is.EqualTo(dest));
|
||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_DisplayName, name), Throws.Nothing);
|
||||
//Assert.That((string)dnld.GetProperty(DODownloadProperty.DODownloadProperty_DisplayName), Is.EqualTo(name));
|
||||
var tw = TestContext.Out;
|
||||
Progress<DO_DOWNLOAD_STATUS> progress = new();
|
||||
progress.ProgressChanged += (s, e) => tw.WriteLine($"{e.State} - {100*e.BytesTransferred/e.BytesTotal}%");
|
||||
CancellationTokenSource cancel = new();
|
||||
var task = Downloader.DownloadAsync(uri, dest, false, name, true, null, progress, null, cancel.Token);
|
||||
cancel.Cancel();
|
||||
Assert.That(task.IsCanceled, Is.True);
|
||||
}
|
||||
|
||||
//AutoResetEvent done = new(false);
|
||||
//Callback callback = new();
|
||||
//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);
|
||||
//Assert.That(() => dnld.SetProperty(DODownloadProperty.DODownloadProperty_CallbackInterface, wrp), Throws.Nothing);
|
||||
[Test]
|
||||
public void Test()
|
||||
{
|
||||
SetupDownload(out var dnld, out var tempRoot, out var dest, out var done);
|
||||
|
||||
//dnld.Start();
|
||||
//done.WaitOne();
|
||||
//dnld.Finalize();
|
||||
dnld.Start();
|
||||
Assert.That(done.WaitOne(TimeSpan.FromMinutes(1)), Is.True);
|
||||
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.Ole32;
|
||||
|
||||
#nullable enable
|
||||
namespace Vanara.Utilities
|
||||
{
|
||||
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();
|
||||
IDODownload dnld = mgr.CreateDownload();
|
||||
CoSetProxyBlanket(dnld, dwImpLevel: Rpc.RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE).ThrowIfFailed();
|
||||
|
||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_Uri, url);
|
||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_LocalPath, destPath);
|
||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_ForegroundPriority, true);
|
||||
DownloadStatusCallback callback = new(progress, cancellationToken);
|
||||
// Create a task completion source
|
||||
TaskCompletionSource<byte[]> tcs = new();
|
||||
|
||||
// Set properties
|
||||
DownloadStatusCallback callback = new(Callback_StatusChange);
|
||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_CallbackInterface, callback);
|
||||
if (!string.IsNullOrEmpty(displayName))
|
||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_DisplayName, displayName!);
|
||||
if (!string.IsNullOrEmpty(contentId))
|
||||
dnld.SetProperty(DODownloadProperty.DODownloadProperty_ContentId, contentId!);
|
||||
if (foregroundPriority)
|
||||
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(() =>
|
||||
{
|
||||
dnld.Start();
|
||||
cancellationToken.Register(dnld.Abort);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
dnld.Abort();
|
||||
return new byte[0];
|
||||
}
|
||||
if (callback.Wait())
|
||||
{
|
||||
dnld.Finalize();
|
||||
if (computeHash)
|
||||
return SHA256.Create().ComputeHash(File.ReadAllBytes(destPath));
|
||||
}
|
||||
return new byte[0];
|
||||
}, cancellationToken);
|
||||
}
|
||||
// Start download
|
||||
cancellationToken.Register(() => { dnld.Abort(); tcs.TrySetCanceled(cancellationToken); }, false);
|
||||
dnld.Start(ranges);
|
||||
|
||||
public static IEnumerable<IDODownload> EnumDownloads(DODownloadProperty? category = null) => new IDOManager().EnumDownloads(category);
|
||||
}
|
||||
// Wait for completion or failure
|
||||
return tcs.Task;
|
||||
|
||||
[ComVisible(true), Guid("90AFD61C-C21C-4627-8A9A-E3268BC89051"), ClassInterface(ClassInterfaceType.None)]
|
||||
public class DownloadStatusCallback : IDODownloadStatusCallback
|
||||
// Process status change
|
||||
void Callback_StatusChange(DO_DOWNLOAD_STATUS currentStatus)
|
||||
{
|
||||
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()!;
|
||||
|
||||
{
|
||||
tcs.TrySetException(currentStatus.Error.GetException()!);
|
||||
return;
|
||||
}
|
||||
Report();
|
||||
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;
|
||||
dnld.Finalize();
|
||||
tcs.TrySetResult(computeHash && File.Exists(destPath) ? SHA256.Create().ComputeHash(File.ReadAllBytes(destPath)) : []);
|
||||
break;
|
||||
|
||||
case DODownloadState.DODownloadState_Aborted:
|
||||
return false;
|
||||
case DODownloadState.DODownloadState_Transferring:
|
||||
case DODownloadState.DODownloadState_Created:
|
||||
case DODownloadState.DODownloadState_Paused:
|
||||
case DODownloadState.DODownloadState_Finalized:
|
||||
break;
|
||||
}
|
||||
|
||||
void Report() { if (currentStatus.BytesTransferred > 0 || currentStatus.BytesTotal > 0) progress?.Report(currentStatus); }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// <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
|
||||
{
|
||||
HRESULT IDODownloadStatusCallback.OnStatusChange(IDODownload download, in DO_DOWNLOAD_STATUS status)
|
||||
{
|
||||
lock (lockObj)
|
||||
currentStatus = status;
|
||||
progress?.Report(currentStatus);
|
||||
StatusChange?.Invoke(status);
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue