public void JobModification(IBackgroundCopyJob pJob, uint dwReserved) { BitsJob job; if (null != this.onJobModified) { Guid guid; pJob.GetId(out guid); if (manager.Jobs.ContainsKey(guid)) { job = manager.Jobs[guid]; } else { // Update Joblist to check whether the job still exists. If not, just return manager.EnumJobs(manager.currentOwner); if (manager.Jobs.ContainsKey(guid)) { job = manager.Jobs[guid]; } else { return; } } this.onJobModified(this, new NotificationEventArgs(job)); //forward event if (job.notificationTarget != null) { job.notificationTarget.JobModification(pJob, dwReserved); } } }
/// <summary> /// Prepares a BITS job adding the files and creating the required folders. /// </summary> /// <param name="backgroundCopyJob">The BITS job information.</param> /// <param name="task">The DownloadTask instace.</param> private static void PrepareJob(IBackgroundCopyJob backgroundCopyJob, DownloadTask task) { Guid jobID; backgroundCopyJob.GetId(out jobID); task.JobId = jobID; DownloadFile sourceFile = task.DownloadItem.File; string src = sourceFile.Source; // to defend against config errors, check to see if the path given is UNC; // if so, throw immediately there's a misconfiguration. Paths to BITS must be HTTP or HTTPS if (FileHelper.IsUncPath(src)) { Exception ex = new DownloaderException("Download location must be HTTP or HTTPS URL"); Logger.Error(ex); throw ex; } //TODO: how about duplicate filenames? string dest = Path.Combine(task.DownloadFilesBase, sourceFile.LocalName); if (!Directory.Exists(Path.GetDirectoryName(dest))) { Directory.CreateDirectory(Path.GetDirectoryName(dest)); } // add this file to the job backgroundCopyJob.AddFile(src, dest); }
/// <summary> /// Centralizes all chores related to stopping and cancelling a copy job, and getting back /// from BITS the errors incurred during the job. /// </summary> /// <param name="task">reference to the job associated task</param> /// <param name="pJob">reference to the copy job object (not job id)</param> /// <param name="pError">reference to the COM error reported by bits (might be null)</param> /// <param name="ex">reference to an exception cosidered as an error (might be null)</param> private void OnJobError(DownloadTask task, IBackgroundCopyJob pJob, IBackgroundCopyError pError, Exception ex) { Exception finalException = ex; if (pJob != null) { // get information about this job string jobDesc; pJob.GetDescription(out jobDesc); string jobName; pJob.GetDisplayName(out jobName); Guid jobID; pJob.GetId(out jobID); try { // if the error hasn't been reported, try to get it if (pError == null) { pJob.GetError(out pError); } } catch (COMException e) { Logger.Error(e); if (e.ErrorCode != ExceptionCodeNotAnError) { throw; } } // If we've got the native error, wrap into a nicer exception if (pError != null) { var BitsEx = new BitsDownloadErrorException(pError, (uint)CultureIdForGettingComErrorMessages); cumulativeErrorMessage += BitsEx.Message + Environment.NewLine; finalException = BitsEx; } BG_JOB_STATE state; pJob.GetState(out state); if (state != BG_JOB_STATE.BG_JOB_STATE_ACKNOWLEDGED && state != BG_JOB_STATE.BG_JOB_STATE_CANCELLED) { pJob.Cancel(); } RemoveCopyJobEntry(jobID); } OnDownloadError(new DownloadTaskErrorEventArgs(task, finalException)); Logger.Error(finalException); //throw finalException; }
/// <summary>Called when an error occurs.</summary> /// <param name="copyJob">Contains job-related information, such as the number of bytes and files transferred before the error occurred. It also contains the methods to resume and cancel the job. Do not release pJob; BITS releases the interface when the JobError method returns.</param> /// <param name="error">Contains error information, such as the file being processed at the time the fatal error occurred and a description of the error. Do not release pError; BITS releases the interface when the JobError method returns.</param> public void JobError(IBackgroundCopyJob copyJob, IBackgroundCopyError error) { if (this.manager == null) { return; } BitsJob job; if (null == this.errorOccurred) { return; } Guid guid; copyJob.GetId(out guid); if (this.manager.Jobs.ContainsKey(guid)) { job = this.manager.Jobs[guid]; } else { // Update Job list to check whether the job still exists. If not, just return this.manager.EnumJobs(this.manager.CurrentOwner); if (this.manager.Jobs.ContainsKey(guid)) { job = this.manager.Jobs[guid]; } else { return; } } this.errorOccurred(this, new ErrorNotificationEventArgs(job, new BitsError(job, error))); // forward event if (job.NotificationTarget == null) { } else { try { job.NotificationTarget.JobError(copyJob, error); } catch (COMException) { } } }
/// <summary> /// Centralizes all chores related to stopping and cancelling a copy job, and getting back /// from BITS the errors incurred during the job. /// </summary> /// <param name="copyJob">reference to the copy job object (not job id)</param> /// <param name="errMessage">a cumulative error message passed by reference so /// that additions can be made</param> private void HandleDownloadErrorCancelJob( IBackgroundCopyJob copyJob, ref string errMessage) { string singleError = ""; string jobDesc = ""; string jobName = ""; Guid jobID = Guid.Empty; IBackgroundCopyError copyError = null; try { // check if job is null; don't try to clean up null references! if (null != copyJob) { // get information about this job for reporting the error copyJob.GetDescription(out jobDesc); copyJob.GetDisplayName(out jobName); copyJob.GetId(out jobID); // find out what the error was copyJob.GetError(out copyError); // use the culture id specified in RESX to tell COM which culture to return err message in: copyError.GetErrorDescription((uint)CULTURE_ID_FOR_COM, out singleError); // add error to our "stack" of errors: errMessage += singleError + Environment.NewLine; // notify BITS that we consider this job a loss, forget about it: copyJob.Cancel(); // remove job from collection RemoveCopyJobEntry(jobID); // log error, but don't throw here; let dnldmgr take care of error // NOTE that errMessage is used cumulatively for full track of problem errMessage = ApplicationUpdateManager.TraceWrite("[BITSDownloader]", "RES_EXCEPTION_BITSBackgroundCopyError", jobID, jobName, jobDesc, errMessage); ExceptionManager.Publish(new Exception(errMessage)); } } finally { if (null != copyError) { Marshal.ReleaseComObject(copyError); copyError = null; } } }
/// <summary> /// Locate the DownloadTask associated with the given background job. /// </summary> /// <param name="pJob">The job reference.</param> /// <returns>The DownloadTask for that job.</returns> private static DownloadTask FindTask(IBackgroundCopyJob pJob) { Guid jobID; pJob.GetId(out jobID); foreach (var task in BackgroundDownloadManager.GetTasks()) { if (task.JobId == jobID) { return(task); } } return(null); }
public void JobModification(IBackgroundCopyJob pJob, uint dwReserved) { BitsJob job; if (null != this.onJobModified) { Guid guid; pJob.GetId(out guid); if (manager.Jobs.ContainsKey(guid)) { job = manager.Jobs[guid]; } else { job = new BitsJob(manager, pJob); } this.onJobModified(this, new NotificationEventArgs(job)); } }
public void JobError(IBackgroundCopyJob pJob, IBackgroundCopyError pError) { BitsJob job; if (null != this.onJobErrored) { Guid guid; pJob.GetId(out guid); if (manager.Jobs.ContainsKey(guid)) { job = manager.Jobs[guid]; } else { job = new BitsJob(manager, pJob); } this.onJobErrored(this, new ErrorNotificationEventArgs(job, new BitsError(job, pError))); } }
/// <summary> /// Gets all the jobs currently being managed with the system. /// </summary> public IEnumerable <IDownloadJob> GetAll() { var jobs = new List <DownloadJob> (); IBackgroundCopyManager bitsManager = null; IEnumBackgroundCopyJobs enumJobs = null; try { bitsManager = (IBackgroundCopyManager) new BackgroundCopyManager(); bitsManager.EnumJobs(0, out enumJobs); uint fetched; IBackgroundCopyJob bitsJob = null; try { enumJobs.Next(1, out bitsJob, out fetched); while (fetched == 1) { Guid id; bitsJob.GetId(out id); jobs.Add(new DownloadJob(id, bitsJob)); enumJobs.Next(1, out bitsJob, out fetched); } } finally { if (bitsJob != null) { Marshal.ReleaseComObject(bitsJob); } } return(jobs.ToArray()); } finally { if (enumJobs != null) { Marshal.ReleaseComObject(enumJobs); } if (bitsManager != null) { Marshal.ReleaseComObject(bitsManager); } } }
public void JobModification(IBackgroundCopyJob pJob, uint dwReserved) { BitsJob job; if (null != this.onJobModified) { Guid guid; pJob.GetId(out guid); if (manager.Jobs.ContainsKey(guid)) { job = manager.Jobs[guid]; } else { // Update Joblist to check whether the job still exists. If not, just return manager.EnumJobs(manager.currentOwner); if (manager.Jobs.ContainsKey(guid)) { job = manager.Jobs[guid]; } else return; } this.onJobModified(this, new NotificationEventArgs(job)); //forward event if (job.notificationTarget != null) job.notificationTarget.JobModification(pJob, dwReserved); } }
/// <summary> /// used by externally visible overload. /// </summary> /// <param name="isDisposing">whether or not to clean up managed + unmanaged/large (true) or just unmanaged(false)</param> private void Dispose(bool isDisposing) { const uint BG_JOB_ENUM_CURRENT_USER = 0; // const uint BG_JOB_ENUM_ALL_USERS = 0x0001; leads to ACCESS DENIED errors IBackgroundCopyManager mgr = null; IEnumBackgroundCopyJobs jobs = null; IBackgroundCopyJob job = null; if (isDisposing) { try { mgr = (IBackgroundCopyManager)(new BackgroundCopyManager()); mgr.EnumJobs(BG_JOB_ENUM_CURRENT_USER, out jobs); uint numJobs; jobs.GetCount(out numJobs); // lock the jobs collection for duration of this operation lock (bitsDownloaderJobs.SyncRoot) { for (int i = 0; i < numJobs; i++) { // use jobs interface to walk through getting each job uint fetched; jobs.Next(1, out job, out fetched); // get jobid guid Guid jobID; job.GetId(out jobID); // check if the job is in OUR collection; if so cancel it. we obviously don't want to get // jobs from other Updater threads/processes, or other BITS jobs on the machine! if (bitsDownloaderJobs.Contains(jobID)) { // take ownership just in case, and cancel() it job.TakeOwnership(); job.Cancel(); // remove from our collection bitsDownloaderJobs.Remove(jobID); } } } } finally { if (null != mgr) { Marshal.ReleaseComObject(mgr); mgr = null; } if (null != jobs) { Marshal.ReleaseComObject(jobs); jobs = null; } if (null != job) { Marshal.ReleaseComObject(job); job = null; } } } }
/// <summary> /// Waits for the download to complete, for the synchronous usage of the downloader. /// </summary> /// <param name="backgroundCopyJob">The job instance reference.</param> /// <param name="maxWaitTime">The maximum wait time.</param> /// <param name="task">The updater task instance.</param> private void WaitForDownload(DownloadTask task, IBackgroundCopyJob backgroundCopyJob, TimeSpan maxWaitTime) { bool isCompleted = false; bool isSuccessful = false; try { Guid jobID; backgroundCopyJob.GetId(out jobID); // set endtime to current tickcount + allowable # milliseconds to wait for job double endTime = Environment.TickCount + maxWaitTime.TotalMilliseconds; while (!isCompleted) { BG_JOB_STATE state; backgroundCopyJob.GetState(out state); switch (state) { case BG_JOB_STATE.BG_JOB_STATE_SUSPENDED: { OnDownloadStarted(new TaskEventArgs(task)); backgroundCopyJob.Resume(); break; } case BG_JOB_STATE.BG_JOB_STATE_ERROR: { // use utility to: // a) get error info // b) report it // c) cancel and remove copy job OnJobError(task, backgroundCopyJob, null, null); // complete loop, but DON'T say it's successful isCompleted = true; break; } case BG_JOB_STATE.BG_JOB_STATE_TRANSIENT_ERROR: { // NOTE: during debugging + test, transient errors resulted in complete job failure about 90% // of the time. Therefore we treat transients just like full errors, and CANCEL the job // use utility to manage error etc. OnJobError(task, backgroundCopyJob, null, null); // stop the loop, set completed to true but not successful isCompleted = true; break; } case BG_JOB_STATE.BG_JOB_STATE_TRANSFERRING: { OnJobModification(task, backgroundCopyJob); break; } case BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED: { OnJobTransferred(task, backgroundCopyJob); isCompleted = true; isSuccessful = true; break; } default: break; } if (isCompleted) { break; } if (endTime < Environment.TickCount) { var ex = new DownloaderException("Download attempt timed out"); OnJobError(task, backgroundCopyJob, null, ex); break; } // Avoid 100% CPU utilisation with too tight a loop, let download happen. Thread.Sleep(TimeToWaitDuringSynchronousDownload); } if (!isSuccessful) { // create message + error, package it, publish var ex = new DownloaderException(String.Format("Download attempt for {0} failed", task.DownloadItem.ItemId)); OnJobError(task, backgroundCopyJob, null, ex); } } catch (ThreadInterruptedException tie) { // if interrupted, clean up job OnJobError(task, backgroundCopyJob, null, tie); } }
/// <summary> /// Centralizes all chores related to stopping and cancelling a copy job, and getting back /// from BITS the errors incurred during the job. /// </summary> /// <param name="copyJob">reference to the copy job object (not job id)</param> /// <param name="errMessage">a cumulative error message passed by reference so /// that additions can be made</param> private void HandleDownloadErrorCancelJob( IBackgroundCopyJob copyJob, ref string errMessage) { string singleError = ""; string jobDesc = ""; string jobName = ""; Guid jobID = Guid.Empty; IBackgroundCopyError copyError = null; try { // check if job is null; don't try to clean up null references! if( null != copyJob ) { // get information about this job for reporting the error copyJob.GetDescription( out jobDesc ); copyJob.GetDisplayName( out jobName ); copyJob.GetId( out jobID ); // find out what the error was copyJob.GetError( out copyError ); // use the culture id specified in RESX to tell COM which culture to return err message in: copyError.GetErrorDescription( (uint)CULTURE_ID_FOR_COM, out singleError ); // add error to our "stack" of errors: errMessage += singleError + Environment.NewLine; // notify BITS that we consider this job a loss, forget about it: copyJob.Cancel(); // remove job from collection RemoveCopyJobEntry( jobID ); // log error, but don't throw here; let dnldmgr take care of error // NOTE that errMessage is used cumulatively for full track of problem errMessage = ApplicationUpdateManager.TraceWrite( "[BITSDownloader]", "RES_EXCEPTION_BITSBackgroundCopyError", jobID, jobName, jobDesc, errMessage ); ExceptionManager.Publish( new Exception( errMessage ) ); } } finally { if( null != copyError ) { Marshal.ReleaseComObject( copyError ); copyError = null; } } }