2 using System.ComponentModel;
3 using System.Reflection;
4 using System.Threading;
6 using System.Windows.Forms;
8 using System.Security.Cryptography.X509Certificates;
9 using System.Net.Security;
11 using Point = System.Drawing.Point;
13 namespace AppLimit.NetSparkle
15 public delegate void LoopStartedOperation(Object sender);
16 public delegate void LoopFinishedOperation(Object sender, Boolean UpdateRequired);
19 /// Everytime when netsparkle detects an update the
20 /// consumer can decide what should happen as next with the help
21 /// of the UpdateDatected event
23 public enum nNextUpdateAction
25 showStandardUserInterface = 1,
26 performUpdateUnattended = 2,
31 /// Contains all information for the update detected event
33 public class UpdateDetectedEventArgs : EventArgs
35 public nNextUpdateAction NextAction;
36 public NetSparkleConfiguration ApplicationConfig;
37 public NetSparkleAppCastItem LatestVersion;
41 /// This delegate will be used when an update was detected to allow library
42 /// consumer to add own user interface capabilities.
44 /// <param name="sender"></param>
45 /// <param name="e"></param>
46 public delegate void UpdateDetected(Object sender, UpdateDetectedEventArgs e);
48 public class Sparkle : IDisposable
50 private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
52 private BackgroundWorker _worker = new BackgroundWorker();
54 private String _AppCastUrl;
55 private String _AppReferenceAssembly;
57 private Boolean _DoInitialCheck;
58 private Boolean _ForceInitialCheck;
60 private EventWaitHandle _exitHandle;
61 private EventWaitHandle _loopingHandle;
63 private NetSparkleMainWindows _DiagnosticWindow;
65 private TimeSpan _CheckFrequency;
68 /// Enables system profiling against a profile server
70 public Boolean EnableSystemProfiling = false;
73 /// Hides the release notes view when an update was found. This
74 /// mode is switched on automatically when no sparkle:releaseNotesLink
75 /// tag was found in the app cast
77 public Boolean HideReleaseNotes = false;
80 /// Contains the profile url for System profiling
82 public Uri SystemProfileUrl;
85 /// This event will be raised when a check loop will be started
87 public event LoopStartedOperation checkLoopStarted;
90 /// This event will be raised when a check loop is finished
92 public event LoopFinishedOperation checkLoopFinished;
95 /// This event can be used to override the standard user interface
96 /// process when an update is detected
98 public event UpdateDetected updateDetected;
101 /// This property holds an optional application icon
102 /// which will be displayed in the software update dialog. The icon has
103 /// to be 48x48 pixels.
105 public Image ApplicationIcon { get; set; }
108 /// This property returns an optional application icon
109 /// which will displayed in the windows as self
111 public Icon ApplicationWindowIcon { get; set; }
114 /// This property enables a diagnostic window for debug reasons
116 public Boolean ShowDiagnosticWindow { get; set; }
119 /// This property enables the silent mode, this means
120 /// the application will be updated without user interaction
122 public Boolean EnableSilentMode { get; set; }
125 /// This property returns true when the upadete loop is running
126 /// and files when the loop is not running
128 public Boolean IsUpdateLoopRunning
132 return _loopingHandle.WaitOne(0);
137 /// This property defines if we trust every ssl connection also when
138 /// this connection has not a valid cert
140 public Boolean TrustEverySSLConnection { get; set; }
142 public string LatestVersion { get; set; }
145 /// ctor which needs the appcast url
147 /// <param name="appcastUrl"></param>
148 public Sparkle(String appcastUrl)
149 : this(appcastUrl, null)
153 /// ctor which needs the appcast url and a referenceassembly
155 public Sparkle(String appcastUrl, String referenceAssembly)
156 : this(appcastUrl, referenceAssembly, false)
160 /// ctor which needs the appcast url and a referenceassembly
162 public Sparkle(String appcastUrl, String referenceAssembly, Boolean ShowDiagnostic)
164 // preconfige ssl trust
165 TrustEverySSLConnection = false;
167 // configure ssl cert link
168 ServicePointManager.ServerCertificateValidationCallback += RemoteCertificateValidation;
170 // enable visual style to ensure that we have XP style or higher
171 // also in WPF applications
172 System.Windows.Forms.Application.EnableVisualStyles();
175 ApplicationIcon = null;
176 _AppReferenceAssembly = null;
179 ShowDiagnosticWindow = ShowDiagnostic;
181 // create the diagnotic window
182 _DiagnosticWindow = new NetSparkleMainWindows();
184 // set the reference assembly
185 if (referenceAssembly != null)
187 _AppReferenceAssembly = referenceAssembly;
188 _DiagnosticWindow.Report("Checking the following file: " + _AppReferenceAssembly);
192 ShowDiagnosticWindowIfNeeded();
194 // adjust the delegates
195 _worker.WorkerReportsProgress = true;
196 _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
197 _worker.ProgressChanged += new ProgressChangedEventHandler(_worker_ProgressChanged);
199 // build the wait handle
200 _exitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
201 _loopingHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
204 _AppCastUrl = appcastUrl;
205 _DiagnosticWindow.Report("Using the following url: " + _AppCastUrl);
209 /// The method starts a NetSparkle background loop
210 /// If NetSparkle is configured to check for updates on startup, proceeds to perform
211 /// the check. You should only call this function when your app is initialized and
212 /// shows its main window.
214 /// <param name="doInitialCheck"></param>
215 public void StartLoop(Boolean doInitialCheck)
217 StartLoop(doInitialCheck, false);
221 /// The method starts a NetSparkle background loop
222 /// If NetSparkle is configured to check for updates on startup, proceeds to perform
223 /// the check. You should only call this function when your app is initialized and
224 /// shows its main window.
226 /// <param name="doInitialCheck"></param>
227 /// <param name="checkFrequency"></param>
228 public void StartLoop(Boolean doInitialCheck, TimeSpan checkFrequency)
230 StartLoop(doInitialCheck, false, checkFrequency);
234 /// The method starts a NetSparkle background loop
235 /// If NetSparkle is configured to check for updates on startup, proceeds to perform
236 /// the check. You should only call this function when your app is initialized and
237 /// shows its main window.
239 /// <param name="doInitialCheck"></param>
240 /// <param name="forceInitialCheck"></param>
241 public void StartLoop(Boolean doInitialCheck, Boolean forceInitialCheck)
243 StartLoop(doInitialCheck, forceInitialCheck, TimeSpan.FromHours(24));
247 /// The method starts a NetSparkle background loop
248 /// If NetSparkle is configured to check for updates on startup, proceeds to perform
249 /// the check. You should only call this function when your app is initialized and
250 /// shows its main window.
252 /// <param name="doInitialCheck"></param>
253 /// <param name="forceInitialCheck"></param>
254 /// <param name="checkFrequency"></param>
255 public void StartLoop(Boolean doInitialCheck, Boolean forceInitialCheck, TimeSpan checkFrequency)
257 // first set the event handle
258 _loopingHandle.Set();
260 // Start the helper thread as a background worker to
261 // get well ui interaction
264 ShowDiagnosticWindowIfNeeded();
267 _DoInitialCheck = doInitialCheck;
268 _ForceInitialCheck = forceInitialCheck;
269 _CheckFrequency = checkFrequency;
271 // create and configure the worker
272 _DiagnosticWindow.Report("Starting background worker");
275 _worker.RunWorkerAsync();
279 /// This method will stop the sparkle background loop and is called
280 /// through the disposable interface automatically
282 public void StopLoop()
284 // ensure the work will finished
289 /// Is called in the using context and will stop all background activities
291 public void Dispose()
297 /// This method updates the profile information which can be sended to the server if enabled
299 /// <param name="config"></param>
300 public void UpdateSystemProfileInformation(NetSparkleConfiguration config)
302 // check if profile data is enabled
303 if (!EnableSystemProfiling)
306 // check if we need an update
307 if (DateTime.Now - config.LastProfileUpdate < new TimeSpan(7, 0, 0, 0))
310 // touch the profile update time
311 config.TouchProfileTime();
313 // start the profile thread
314 Thread t = new Thread(ProfileDataThreadStart);
319 /// Profile data thread
321 /// <param name="obj"></param>
322 private void ProfileDataThreadStart(object obj)
326 if (SystemProfileUrl != null)
329 NetSparkleConfiguration config = obj as NetSparkleConfiguration;
332 NetSparkleDeviceInventory inv = new NetSparkleDeviceInventory(config);
333 inv.CollectInventory();
336 String requestUrl = inv.BuildRequestUrl(SystemProfileUrl.ToString() + "?");
338 HttpWebRequest.DefaultWebProxy = HttpWebRequest.GetSystemWebProxy();
340 // perform the webrequest
341 HttpWebRequest request = HttpWebRequest.Create(requestUrl) as HttpWebRequest;
342 using (WebResponse response = request.GetResponse())
344 // close the response
351 // No exception during data send
352 ReportDiagnosticMessage(ex.Message);
357 /// This method checks if an update is required. During this process the appcast
358 /// will be downloaded and checked against the reference assembly. Ensure that
359 /// the calling process has access to the internet and read access to the
360 /// reference assembly. This method is also called from the background loops.
362 /// <param name="config"></param>
363 /// <returns></returns>
364 public Boolean IsUpdateRequired(NetSparkleConfiguration config, out NetSparkleAppCastItem latestVersion)
367 ReportDiagnosticMessage("Downloading and checking appcast");
370 NetSparkleAppCast cast = new NetSparkleAppCast(_AppCastUrl, config);
372 // check if any updates are available
375 latestVersion = cast.GetLatestVersion();
379 // show the exeception message
380 ReportDiagnosticMessage("Error during app cast download: " + e.Message);
382 // just null the version info
383 latestVersion = null;
386 if (latestVersion == null)
388 ReportDiagnosticMessage("No version information in app cast found");
393 ReportDiagnosticMessage("Lastest version on the server is " + latestVersion.Version);
396 // set the last check time
397 ReportDiagnosticMessage("Touch the last check timestamp");
398 config.TouchCheckTime();
400 // check if the available update has to be skipped
401 if (latestVersion.Version.Equals(config.SkipThisVersion))
403 ReportDiagnosticMessage("Latest update has to be skipped (user decided to skip version " + config.SkipThisVersion + ")");
407 // check if the version will be the same then the installed version
408 Version v1 = new Version(config.InstalledVersion);
409 Version v2 = new Version(latestVersion.Version);
413 ReportDiagnosticMessage("Installed version is valid, no update needed (" + config.InstalledVersion + ")");
417 // ok we need an update
422 /// This method reads the local sparkle configuration for the given
423 /// reference assembly
425 /// <param name="AppReferenceAssembly"></param>
426 /// <returns></returns>
427 public NetSparkleConfiguration GetApplicationConfig()
429 NetSparkleConfiguration config;
430 config = new NetSparkleConfiguration(_AppReferenceAssembly);
435 /// This method shows the update ui and allows to perform the
438 /// <param name="currentItem"></param>
439 public void ShowUpdateNeededUI(NetSparkleAppCastItem currentItem)
443 NetSparkleForm frm = new NetSparkleForm(currentItem, ApplicationIcon, ApplicationWindowIcon);
445 // configure the form
448 if (HideReleaseNotes)
449 frm.RemoveReleaseNotesControls();
453 DialogResult dlgResult = frm.ShowDialog();
456 if (dlgResult == DialogResult.No)
459 NetSparkleConfiguration config = new NetSparkleConfiguration(_AppReferenceAssembly);
460 config.SetVersionToSkip(currentItem.Version);
462 else if (dlgResult == DialogResult.Yes)
464 // download the binaries
465 InitDownloadAndInstallProcess(currentItem);
471 /// This method reports a message in the diagnostic window
473 /// <param name="message"></param>
474 public void ReportDiagnosticMessage(String message)
477 if (_DiagnosticWindow.InvokeRequired)
479 _DiagnosticWindow.Invoke(new Action<String>(ReportDiagnosticMessage), message);
483 _DiagnosticWindow.Report(message);
488 /// This method will be executed as worker thread
490 /// <param name="sender"></param>
491 /// <param name="e"></param>
492 void _worker_DoWork(object sender, DoWorkEventArgs e)
494 // store the did run once feature
495 Boolean goIntoLoop = true;
496 Boolean checkTSP = true;
497 Boolean doInitialCheck = _DoInitialCheck;
498 Boolean isInitialCheck = true;
500 // start our lifecycles
504 Boolean bUpdateRequired = false;
507 if (checkLoopStarted != null)
508 checkLoopStarted(this);
511 if (doInitialCheck == false)
513 ReportDiagnosticMessage("Initial check prohibited, going to wait");
514 doInitialCheck = true;
519 ReportDiagnosticMessage("Starting update loop...");
522 ReportDiagnosticMessage("Reading config...");
523 NetSparkleConfiguration config = GetApplicationConfig();
526 Boolean checkTSPInternal = checkTSP;
528 if (isInitialCheck && checkTSPInternal)
529 checkTSPInternal = !_ForceInitialCheck;
531 // check if it's ok the recheck to software state
532 if (checkTSPInternal)
534 TimeSpan csp = DateTime.Now - config.LastCheckTime;
535 if (csp < _CheckFrequency)
537 ReportDiagnosticMessage(String.Format("Update check performed within the last {0} minutes!", _CheckFrequency.TotalMinutes));
544 // when sparkle will be deactivated wait an other cycle
545 if (config.CheckForUpdate == false)
547 ReportDiagnosticMessage("Check for updates disabled");
551 // update the runonce feature
552 goIntoLoop = !config.DidRunOnce;
554 // update profile information is needed
555 UpdateSystemProfileInformation(config);
557 // check if update is required
558 NetSparkleAppCastItem latestVersion = null;
559 bUpdateRequired = IsUpdateRequired(config, out latestVersion);
560 this.LatestVersion = latestVersion.Version;
561 if (!bUpdateRequired)
564 // show the update window
565 ReportDiagnosticMessage("Update needed from version " + config.InstalledVersion + " to version " + latestVersion.Version);
567 // send notification if needed
568 UpdateDetectedEventArgs ev = new UpdateDetectedEventArgs() { NextAction = nNextUpdateAction.showStandardUserInterface, ApplicationConfig = config, LatestVersion = latestVersion };
569 if (updateDetected != null)
570 updateDetected(this, ev);
573 switch(ev.NextAction)
575 case nNextUpdateAction.performUpdateUnattended:
577 ReportDiagnosticMessage("Unattended update whished from consumer");
578 EnableSilentMode = true;
579 _worker.ReportProgress(1, latestVersion);
582 case nNextUpdateAction.prohibitUpdate:
584 ReportDiagnosticMessage("Update prohibited from consumer");
587 case nNextUpdateAction.showStandardUserInterface:
590 ReportDiagnosticMessage("Standard UI update whished from consumer");
591 _worker.ReportProgress(1, latestVersion);
597 // reset initialcheck
598 isInitialCheck = false;
601 if (checkLoopFinished != null)
602 checkLoopFinished(this, bUpdateRequired);
604 // report wait statement
605 ReportDiagnosticMessage(String.Format("Sleeping for an other {0} minutes, exit event or force update check event", _CheckFrequency.TotalMinutes));
612 // build the event array
613 WaitHandle[] handles = new WaitHandle[1];
614 handles[0] = _exitHandle;
617 int i = WaitHandle.WaitAny(handles, _CheckFrequency);
618 if (WaitHandle.WaitTimeout == i)
620 ReportDiagnosticMessage(String.Format("{0} minutes are over", _CheckFrequency.TotalMinutes));
624 // check the exit hadnle
627 ReportDiagnosticMessage("Got exit signal");
631 // check an other check needed
634 ReportDiagnosticMessage("Got force update check signal");
639 } while (goIntoLoop);
641 // reset the islooping handle
642 _loopingHandle.Reset();
646 /// This method will be notified
648 /// <param name="sender"></param>
649 /// <param name="e"></param>
650 private void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
652 switch (e.ProgressPercentage)
656 // get the current item
657 NetSparkleAppCastItem currentItem = e.UserState as NetSparkleAppCastItem;
659 // show the update ui
660 if (EnableSilentMode == true)
661 InitDownloadAndInstallProcess(currentItem);
663 ShowUpdateNeededUI(currentItem);
669 ReportDiagnosticMessage(e.UserState.ToString());
675 private void InitDownloadAndInstallProcess(NetSparkleAppCastItem item)
677 NetSparkleDownloadProgress dlProgress = new NetSparkleDownloadProgress(this, item, _AppReferenceAssembly, ApplicationIcon, ApplicationWindowIcon, EnableSilentMode);
678 dlProgress.ShowDialog();
681 private void ShowDiagnosticWindowIfNeeded()
683 if (_DiagnosticWindow.InvokeRequired)
685 _DiagnosticWindow.Invoke(new Action(ShowDiagnosticWindowIfNeeded));
689 // check the diagnotic value
690 NetSparkleConfiguration config = new NetSparkleConfiguration(_AppReferenceAssembly);
691 if (config.ShowDiagnosticWindow || ShowDiagnosticWindow)
693 Point newLocation = new Point();
695 newLocation.X = Screen.PrimaryScreen.Bounds.Width - _DiagnosticWindow.Width;
698 _DiagnosticWindow.Location = newLocation;
699 _DiagnosticWindow.Show();
704 private bool RemoteCertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
706 if (TrustEverySSLConnection)
708 // verify if we talk about our app cast dll
709 HttpWebRequest req = sender as HttpWebRequest;
711 return (certificate is X509Certificate2) ? ((X509Certificate2)certificate).Verify() : false;
713 // if so just return our trust
714 if (req.RequestUri.Equals(new Uri(_AppCastUrl)))
717 return (certificate is X509Certificate2) ? ((X509Certificate2)certificate).Verify() : false;
722 return (certificate is X509Certificate2) ? ((X509Certificate2)certificate).Verify() : false;