using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using Vanara.PInvoke; using static Vanara.PInvoke.BITS; namespace Vanara.IO; /// /// 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. /// public static partial class BackgroundCopyManager { private static readonly ComReleaser ciMgr = ComReleaserFactory.Create(new IBackgroundCopyManager()); [MaybeNull] private static Version ver; /// Gets the list of currently queued jobs for all users. public static BackgroundCopyJobCollection Jobs { get; } = new BackgroundCopyJobCollection(); /// Gets an object that manages the pool of peers from which you can download content. /// The peer cache administration. public static PeerCacheAdministration PeerCacheAdministration { get; } = new PeerCacheAdministration(ciMgr.Item); /// Retrieves the running version of BITS. public static Version Version { get { try { return ver ??= GetVer(); } catch { return new Version(); } static Version GetVer() { string dllPath = Microsoft.Win32.Registry.GetValue($@"HKEY_CLASSES_ROOT\CLSID\{typeof(IBackgroundCopyManager).GUID:B}\InProcServer32", null, null)?.ToString() ?? Environment.ExpandEnvironmentVariables($@"%SystemRoot%\{(Environment.Is64BitProcess != Environment.Is64BitOperatingSystem ? "sysnative" : "system32")}\qmgr.dll"); var fi = System.Diagnostics.FileVersionInfo.GetVersionInfo(dllPath); return $"{fi.FileMajorPart}.{fi.FileMinorPart}" switch { "7.8" when fi.FileBuildPart >= 18362 => new Version(10, 3), "7.8" when fi.FileBuildPart >= 17763 => new Version(10, 2), "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), }; } } } internal static IBackgroundCopyManager IMgr => ciMgr.Item; /// Copies an existing file to a new file using BITS. Overwriting a file of the same name is not allowed. /// The file to copy. /// The name of the destination file. public static void Copy(string sourceFileName, string destFileName) => CopyTemplate(destFileName, new CancellationToken(false), null, f => f.Add(sourceFileName, destFileName)); /// Copies an existing file to a new file using BITS. Overwriting a file of the same name is not allowed. /// The file to copy. /// The name of the destination file. /// The token to monitor for cancellation requests. /// An optional delegate that can be used to track the progress of the operation asynchronously. /// A task that represents the asynchronous copy operation. /// /// Below is an example of how to use this function with cancellation support and a progress delegate: /// /// // 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<Tuple<BackgroundCopyJobState, byte>>(t => 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); /// /// public static async Task CopyAsync(string sourceFileName, string destFileName, [Optional] CancellationToken cancellationToken, [Optional] IProgress>? progress) { #if NET40 await TaskEx.Run(() => CopyTemplate(destFileName, cancellationToken, (s, p) => progress?.Report(new Tuple(s,p)), f => f.Add(sourceFileName, destFileName)), cancellationToken); #else await Task.Run(() => CopyTemplate(destFileName, cancellationToken, (s, p) => progress?.Report(new Tuple(s, p)), f => f.Add(sourceFileName, destFileName)), cancellationToken); #endif cancellationToken.ThrowIfCancellationRequested(); } internal static IBackgroundCopyJob CreateJob(string displayName, BG_JOB_TYPE jobType = BG_JOB_TYPE.BG_JOB_TYPE_DOWNLOAD) { try { IMgr.CreateJob(displayName, jobType, out Guid newJobID, out IBackgroundCopyJob newJob); return newJob; } catch (COMException cex) { throw new BackgroundCopyException(cex); } } internal static IEnumBackgroundCopyJobs EnumJobs(BG_JOB_ENUM type = BG_JOB_ENUM.BG_JOB_ENUM_ALL_USERS) => IMgr.EnumJobs(type); internal static string? GetErrorMessage(HRESULT hResult) { try { return IMgr.GetErrorDescription(hResult, (uint)((short)System.Globalization.CultureInfo.CurrentCulture.LCID)); } catch (COMException) { return null; } } internal static IBackgroundCopyJob? GetJob(Guid jobId) { try { return IMgr.GetJob(jobId); } catch (COMException cex) { if ((uint)cex.ErrorCode != 0x80200001) throw new BackgroundCopyException(cex); } return null; } /// Checks if the current user has administrator rights. internal static bool IsCurrentUserAdministrator() { using var identity = WindowsIdentity.GetCurrent(); return new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator); } private static void CopyTemplate(string destFileName, CancellationToken ct, Action? report, Action add) { BackgroundCopyJobType type = (Uri.TryCreate(destFileName, UriKind.Absolute, out Uri? uri) && !uri.IsFile) ? BackgroundCopyJobType.Upload : BackgroundCopyJobType.Download; using BackgroundCopyJob mainJob = Jobs.Add("Temp" + Guid.NewGuid().ToString(), string.Empty, type); using var manualReset = new ManualResetEventSlim(false); BackgroundCopyException? err = null; // Set event handlers for job, these are weak references. mainJob.Completed += OnCompleted; mainJob.Error += OnError; mainJob.FileTransferred += OnFileTransferred; mainJob.FileRangesTransferred += OnFileRangesTransferred; add(mainJob.Files); mainJob.Resume(); manualReset.Wait(ct); var raiseException = false; if (ct.IsCancellationRequested) { mainJob.Cancel(); raiseException = true; } // Remove weak references to prevent memory leak. mainJob.FileRangesTransferred -= OnFileRangesTransferred; mainJob.FileTransferred -= OnFileTransferred; mainJob.Completed -= OnCompleted; mainJob.Error -= OnError; if (raiseException) throw new OperationCanceledException(); if (null != err) throw err; // Better performance when event methods are defined seperately, preferably static. void OnCompleted(object? s, BackgroundCopyJobEventArgs e) { if (s is BackgroundCopyJob job) { ReportProgress(job, BackgroundCopyJobState.Transferred); job.Complete(); manualReset.Set(); } } void OnError(object? s, BackgroundCopyJobEventArgs e) { if (s is BackgroundCopyJob job) { err = job.LastError; job.Cancel(); manualReset.Set(); } } void OnFileTransferred(object? s, BackgroundCopyFileTransferredEventArgs e) { if (s is BackgroundCopyJob job) ReportProgress(job, job.State); } void OnFileRangesTransferred(object? s, BackgroundCopyFileRangesTransferredEventArgs e) { if (s is BackgroundCopyJob job) ReportProgress(job, job.State); } void ReportProgress(BackgroundCopyJob job, BackgroundCopyJobState state) => report?.Invoke(state, job.Progress.PercentComplete); } }