mirror of https://github.com/dahall/Vanara.git
862 lines
38 KiB
C#
862 lines
38 KiB
C#
using System;
|
||
using System.ComponentModel;
|
||
using System.Linq;
|
||
using System.Runtime.InteropServices;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
using Vanara.Extensions;
|
||
using static Vanara.PInvoke.BITS;
|
||
|
||
namespace Vanara.IO
|
||
{
|
||
/// <summary>
|
||
/// Provides job-related progress information, such as the number of bytes and files transferred. For upload jobs, the progress applies
|
||
/// to the upload file, not the reply file.
|
||
/// </summary>
|
||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||
public struct BackgroundCopyJobProgress
|
||
{
|
||
internal BG_JOB_PROGRESS prog;
|
||
|
||
/// <summary>
|
||
/// <para>
|
||
/// Total number of bytes to transfer for all files in the job. If the value UInt64.MaxValue, the total size of all files in the job
|
||
/// has not been determined. BITS does not set this value if it cannot determine the size of one of the files. For example, if the
|
||
/// specified file or server does not exist, BITS cannot determine the size of the file.
|
||
/// </para>
|
||
/// <para>
|
||
/// If you are downloading ranges from the file, <c>BytesTotal</c> includes the total number of bytes you want to download from the file.
|
||
/// </para>
|
||
/// </summary>
|
||
public ulong BytesTotal { get => prog.BytesTotal; set => prog.BytesTotal = value; }
|
||
|
||
/// <summary>Number of bytes transferred.</summary>
|
||
public ulong BytesTransferred { get => prog.BytesTransferred; set => prog.BytesTransferred = value; }
|
||
|
||
/// <summary>Total number of files to transfer for this job.</summary>
|
||
public uint FilesTotal { get => prog.FilesTotal; set => prog.FilesTotal = value; }
|
||
|
||
/// <summary>Number of files transferred.</summary>
|
||
public uint FilesTransferred { get => prog.FilesTransferred; set => prog.FilesTransferred = value; }
|
||
|
||
/// <summary>Gets the percent of total bytes transferred represented as a number between 0 and 100.</summary>
|
||
public byte PercentComplete => (byte)(BytesTotal == 0 ? 0f : BytesTransferred * 100f / BytesTotal);
|
||
|
||
internal BackgroundCopyJobProgress(BG_JOB_PROGRESS p) => prog = p;
|
||
|
||
/// <summary>Performs an implicit conversion from <see cref="BG_JOB_PROGRESS"/> to <see cref="BackgroundCopyJobProgress"/>.</summary>
|
||
/// <param name="p">The BG_JOB_PROGRESS instance.</param>
|
||
/// <returns>The result of the conversion.</returns>
|
||
public static implicit operator BackgroundCopyJobProgress(BG_JOB_PROGRESS p) => new BackgroundCopyJobProgress(p);
|
||
}
|
||
|
||
/// <summary>Provides progress information related to the reply portion of an upload-reply job.</summary>
|
||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||
public struct BackgroundCopyJobReplyProgress
|
||
{
|
||
internal BG_JOB_REPLY_PROGRESS prog;
|
||
|
||
/// <summary>Size of the file in bytes. The value is <c>UInt64.MaxValue</c> if the reply has not begun.</summary>
|
||
public ulong BytesTotal { get => prog.BytesTotal; set => prog.BytesTotal = value; }
|
||
|
||
/// <summary>Number of bytes transferred.</summary>
|
||
public ulong BytesTransferred { get => prog.BytesTransferred; set => prog.BytesTransferred = value; }
|
||
|
||
internal BackgroundCopyJobReplyProgress(BG_JOB_REPLY_PROGRESS p) => prog = p;
|
||
|
||
/// <summary>Performs an implicit conversion from <see cref="BG_JOB_REPLY_PROGRESS"/> to <see cref="BackgroundCopyJobReplyProgress"/>.</summary>
|
||
/// <param name="p">The BG_JOB_REPLY_PROGRESS instance.</param>
|
||
/// <returns>The result of the conversion.</returns>
|
||
public static implicit operator BackgroundCopyJobReplyProgress(BG_JOB_REPLY_PROGRESS p) => new BackgroundCopyJobReplyProgress(p);
|
||
}
|
||
|
||
/// <summary>Used by <see cref="BackgroundCopyJob.FileRangesTransferred"/> events.</summary>
|
||
public class BackgroundCopyFileRangesTransferredEventArgs : BackgroundCopyFileTransferredEventArgs
|
||
{
|
||
internal BackgroundCopyFileRangesTransferredEventArgs(BackgroundCopyJob job, IBackgroundCopyFile file, BG_FILE_RANGE[] ranges) : base(job, file) => Ranges = Array.ConvertAll(ranges, r => (BackgroundCopyFileRange)r);
|
||
|
||
/// <summary>
|
||
/// An array of the files ranges that have transferred since the last call to FileRangesTransferred or the last call to the
|
||
/// RequestFileRanges method.
|
||
/// </summary>
|
||
public BackgroundCopyFileRange[] Ranges { get; private set; }
|
||
}
|
||
|
||
/// <summary>Used by <see cref="BackgroundCopyJob.FileTransferred"/> events.</summary>
|
||
public class BackgroundCopyFileTransferredEventArgs : BackgroundCopyJobEventArgs
|
||
{
|
||
internal BackgroundCopyFileTransferredEventArgs(BackgroundCopyJob job, IBackgroundCopyFile pFile) : base(job) => FileInfo = new BackgroundCopyFileInfo(pFile);
|
||
|
||
/// <summary>A BackgroundCopyFileInfo object that contains information about the file.</summary>
|
||
public BackgroundCopyFileInfo FileInfo { get; private set; }
|
||
}
|
||
|
||
/// <summary>A job in the Background Copy Service (BITS)</summary>
|
||
/// <example>
|
||
/// Below is an example of how to use a job to copy multiple files in the background with progress reporting:
|
||
/// <code title="Using BITS jobs">// ===== Create a download job (default) =================
|
||
/// using var job = BackgroundCopyManager.Jobs.Add("Download directory");
|
||
/// // Create an event to signal completion
|
||
/// var evt = new AutoResetEvent(false);
|
||
/// // Set properties on the job
|
||
/// job.Credentials.Add(BackgroundCopyJobCredentialScheme.Digest, BackgroundCopyJobCredentialTarget.Proxy, "user", "mypwd");
|
||
/// job.CustomHeaders = new System.Net.WebHeaderCollection() { "A1:Test", "A2:Prova" };
|
||
/// job.MinimumNotificationInterval = TimeSpan.FromSeconds(1);
|
||
/// // Set event handlers for job
|
||
/// job.Completed += (s, e) => { System.Diagnostics.Debug.WriteLine("Job completed."); job.Complete(); evt.Set(); };
|
||
/// job.Error += (s, e) => throw job.LastError;
|
||
/// job.FileTransferred += (s, e) => System.Diagnostics.Debug.WriteLine($"{e.FileInfo.LocalFilePath} of size {e.FileInfo.BytesTransferred} bytes was transferred.");
|
||
/// // Add download file information
|
||
/// // You can optionally add files individually using job.Files.Add()
|
||
/// job.Files.AddRange(@"https://server/temp/", @"D:\", new[] { "file.mp4", "file.exe", "file.txt" });
|
||
/// // Start (resume) the job.
|
||
/// job.Resume();
|
||
/// // Wait for the completion event for an appropriate amount of time
|
||
/// if (!evt.WaitOne(20000))
|
||
/// throw new InvalidOperationException();</code></example>
|
||
public class BackgroundCopyJob : IDisposable
|
||
{
|
||
internal static readonly TimeSpan DEFAULT_RETRY_DELAY = TimeSpan.FromSeconds(600); //10 minutes (600 seconds)
|
||
internal static readonly TimeSpan DEFAULT_RETRY_PERIOD = TimeSpan.FromSeconds(1209600); //20160 minutes (1209600 seconds)
|
||
internal static readonly TimeSpan DEFAULT_TIMEOUT = TimeSpan.FromSeconds(7776000); // 7776000 seconds
|
||
|
||
private IBackgroundCopyJob m_ijob;
|
||
private Notifier m_notifier;
|
||
|
||
internal BackgroundCopyJob(IBackgroundCopyJob ijob)
|
||
{
|
||
m_ijob = ijob ?? throw new ArgumentNullException(nameof(ijob));
|
||
m_notifier = new Notifier(this);
|
||
m_ijob.SetNotifyInterface(m_notifier);
|
||
NotifyFlags = BG_NOTIFY.BG_NOTIFY_FILE_RANGES_TRANSFERRED | BG_NOTIFY.BG_NOTIFY_FILE_TRANSFERRED | BG_NOTIFY.BG_NOTIFY_JOB_ERROR | BG_NOTIFY.BG_NOTIFY_JOB_MODIFICATION | BG_NOTIFY.BG_NOTIFY_JOB_TRANSFERRED;
|
||
Files = new BackgroundCopyFileCollection(m_ijob);
|
||
Credentials = new BackgroundCopyJobCredentials(IJob2);
|
||
}
|
||
|
||
/// <summary>Occurs when all of the files in the job have been transferred.</summary>
|
||
public event EventHandler<BackgroundCopyJobEventArgs> Completed;
|
||
|
||
/// <summary>Fires when an error occurs.</summary>
|
||
public event EventHandler<BackgroundCopyJobEventArgs> Error;
|
||
|
||
/// <summary>Occurs when file ranges have been transferred.</summary>
|
||
public event EventHandler<BackgroundCopyFileRangesTransferredEventArgs> FileRangesTransferred;
|
||
|
||
/// <summary>Occurs when a file has been transferred.</summary>
|
||
public event EventHandler<BackgroundCopyFileTransferredEventArgs> FileTransferred;
|
||
|
||
/// <summary>
|
||
/// Occurs when the job has been modified. For example, a property value changed, the state of the job changed, or progress is made
|
||
/// transferring the files.
|
||
/// </summary>
|
||
public event EventHandler<BackgroundCopyJobEventArgs> Modified;
|
||
|
||
/// <summary>Gets or sets the flags that identify the owner and ACL information to maintain when transferring a file using SMB.</summary>
|
||
public BackgroundCopyACLFlags ACLFlags
|
||
{
|
||
get => RunAction(() => (BackgroundCopyACLFlags)IJob3.GetFileACLFlags(), BackgroundCopyACLFlags.None);
|
||
set => RunAction(() => IJob3.SetFileACLFlags((BG_COPY_FILE)value));
|
||
}
|
||
|
||
/// <summary>Retrieves the client certificate from the job.</summary>
|
||
public X509Certificate2 Certificate
|
||
{
|
||
get
|
||
{
|
||
IHttpOp.GetClientCertificate(out var loc, out var mstore, out var blob, out var subj);
|
||
if (blob.IsInvalid) return null;
|
||
var store = mstore;
|
||
switch (store)
|
||
{
|
||
case "MY":
|
||
store = "My";
|
||
break;
|
||
|
||
case "ROOT":
|
||
store = "Root";
|
||
break;
|
||
|
||
case "SPC":
|
||
store = "TrustedPublisher";
|
||
break;
|
||
|
||
case "CA":
|
||
default:
|
||
break;
|
||
}
|
||
var xstore = new X509Store(store, (StoreLocation)(loc + 1));
|
||
xstore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
|
||
return xstore.Certificates.Find(X509FindType.FindBySubjectName, subj, false).OfType<X509Certificate2>().FirstOrDefault() ??
|
||
new X509Certificate2(blob.ToArray<byte>(20));
|
||
}
|
||
}
|
||
|
||
/// <summary>The credentials to use for a proxy or remote server user authentication request.</summary>
|
||
public BackgroundCopyJobCredentials Credentials { get; private set; }
|
||
|
||
/// <summary>Gets or sets one or more custom HTTP headers to include in HTTP requests.</summary>
|
||
public System.Net.WebHeaderCollection CustomHeaders
|
||
{
|
||
get
|
||
{
|
||
var hdr = new System.Net.WebHeaderCollection();
|
||
var str = RunAction(() => IHttpOp.GetCustomHeaders().ToString(), null);
|
||
if (str != null)
|
||
{
|
||
foreach (var s in str.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries))
|
||
hdr.Add(s);
|
||
}
|
||
return hdr;
|
||
}
|
||
set => RunAction(() => IHttpOp.SetCustomHeaders(value.Count == 0 ? null : string.Join("\n", value.AllKeys.Select(k => $"{k}:{value[k]}").ToArray())));
|
||
}
|
||
|
||
/// <summary>Gets or sets the description of the job.</summary>
|
||
[DefaultValue("")]
|
||
public string Description
|
||
{
|
||
get => RunAction(() => m_ijob.GetDescription(), string.Empty);
|
||
set => RunAction(() => m_ijob.SetDescription(value));
|
||
}
|
||
|
||
/// <summary>Gets or sets a value indicating whether notifications are disabled.</summary>
|
||
/// <value><c>true</c> if notifications are disabled; otherwise, <c>false</c>.</value>
|
||
[DefaultValue(false)]
|
||
public bool DisableNotifications
|
||
{
|
||
get => NotifyFlags.IsFlagSet(BG_NOTIFY.BG_NOTIFY_DISABLE);
|
||
set => NotifyFlags = NotifyFlags.SetFlags(BG_NOTIFY.BG_NOTIFY_DISABLE, value);
|
||
}
|
||
|
||
/// <summary>Gets or sets the display name of the job.</summary>
|
||
public string DisplayName
|
||
{
|
||
get => RunAction(() => m_ijob.GetDisplayName(), string.Empty);
|
||
set => RunAction(() => m_ijob.SetDisplayName(value));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Marks a BITS job as being willing to download content which does not support the normal HTTP requirements for BITS downloads:
|
||
/// HEAD requests, the Content-Length header, and the Content-Range header. Downloading this type of content is opt-in, because BITS
|
||
/// cannot pause and resume downloads jobs without that support. If a job with this property enabled is interrupted for any reason,
|
||
/// such as a temporary loss of network connectivity or the system rebooting, BITS will restart the download from the beginning
|
||
/// instead of resuming where it left off. BITS also cannot throttle bandwidth usage for dynamic downloads; BITS will not perform
|
||
/// unthrottled transfers for any job that does not have BG_JOB_PRIORITY_FOREGROUND assigned, so you should typically set that
|
||
/// priority every time you use set a job as allowing dynamic content.
|
||
/// <para>
|
||
/// This property is only supported for BG_JOB_TYPE_DOWNLOAD jobs. It is not supported for downloads that use FILE_RANGES. This
|
||
/// property may only be set prior to the first time Resume is called on a job.
|
||
/// </para>
|
||
/// </summary>
|
||
[DefaultValue(false)]
|
||
public bool DynamicContent
|
||
{
|
||
get => (bool)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_DYNAMIC_CONTENT);
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_DYNAMIC_CONTENT, value);
|
||
}
|
||
|
||
/// <summary>Gets the number of errors that have occurred in this job.</summary>
|
||
public int ErrorCount => RunAction(() => (int)m_ijob.GetErrorCount());
|
||
|
||
/// <summary>Manages the files that are a part of this job.</summary>
|
||
public BackgroundCopyFileCollection Files { get; private set; }
|
||
|
||
/// <summary>
|
||
/// Marks a BITS job as not requiring strong reliability guarantees. Enabling this property will cause BITS to avoid persisting
|
||
/// information about normal job progress, which BITS normally does periodically. In the event of an unexpected shutdown, such as a
|
||
/// power loss, during a transfer, this will cause BITS to lose progress and restart the job from the beginning instead of resuming
|
||
/// from where it left off as usual. However, it will also reduce the number of disk writes BITS makes over the course of a job<6F>s
|
||
/// lifetime, which can improve performance for smaller jobs.
|
||
/// <para>
|
||
/// This property also causes BITS to download directly into the destination file, instead of downloading to a temporary file and
|
||
/// moving the temporary file to the final destination once the transfer is complete.This means that BITS will not clean up any
|
||
/// partially downloaded content if a job is canceled or encounters a fatal error condition; the BITS caller is responsible for
|
||
/// cleaning up the destination file, if it gets created.However, it will also slightly reduce disk overhead.
|
||
/// </para>
|
||
/// <para>
|
||
/// This property is only recommended for scenarios which involve high numbers of small jobs(under 1MB) and which do not require
|
||
/// reliability to power loss or other unexpected shutdown events.The performance savings are not generally significant for small
|
||
/// numbers of jobs or for larger jobs.
|
||
/// </para>
|
||
/// <para>
|
||
/// This property is only supported for BG_JOB_TYPE_DOWNLOAD jobs. This property may only be set prior to adding any files to a job.
|
||
/// </para>
|
||
/// </summary>
|
||
[DefaultValue(false)]
|
||
public bool HighPerformance
|
||
{
|
||
get => (bool)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_HIGH_PERFORMANCE);
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_HIGH_PERFORMANCE, value);
|
||
}
|
||
|
||
/// <summary>Gets the job identifier.</summary>
|
||
public Guid ID => RunAction(() => m_ijob.GetId(), Guid.Empty);
|
||
|
||
/// <summary>Gets the type of job, such as download.</summary>
|
||
public BackgroundCopyJobType JobType => RunAction(() => (BackgroundCopyJobType)m_ijob.GetType(), BackgroundCopyJobType.Download);
|
||
|
||
/// <summary>Gets the last exception that occurred in the job.</summary>
|
||
public BackgroundCopyException LastError
|
||
{
|
||
get
|
||
{
|
||
var state = State;
|
||
if (state != BackgroundCopyJobState.Error && state != BackgroundCopyJobState.TransientError)
|
||
return null;
|
||
var err = RunAction(() => m_ijob.GetError());
|
||
return err == null ? null : new BackgroundCopyException(err);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// The ID for marking the maximum number of bytes a BITS job will be allowed to download in total. This property is intended for use
|
||
/// with BITS_JOB_PROPERTY_DYNAMIC_CONTENT, where you may not be able to determine the size of the file to be downloaded ahead of
|
||
/// time but would like to cap the total possible download size.
|
||
/// <para>
|
||
/// This property is only supported for BG_JOB_TYPE_DOWNLOAD jobs. This property may only be set prior to the first time Resume is
|
||
/// called on a job.
|
||
/// </para>
|
||
/// </summary>
|
||
[DefaultValue(0)]
|
||
public ulong MaxDownloadSize
|
||
{
|
||
get => (ulong)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MAX_DOWNLOAD_SIZE);
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MAX_DOWNLOAD_SIZE, value);
|
||
}
|
||
|
||
/// <summary>Sets the maximum time that BITS will spend transferring the files in the job.</summary>
|
||
/// <value>
|
||
/// Maximum time, in seconds, that BITS will spend transferring the files in the job. The default is 7,776,000 seconds (90 days).
|
||
/// </value>
|
||
[DefaultValue(typeof(TimeSpan), "90.00:00:00")]
|
||
public TimeSpan MaximumDownloadTime
|
||
{
|
||
get => RunAction(() => TimeSpan.FromSeconds((int)IJob4.GetMaximumDownloadTime()), DEFAULT_TIMEOUT);
|
||
set => RunAction(() => IJob4.SetMaximumDownloadTime((uint)value.TotalSeconds));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Used to control the timing of BITS JobNotification and FileRangesTransferred notifications. Enabling this property lets a user be
|
||
/// notified at a different rate. This property may be changed while a transfer is ongoing; however, the new rate may not be applied
|
||
/// immediately. The default value is 500 milliseconds.
|
||
/// </summary>
|
||
[DefaultValue(typeof(TimeSpan), "00:00:00")]
|
||
public TimeSpan MinimumNotificationInterval
|
||
{
|
||
get => TimeSpan.FromMilliseconds((uint)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MINIMUM_NOTIFICATION_INTERVAL_MS));
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MINIMUM_NOTIFICATION_INTERVAL_MS, (uint)value.TotalMilliseconds);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the minimum length of time, in seconds, that BITS waits after encountering a transient error before trying to
|
||
/// transfer the file. The default retry delay is 600 seconds (10 minutes). The minimum retry delay that you can specify is 60
|
||
/// seconds. If you specify a value less than 60 seconds, BITS changes the value to 60 seconds.
|
||
/// </summary>
|
||
[DefaultValue(typeof(TimeSpan), "00:10:00")]
|
||
public TimeSpan MinimumRetryDelay
|
||
{
|
||
get => RunAction(() => TimeSpan.FromSeconds((int)m_ijob.GetMinimumRetryDelay()), DEFAULT_RETRY_DELAY);
|
||
set => RunAction(() => m_ijob.SetMinimumRetryDelay((uint)value.TotalSeconds));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the length of time, in seconds, that BITS tries to transfer the file after the first transient error occurs. The
|
||
/// default retry period is 1,209,600 seconds (14 days). Set the retry period to 0 to prevent retries and to force the job into the
|
||
/// Error state for all errors.
|
||
/// </summary>
|
||
[DefaultValue(typeof(TimeSpan), "14.00:00:00")]
|
||
public TimeSpan NoProgressTimeout
|
||
{
|
||
get => RunAction(() => TimeSpan.FromSeconds((int)m_ijob.GetNoProgressTimeout()), DEFAULT_RETRY_PERIOD);
|
||
set => RunAction(() => m_ijob.SetNoProgressTimeout((uint)value.TotalSeconds));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Used to register a COM callback by CLSID to receive notifications about the progress and completion of a BITS job. The CLSID must
|
||
/// refer to a class associated with a registered out-of-process COM server. It may also be set to GUID_NULL to clear a previously
|
||
/// set notification CLSID.
|
||
/// </summary>
|
||
[DefaultValue(typeof(Guid), "00000000000000000000000000000000")]
|
||
public Guid NotificationCLSID
|
||
{
|
||
get => (Guid)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_NOTIFICATION_CLSID);
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_NOTIFICATION_CLSID, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the program to execute if the job enters the Error or Transferred state. BITS executes the program in the context of
|
||
/// the user who called this method.
|
||
/// </summary>
|
||
[DefaultValue(null)]
|
||
public string NotifyProgram
|
||
{
|
||
get
|
||
{
|
||
string p, a;
|
||
try
|
||
{
|
||
IJob2.GetNotifyCmdLine(out p, out a);
|
||
}
|
||
catch
|
||
{
|
||
return string.Empty;
|
||
}
|
||
if (string.IsNullOrEmpty(a))
|
||
{
|
||
if (p == null)
|
||
return string.Empty;
|
||
else
|
||
return p;
|
||
}
|
||
return string.Format("\"{0}\" {1}", p, a);
|
||
}
|
||
set
|
||
{
|
||
string p = value, a = string.Empty;
|
||
if (string.IsNullOrEmpty(value))
|
||
p = a = null;
|
||
else
|
||
{
|
||
if (value[0] == '"')
|
||
{
|
||
var i = p.IndexOf('"', 1);
|
||
if (i + 3 <= p.Length)
|
||
a = p.Substring(i + 2);
|
||
p = p.Substring(1, i - 1);
|
||
}
|
||
else
|
||
{
|
||
var i = p.IndexOf(' ');
|
||
if (i + 2 <= p.Length)
|
||
a = p.Substring(i + 1);
|
||
p = p.Substring(0, i);
|
||
}
|
||
}
|
||
RunAction(() => IJob2.SetNotifyCmdLine(p, a));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// The ID that is used to control whether a job is in On Demand mode. On Demand jobs allow the app to request particular ranges for
|
||
/// a file download instead of downloading from the start to the end. The default value is FALSE; the job is not on-demand. Ranges
|
||
/// are requested using the IBackgroundCopyFile6::RequestFileRanges method.
|
||
/// <para>
|
||
/// The requirements for a BITS_JOB_PROPERTY_ON_DEMAND_MODE job is that the transfer must be a BG_JOB_TYPE_DOWNLOAD job. The job must
|
||
/// not be DYNAMIC and the server must be an HTTP or HTTPS server and the server requirements for range support must all be met.
|
||
/// </para>
|
||
/// </summary>
|
||
[DefaultValue(false)]
|
||
public bool OnDemand
|
||
{
|
||
get => (bool)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ON_DEMAND_MODE);
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ON_DEMAND_MODE, value);
|
||
}
|
||
|
||
/// <summary>Retrieve the identity of the job's owner.</summary>
|
||
public System.Security.Principal.SecurityIdentifier Owner => RunAction(() => new System.Security.Principal.SecurityIdentifier((string)m_ijob.GetOwner()));
|
||
|
||
/// <summary>Gets the integrity level of the token of the owner that created or took ownership of the job.</summary>
|
||
/// <value>Integrity level of the token of the owner that created or took ownership of the job.</value>
|
||
[DefaultValue(8192)]
|
||
public uint OwnerIntegrityLevel => RunAction(() => IJob4.GetOwnerIntegrityLevel());
|
||
|
||
/// <summary>
|
||
/// Gets a value that determines if the token of the owner was elevated at the time they created or took ownership of the job.
|
||
/// </summary>
|
||
/// <value>Is TRUE if the token of the owner was elevated at the time they created or took ownership of the job; otherwise, FALSE.</value>
|
||
[DefaultValue(false)]
|
||
public bool OwnerIsElevated => RunAction(() => IJob4.GetOwnerElevationState());
|
||
|
||
/// <summary>
|
||
/// Gets or sets the priority level for the job. The priority level determines when the job is processed relative to other jobs in
|
||
/// the transfer queue.
|
||
/// </summary>
|
||
[DefaultValue(BackgroundCopyJobPriority.Normal)]
|
||
public BackgroundCopyJobPriority Priority
|
||
{
|
||
get => RunAction(() => (BackgroundCopyJobPriority)m_ijob.GetPriority(), BackgroundCopyJobPriority.Normal);
|
||
set => RunAction(() => m_ijob.SetPriority((BG_JOB_PRIORITY)value));
|
||
}
|
||
|
||
/// <summary>Gets the job-related progress information, such as the number of bytes and files transferred.</summary>
|
||
public BackgroundCopyJobProgress Progress => RunAction(() => new BackgroundCopyJobProgress(m_ijob.GetProgress()), new BackgroundCopyJobProgress());
|
||
|
||
/// <summary>
|
||
/// Gets or sets the proxy information that the job uses to transfer the files. A <c>null</c> value represents the system default
|
||
/// proxy settings.
|
||
/// </summary>
|
||
/// <exception cref="ArgumentException">
|
||
/// The WebProxy.Credentials property value contains a value. Use the SetCredentials method instead.
|
||
/// </exception>
|
||
[DefaultValue(null)]
|
||
public System.Net.WebProxy Proxy
|
||
{
|
||
get => RunAction(() =>
|
||
{
|
||
m_ijob.GetProxySettings(out var pUse, out var pList, out var byList);
|
||
if (pUse == BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_OVERRIDE)
|
||
return new System.Net.WebProxy(pList.ToString()?.Split(' ').FirstOrDefault(), true, byList.ToString()?.Split(' '));
|
||
else if (pUse == BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_NO_PROXY)
|
||
return new System.Net.WebProxy();
|
||
return null;
|
||
});
|
||
set => RunAction(() =>
|
||
{
|
||
if (value == null)
|
||
m_ijob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_PRECONFIG, null, null);
|
||
else if (string.IsNullOrEmpty(value.Address.AbsoluteUri))
|
||
m_ijob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_NO_PROXY, null, null);
|
||
else
|
||
m_ijob.SetProxySettings(BG_JOB_PROXY_USAGE.BG_JOB_PROXY_USAGE_OVERRIDE, value.Address.AbsoluteUri, string.Join(" ", value.BypassList));
|
||
if (value.Credentials != null)
|
||
throw new ArgumentException("The set Proxy property does not support proxy credentials. Please use the SetCredentials method.");
|
||
});
|
||
}
|
||
|
||
/// <summary>Gets an in-memory copy of the reply data from the server application.</summary>
|
||
public byte[] ReplyData => RunAction(() =>
|
||
{
|
||
IJob2.GetReplyData(out var pdata, out var cRet);
|
||
return (cRet > 0) ? pdata.ToArray<byte>((int)cRet) : new byte[0];
|
||
}, new byte[0]);
|
||
|
||
/// <summary>Gets or sets the name of the file that contains the reply data from the server application.</summary>
|
||
[DefaultValue(null)]
|
||
public string ReplyFileName
|
||
{
|
||
get => RunAction(() => IJob2.GetReplyFileName(), string.Empty);
|
||
set => RunAction(() => IJob2.SetReplyFileName(value));
|
||
}
|
||
|
||
/// <summary>Gets progress information related to the transfer of the reply data from an upload-reply job.</summary>
|
||
public BackgroundCopyJobReplyProgress ReplyProgress => RunAction(() => new BackgroundCopyJobReplyProgress(IJob2.GetReplyProgress()), new BackgroundCopyJobReplyProgress());
|
||
|
||
/// <summary>
|
||
/// Gets or sets the flags for HTTP that determine whether the certificate revocation list is checked and certain certificate errors
|
||
/// are ignored, and the policy to use when a server redirects the HTTP request.
|
||
/// </summary>
|
||
[DefaultValue(BackgroundCopyJobSecurity.AllowSilentRedirect)]
|
||
public BackgroundCopyJobSecurity SecurityOptions
|
||
{
|
||
get => RunAction(() => (BackgroundCopyJobSecurity)IHttpOp.GetSecurityFlags(), BackgroundCopyJobSecurity.AllowSilentRedirect);
|
||
set => RunAction(() => IHttpOp.SetSecurityFlags((BG_HTTP_SECURITY)value));
|
||
}
|
||
|
||
/// <summary>Gets the current state of the job.</summary>
|
||
public BackgroundCopyJobState State => RunAction(() => (BackgroundCopyJobState)m_ijob.GetState(), BackgroundCopyJobState.Error);
|
||
|
||
/// <summary>
|
||
/// Used to control transfer behavior over cellular and/or similar networks. This property may be changed while a transfer is ongoing
|
||
/// <20> the new cost flags will take effect immediately.
|
||
/// </summary>
|
||
[DefaultValue(BackgroundCopyCost.TransferUnrestricted)]
|
||
public BackgroundCopyCost TransferBehavior
|
||
{
|
||
get => (BackgroundCopyCost)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ID_COST_FLAGS);
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ID_COST_FLAGS, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Marks a BITS job as being willing to include default credentials in requests to proxy servers. Enabling this property is
|
||
/// equivalent to setting a WinHTTP security level of WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM on the requests that BITS makes on the
|
||
/// user<65>s behalf. The user BITS retrieves stored credentials from the is the same as the one it makes network requests on behalf of:
|
||
/// BITS will normally use the job owner<65>s credentials, unless you have explicitly provided a network helper token, in which case
|
||
/// BITS will use the network helper token<65>s credentials.
|
||
/// <para>Only the BG_AUTH_TARGET_PROXY target is supported.</para>
|
||
/// </summary>
|
||
[DefaultValue(BackgroundCopyJobCredentialTarget.Undefined)]
|
||
public BackgroundCopyJobCredentialTarget UseStoredCredentials
|
||
{
|
||
get => (BackgroundCopyJobCredentialTarget)(BG_AUTH_TARGET)GetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_USE_STORED_CREDENTIALS);
|
||
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_USE_STORED_CREDENTIALS, value);
|
||
}
|
||
|
||
/// <summary>Gets the time the job was created.</summary>
|
||
public DateTime CreationTime => Times.CreationTime.ToDateTime();
|
||
|
||
/// <summary>Gets the time the job was last modified or bytes were transferred.</summary>
|
||
public DateTime ModificationTime => Times.ModificationTime.ToDateTime();
|
||
|
||
/// <summary>Gets the time the job entered the Transferred state.</summary>
|
||
public DateTime TransferCompletionTime => Times.TransferCompletionTime.ToDateTime();
|
||
|
||
private IBackgroundCopyJobHttpOptions IHttpOp => GetDerived<IBackgroundCopyJobHttpOptions>();
|
||
|
||
private IBackgroundCopyJob2 IJob2 => GetDerived<IBackgroundCopyJob2>();
|
||
|
||
private IBackgroundCopyJob3 IJob3 => GetDerived<IBackgroundCopyJob3>();
|
||
|
||
private IBackgroundCopyJob4 IJob4 => GetDerived<IBackgroundCopyJob4>();
|
||
|
||
private IBackgroundCopyJob5 IJob5 => GetDerived<IBackgroundCopyJob5>();
|
||
|
||
private BG_NOTIFY NotifyFlags
|
||
{
|
||
get => RunAction(() => m_ijob.GetNotifyFlags(), (BG_NOTIFY)0);
|
||
set => RunAction(() =>
|
||
{
|
||
var st = State;
|
||
if (st != BackgroundCopyJobState.Acknowledged && st != BackgroundCopyJobState.Cancelled)
|
||
m_ijob.SetNotifyFlags(value);
|
||
});
|
||
}
|
||
|
||
private BG_JOB_TIMES Times => RunAction(() => m_ijob.GetTimes(), new BG_JOB_TIMES());
|
||
|
||
/// <summary>
|
||
/// Use the Cancel method to delete the job from the transfer queue and to remove related temporary files from the client (downloads)
|
||
/// and server (uploads). You can cancel a job at any time; however, the job cannot be recovered after it is canceled.
|
||
/// </summary>
|
||
public void Cancel() => RunAction(() => m_ijob.Cancel());
|
||
|
||
/// <summary>
|
||
/// Use the RemoveCredentials method to remove credentials from use. The credentials must match an existing target and scheme pair
|
||
/// that you specified using the IBackgroundCopyJob2::SetCredentials method. There is no method to retrieve the credentials you have set.
|
||
/// </summary>
|
||
public void ClearCredentials()
|
||
{
|
||
try { IJob2.RemoveCredentials(BG_AUTH_TARGET.BG_AUTH_TARGET_SERVER, BG_AUTH_SCHEME.BG_AUTH_SCHEME_BASIC); }
|
||
catch (COMException) { }
|
||
try { IJob2.RemoveCredentials(BG_AUTH_TARGET.BG_AUTH_TARGET_SERVER, BG_AUTH_SCHEME.BG_AUTH_SCHEME_NTLM); }
|
||
catch (COMException) { }
|
||
}
|
||
|
||
/// <summary>Use the Complete method to end the job and save the transferred files on the client.</summary>
|
||
public void Complete() => RunAction(() => m_ijob.Complete());
|
||
|
||
/// <summary>Returns a hash code for this instance.</summary>
|
||
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
|
||
public override int GetHashCode() => ID.GetHashCode();
|
||
|
||
/// <summary>
|
||
/// Use the ReplaceRemotePrefix method to replace the beginning text of all remote names in the download job with the given string.
|
||
/// </summary>
|
||
/// <param name="oldPrefix">
|
||
/// String that identifies the text to replace in the remote name. The text must start at the beginning of the remote name.
|
||
/// </param>
|
||
/// <param name="newPrefix">String that contains the replacement text.</param>
|
||
public void ReplaceRemotePrefix(string oldPrefix, string newPrefix) => RunAction(() => IJob3.ReplaceRemotePrefix(oldPrefix, newPrefix));
|
||
|
||
/// <summary>Use the Resume method to activate a new job or restart a job that has been suspended.</summary>
|
||
public void Resume() => RunAction(() => m_ijob.Resume());
|
||
|
||
/// <summary>Specifies the identifier of the client certificate to use for client authentication in an HTTPS (SSL) request.</summary>
|
||
/// <param name="store">The certificate store.</param>
|
||
/// <param name="cert">The certificate.</param>
|
||
public void SetCertificate(X509Store store, X509Certificate2 cert)
|
||
{
|
||
var loc = BG_CERT_STORE_LOCATION.BG_CERT_STORE_LOCATION_CURRENT_USER;
|
||
switch (store.Location)
|
||
{
|
||
case StoreLocation.LocalMachine:
|
||
loc = BG_CERT_STORE_LOCATION.BG_CERT_STORE_LOCATION_LOCAL_MACHINE;
|
||
break;
|
||
|
||
case StoreLocation.CurrentUser:
|
||
default:
|
||
break;
|
||
}
|
||
IHttpOp.SetClientCertificateByID(loc, store.Name, cert.GetCertHash());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Use the SetCredentials method to specify the credentials to use for a proxy or remote server user authentication request.
|
||
/// </summary>
|
||
/// <param name="cred">Identifies the user's credentials to use for user authentication.</param>
|
||
/// <param name="target">Identifies the target for these credentials.</param>
|
||
public void SetCredentials(System.Net.NetworkCredential cred, BackgroundCopyJobCredentialTarget target = BackgroundCopyJobCredentialTarget.Server)
|
||
{
|
||
var ac = new BG_AUTH_CREDENTIALS { Target = (BG_AUTH_TARGET)target };
|
||
if (string.IsNullOrEmpty(cred.Domain))
|
||
{
|
||
if (!string.IsNullOrEmpty(cred.UserName))
|
||
{
|
||
ac.Scheme = BG_AUTH_SCHEME.BG_AUTH_SCHEME_BASIC;
|
||
ac.Credentials.Basic.UserName = cred.UserName;
|
||
ac.Credentials.Basic.Password = cred.Password;
|
||
}
|
||
else
|
||
ac.Scheme = BG_AUTH_SCHEME.BG_AUTH_SCHEME_NTLM;
|
||
}
|
||
else
|
||
{
|
||
ac.Scheme = BG_AUTH_SCHEME.BG_AUTH_SCHEME_NTLM;
|
||
ac.Credentials.Basic.UserName = string.Concat(cred.Domain, '\\', cred.UserName);
|
||
ac.Credentials.Basic.Password = cred.Password;
|
||
}
|
||
RunAction(() => IJob2.SetCredentials(ref ac));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Use the Suspend method to suspend a job. New jobs, jobs that are in error, and jobs that have finished transferring files are
|
||
/// automatically suspended.
|
||
/// </summary>
|
||
public void Suspend() => RunAction(() => m_ijob.Suspend());
|
||
|
||
/// <summary>
|
||
/// Use the TakeOwnership method to change ownership of the job to the current user. To take ownership of the job, the user must have
|
||
/// administrator privileges on the client.
|
||
/// </summary>
|
||
public void TakeOwnership() => RunAction(() => m_ijob.TakeOwnership());
|
||
|
||
/// <summary>Returns a <see cref="System.String"/> that represents this instance.</summary>
|
||
/// <returns>A <see cref="System.String"/> that represents this instance.</returns>
|
||
public override string ToString() => string.Concat($"Job: {DisplayName}", string.IsNullOrEmpty(Description) ? "" : $" - {Description}", $" ({ID})");
|
||
|
||
/// <summary>Disposes of the BackgroundCopyJob object.</summary>
|
||
void IDisposable.Dispose()
|
||
{
|
||
try
|
||
{
|
||
NotifyFlags = 0;
|
||
m_ijob.SetNotifyInterface(null);
|
||
if (State == BackgroundCopyJobState.Transferred)
|
||
Complete();
|
||
if (State != BackgroundCopyJobState.Acknowledged)
|
||
Cancel();
|
||
}
|
||
catch { }
|
||
Files = null;
|
||
m_ijob = null;
|
||
m_notifier = null;
|
||
}
|
||
|
||
/// <summary>Called when the job has completed.</summary>
|
||
protected virtual void OnCompleted() => Completed?.Invoke(this, new BackgroundCopyJobEventArgs(this));
|
||
|
||
/// <summary>Called when an error occurs.</summary>
|
||
/// <param name="err">The error.</param>
|
||
protected virtual void OnError(IBackgroundCopyError err) => Error?.Invoke(this, new BackgroundCopyJobEventArgs(this));
|
||
|
||
/// <summary>Called when a file range has been transferred.</summary>
|
||
/// <param name="file">The file being transferred.</param>
|
||
/// <param name="ranges">The ranges transferred.</param>
|
||
protected virtual void OnFileRangesTransferred(IBackgroundCopyFile file, BG_FILE_RANGE[] ranges) => FileRangesTransferred?.Invoke(this, new BackgroundCopyFileRangesTransferredEventArgs(this, file, ranges));
|
||
|
||
/// <summary>Called when a file transfer is completed.</summary>
|
||
/// <param name="pFile">The transferred file.</param>
|
||
protected virtual void OnFileTransferred(IBackgroundCopyFile pFile) => FileTransferred?.Invoke(this, new BackgroundCopyFileTransferredEventArgs(this, pFile));
|
||
|
||
/// <summary>Called when the job has been modified.</summary>
|
||
protected virtual void OnModified() => Modified?.Invoke(this, new BackgroundCopyJobEventArgs(this));
|
||
|
||
private T GetDerived<T>() where T : class
|
||
{
|
||
var ret = m_ijob as T;
|
||
return ret ?? throw new PlatformNotSupportedException();
|
||
}
|
||
|
||
private object GetProperty(BITS_JOB_PROPERTY_ID id)
|
||
{
|
||
var value = RunAction(() => IJob5.GetProperty(id));
|
||
switch (id)
|
||
{
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MAX_DOWNLOAD_SIZE:
|
||
return value.Uint64;
|
||
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ID_COST_FLAGS:
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MINIMUM_NOTIFICATION_INTERVAL_MS:
|
||
return value.Dword;
|
||
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_NOTIFICATION_CLSID:
|
||
return value.ClsID;
|
||
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_DYNAMIC_CONTENT:
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_HIGH_PERFORMANCE:
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ON_DEMAND_MODE:
|
||
return value.Enable;
|
||
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_USE_STORED_CREDENTIALS:
|
||
return value.Target;
|
||
|
||
default:
|
||
throw new ArgumentOutOfRangeException(nameof(id));
|
||
}
|
||
}
|
||
|
||
private void HandleCOMException(COMException cex)
|
||
{
|
||
if (State == BackgroundCopyJobState.Error || State == BackgroundCopyJobState.TransientError)
|
||
{
|
||
OnError(m_ijob.GetError());
|
||
}
|
||
else
|
||
throw new BackgroundCopyException(cex);
|
||
}
|
||
|
||
private void RunAction(Action action)
|
||
{
|
||
try { action(); }
|
||
catch (COMException cex) { HandleCOMException(cex); }
|
||
}
|
||
|
||
private T RunAction<T>(Func<T> action, T def = default)
|
||
{
|
||
try { return action(); }
|
||
catch (COMException cex) { HandleCOMException(cex); }
|
||
return def;
|
||
}
|
||
|
||
private void SetProperty(BITS_JOB_PROPERTY_ID id, object value)
|
||
{
|
||
var str = new BITS_JOB_PROPERTY_VALUE();
|
||
switch (id)
|
||
{
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MAX_DOWNLOAD_SIZE:
|
||
str.Uint64 = (ulong)value;
|
||
break;
|
||
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ID_COST_FLAGS:
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_MINIMUM_NOTIFICATION_INTERVAL_MS:
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_USE_STORED_CREDENTIALS:
|
||
str.Dword = (uint)value;
|
||
break;
|
||
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_NOTIFICATION_CLSID:
|
||
str.ClsID = (Guid)value;
|
||
break;
|
||
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_DYNAMIC_CONTENT:
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_HIGH_PERFORMANCE:
|
||
case BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ON_DEMAND_MODE:
|
||
str.Enable = (bool)value;
|
||
break;
|
||
|
||
default:
|
||
throw new ArgumentOutOfRangeException(nameof(id));
|
||
}
|
||
RunAction(() => IJob5.SetProperty(id, str));
|
||
}
|
||
|
||
[ComVisible(true)]
|
||
internal class Notifier : IBackgroundCopyCallback, IBackgroundCopyCallback2, IBackgroundCopyCallback3
|
||
{
|
||
private readonly BackgroundCopyJob parent;
|
||
|
||
public Notifier(BackgroundCopyJob job) => parent = job;
|
||
|
||
private Notifier()
|
||
{
|
||
}
|
||
|
||
public void FileRangesTransferred(IBackgroundCopyJob job, IBackgroundCopyFile file, uint rangeCount, BG_FILE_RANGE[] ranges) => parent.OnFileRangesTransferred(file, ranges);
|
||
|
||
public void FileTransferred(IBackgroundCopyJob pJob, IBackgroundCopyFile pFile) => parent.OnFileTransferred(pFile);
|
||
|
||
public void JobError(IBackgroundCopyJob pJob, IBackgroundCopyError pError) => parent.OnError(pError);
|
||
|
||
public void JobModification(IBackgroundCopyJob pJob, uint dwReserved) => parent.OnModified();
|
||
|
||
public void JobTransferred(IBackgroundCopyJob pJob) => parent.OnCompleted();
|
||
}
|
||
}
|
||
|
||
/// <summary>Event argument for background copy job.</summary>
|
||
public class BackgroundCopyJobEventArgs : EventArgs
|
||
{
|
||
internal BackgroundCopyJobEventArgs(BackgroundCopyJob j) => Job = j;
|
||
|
||
/// <summary>Gets the job being processed.</summary>
|
||
/// <value>The job.</value>
|
||
public BackgroundCopyJob Job { get; private set; }
|
||
}
|
||
} |