Updated BITS documentation (added remarks and code examples), cleanup up code and optimized single file copy in wrapper.

pull/83/head
David Hall 2019-10-22 08:25:34 -06:00
parent 10587e3ee7
commit f09713991a
5 changed files with 497 additions and 342 deletions

View File

@ -14,10 +14,7 @@ namespace Vanara.IO
{
private IBackgroundCopyJob m_ijob;
internal BackgroundCopyFileCollection(IBackgroundCopyJob ijob)
{
m_ijob = ijob;
}
internal BackgroundCopyFileCollection(IBackgroundCopyJob ijob) => m_ijob = ijob;
internal BackgroundCopyFileCollection()
{
@ -45,18 +42,53 @@ namespace Vanara.IO
/// <summary>Add a file to a download or an upload job. Only one file can be added to upload jobs.</summary>
/// <param name="remoteFilePath">
/// Contains the name of the file on the server (for example, http://[server]/[path]/file.ext). The format of the name must conform to the transfer
/// protocol you use. You cannot use wildcards in the path or file name. The URL must contain only legal URL characters; no escape processing is
/// performed. The URL is limited to 2,200 characters, not including the null terminator. You can use SMB to express the remote name of the file to
/// download or upload; there is no SMB support for upload-reply jobs. You can specify the remote name as a UNC path, full path with a network drive, or
/// use the "file://" prefix.
/// Contains the name of the file on the server (for example, http://[server]/[path]/file.ext). The format of the name must conform
/// to the transfer protocol you use. You cannot use wildcards in the path or file name. The URL must contain only legal URL
/// characters; no escape processing is performed. The URL is limited to 2,200 characters, not including the null terminator. You can
/// use SMB to express the remote name of the file to download or upload; there is no SMB support for upload-reply jobs. You can
/// specify the remote name as a UNC path, full path with a network drive, or use the "file://" prefix.
/// </param>
/// <param name="localFilePath">
/// Contains the name of the file on the client. The file name must include the full path (for example, d:\myapp\updates\file.ext). You cannot use
/// wildcards in the path or file name, and directories in the path must exist. The user must have permission to write to the local directory for
/// downloads and the reply portion of an upload-reply job. BITS does not support NTFS streams. Instead of using network drives, which are session
/// specific, use UNC paths.
/// Contains the name of the file on the client. The file name must include the full path (for example, d:\myapp\updates\file.ext).
/// You cannot use wildcards in the path or file name, and directories in the path must exist. The user must have permission to write
/// to the local directory for downloads and the reply portion of an upload-reply job. BITS does not support NTFS streams. Instead of
/// using network drives, which are session specific, use UNC paths.
/// </param>
/// <remarks>
/// <para>
/// To add more than one file at a time to a job, call the <see cref="AddRange(Uri, DirectoryInfo, IEnumerable{string})"/> method. It
/// is more efficient to call the <c>AddRange</c> method when adding multiple files to a job than to call the <c>Add</c> method in a loop.
/// </para>
/// <para>
/// To add a file to a job from which BITS downloads ranges of data from the file, call the
/// <see cref="Add(string, string, long, long)"/> method.
/// </para>
/// <para>Upload jobs can only contain one file. If you add a second file, the method returns BG_E_TOO_MANY_FILES.</para>
/// <para>
/// For downloads, BITS guarantees that the version of a file (based on file size and date, not content) that it transfers will be
/// consistent; however, it does not guarantee that a set of files will be consistent. For example, if BITS is in the middle of
/// downloading the second of two files in the job at the time that the files are updated on the server, BITS restarts the download
/// of the second file; however, the first file is not downloaded again.
/// </para>
/// <para>
/// Note that if you own the file being downloaded from the server, you should create a new URL for each new version of the file. If
/// you use the same URL for new versions of the file, some proxy servers may serve stale data from their cache because they do not
/// verify with the original server if the file is stale.
/// </para>
/// <para>
/// For uploads, BITS generates an error if the local file changes while the file is transferring. The error code is
/// BG_E_FILE_CHANGED and the context is BG_ERROR_CONTEXT_LOCAL_FILE.
/// </para>
/// <para>
/// BITS transfers the files within a job sequentially. If an error occurs while transferring a file, the job moves to an error state
/// and no more files within the job are processed until the error is resolved.
/// </para>
/// <para>
/// By default, a user can add up to 200 files to a job. This limit does not apply to administrators or service accounts. To change
/// the default, set the <c>MaxFilesPerJob</c> group policies.
/// </para>
/// <para><c>Prior to Windows Vista:</c> There is no limit on the number of files that a user can add to a job.</para>
/// </remarks>
public void Add(string remoteFilePath, string localFilePath)
{
try
@ -71,17 +103,17 @@ namespace Vanara.IO
/// <summary>Add a file to a download job and specify the ranges of the file you want to download.</summary>
/// <param name="remoteFilePath">
/// Contains the name of the file on the server (for example, http://[server]/[path]/file.ext). The format of the name must conform to the transfer
/// protocol you use. You cannot use wildcards in the path or file name. The URL must contain only legal URL characters; no escape processing is
/// performed. The URL is limited to 2,200 characters, not including the null terminator. You can use SMB to express the remote name of the file to
/// download or upload; there is no SMB support for upload-reply jobs. You can specify the remote name as a UNC path, full path with a network drive, or
/// use the "file://" prefix.
/// Contains the name of the file on the server (for example, http://[server]/[path]/file.ext). The format of the name must conform
/// to the transfer protocol you use. You cannot use wildcards in the path or file name. The URL must contain only legal URL
/// characters; no escape processing is performed. The URL is limited to 2,200 characters, not including the null terminator. You can
/// use SMB to express the remote name of the file to download or upload; there is no SMB support for upload-reply jobs. You can
/// specify the remote name as a UNC path, full path with a network drive, or use the "file://" prefix.
/// </param>
/// <param name="localFilePath">
/// Contains the name of the file on the client. The file name must include the full path (for example, d:\myapp\updates\file.ext). You cannot use
/// wildcards in the path or file name, and directories in the path must exist. The user must have permission to write to the local directory for
/// downloads and the reply portion of an upload-reply job. BITS does not support NTFS streams. Instead of using network drives, which are session
/// specific, use UNC paths.
/// Contains the name of the file on the client. The file name must include the full path (for example, d:\myapp\updates\file.ext).
/// You cannot use wildcards in the path or file name, and directories in the path must exist. The user must have permission to write
/// to the local directory for downloads and the reply portion of an upload-reply job. BITS does not support NTFS streams. Instead of
/// using network drives, which are session specific, use UNC paths.
/// </param>
/// <param name="initialOffset">Zero-based offset to the beginning of the range of bytes to download from a file.</param>
/// <param name="length">Number of bytes in the range.</param>
@ -103,16 +135,19 @@ namespace Vanara.IO
/// <summary>Add a list of files to download from a URL.</summary>
/// <param name="remoteUrlRoot">
/// Contains the name of the directory on the server (for example, http://[server]/[path]/). The format of the name must conform to the transfer protocol
/// you use. You cannot use wildcards in the path or file name. The URL must contain only legal URL characters; no escape processing is performed. The
/// URL is limited to 2,200 characters, not including the null terminator. You can use SMB to express the remote name of the file to download or upload;
/// there is no SMB support for upload-reply jobs. You can specify the remote name as a UNC path, full path with a network drive, or use the "file://" prefix.
/// Contains the name of the directory on the server (for example, http://[server]/[path]/). The format of the name must conform to
/// the transfer protocol you use. You cannot use wildcards in the path or file name. The URL must contain only legal URL characters;
/// no escape processing is performed. The URL is limited to 2,200 characters, not including the null terminator. You can use SMB to
/// express the remote name of the file to download or upload; there is no SMB support for upload-reply jobs. You can specify the
/// remote name as a UNC path, full path with a network drive, or use the "file://" prefix.
/// </param>
/// <param name="localDirectory">
/// Contains the name of the directory on the client. The directory must exist. The user must have permission to write to the directory for downloads.
/// BITS does not support NTFS streams. Instead of using network drives, which are session specific, use UNC paths.
/// Contains the name of the directory on the client. The directory must exist. The user must have permission to write to the
/// directory for downloads. BITS does not support NTFS streams. Instead of using network drives, which are session specific, use UNC paths.
/// </param>
/// <param name="files">
/// List of relative file names to retrieve from the remote directory. Filename will be appended to both the remoteUrlRoot and the localDirectory.
/// </param>
/// <param name="files">List of file names to retrieve. Filename will be appended to both the remoteUrlRoot and the localDirectory.</param>
public void AddRange(Uri remoteUrlRoot, DirectoryInfo localDirectory, IEnumerable<string> files)
{
var array = files.Select(s => new BG_FILE_INFO(new Uri(remoteUrlRoot, s).AbsoluteUri, Path.Combine(localDirectory.FullName, s))).ToArray();
@ -126,25 +161,32 @@ namespace Vanara.IO
}
}
/// <summary>Disposes of the BackgroundCopyFileSet object.</summary>
void IDisposable.Dispose()
{
m_ijob = null;
}
/// <summary>Add a list of files to download from a URL.</summary>
/// <param name="remoteUrlRoot">
/// Contains the name of the directory on the server (for example, http://[server]/[path]/). The format of the name must conform to
/// the transfer protocol you use. You cannot use wildcards in the path or file name. The URL must contain only legal URL characters;
/// no escape processing is performed. The URL is limited to 2,200 characters, not including the null terminator. You can use SMB to
/// express the remote name of the file to download or upload; there is no SMB support for upload-reply jobs. You can specify the
/// remote name as a UNC path, full path with a network drive, or use the "file://" prefix.
/// </param>
/// <param name="localDirectory">
/// Contains the name of the directory on the client. The directory must exist. The user must have permission to write to the
/// directory for downloads. BITS does not support NTFS streams. Instead of using network drives, which are session specific, use UNC paths.
/// </param>
/// <param name="files">
/// List of relative file names to retrieve from the remote directory. Filename will be appended to both the remoteUrlRoot and the localDirectory.
/// </param>
public void AddRange(string remoteUrlRoot, string localDirectory, IEnumerable<string> files) => AddRange(new Uri(remoteUrlRoot), new DirectoryInfo(localDirectory), files);
/// <summary>
/// Returns an object that implements the <see cref="IEnumerator"/> interface and that can iterate through the <see cref="BackgroundCopyFileInfo"/>
/// objects within the <see cref="BackgroundCopyFileCollection"/> collection.
/// Returns an object that implements the <see cref="IEnumerator"/> interface and that can iterate through the
/// <see cref="BackgroundCopyFileInfo"/> objects within the <see cref="BackgroundCopyFileCollection"/> collection.
/// </summary>
/// <returns>
/// Returns an object that implements the <see cref="IEnumerator"/> interface and that can iterate through the <see cref="BackgroundCopyFileInfo"/>
/// objects within the <see cref="BackgroundCopyFileCollection"/> collection.
/// Returns an object that implements the <see cref="IEnumerator"/> interface and that can iterate through the
/// <see cref="BackgroundCopyFileInfo"/> objects within the <see cref="BackgroundCopyFileCollection"/> collection.
/// </returns>
public IEnumerator<BackgroundCopyFileInfo> GetEnumerator()
{
var ienum = m_ijob.EnumFiles();
return new Enumerator(ienum);
}
public IEnumerator<BackgroundCopyFileInfo> GetEnumerator() => new Enumerator(m_ijob.EnumFiles());
/// <summary>Adds an item to the <see cref="ICollection{T}"/>.</summary>
/// <param name="item">The object to add to the <see cref="ICollection{T}"/>.</param>
@ -155,33 +197,28 @@ namespace Vanara.IO
/// <summary>Removes all items from the <see cref="ICollection{T}"/>.</summary>
/// <exception cref="NotImplementedException"></exception>
void ICollection<BackgroundCopyFileInfo>.Clear()
{
throw new NotSupportedException();
}
void ICollection<BackgroundCopyFileInfo>.Clear() => throw new NotSupportedException();
/// <summary>Determines whether the <see cref="ICollection{T}"/> contains a specific value.</summary>
/// <param name="item">The object to locate in the <see cref="ICollection{T}"/>.</param>
/// <returns>true if <paramref name="item"/> is found in the <see cref="ICollection{T}"/>; otherwise, false.</returns>
/// <exception cref="NotImplementedException"></exception>
bool ICollection<BackgroundCopyFileInfo>.Contains(BackgroundCopyFileInfo item)
{
throw new NotSupportedException();
}
bool ICollection<BackgroundCopyFileInfo>.Contains(BackgroundCopyFileInfo item) => throw new NotSupportedException();
/// <summary>
/// Copies the elements of the <see cref="ICollection{T}"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
/// Copies the elements of the <see cref="ICollection{T}"/> to an <see cref="T:System.Array"/>, starting at a particular
/// <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">
/// The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="ICollection{T}"/>. The
/// <see cref="T:System.Array"/> must have zero-based indexing.
/// The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from
/// <see cref="ICollection{T}"/>. The <see cref="T:System.Array"/> must have zero-based indexing.
/// </param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
/// <exception cref="NotImplementedException"></exception>
void ICollection<BackgroundCopyFileInfo>.CopyTo(BackgroundCopyFileInfo[] array, int arrayIndex)
{
throw new NotSupportedException();
}
void ICollection<BackgroundCopyFileInfo>.CopyTo(BackgroundCopyFileInfo[] array, int arrayIndex) => throw new NotSupportedException();
/// <summary>Disposes of the BackgroundCopyFileSet object.</summary>
void IDisposable.Dispose() => m_ijob = null;
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="IEnumerator"/> object that can be used to iterate through the collection.</returns>
@ -190,14 +227,11 @@ namespace Vanara.IO
/// <summary>Removes the first occurrence of a specific object from the <see cref="ICollection{T}"/>.</summary>
/// <param name="item">The object to remove from the <see cref="ICollection{T}"/>.</param>
/// <returns>
/// true if <paramref name="item"/> was successfully removed from the <see cref="ICollection{T}"/>; otherwise, false. This method also returns false if
/// <paramref name="item"/> is not found in the original <see cref="ICollection{T}"/>.
/// true if <paramref name="item"/> was successfully removed from the <see cref="ICollection{T}"/>; otherwise, false. This method
/// also returns false if <paramref name="item"/> is not found in the original <see cref="ICollection{T}"/>.
/// </returns>
/// <exception cref="NotImplementedException"></exception>
bool ICollection<BackgroundCopyFileInfo>.Remove(BackgroundCopyFileInfo item)
{
throw new NotSupportedException();
}
bool ICollection<BackgroundCopyFileInfo>.Remove(BackgroundCopyFileInfo item) => throw new NotSupportedException();
internal BackgroundCopyFileInfo[] GetBCFIArray() => this.ToArray();
@ -214,54 +248,20 @@ namespace Vanara.IO
}
/// <summary>
/// An implementation the <see cref="IEnumerator"/> interface that can iterate through the <see cref="BackgroundCopyFileInfo"/> objects within the
/// <see cref="BackgroundCopyFileCollection"/> collection.
/// An implementation the <see cref="IEnumerator"/> interface that can iterate through the <see cref="BackgroundCopyFileInfo"/>
/// objects within the <see cref="BackgroundCopyFileCollection"/> collection.
/// </summary>
private sealed class Enumerator : IEnumerator<BackgroundCopyFileInfo>
private sealed class Enumerator : Vanara.Collections.IEnumeratorFromNext<IEnumBackgroundCopyFiles, BackgroundCopyFileInfo>
{
private IBackgroundCopyFile icurrentfile;
private IEnumBackgroundCopyFiles ienum;
internal Enumerator(IEnumBackgroundCopyFiles enumfiles)
internal Enumerator(IEnumBackgroundCopyFiles enumfiles) : base(enumfiles, TryGetNext, e => e.Reset())
{
ienum = enumfiles;
ienum.Reset();
}
/// <summary>
/// Gets the <see cref="BackgroundCopyFileInfo"/> object in the <see cref="BackgroundCopyFileCollection"/> collection to which the enumerator is pointing.
/// </summary>
public BackgroundCopyFileInfo Current => icurrentfile != null ? new BackgroundCopyFileInfo(icurrentfile) : throw new InvalidOperationException();
/// <summary>
/// Gets the <see cref="BackgroundCopyFileInfo"/> object in the <see cref="BackgroundCopyFileCollection"/> collection to which the enumerator is pointing.
/// </summary>
object IEnumerator.Current => Current;
/// <summary>Disposes of the Enumerator object.</summary>
public void Dispose()
private static bool TryGetNext(IEnumBackgroundCopyFiles e, out BackgroundCopyFileInfo i)
{
ienum = null;
icurrentfile = null;
}
/// <summary>Moves the enumerator index to the next object in the collection.</summary>
/// <returns></returns>
public bool MoveNext()
{
try
{
icurrentfile = ienum.Next(1)?.FirstOrDefault();
return icurrentfile != null;
}
catch { return false; }
}
/// <summary>Resets the enumerator index to the beginning of the <see cref="BackgroundCopyFileCollection"/> collection.</summary>
public void Reset()
{
icurrentfile = null;
ienum.Reset();
var ifi = e.Next(1)?.FirstOrDefault();
i = ifi != null ? new BackgroundCopyFileInfo(ifi) : null;
return i != null;
}
}
}

View File

@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Vanara.Extensions;
using Vanara.InteropServices;
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.
/// 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
@ -22,23 +19,29 @@ namespace Vanara.IO
/// <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.
/// 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>
/// <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; }
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>
@ -54,10 +57,11 @@ namespace Vanara.IO
/// <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; }
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>
@ -68,13 +72,11 @@ namespace Vanara.IO
/// <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);
}
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.
/// 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; }
}
@ -82,16 +84,35 @@ namespace Vanara.IO
/// <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);
}
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 Backgroup Copy Service (BITS)</summary>
/// <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) =&gt; { System.Diagnostics.Debug.WriteLine("Job completed."); job.Complete(); evt.Set(); };
/// job.Error += (s, e) =&gt; throw job.LastError;
/// job.FileTransferred += (s, e) =&gt; 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)
@ -124,7 +145,8 @@ namespace Vanara.IO
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.
/// 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;
@ -140,34 +162,34 @@ namespace Vanara.IO
{
get
{
IHttpOp.GetClientCertificate(out BG_CERT_STORE_LOCATION loc, out var mstore, out var blob, out var subj);
IHttpOp.GetClientCertificate(out var loc, out var mstore, out var blob, out var subj);
if (blob.IsInvalid) return null;
string store = mstore;
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, (string)subj, false).OfType<X509Certificate2>().FirstOrDefault() ??
return xstore.Certificates.Find(X509FindType.FindBySubjectName, subj, false).OfType<X509Certificate2>().FirstOrDefault() ??
new X509Certificate2(blob.ToArray<byte>(20));
}
}
/// <summary>Gets the time the job was created.</summary>
public DateTime CreationTime => Times.CreationTime.ToDateTime();
/// <summary>The credentials to use for a proxy or remote server user authentication request.</summary>
public BackgroundCopyJobCredentials Credentials { get; private set; }
@ -212,8 +234,19 @@ namespace Vanara.IO
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>
/// <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
{
@ -221,16 +254,33 @@ namespace Vanara.IO
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_DYNAMIC_CONTENT, value);
}
/// <summary>Gets the number of errors that have occured in this job.</summary>
/// <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 jobs 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 cancelled 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>
/// <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 jobs
/// 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
{
@ -244,7 +294,7 @@ namespace Vanara.IO
/// <summary>Gets the type of job, such as download.</summary>
public BackgroundCopyJobType JobType => RunAction(() => (BackgroundCopyJobType)m_ijob.GetType(), BackgroundCopyJobType.Download);
/// <summary>Gets the last exeception that occured in the job.</summary>
/// <summary>Gets the last exception that occurred in the job.</summary>
public BackgroundCopyException LastError
{
get
@ -257,8 +307,15 @@ namespace Vanara.IO
}
}
/// <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>
/// <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
{
@ -267,7 +324,9 @@ namespace Vanara.IO
}
/// <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>
/// <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
{
@ -275,7 +334,11 @@ namespace Vanara.IO
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>
/// <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
{
@ -284,9 +347,9 @@ namespace Vanara.IO
}
/// <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.
/// 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
@ -295,12 +358,10 @@ namespace Vanara.IO
set => RunAction(() => m_ijob.SetMinimumRetryDelay((uint)value.TotalSeconds));
}
/// <summary>Gets the time the job was last modified or bytes were transferred.</summary>
public DateTime ModificationTime => Times.ModificationTime.ToDateTime();
/// <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.
/// 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
@ -309,7 +370,11 @@ namespace Vanara.IO
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>
/// <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
{
@ -318,8 +383,8 @@ namespace Vanara.IO
}
/// <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.
/// 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
@ -353,14 +418,14 @@ namespace Vanara.IO
{
if (value[0] == '"')
{
int i = p.IndexOf('"', 1);
var i = p.IndexOf('"', 1);
if (i + 3 <= p.Length)
a = p.Substring(i + 2);
p = p.Substring(1, i - 1);
}
else
{
int i = p.IndexOf(' ');
var i = p.IndexOf(' ');
if (i + 2 <= p.Length)
a = p.Substring(i + 1);
p = p.Substring(0, i);
@ -370,8 +435,15 @@ namespace Vanara.IO
}
}
/// <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>
/// <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
{
@ -387,13 +459,16 @@ namespace Vanara.IO
[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>
/// <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.
/// 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
@ -406,9 +481,12 @@ namespace Vanara.IO
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.
/// 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>
/// <exception cref="ArgumentException">
/// The WebProxy.Credentials property value contains a value. Use the SetCredentials method instead.
/// </exception>
[DefaultValue(null)]
public System.Net.WebProxy Proxy
{
@ -435,14 +513,11 @@ namespace Vanara.IO
}
/// <summary>Gets an in-memory copy of the reply data from the server application.</summary>
public byte[] ReplyData
public byte[] ReplyData => RunAction(() =>
{
get => RunAction(() =>
{
IJob2.GetReplyData(out var pdata, out var cRet);
return (cRet > 0) ? pdata.ToArray<byte>((int)cRet) : new byte[0];
}, new byte[0]);
}
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)]
@ -455,12 +530,9 @@ namespace Vanara.IO
/// <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 the current state of the job.</summary>
public BackgroundCopyJobState State => RunAction(() => (BackgroundCopyJobState)m_ijob.GetState(), BackgroundCopyJobState.Error);
/// <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.
/// 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
@ -469,7 +541,13 @@ namespace Vanara.IO
set => RunAction(() => IHttpOp.SetSecurityFlags((BG_HTTP_SECURITY)value));
}
/// <summary>Used to control transfer behavior over cellular and/or similar networks. This property may be changed while a transfer is ongoing the new cost flags will take effect immediately.</summary>
/// <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
/// the new cost flags will take effect immediately.
/// </summary>
[DefaultValue(BackgroundCopyCost.TransferUnrestricted)]
public BackgroundCopyCost TransferBehavior
{
@ -477,11 +555,14 @@ namespace Vanara.IO
set => SetProperty(BITS_JOB_PROPERTY_ID.BITS_JOB_PROPERTY_ID_COST_FLAGS, value);
}
/// <summary>Gets the time the job entered the Transferred state.</summary>
public DateTime TransferCompletionTime => Times.TransferCompletionTime.ToDateTime();
/// <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 users 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 owners credentials, unless you have explicitly provided a network helper token, in which case BITS will use the network helper tokens credentials.
/// <para>Only the BG_AUTH_TARGET_PROXY target is supported.</para></summary>
/// <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
/// users 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 owners credentials, unless you have explicitly provided a network helper token, in which case
/// BITS will use the network helper tokens credentials.
/// <para>Only the BG_AUTH_TARGET_PROXY target is supported.</para>
/// </summary>
[DefaultValue(BackgroundCopyJobCredentialTarget.Undefined)]
public BackgroundCopyJobCredentialTarget UseStoredCredentials
{
@ -489,10 +570,23 @@ namespace Vanara.IO
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
@ -500,7 +594,7 @@ namespace Vanara.IO
get => RunAction(() => m_ijob.GetNotifyFlags(), (BG_NOTIFY)0);
set => RunAction(() =>
{
BackgroundCopyJobState st = State;
var st = State;
if (st != BackgroundCopyJobState.Acknowledged && st != BackgroundCopyJobState.Cancelled)
m_ijob.SetNotifyFlags(value);
});
@ -509,14 +603,14 @@ namespace Vanara.IO
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.
/// 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.
/// 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()
{
@ -533,8 +627,12 @@ namespace Vanara.IO
/// <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>
/// <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));
@ -552,6 +650,7 @@ namespace Vanara.IO
case StoreLocation.LocalMachine:
loc = BG_CERT_STORE_LOCATION.BG_CERT_STORE_LOCATION_LOCAL_MACHINE;
break;
case StoreLocation.CurrentUser:
default:
break;
@ -559,7 +658,9 @@ namespace Vanara.IO
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>
/// <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)
@ -586,18 +687,19 @@ namespace Vanara.IO
}
/// <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.
/// 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.
/// 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>
/// <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>
@ -607,6 +709,10 @@ namespace Vanara.IO
{
NotifyFlags = 0;
m_ijob.SetNotifyInterface(null);
if (State == BackgroundCopyJobState.Transferred)
Complete();
if (State != BackgroundCopyJobState.Acknowledged)
Cancel();
}
catch { }
Files = null;
@ -616,35 +722,27 @@ namespace Vanara.IO
}
/// <summary>Called when the job has completed.</summary>
protected virtual void OnCompleted()
{
Completed?.Invoke(this, new BackgroundCopyJobEventArgs(this));
}
protected virtual void OnCompleted() => Completed?.Invoke(this, new BackgroundCopyJobEventArgs(this));
protected virtual void OnError(IBackgroundCopyError err)
{
Error?.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));
protected virtual void OnFileRangesTransferred(IBackgroundCopyFile file, BG_FILE_RANGE[] ranges)
{
FileRangesTransferred?.Invoke(this, new BackgroundCopyFileRangesTransferredEventArgs(this, file, ranges));
}
/// <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));
protected virtual void OnFileTransferred(IBackgroundCopyFile pFile)
{
FileTransferred?.Invoke(this, new BackgroundCopyFileTransferredEventArgs(this, pFile));
}
/// <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));
}
protected virtual void OnModified() => Modified?.Invoke(this, new BackgroundCopyJobEventArgs(this));
private T GetDerived<T>() where T : class
{
T ret = m_ijob as T;
var ret = m_ijob as T;
return ret ?? throw new PlatformNotSupportedException();
}
@ -655,17 +753,22 @@ namespace Vanara.IO
{
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));
}
@ -702,19 +805,23 @@ namespace Vanara.IO
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));
}
@ -724,49 +831,30 @@ namespace Vanara.IO
[ComVisible(true)]
internal class Notifier : IBackgroundCopyCallback, IBackgroundCopyCallback2, IBackgroundCopyCallback3
{
private BackgroundCopyJob parent;
private readonly BackgroundCopyJob parent;
public Notifier(BackgroundCopyJob job)
public Notifier(BackgroundCopyJob job) => parent = job;
private Notifier()
{
parent = job;
}
private Notifier() { }
public void FileRangesTransferred(IBackgroundCopyJob job, IBackgroundCopyFile file, uint rangeCount, BG_FILE_RANGE[] ranges) => parent.OnFileRangesTransferred(file, ranges);
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 FileTransferred(IBackgroundCopyJob pJob, IBackgroundCopyFile pFile)
{
parent.OnFileTransferred(pFile);
}
public void JobError(IBackgroundCopyJob pJob, IBackgroundCopyError pError) => parent.OnError(pError);
public void JobError(IBackgroundCopyJob pJob, IBackgroundCopyError pError)
{
parent.OnError(pError);
}
public void JobModification(IBackgroundCopyJob pJob, uint dwReserved) => parent.OnModified();
public void JobModification(IBackgroundCopyJob pJob, uint dwReserved)
{
parent.OnModified();
}
public void JobTransferred(IBackgroundCopyJob pJob)
{
parent.OnCompleted();
}
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;
}
internal BackgroundCopyJobEventArgs(BackgroundCopyJob j) => Job = j;
/// <summary>Gets the job being processed.</summary>
/// <value>The job.</value>

View File

@ -3,28 +3,23 @@ using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Vanara.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.BITS;
namespace Vanara.IO
{
/// <summary>
/// Use the BackgroundCopyManager to create transfer jobs, retrieve an enumerator object that contains the jobs in the queue, and to retrieve individual jobs
/// from the queue.
/// Use the BackgroundCopyManager to create transfer jobs, retrieve an enumerator object that contains the jobs in the queue, and to
/// retrieve individual jobs from the queue.
/// </summary>
public static partial class BackgroundCopyManager
{
private static readonly ComReleaser<IBackgroundCopyManager> ciMgr = ComReleaserFactory.Create(new IBackgroundCopyManager());
private static Version ver;
/// <summary>Initializes a new instance of the <see cref="BackgroundCopyManager"/> class.</summary>
static BackgroundCopyManager()
{
IMgr = new IBackgroundCopyManager();
Jobs = new BackgroundCopyJobCollection();
}
/// <summary>Gets the list of currently queued jobs for all users.</summary>
public static BackgroundCopyJobCollection Jobs { get; private set; }
public static BackgroundCopyJobCollection Jobs { get; } = new BackgroundCopyJobCollection();
/// <summary>Retrieves the running version of BITS.</summary>
public static Version Version
@ -34,32 +29,31 @@ namespace Vanara.IO
try { return ver ?? (ver = GetVer()); }
catch { return new Version(); }
Version GetVer()
static Version GetVer()
{
var fi = System.Diagnostics.FileVersionInfo.GetVersionInfo(Environment.ExpandEnvironmentVariables(@"%WinDir%\Sysnative\qmgr.dll"));
switch ($"{fi.FileMajorPart}.{fi.FileMinorPart}")
return $"{fi.FileMajorPart}.{fi.FileMinorPart}" switch
{
case "7.8": return new Version(10, 1);
case "7.7": return new Version(5, 0);
case "7.5": return new Version(4, 0);
case "7.0": return new Version(3, 0);
case "6.7": return new Version(2, 5);
case "6.6": return new Version(2, 0);
case "6.5": return new Version(1, 5);
case "6.2": return new Version(1, 2);
default: return new Version(1, 0);
}
"7.8" => new Version(10, 1),
"7.7" => new Version(5, 0),
"7.5" => new Version(4, 0),
"7.0" => new Version(3, 0),
"6.7" => new Version(2, 5),
"6.6" => new Version(2, 0),
"6.5" => new Version(1, 5),
"6.2" => new Version(1, 2),
_ => new Version(1, 0),
};
}
}
}
private static IBackgroundCopyManager IMgr => ciMgr.Item;
/// <summary>Copies an existing file to a new file using BITS. Overwriting a file of the same name is not allowed.</summary>
/// <param name="sourceFileName">The file to copy.</param>
/// <param name="destFileName">The name of the destination file.</param>
public static void Copy(string sourceFileName, string destFileName)
{
CopyTemplate(sourceFileName, destFileName, () => false, System.Threading.Thread.Sleep, null, f => f.Add(sourceFileName, destFileName));
}
public static void Copy(string sourceFileName, string destFileName) => CopyTemplate(destFileName, new CancellationToken(false), null, f => f.Add(sourceFileName, destFileName));
/// <summary>Copies an existing file to a new file using BITS. Overwriting a file of the same name is not allowed.</summary>
/// <param name="sourceFileName">The file to copy.</param>
@ -67,20 +61,32 @@ namespace Vanara.IO
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <param name="progress">An optional delegate that can be used to track the progress of the operation asynchronously.</param>
/// <returns>A task that represents the asynchronous copy operation.</returns>
/// <example>
/// Below is an example of how to use this function with cancellation support and a progress delegate:
/// <code title="Async background file copy">
/// // Using a cancellation token allows the UI to respond to a cancellation request
/// var cts = new CancellationTokenSource();
/// // This progress delegate writes the status to the console, but it could also up the UI
/// var prog = new Progress&lt;Tuple&lt;BackgroundCopyJobState, byte&gt;&gt;(t =&gt; Console.WriteLine($"{t.Item2}% : {t.Item1}"));
/// // This optionally tells the copy routine to cancel if it takes longer than 2 seconds.
/// // You don't need this and can rely on other user cancellation methods.
/// cts.CancelAfter(2000);
/// // This is the copy routine.
/// // 'src' is the source file path.
/// // 'dest' is the destination file path.
/// BackgroundCopyManager.CopyAsync(src, dest, cts.Token, prog);
/// </code>
/// </example>
public static async Task CopyAsync(string sourceFileName, string destFileName, CancellationToken cancellationToken, IProgress<Tuple<BackgroundCopyJobState, byte>> progress)
{
#if NET40
await TaskEx.Run(() => CopyTemplate(sourceFileName, destFileName, () => cancellationToken.IsCancellationRequested,
Thread.Sleep, (s, p) => progress?.Report(new Tuple<BackgroundCopyJobState, byte>(s,p)), f => f.Add(sourceFileName, destFileName)), cancellationToken);
await TaskEx.Run(() => CopyTemplate(destFileName, cancellationToken, (s, p) => progress?.Report(new Tuple<BackgroundCopyJobState, byte>(s,p)), f => f.Add(sourceFileName, destFileName)), cancellationToken);
#else
await Task.Run(() => CopyTemplate(sourceFileName, destFileName, () => cancellationToken.IsCancellationRequested,
Thread.Sleep, (s, p) => progress?.Report(new Tuple<BackgroundCopyJobState, byte>(s, p)), f => f.Add(sourceFileName, destFileName)), cancellationToken);
await Task.Run(() => CopyTemplate(destFileName, cancellationToken, (s, p) => progress?.Report(new Tuple<BackgroundCopyJobState, byte>(s, p)), f => f.Add(sourceFileName, destFileName)), cancellationToken);
#endif
cancellationToken.ThrowIfCancellationRequested();
}
private static IBackgroundCopyManager IMgr { get; set; }
internal static IBackgroundCopyJob CreateJob(string displayName, BG_JOB_TYPE jobType = BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD)
{
try
@ -132,51 +138,30 @@ namespace Vanara.IO
return wp.IsInRole(WindowsBuiltInRole.Administrator);
}
private static void CopyTemplate(string sourceFileName, string destFileName, Func<bool> shouldCancel, Action<int> delay, Action<BackgroundCopyJobState, byte> report, Action<BackgroundCopyFileCollection> add)
private static void CopyTemplate(string destFileName, CancellationToken ct, Action<BackgroundCopyJobState, byte> report, Action<BackgroundCopyFileCollection> add)
{
var type = (Uri.TryCreate(destFileName, UriKind.Absolute, out var uri) && !uri.IsFile) ? BackgroundCopyJobType.Upload : BackgroundCopyJobType.Download;
using (var job = Jobs.Add("Temp" + Guid.NewGuid().ToString(), "", type))
using var job = Jobs.Add("Temp" + Guid.NewGuid().ToString(), "", type);
using var evt = new ManualResetEventSlim(false);
BackgroundCopyException err = null;
job.Completed += (s, e) => { ReportProgress(BackgroundCopyJobState.Transferred); job.Complete(); evt.Set(); };
job.Error += (s, e) => { err = job.LastError; job.Cancel(); evt.Set(); };
job.FileTransferred += (s, e) => ReportProgress(job.State);
job.FileRangesTransferred += (s, e) => ReportProgress(job.State);
add(job.Files);
job.Resume();
evt.Wait(ct);
if (ct.IsCancellationRequested)
{
job.DisableNotifications = true;
add(job.Files);
BackgroundCopyJobState state = BackgroundCopyJobState.Connecting;
job.Resume();
do
{
switch (state = job.State)
{
case BackgroundCopyJobState.Queued:
case BackgroundCopyJobState.Connecting:
case BackgroundCopyJobState.Transferring:
case BackgroundCopyJobState.Suspended:
ReportProgress();
break;
case BackgroundCopyJobState.Error:
case BackgroundCopyJobState.TransientError:
throw job.LastError;
case BackgroundCopyJobState.Transferred:
ReportProgress();
job.Complete();
return;
case BackgroundCopyJobState.Acknowledged:
case BackgroundCopyJobState.Cancelled:
return;
default:
throw new InvalidOperationException("Unknown job state");
}
if (shouldCancel())
{
job.Cancel();
break;
}
delay(1000);
job.Cancel();
throw new OperationCanceledException();
}
if (err != null)
throw err;
void ReportProgress()
{
report?.Invoke(state, job.Progress.PercentComplete);
}
} while (state != BackgroundCopyJobState.Transferred && state != BackgroundCopyJobState.Error && state != BackgroundCopyJobState.TransientError);
void ReportProgress(BackgroundCopyJobState state)
{
report?.Invoke(state, job.Progress.PercentComplete);
}
}
}

View File

@ -1403,12 +1403,52 @@ namespace Vanara.PInvoke
/// <summary>Adds a single file to the job.</summary>
/// <param name="RemoteUrl">
/// Null-terminated string that contains the name of the file on the server. For information on specifying the remote name, see
/// the RemoteName member and Remarks section of the BG_FILE_INFO structure.
/// the <c>RemoteName</c> member and Remarks section of the BG_FILE_INFO structure.
/// </param>
/// <param name="LocalName">
/// Null-terminated string that contains the name of the file on the client. For information on specifying the local name, see
/// the LocalName member and Remarks section of the BG_FILE_INFO structure.
/// the <c>LocalName</c> member and Remarks section of the BG_FILE_INFO structure.
/// </param>
/// <remarks>
/// <para>
/// To add more than one file at a time to a job, call the IBackgroundCopyJob::AddFileSet method. It is more efficient to call
/// the <c>AddFileSet</c> method when adding multiple files to a job than to call the <c>AddFile</c> method in a loop. For more
/// information, see Adding Files to a Job.
/// </para>
/// <para>
/// To add a file to a job from which BITS downloads ranges of data from the file, call the
/// IBackgroundCopyJob3::AddFileWithRanges method.
/// </para>
/// <para>Upload jobs can only contain one file. If you add a second file, the method returns BG_E_TOO_MANY_FILES.</para>
/// <para>
/// For downloads, BITS guarantees that the version of a file (based on file size and date, not content) that it transfers will
/// be consistent; however, it does not guarantee that a set of files will be consistent. For example, if BITS is in the middle
/// of downloading the second of two files in the job at the time that the files are updated on the server, BITS restarts the
/// download of the second file; however, the first file is not downloaded again.
/// </para>
/// <para>
/// Note that if you own the file being downloaded from the server, you should create a new URL for each new version of the file.
/// If you use the same URL for new versions of the file, some proxy servers may serve stale data from their cache because they
/// do not verify with the original server if the file is stale.
/// </para>
/// <para>
/// For uploads, BITS generates an error if the local file changes while the file is transferring. The error code is
/// BG_E_FILE_CHANGED and the context is BG_ERROR_CONTEXT_LOCAL_FILE.
/// </para>
/// <para>
/// BITS transfers the files within a job sequentially. If an error occurs while transferring a file, the job moves to an error
/// state and no more files within the job are processed until the error is resolved.
/// </para>
/// <para>
/// By default, a user can add up to 200 files to a job. This limit does not apply to administrators or service accounts. To
/// change the default, set the <c>MaxFilesPerJob</c> group policies.
/// </para>
/// <para><c>Prior to Windows Vista:</c> There is no limit on the number of files that a user can add to a job.</para>
/// <para>For scalability concerns, see Best Practices When Using BITS.</para>
/// <para>Examples</para>
/// <para>For an example that adds a single file to a job, see Adding Files to a Job.</para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/bits/nf-bits-ibackgroundcopyjob-addfile
void AddFile([In, MarshalAs(UnmanagedType.LPWStr)] string RemoteUrl, [In, MarshalAs(UnmanagedType.LPWStr)] string LocalName);
/// <summary>Retrieves an IEnumBackgroundCopyFiles interface pointer that you use to enumerate the files in a job.</summary>

View File

@ -1,19 +1,19 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
namespace Vanara.IO.Tests
{
[TestFixture()]
public class BackgroundCopyTests
{
const string jname = "TestJob";
const string src = @"file:///C:/Temp/Holes.mp4";
const string dest = @"D:\dest.bin";
private const string jname = "TestJob";
private const string src = @"file:///C:/Temp/Holes.mp4";
private const string dest = @"D:\dest.bin";
[Test]
public void EnumJobTest()
@ -25,10 +25,7 @@ namespace Vanara.IO.Tests
}
[Test]
public void VerTest()
{
Assert.That(BackgroundCopyManager.Version, Is.GreaterThanOrEqualTo(new Version(10, 0)));
}
public void VerTest() => Assert.That(BackgroundCopyManager.Version, Is.GreaterThanOrEqualTo(new Version(10, 0)));
[Test]
public void JobCollTest()
@ -53,7 +50,7 @@ namespace Vanara.IO.Tests
public void FileCollTest()
{
var job = BackgroundCopyManager.Jobs.Add(jname);
System.IO.File.Delete(dest);
File.Delete(dest);
Assert.That(() => job.Files.Add(src, dest), Throws.Nothing);
Assert.That(job.Files.Count, Is.EqualTo(1));
Assert.That(job.Files.Count(), Is.EqualTo(1));
@ -64,22 +61,22 @@ namespace Vanara.IO.Tests
[Test]
public void CopyTest()
{
System.IO.File.Delete(dest);
File.Delete(dest);
Assert.That(() => BackgroundCopyManager.Copy(src, dest), Throws.Nothing);
Assert.That(System.IO.File.Exists(dest));
System.IO.File.Delete(dest);
Assert.That(File.Exists(dest));
File.Delete(dest);
}
[Test]
public void CopyAsyncCancelReportTest()
{
System.IO.File.Delete(dest);
File.Delete(dest);
var cts = new CancellationTokenSource();
var l = new List<string>();
var prog = new Progress<Tuple<BackgroundCopyJobState, byte>>(t => l.Add($"{t.Item2}% : {t.Item1}"));
cts.CancelAfter(2000);
Assert.That(() => BackgroundCopyManager.CopyAsync(src, dest, cts.Token, prog), Throws.TypeOf<OperationCanceledException>());
Assert.That(System.IO.File.Exists(dest), Is.False);
Assert.That(File.Exists(dest), Is.False);
Assert.That(l.Count, Is.GreaterThanOrEqualTo(0));
TestContext.Write(string.Join("\r\n", l));
}
@ -87,12 +84,12 @@ namespace Vanara.IO.Tests
[Test]
public void CopyAsyncReportTest()
{
System.IO.File.Delete(dest);
File.Delete(dest);
var cts = new CancellationTokenSource();
var l = new List<string>();
var prog = new Progress<Tuple<BackgroundCopyJobState, byte>>(t => l.Add($"{t.Item2}% : {t.Item1}"));
Assert.That(() => BackgroundCopyManager.CopyAsync(src, dest, cts.Token, prog), Throws.Nothing);
Assert.That(System.IO.File.Exists(dest), Is.True);
Assert.That(File.Exists(dest), Is.True);
Assert.That(l.Count, Is.GreaterThan(0));
TestContext.Write(string.Join("\r\n", l));
}
@ -100,11 +97,11 @@ namespace Vanara.IO.Tests
[Test]
public void CopyAsyncTest()
{
System.IO.File.Delete(dest);
File.Delete(dest);
var cts = new CancellationTokenSource();
Assert.That(() => BackgroundCopyManager.CopyAsync(src, dest, cts.Token, null), Throws.Nothing);
Assert.That(System.IO.File.Exists(dest), Is.True);
System.IO.File.Delete(dest);
Assert.That(File.Exists(dest), Is.True);
File.Delete(dest);
}
[Test]
@ -120,6 +117,51 @@ namespace Vanara.IO.Tests
store.Close();
}
[Test]
public void JobExample()
{
try
{
var evt = new AutoResetEvent(false);
// ===== Create a download job (default) =================
using var job = BackgroundCopyManager.Jobs.Add("Download directory");
// 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
job.Files.AddRange(@"file:///C:/Temp/", @"D:\", new[] { "Holes.mp4", "NuGet.exe", "procexp.exe" });
// Start (resume) the job.
job.Resume();
if (!evt.WaitOne(20000))
throw new InvalidOperationException();
// ===== Create an upload job ============================
using var uploadJob = BackgroundCopyManager.Jobs.Add("Upload", null, BackgroundCopyJobType.Upload);
// Set completion handler for job that calls the Complete method to end the job and save all files.
uploadJob.Completed += (s, e) => { job.Complete(); evt.Set(); };
uploadJob.Error += (s, e) => throw uploadJob.LastError;
// Add a single file (multiple files are not supported for upload jobs)
uploadJob.Files.Add("file:///C:/Temp/upload.tmp", @"D:\procexp.exe");
// Start (resume) the job.
uploadJob.Resume();
if (!evt.WaitOne(20000))
throw new InvalidOperationException();
}
finally
{
// Cleanup
File.Delete(@"C:\Temp\upload.tmp");
File.Delete(@"D:\Holes.mp4");
File.Delete(@"D:\NuGet.exe");
File.Delete(@"D:\procexp.exe");
}
}
[Test]
public void JobPropTest()
{
@ -198,9 +240,9 @@ namespace Vanara.IO.Tests
Assert.That(job.Owner, Is.EqualTo(System.Security.Principal.WindowsIdentity.GetCurrent().User));
Assert.That(job.OwnerIntegrityLevel, Is.EqualTo(12288));
Assert.That(job.OwnerIntegrityLevel, Is.EqualTo(8192));
Assert.That(job.OwnerIsElevated, Is.EqualTo(true));
Assert.That(job.OwnerIsElevated, Is.EqualTo(false));
Assert.That(job.Priority, Is.EqualTo(job.GetDefVal<BackgroundCopyJobPriority>(nameof(job.Priority))));
Assert.That(() => job.Priority = BackgroundCopyJobPriority.Low, Throws.Nothing);