/// <summary> /// Used to delete a directory and all its contents. /// </summary> /// <param name="path">full path to directory</param> public static void DeleteDirectory(string path) { if (Directory.Exists(path)) { try { Directory.Delete(path, true); } catch (UnauthorizedAccessException uae) { string error = ApplicationUpdateManager.TraceWrite(uae, "[FileUtility.DeleteDirectory]", "RES_EXCEPTION_UnauthorizedAccessDirectory", path, uae.Message); ExceptionManager.Publish(uae); // throw, this is serious enough to halt: throw uae; } } }
private static void Init() { #region Get the configuration instance try { if (null == _configuration) { _configuration = (UpdaterConfiguration)ConfigurationSettings.GetConfig("appUpdater"); } } catch (ApplicationUpdaterException aex) { string error = ApplicationUpdateManager.TraceWrite(aex, "[UpdaterConfiguration.cctor]", "RES_UnableToLoadConfiguration", AppDomain.CurrentDomain.SetupInformation.ConfigurationFile); ApplicationUpdaterException theException = new ApplicationUpdaterException(error, aex); ExceptionManager.Publish(theException); throw theException; } catch (ConfigurationException configEx) { string error = ApplicationUpdateManager.TraceWrite(configEx, "[UpdaterConfiguration.cctor]", "RES_UnableToLoadConfiguration", AppDomain.CurrentDomain.SetupInformation.ConfigurationFile); ConfigurationException plusConfEx = new ConfigurationException(error, configEx); ExceptionManager.Publish(plusConfEx); throw plusConfEx; } catch (Exception exception) { // for general exception, just publish and throw no more is reasonable to add ExceptionManager.Publish(exception); throw exception; } if (_configuration == null) { string error = ApplicationUpdateManager.TraceWrite("[UpdaterConfiguration.cctor]", "RES_UnableToLoadConfiguration", AppDomain.CurrentDomain.SetupInformation.ConfigurationFile); ApplicationUpdaterException theException = new ApplicationUpdaterException(error); ExceptionManager.Publish(theException); throw theException; } #endregion }
/// <summary> /// Returns an object instantiated by the Activator, using fully-qualified asm/type supplied. /// Permits construction arguments to be supplied which it passes to the object's constructor on instantiation. /// </summary> /// <param name="assemblyName">fully-qualified assembly name</param> /// <param name="typeName">the type name</param> /// <param name="constructorArguments">constructor arguments for type to be created</param> /// <returns>instance of requested assembly/type typed as System.Object</returns> public static object Create(string assemblyName, string typeName, object[] constructorArguments) { Assembly assemblyInstance = null; Type typeInstance = null; try { // use full asm name to get assembly instance assemblyInstance = Assembly.Load(assemblyName.Trim()); } catch (Exception e) { string error = ApplicationUpdateManager.TraceWrite("[GenericFactory.Create]", "RES_EXCEPTION_CantLoadAssembly", assemblyName, typeName); TypeLoadException tle = new TypeLoadException(error, e); ExceptionManager.Publish(tle); throw tle; } try { // use type name to get type from asm; note we WANT case specificity typeInstance = assemblyInstance.GetType(typeName.Trim(), true, false); // now attempt to actually create an instance, passing constructor args if available if (constructorArguments != null) { return(Activator.CreateInstance(typeInstance, constructorArguments)); } else { return(Activator.CreateInstance(typeInstance)); } } catch (Exception e) { string error = ApplicationUpdateManager.TraceWrite("[GenericFactory.Create]", "RES_EXCEPTION_CantCreateInstanceUsingActivate", assemblyName, typeName); TypeLoadException tle = new TypeLoadException(error, e); ExceptionManager.Publish(tle); throw tle; } }
/// <summary> /// This method compares the recently downloaded file with the client version. /// If the server version is greater compared with the client version, the /// return value is <c>true</c>. /// </summary> /// <returns>true if client version older than server</returns> private bool IsClientUpdateNeeded() { ClientApplicationInfo clientInfo = null; // get client info from its config file clientInfo = ClientApplicationInfo.Deserialize(_application.Client.XmlFile); ApplicationUpdateManager.TraceWrite("[DownloaderManager.ClientMustBeUpdated]", "RES_MESSAGE_ClientServerVersionCheck", _application.Name, clientInfo.InstalledVersion, _server.AvailableVersion); // get both versions, return comparison Version clientVersion = null; clientVersion = new Version(clientInfo.InstalledVersion); Version serverVersion = null; serverVersion = new Version(_server.AvailableVersion); return(serverVersion > clientVersion); }
/// <summary> /// Internal helper, stops the specified DownloadManager by application name /// </summary> private void StopUpdaterHelper(string appName) { bool isGoodStop = false; // unpackage objects from holder DownloaderManager dnldMgr = ((DnldMgrHolder)_dnldHolders[appName]).dnldMgr; Thread dnldThread = ((DnldMgrHolder)_dnldHolders[appName]).dnldThread; // signal downloaderMgr it's time to stop, chance to exit gracefully: if (null != dnldMgr) { dnldMgr.MustStopUpdating.Set(); } // check if the thread is event started if ((null != dnldThread) && (System.Threading.ThreadState.Unstarted != dnldThread.ThreadState)) { // wait to join for reasonable timeout isGoodStop = dnldThread.Join(TIMEOUT_THREAD_JOIN); } else { isGoodStop = true; } // if it's not a clean join, then interrupt thread if (!isGoodStop) { dnldThread.Interrupt(); // log problem ApplicationUpdateManager.TraceWrite("[ApplicationUpdateManager.StopUpdater]", "RES_StopUpdaterInterruptThread", dnldThread.Name); } // announce we are stopping: ApplicationUpdateManager.TraceWrite( "[ApplicationUpdateManager.StopUpdater]", "RESX_MESSAGE_UpdaterStopped", dnldMgr.ApplicationName, DateTime.Now.ToString(Resource.ResourceManager["RESX_DateTimeToStringFormat"], CultureInfo.CurrentCulture)); }
/// <summary> /// Updater start method /// </summary> /// <remarks> /// This method iterates through the applications to update, and starts each on its own thread /// </remarks> public void StartUpdater() { // lock the holder collection while we iterate through it lock (_dnldHolders.SyncRoot) { // cycle through collection of holders, starting each thread foreach (DictionaryEntry de in _dnldHolders) { DnldMgrHolder holder = (DnldMgrHolder)de.Value; // start the thread holder.dnldThread.Start(); // announce we have started: ApplicationUpdateManager.TraceWrite( "[ApplicationUpdateManager.StartUpdater]", "RESX_MESSAGE_UpdaterStarted", holder.dnldMgr.ApplicationName, DateTime.Now.ToString(Resource.ResourceManager["RESX_DateTimeToStringFormat"], CultureInfo.CurrentCulture)); } } }
/// <summary> /// Utility method that splits a full type name (assembly + type) into the constituent five parts, trims those parts, and throws an error if there are not exactly five members. /// </summary> /// <param name="fullType">the assembly + type names, fully qualified</param> /// <param name="typeName">the type name, full</param> /// <param name="assemblyName">they fully-qualified assembly name including name, version, culture, and public key token</param> private static void SplitType(string fullType, out string typeName, out string assemblyName) { string[] parts = fullType.Split(COMMA_DELIMITER.ToCharArray()); // ms--most common source of errors is bad configuration, especially of the assembly+type definitions in config files. // Assert() here so we can be alerted during debugging. Debug.Assert(5 == parts.Length, "in GenericFactory.SplitType, passed fullType = " + fullType + " and it would not split 5 ways."); if (5 != parts.Length) { // pad parts to satisfy error message--not best, but makes clearer errors: string[] errorParts = new string[] { "<undefined>", "<undefined>", "<undefined>", "<undefined>", "<undefined>" }; for (int i = 0; i < 5; i++) { if (i < parts.Length) { errorParts[i] = parts[i]; } } string error = ApplicationUpdateManager.TraceWrite("[GenericFactory.SplitType]", "RES_EXCEPTION_BadTypeArgumentInFactory", errorParts); // publish this exception ArgumentException ae = new ArgumentException(error, "fullType"); ExceptionManager.Publish(ae); throw ae; } else { // package type name: typeName = parts[0].Trim(); // package fully-qualified assembly name separated by commas assemblyName = String.Concat(parts[1].Trim() + COMMA_DELIMITER, parts[2].Trim() + COMMA_DELIMITER, parts[3].Trim() + COMMA_DELIMITER, parts[4].Trim()); // return return; } }
/// <summary> /// Uses a file path to load an assembly. Then instantiates the requested type by searching interfaces. /// Returns an object instantiated by the Activator, using fully-qualified combined assembly-type supplied. /// Assembly parameter example: "Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" /// Type parameter example: "Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator" /// </summary> /// <param name="filePath">full path to assembly</param> /// <param name="interfaceToActivate">the Type representing the interface to activate</param> /// <returns></returns> public static object Create(string filePath, Type interfaceToActivate) { Assembly asm = null; Type typeInstance = null; Type[] types = null; Object obj = null; try { asm = Assembly.LoadFrom(filePath); types = asm.GetTypes(); // walk through all types in assembly, find the one that IS IPP and use its info foreach (Type type in types) { // search for interface by string name in this type typeInstance = type.GetInterface(interfaceToActivate.FullName, false); // if we find the interface, return the type that implements it if (null != typeInstance) { // we found the first instance of the given interface // THERE MAY BE MORE but this is a convenience method. typeInstance = type; break; } } obj = asm.CreateInstance(typeInstance.FullName); } catch (Exception e) { string error = ApplicationUpdateManager.TraceWrite("[GenericFactory.Create]", "RES_EXCEPTION_CantCreateInstanceFromFilePath", filePath, interfaceToActivate); TypeLoadException tle = new TypeLoadException(error, e); ExceptionManager.Publish(tle); throw tle; } return(obj); }
/// <summary> /// This method helps restart the app. It expects a DnldMgrHolder to be packaged in the object argument; it unpacks this holder /// and starts the downloadermanager it contains on the thread it contains /// </summary> /// <param name="state">an object wrapper around a DnldMgrHolder</param> private void RestartUpdaterHelper(object state) { DnldMgrHolder holder; // extract our holder from the callback's object passed in here as "state" holder = (DnldMgrHolder)state; // dispose the timer if (null != holder.restartTimer) { holder.restartTimer.Dispose(); } // start it holder.dnldThread.Start(); // log restart ApplicationUpdateManager.TraceWrite( "[ApplicationUpdateManager.RestartUpdater]", "RES_RestartedUpdater", holder.dnldMgr.ApplicationName, DateTime.Now.ToString(Resource.ResourceManager["RESX_DateTimeToStringFormat"], CultureInfo.CurrentCulture)); }
/// <summary> /// Wraps the stopping of the IPP thread; this thread ventures outside and does work through IPP.Run(), may take a while; /// we may need to reign it in, so stopping + error collection code here. /// Common errors in IPP thread: TheadInterruptedException + TAE, or frank Exception if those not properly handled. /// </summary> private void StopPostProcessorThread() { string threadName = ""; try { // join/interrupt the IPOstProcessor thread; it should catch internally and know to Dispose() NOW! if (null != _ippThread) { // cache name here threadName = _ippThread.Name; // NOTE: if you need to do very long-running work, such as large installs or extensive file manipulation, it // is HIGHLY RECOMMENDED you merely use the IPP Run() method __to spawn a separate process__, // and in turn do all that long-running work in _that_ process, returning our IPP thread as soon as new Process launches. // Then you're not at the mercy of Updater's lifetime to complete potentially sensitive work! // give it a chance, if it's completed it should Join() otherwise Interrupt() it // Interrupt will only wake it if it's WaitSleepJoin; it will not help if the thread is in unmanaged code if (!_ippThread.Join(MILLISECOND_WAIT_TIMEOUT_IPP)) { _ippThread.Interrupt(); } // give another chance to clean up, then abort to prevent having it hang around if (!_ippThread.Join(MILLISECOND_WAIT_TIMEOUT_IPP)) { _ippThread.Abort(); } } } catch (Exception e) { ApplicationUpdateManager.TraceWrite(e, "[DownloaderManager.StopPostProcessorThread]", "RES_EXCEPTION_StoppingIPP", threadName, e.Message); ExceptionManager.Publish(e); // SWALLOW this exception, because we WANT other applications to continue downloading. Just exit quietly after logging the problem. // downloads should not stop because of IPP errors. } }
internal static string TraceWrite(Exception e, string location, string messageKey, params object[] args) { string message = ""; // get recursive error dump string StringBuilder sb = new StringBuilder(5000); ApplicationUpdateManager.RecurseErrorStack(e, ref sb); string error = sb.ToString(); if (null != messageKey && null != args) { message = FormatMessage(messageKey, args); } Trace.WriteLine(""); Trace.WriteLine(location + " : "); Trace.Indent(); Trace.WriteLine(message); Trace.WriteLine(error); Trace.WriteLine(""); Trace.Unindent(); return(message); }
/// <summary> /// Uses an XML file as source of data. Populate a ClientApplicationInfo object and return it. /// </summary> /// <param name="filePath">The XML file path</param> /// <returns></returns> public static ClientApplicationInfo Deserialize(string filePath) { // Cannot use xml-deserialization here ApplicationUpdateManager.TraceWrite("[ClientApplicationInfo.Deserialize]", "RES_MESSAGE_DeserializingClientApplicationInfo", filePath); // create xml doc XmlDocument doc = new XmlDocument(); XmlNode node = null; ClientApplicationInfo clientAppInfo = new ClientApplicationInfo(); try { // load it doc.Load(filePath); // select the sub-node we want: // NOTE that we are using a regular app.config, specifically AppStart.exe.config; // SO we can't just treat it like a regular xml-deserialization because the serializer doesn't like the extra // nodes. Therefore just walk the XML to get values for class node = doc.SelectSingleNode("configuration/appStart/ClientApplicationInfo"); // set values clientAppInfo.AppFolderName = node.SelectSingleNode("appFolderName").InnerText; clientAppInfo.AppExeName = node.SelectSingleNode("appExeName").InnerText; clientAppInfo.InstalledVersion = node.SelectSingleNode("installedVersion").InnerText; } catch (Exception e) { string error = ApplicationUpdateManager.TraceWrite(e, "[ClientApplicationInfo.Deserialize]", "RES_EXCEPTION_CouldNotDeserializeClientApplicationInfo", e.Message, filePath); ExceptionManager.Publish(e); throw e; } // return it return(clientAppInfo); }
/// <summary> /// Deserializes the configuration in an XML file /// </summary> /// <param name="filePath">The XML file path</param> /// <returns>ServerApplicationInfo object</returns> public static ServerApplicationInfo Deserialize(string filePath) { ServerApplicationInfo serverInfo = null; XmlSerializer _serializer = new XmlSerializer(typeof(ServerApplicationInfo)); ApplicationUpdateManager.TraceWrite("[ServerApplicationInfo.Deserialize]", "RES_MESSAGE_DeserializeServerApplicationInfo", filePath); try { using (FileStream fs = new FileStream(filePath, FileMode.Open)) { serverInfo = (ServerApplicationInfo)_serializer.Deserialize(new XmlTextReader(fs)); } // return it return(serverInfo); } catch (Exception e) { ApplicationUpdateManager.TraceWrite(e, "[ServerApplicationInfo.Deserialize]", "RES_EXCEPTION_CouldNotDeserializeServerApplicationInfo", e.Message, filePath); ExceptionManager.Publish(e); throw e; } }
/// <summary> /// Using manifest information in node @"<postProcessor type='' assembly=''></postProcessor>", /// this instantiates the object. The object MUST implement IPostProcessor interface. That interface /// has a single method "void Run()" /// The object is used to do post-update actions such as run custom installers, make event logs, clean up /// old installs, etc. /// /// NOTE: /// The PostProcessor runs from a thread _spawned by this thread_, and under _THIS_ security context. /// Therefore if AppUpdater /// is running as a high-privilege user, the PostProcessor can do quite a bit...BE CAREFUL. /// ALSO note that _we are using a separate thread_, so PostProcessor does NOT block this thread. /// </summary> private void RunPostProcessor() { IPostProcessor ipp = null; object postProcessor = null; // FIRST, check if PostProcessor config node is null if (null == _server.PostProcessor) { return; } // we must run IPP _from the new version directory_, not the temp dir...temp dir is gone/going away; // use Path.Combine serially to ensure correct pathing. string ippPath = Path.Combine(Path.Combine(_application.Client.BaseDir, _server.AvailableVersion), _server.PostProcessor.Name); // check if file actually exists: if (!File.Exists(ippPath)) { return; } // swallow all post-processor errors but log them try { // instantiate IPP using GenericFactory postProcessor = GenericFactory.Create(ippPath, typeof(IPostProcessor)); // check if it's right interface so we can fail very quietly here if (postProcessor is IPostProcessor) { ipp = postProcessor as IPostProcessor; //*** THREADING *** // here we create our member thread, and tell it to Run() IPP // this way we are not blocked here, IPP may be very long-running if it // does un-installers, works with files, etc... // NOTE // 1) we are not attempting to clean up this thread, catch exceptions, etc. for it; Run() must do that // 2) IF the IPP needs to Dispose(), it must do so internally when it unwinds its stack-- // so when Run() is complete, call Dispose() internally // 3) the IPP should be aware that the parent app might shut down ungracefully--SO it should sink the // ProcessExit event and do cleanup // 4) in case it is Abort()'ed or Interrupt()'ed, it should catch both those exceptions and clean up. // _ippThread = new Thread(new ThreadStart(ipp.Run)); _ippThread.Name = "IPostProcessor_Thread_" + _application.Name; _ippThread.Start(); } else { ApplicationUpdateManager.TraceWrite("[DownloaderManager.RunPostProcessor]", "RES_MESSAGE_IPPWouldNotCast", ippPath, postProcessor.GetType()); } } catch (ThreadInterruptedException tie) { ApplicationUpdateManager.TraceWrite(tie, "[DownloaderManager.RunPostProcessor]", "RES_EXCEPTION_ThreadInterruptedException", Thread.CurrentThread.Name); throw tie; } catch (Exception e) { ApplicationUpdateManager.TraceWrite(e, "[DownloaderManager.RunPostProcessor]", "RES_EXCEPTION_RunningIPP", _server.PostProcessor.Name, _server.PostProcessor.Type, _server.PostProcessor.Assembly); ExceptionManager.Publish(e); } return; }
/// <summary> /// Default constructor /// </summary> public ApplicationUpdateManager() { DnldMgrHolder holder; // initialize the hybriddict we're using to hold DnldMgrHolder structs _dnldHolders = new HybridDictionary(); #region Configuration Testing // attempt to load configuration first. If there is an error, throw here and go no further--config is essential try { UpdaterConfiguration config = UpdaterConfiguration.Instance; } catch (Exception e) { // throw right away after logging, we cannot continue ExceptionManager.Publish(e); throw e; } #endregion #region Set Up File-Based Logging SetupLogging(); #endregion #region Create Managers, Create Validator + Downloader, Construct Managers with their respective objects (downloader + validator) // iterate through applications array (which in turn uses config to get this info) // at each application, create a new DownloaderManager + its objects + attendant thread foreach (ApplicationConfiguration application in UpdaterConfiguration.Instance.Applications) { // use helper function to create + package a complete DnldMgrHolder + the inner DownloaderManager and all its parts holder = SetupDownloadManager(application); // check if app of this name exists already; CANNOT HAVE two apps of same name being updated if (_dnldHolders.Contains(application.Name)) { string error = ApplicationUpdateManager.TraceWrite("[ApplicationUpdateManager.ctor]", "RES_EXCEPTION_DuplicateApplicationName", application.Name); ConfigurationException configE = new ConfigurationException(error); ExceptionManager.Publish(configE); throw configE; } // add the holder to the hashtable for starting later _dnldHolders.Add(application.Name, holder); } #endregion #region Hook ProcessExit // wrap in try in case security not allowing us to hook this, log and continue it's not crucial try { // hook the AppDomain.ProcessExit event so that we can gracefully interrupt threads and clean up if // process is killed AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit); } catch (SecurityException se) { // log and continue ApplicationUpdateManager.TraceWrite(se, "[ApplicationUpdateManager.CTOR]", "RES_EXCEPTION_ConfigCantLoadInAUConstructor"); ExceptionManager.Publish(se); } #endregion }
/// <summary> /// Checks job status; if job is NOT downloading, returns immediately--these status /// settings indicate the job is "in process" /// Then queries IDownloader instance for job status. If job is "Ready", set job status /// to "validating" and initiates validation. For all other status, sets job status /// to returned status and returns from function. /// </summary> private void CheckDownloadCompleteOrError() { // check if the job status is anything BUT downloading. If it's error, cancelled, validating, or ready, we don't // need to query downloader. In fact doing so will cause an exception in downloader, since it is not downloading it anymore. if (JobStatus.Downloading != _dnldJob.Status) { return; } // wrapped in try-catch because it might take a while to poll, and if we're interrupted we need to handle properly // also crucial to log failures of downloader here... try { switch (_downloader.GetJobStatus(_dnldJob.JobId)) { // check if job is ready from downloader; if so, set status to ready in table and signal Validator case JobStatus.Ready: { // if job ready, annotate in the DownloadJobStatusEntry status field, make it "Validating" _dnldJob.Status = JobStatus.Validating; //* TELL THE VALIDATOR TO GET WORKING ON THIS DOWNLOAD ValidateFilesAndCleanup(); //* NOTIFY LISTENERS THAT DOWNLOAD IS COMPLETE this.OnDownloadCompleted(); //* WRITE TO TRACE DNLD COMPLETE ApplicationUpdateManager.TraceWrite("[DownloaderManager.CheckDownloadCompleteOrError]", "RES_MESSAGE_DownloadComplete", _guidEndDnld0, _guidEndDnld1, _guidEndDnld2, _guidEndDnld3, _guidEndDnld4); break; } // check if job errored; if so, set error to true and leave. case JobStatus.Error: { _dnldJob.Status = JobStatus.Error; break; } case JobStatus.Cancelled: { _dnldJob.Status = JobStatus.Cancelled; break; } case JobStatus.Downloading: { _dnldJob.Status = JobStatus.Downloading; break; } default: { break; } } } // don't swallow TIE's, rethrow catch (ThreadInterruptedException tie) { throw tie; } catch (Exception e) { string error = ApplicationUpdateManager.TraceWrite(e, "[DownloaderManager.CheckDownloadCompleteOrError]", "RES_ErrorDownloadingJob", _application.Name, e.Message); ExceptionManager.Publish(new ApplicationUpdaterException(error, e)); } }
private void SetupLogging() { if (null != UpdaterConfiguration.Instance.Logging) { FileStream fs = null; // create time-based log file name // get the dir path for the log file string path = UpdaterConfiguration.Instance.Logging.LogPath; // if no path return if (path.Length > 0) { path = Path.GetDirectoryName(UpdaterConfiguration.Instance.Logging.LogPath); } else { return; } // first, see if dir specified exists: if (!Directory.Exists(path)) { string error = ApplicationUpdateManager.TraceWrite("[ApplicationUpdateManager.SetupLogging]", "RES_MESSAGE_UpdaterLogDirectoryNotFound", path); ExceptionManager.Publish(new ConfigurationException(error)); return; } // strip off all except actual file name string logName = Path.GetFileNameWithoutExtension(UpdaterConfiguration.Instance.Logging.LogPath); // append the month-day-year-hour-minute-second for uniqueness logName = logName + "_" + DateTime.Now.ToString(Resource.ResourceManager["RESX_DateTimeToStringFormatLogFile"], CultureInfo.CurrentCulture) + ".log"; // recombine path logName = Path.Combine(path, logName); // attempt to create the file with the given path try { fs = File.Create(logName); } catch (Exception ex) { // if we can't trace, and move along--logging not worth pitching the whole update string error = ApplicationUpdateManager.TraceWrite(ex, "[ApplicationUpdateManager.SetupLogging]", "RES_MESSAGE_UpdaterLogCannotBeWritten", logName); ExceptionManager.Publish(new ConfigurationException(error, ex)); return; } finally { if (null != fs) { fs.Close(); } } // add this log file to trace listeners Trace.Listeners.Add(new TextWriterTraceListener(logName)); // set up trace to auto-flush Trace.AutoFlush = true; } }
/// <summary> /// This figures out how many milliseconds to wait--or, if using ExtendedFormat, queries the helper to find if time has elapsed. If time /// has elapsed, it returns (true if the MRE is set, signalling that ApplicationUpdaterManager has signalled to stop) /// allowing a new download cycle to begin. Otherwise, it loops internally, polling the IDownloader for job status. /// /// </summary> /// <returns>true if we were interrupted (The MustStopUpdating event is signaled)</returns> private bool IsPollerIntervalElapsed() { double mSecToWaitTotal = 0; double mSecWaitedSoFar = 0; double pollingInterval = 0; bool isMustStopSignaled = false; bool isWaitTimeElapsed = false; // check if polling type is extended-format; if so don't try parsing an int from it if (PollingType.ExtendedFormat != UpdaterConfiguration.Instance.Polling.Type) { pollingInterval = double.Parse(UpdaterConfiguration.Instance.Polling.Value, CultureInfo.CurrentCulture); } mSecToWaitTotal = GetPollingIntervalMilliseconds(pollingInterval, UpdaterConfiguration.Instance.Polling.Type); // NOTE: slightly tricky loop here. // it must satisfy three requirements: // 1) we must remain responsive to UpdaterManager in this loop while polling our IDownloader for job status // 2) we must wait a certain time to poll for a new download // 3) we must poll the downloader for completed jobs, and go to validation when job available // so, we: // a) cache the tickcount when we started all this (in RunDownloader) for reference // b) check if UpdateManager has told us to stop (isMustStopSignaled) // c) poll the downloader // d) if we're using extended format wait, check if THAT has elapsed // e) finally get the total loop time by subtracting current tickcount from the cached one; // if we have run out of time or isMustStop is signaled, we leave the loop. // f) if there was a major error in downloader, exit // g) if time waited seems very long (> 2* poll time) then download is taking too long, // assume failure and exit...UNLESS we are using extended time, in which case we just wait // while ((!isWaitTimeElapsed) || (JobStatus.Downloading == _dnldJob.Status)) { // query downloader for job status; it will put status in _dnldJob.Status CheckDownloadCompleteOrError(); // if using Extended Format poll type--check if that helper class tells us we've waited long enough. if (PollingType.ExtendedFormat == UpdaterConfiguration.Instance.Polling.Type) { if (ExtendedFormatHelper.IsExtendedExpired(UpdaterConfiguration.Instance.Polling.Value, _lastUpdate, DateTime.Now)) { _lastUpdate = DateTime.Now; isWaitTimeElapsed = true; } } // IF // calculate the total waited time based on TickCount since started this method // ADD the poll wait time, since we will pass it again before leaving this loop mSecWaitedSoFar = ((Environment.TickCount - _tickCountAtStart) + MILLISECOND_WAIT_POLL_LOOP); // check if allowable time has elapsed, if so set flag isWaitTimeElapsed so main can continue if (mSecWaitedSoFar > mSecToWaitTotal) { isWaitTimeElapsed = true; } // NOTE: JUST IN CASE, we do sanity check here: if we have been waiting for a download for > 2 // polling intervals, then FORCE EXIT, something must be wrong // if using extended format, don't do this check if (mSecWaitedSoFar > (2 * mSecToWaitTotal) && !(PollingType.ExtendedFormat == UpdaterConfiguration.Instance.Polling.Type)) { // if we've waited this long, the job is hung and we need to recycle--throw string error = ApplicationUpdateManager.TraceWrite("[DownloaderManager.IsPollerIntervalElapsed]", "RES_EXCEPTION_WaitedMoreThanTwicePollPeriod", mSecWaitedSoFar, _dnldJob.ApplicationName); Exception ex = new Exception(error); throw ex; } // do a fixed wait on MustStopUpdating, 5 seconds acc. to constant in declarations... isMustStopSignaled = _mustStopUpdating.WaitOne(MILLISECOND_WAIT_POLL_LOOP, false); // if MustStop has been signalled, we must leave the loop regardless if (isMustStopSignaled) { break; } } // finally return isEventSignaled to inform caller of whether we were told to stop during the wait return(isMustStopSignaled); }
/// <summary> /// Determine the files to be downloaded and call the IDownloader to perform the job /// </summary> /// <returns>the job id guid</returns> private Guid DownloadApplicationFiles() { string fullSourcePath = ""; string fullDestinationPath = ""; //Get the location of files on server string sourceLocation = _server.UpdateLocation; // ensure terminal slash sourceLocation = FileUtility.AppendSlashURLorUNC(sourceLocation); //Get the base dir where to copy files string baseDir = _application.Client.TempDir; ApplicationUpdateManager.TraceWrite("[DownloaderManager.DownloadApplicationFiles]", "RES_MESSAGE_DownloadingAppFiles", _application.Name, _application.Client.TempDir); //Prepare remote and local URLs ArrayList sourceFiles = new ArrayList(); ArrayList targetFiles = new ArrayList(); try { foreach (FileConfig file in _server.Files) { // set full source/destination paths complete with filename: fullSourcePath = sourceLocation + file.Name; fullDestinationPath = baseDir + file.UNCName; ApplicationUpdateManager.TraceWrite( "[DownloaderManager.DownloadApplicationFiles]", "RES_MESSAGE_SettingUpFileForDownload", fullSourcePath, fullDestinationPath); // add the source to our source arraylist sourceFiles.Add(fullSourcePath); // add the destination to our destination arraylist targetFiles.Add(fullDestinationPath); // ensure that the local target directory exists // use utility function to extract root path from given file path-- // thus "C:\foo\file.txt" returns "C:\foo\" if (!Directory.Exists(FileUtility.GetRootDirectoryFromFilePath(fullDestinationPath))) { Directory.CreateDirectory(FileUtility.GetRootDirectoryFromFilePath(fullDestinationPath)); } } } // TRY catch (ThreadInterruptedException tie) { // rethrow don't swallow tie, need to clean up throw tie; } catch (Exception e) { string error = ApplicationUpdateManager.TraceWrite( e, "[DownloaderManager.DownloadApplicationFiles]", "RES_EXCEPTION_SettingUpDownloadDirectories", fullSourcePath, fullDestinationPath); // wrap exception in our exception and publish; then throw the raw exception up ExceptionManager.Publish( new ApplicationUpdaterException(Resource.ResourceManager["RES_EXCEPTION_SettingUpDownloadDirectories"], e)); throw e; } try { // send this "job", both source and destination arraylists (as arrays) to the downloader return(_downloader.BeginDownload( (string[])sourceFiles.ToArray(typeof(string)), (string[])targetFiles.ToArray(typeof(string)))); } // don't swallow tie // thread must interrupt gracefully. catch (ThreadInterruptedException tie) { throw tie; } catch (Exception e) { ApplicationUpdateManager.TraceWrite( e, "[DownloaderManager.DownloadApplicationFiles]", "RES_EXCEPTION_UsingDownloader", _downloader.GetType().FullName, _downloader.GetType().AssemblyQualifiedName); throw e; } }
private void BeginFileDownloads() { bool isUpdateNeeded = false; bool isMustStopSignaled = false; try { //Compare metadata server file isUpdateNeeded = IsClientUpdateNeeded(); if (isUpdateNeeded) { // fire event to notify "UpdateAvailable" // NOTE: IF APPLICATION BLOCKS THIS THREAD AND CALLS StopUpdater(), that means this update is being // aborted. Handle gracefully. OnUpdateAvailable( ); // !!!! CHECK if the MRE "MustStopUpdating" is signaled, exit if so; we've been told to stop politely, // don't make us Interrupt() isMustStopSignaled = _mustStopUpdating.WaitOne(MILLISECOND_WAIT_TIMEOUT, false); if (isMustStopSignaled) { return; } ApplicationUpdateManager.TraceWrite("[DownloaderManager.BeginFileDownloads]", "RES_MESSAGE_FilesMustBeUpdated", _application.Name); // ** // downloader, "DOWNLOAD ALL THESE FILES"--ASYNCH // ** // Get Job GUID from downloader // put job GUID in our jobstatus object _dnldJob.JobId = DownloadApplicationFiles( ); // set job status to downloading _dnldJob.Status = JobStatus.Downloading; // ** // FIRE THE DOWNLOADSTARTED EVENT // ** OnDownloadStarted( ); ApplicationUpdateManager.TraceWrite("[DownloaderManager.BeginFileDownloads]", "RES_MESSAGE_DnldJobStatusUpdated", _dnldJob.JobId, _application.Name); } else { ApplicationUpdateManager.TraceWrite("[DownloaderManager.BeginFileDownloads]", "RES_MESSAGE_NoNewVersionOnServer", _application.Name); // we have compared versions. We have found we have most up-to-date. Now delete ServerManifest locally RemoveManifestAndTempFiles(); // set job status to Validating to "dequeue" it _dnldJob.Status = JobStatus.Validating; } } // do not swallow TIE's, throw them catch (ThreadInterruptedException tie) { throw tie; } catch (Exception e) { ApplicationUpdateManager.TraceWrite(e, "[DownloaderManager.BeginFileDownloads]", "RES_GeneralUpdaterError", _application.Name); ExceptionManager.Publish(new Exception(Resource.ResourceManager["RES_GeneralUpdaterError", _application.Name], e)); } }
/// <summary> /// Download the metadata from the server using the synchronous method /// from the IDownloader interface. /// </summary> /// <returns>true if the manifest downloaded and exists as a file at the correct path</returns> private bool IsServerManifestDownloaded() { bool isManifestValid = false; bool isManifestPresent = false; ApplicationUpdateManager.TraceWrite( "[DownloaderManager.IsServerManifestDownloaded]", "RES_MESSAGE_DownloadManifestStarted", _application.Name, DateTime.Now.ToString(Resource.ResourceManager["RESX_DateTimeToStringFormat"], CultureInfo.CurrentCulture)); ApplicationUpdateManager.TraceWrite( "[DownloaderManager.IsServerManifestDownloaded]", "RES_MESSAGE_SourceFileName", _application.Server.ServerManifestFile); ApplicationUpdateManager.TraceWrite( "[DownloaderManager.IsServerManifestDownloaded]", "RES_MESSAGE_DestFileName", _application.Server.ServerManifestFileDestination); // wrap; we don't want to fall through if synch download of manifest fails, we want to wait for next poll time try { //Get the server metadata file (server manifest, commonly called "ServerConfig.xml") _downloader.Download( _application.Server.ServerManifestFile, _application.Server.ServerManifestFileDestination, TimeSpan.FromMilliseconds(_application.Server.MaxWaitXmlFile)); // ** // *** HERE WE POPULATE OUR _server OBJECT TO ENCAPSULATE MANIFEST INFO *** // ** // load the _server (ServerApplicationInfo) object w/ manifest data--THIS IS THE ONLY PLACE WE NEED POPULATE THIS _server = ServerApplicationInfo.Deserialize(_application.Server.ServerManifestFileDestination); } catch (Exception e) { // if exception during manifest download, log it but swallow here; // we don't want to drop out of main loop and end up restarting, we want to wait full interval string message = ApplicationUpdateManager.TraceWrite(e, "[DownloaderManager.IsServerManifestDownloaded]", "RES_EXCEPTION_MetadataCantBeDownloadedOrNotValid", _application.Name); Exception ex = new Exception(message, e); ExceptionManager.Publish(ex); // return false to alert that manifest download NOT successful return(false); } // ** FIRE MANIFEST DOWNLOADED EVENT ** ApplicationUpdateManager.TraceWrite("[DownloaderManager.IsServerManifestDownloaded]", "RES_MESSAGE_DownloadManifestCompleted", _application.Server.ServerManifestFileDestination); this.OnServerManifestDownloaded( ); // ** // ** VALIDATE MANIFEST FILE ** // ** isManifestValid = ValidateManifestFile(); // check if it's still present isManifestPresent = File.Exists(_application.Server.ServerManifestFileDestination); return(isManifestPresent && isManifestValid); }
///** ///*** MAIN METHOD ///** /// <summary> /// This is the main method. /// </summary> /// <remarks>This method does the following tasks:<list> /// <item>1. Get the server metadata information</item> /// <item>2. Compare server version with client versoins</item> /// <item>3. Start the download process if some local files are old.</item> /// </list></remarks> public void RunDownloader() { bool isManifestDownloadSuccess = false; bool isEventSignaled = false; // wrap whole loop in try--we need to catch ThreadInterruptedException and cleanup downloader try { #region While-Loop with Polling Wait and Inner Try-Catch while (true) { ApplicationUpdateManager.TraceWrite("[DownloaderManager.RunDownloader]", "RES_MESSAGE_CheckingOnUpdates", _application.Name); // set the job status to Ready, we want to start fresh-- // on previous passes it may have been set to Validating, but if we are back here in loop we have // completed one full polling pass _dnldJob.Status = JobStatus.Ready; // set the member tickcount so we have timestamp for loop's start _tickCountAtStart = Environment.TickCount; // get the Manifest from server isManifestDownloadSuccess = IsServerManifestDownloaded(); if (isManifestDownloadSuccess) { BeginFileDownloads(); } // now go to the main polling loop, which waits the full pollingInterval. // during the wait loop it checks download job status isEventSignaled = IsPollerIntervalElapsed(); // check if we've been signaled if (isEventSignaled) { break; } } // WHILE #endregion } // TRY // if ThreadInterruptedException, we are being told to stop. Publish, clean up, and leave (see finally) catch (ThreadInterruptedException tie) { ApplicationUpdateManager.TraceWrite(tie, "[DownloaderManager.RunDownloader]", "RES_EXCEPTION_ThreadInterruptedException", Thread.CurrentThread.Name); ExceptionManager.Publish(tie); // OK to swallow this one, we KNOW who did it to us--the ApplicationUpdateManager--and we // just go to Finally now; clean up and unwind stack. Then AUManager (AU==gold) will Join() us gracefully. } catch (Exception e) { ApplicationUpdateManager.TraceWrite(e, "[DownloaderManager.RunDownloader]"); ExceptionManager.Publish(e); // SWALLOW this exception, because we want this application to keep trying to update; // error may be transient...try restarting via BadExitCallback // AND LASTLY: use the "BadExit" callback to notify UpdaterManager something rotten has happened; // it's up to Updater to figure out what to do but in current implementation Updater simply waits a while then restarts this update _badExitCbk(_application.Name); } finally { // let downloader clean up _downloader.Dispose(); // let validator clean up _validator.Dispose(); // delete temp files + manifest RemoveManifestAndTempFiles(); // stop the IPP thread StopPostProcessorThread(); } // FINALLY }