using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.BITS; namespace Vanara.IO { /// Manages the set of files for a background copy job. public class BackgroundCopyFileCollection : ICollection, IDisposable { private IBackgroundCopyJob m_ijob; internal BackgroundCopyFileCollection(IBackgroundCopyJob ijob) => m_ijob = ijob; internal BackgroundCopyFileCollection() { } /// Gets the number of files in the current job. public int Count { get { try { return (int)m_ijob.EnumFiles().GetCount(); } catch (COMException cex) { HandleCOMException(cex); } return 0; } } /// Gets a value indicating whether the is read-only. bool ICollection.IsReadOnly => false; /// Add a file to a download or an upload job. Only one file can be added to upload jobs. /// /// 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 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. /// /// /// /// To add more than one file at a time to a job, call the method. It /// is more efficient to call the AddRange method when adding multiple files to a job than to call the Add method in a loop. /// /// /// To add a file to a job from which BITS downloads ranges of data from the file, call the /// method. /// /// Upload jobs can only contain one file. If you add a second file, the method returns BG_E_TOO_MANY_FILES. /// /// 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. /// /// /// 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. /// /// /// 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. /// /// /// 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. /// /// /// 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 MaxFilesPerJob group policies. /// /// Prior to Windows Vista: There is no limit on the number of files that a user can add to a job. /// public void Add(string remoteFilePath, string localFilePath) { // remoteFilePath must not have a trailing backslash. if (!string.IsNullOrEmpty(remoteFilePath)) remoteFilePath.TrimEnd('/'); try { m_ijob.AddFile(remoteFilePath, localFilePath); } catch (COMException cex) { HandleCOMException(cex); } } /// Add a file to a download job and specify the ranges of the file you want to download. /// /// 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 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. /// /// Zero-based offset to the beginning of the range of bytes to download from a file. /// Number of bytes in the range. public void Add(string remoteFilePath, string localFilePath, long initialOffset, long length = -1) { IBackgroundCopyJob3 ijob3; try { ijob3 = (IBackgroundCopyJob3)m_ijob; } catch { throw new NotSupportedException(); } var rng = new[] { new BG_FILE_RANGE { InitialOffset = (ulong)initialOffset, Length = length == -1 ? ulong.MaxValue : (ulong)length } }; try { ijob3.AddFileWithRanges(remoteFilePath, localFilePath, 1, rng); } catch (COMException cex) { HandleCOMException(cex); } } /// Add a list of files to download from a URL. /// /// 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 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. /// /// /// List of relative file names to retrieve from the remote directory. Filename will be appended to both the remoteUrlRoot and the localDirectory. /// public void AddRange(Uri remoteUrlRoot, DirectoryInfo localDirectory, IEnumerable files) { var array = files.Select(s => new BG_FILE_INFO(new Uri(remoteUrlRoot, s).AbsoluteUri, Path.Combine(localDirectory.FullName, s))).ToArray(); try { m_ijob.AddFileSet((uint)array.Length, array); } catch (COMException cex) { HandleCOMException(cex); } } /// Add a list of files to download from a URL. /// /// 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 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. /// /// /// List of relative file names to retrieve from the remote directory. Filename will be appended to both the remoteUrlRoot and the localDirectory. /// public void AddRange(string remoteUrlRoot, string localDirectory, IEnumerable files) { // remoteUrlRoot must have a trailing backslash. if (!string.IsNullOrEmpty(remoteUrlRoot) && !remoteUrlRoot.EndsWith("/", StringComparison.Ordinal)) remoteUrlRoot += "/"; AddRange(new Uri(remoteUrlRoot), new DirectoryInfo(localDirectory), files); } /// /// Returns an object that implements the interface and that can iterate through the /// objects within the collection. /// /// /// Returns an object that implements the interface and that can iterate through the /// objects within the collection. /// public IEnumerator GetEnumerator() => new Enumerator(m_ijob.EnumFiles()); /// Adds an item to the . /// The object to add to the . /// void ICollection.Add(BackgroundCopyFileInfo item) { } /// Removes all items from the . /// void ICollection.Clear() => throw new NotSupportedException(); /// Determines whether the contains a specific value. /// The object to locate in the . /// true if is found in the ; otherwise, false. /// bool ICollection.Contains(BackgroundCopyFileInfo item) => throw new NotSupportedException(); /// /// Copies the elements of the to an , starting at a particular /// index. /// /// /// The one-dimensional that is the destination of the elements copied from /// . The must have zero-based indexing. /// /// The zero-based index in at which copying begins. /// void ICollection.CopyTo(BackgroundCopyFileInfo[] array, int arrayIndex) => throw new NotSupportedException(); /// Disposes of the BackgroundCopyFileSet object. void IDisposable.Dispose() => m_ijob = null; /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// Removes the first occurrence of a specific object from the . /// The object to remove from the . /// /// true if was successfully removed from the ; otherwise, false. This method /// also returns false if is not found in the original . /// /// bool ICollection.Remove(BackgroundCopyFileInfo item) => throw new NotSupportedException(); internal BackgroundCopyFileInfo[] GetBCFIArray() => this.ToArray(); private void HandleCOMException(COMException cex) { var state = m_ijob.GetState(); if (state == BG_JOB_STATE.BG_JOB_STATE_ERROR || state == BG_JOB_STATE.BG_JOB_STATE_TRANSIENT_ERROR) { var pErr = m_ijob.GetError(); throw new BackgroundCopyException(pErr); } else throw new BackgroundCopyException(cex); } /// /// An implementation the interface that can iterate through the /// objects within the collection. /// private sealed class Enumerator : Vanara.Collections.IEnumeratorFromNext { internal Enumerator(IEnumBackgroundCopyFiles enumfiles) : base(enumfiles, TryGetNext, e => e.Reset()) { } private static bool TryGetNext(IEnumBackgroundCopyFiles e, out BackgroundCopyFileInfo i) { var ifi = e.Next(1)?.FirstOrDefault(); i = ifi is not null ? new BackgroundCopyFileInfo(ifi) : null; return i is not null; } } } }