/// <summary> /// Parse the app cast XML string into a list of <see cref="AppCastItem"/> objects. /// When complete, the Items list should contain the parsed information /// as <see cref="AppCastItem"/> objects. /// </summary> /// <param name="appcast">the non-null string XML app cast</param> private void ParseAppCast(string appcast) { const string itemNode = "item"; Items.Clear(); XDocument doc = XDocument.Parse(appcast); var rss = doc?.Element("rss"); var channel = rss?.Element("channel"); Title = channel?.Element("title")?.Value ?? string.Empty; Language = channel?.Element("language")?.Value ?? "en"; var items = doc.Descendants(itemNode); foreach (var item in items) { var currentItem = AppCastItem.Parse(_config.InstalledVersion, _config.ApplicationName, _castUrl, item, _logWriter); _logWriter.PrintMessage("Found an item in the app cast: version {0} ({1}) -- os = {2}", currentItem?.Version, currentItem?.ShortVersion, currentItem.OperatingSystemString); Items.Add(currentItem); } // sort versions in reverse order Items.Sort((item1, item2) => - 1 * item1.CompareTo(item2)); }
private async void LoadReleaseNotes(List <AppCastItem> items) { AppCastItem latestVersion = items.OrderByDescending(p => p.Version).FirstOrDefault(); string releaseNotes = await _releaseNotesGrabber.DownloadAllReleaseNotes(items, latestVersion, _cancellationToken); wbChangeLog.Invoke((MethodInvoker) delegate { // see https://stackoverflow.com/a/15209861/3938401 wbChangeLog.Navigate("about:blank"); wbChangeLog.Document.OpenNew(true); wbChangeLog.Document.Write(releaseNotes); wbChangeLog.DocumentText = releaseNotes; }); }
public DownloadProgressDialog(AppCastItem item, Icon applicationIcon) { InitializeComponent(); pbxApplicationIcon.Image = applicationIcon.ToBitmap(); Icon = applicationIcon; kbtnInstall.Visible = false; klblHeader.Text = $"Downloading { item.AppName } { item.Version }"; klblDownloadProgress.Text = string.Empty; pbDownload.Maximum = 100; pbDownload.Minimum = 0; pbDownload.Step = 1; }
/// <summary> /// Constructor for UpdateResponseArgs that allows for easy setting /// of the result /// </summary> /// <param name="result">User's response of type UpdateAvailableResult</param> /// <param name="item">Item that the user is responding to an update message for</param> public UpdateResponseEventArgs(UpdateAvailableResult result, AppCastItem item) : base() { Result = result; UpdateItem = item; }
/// <summary> /// Create download progress window /// </summary> /// <param name="item">Appcast item to download</param> public virtual IDownloadProgress CreateProgressWindow(AppCastItem item) { return(new DownloadProgressDialog(item, _applicationIcon)); }
/// <summary> /// Download all of the release notes provided to this function and convert them to HTML /// </summary> /// <param name="items">List of items that you want to display in the release notes</param> /// <param name="latestVersion">The latest version (most current version) of your releases</param> /// <param name="cancellationToken">Token to cancel the async download requests</param> /// <returns></returns> public virtual async Task <string> DownloadAllReleaseNotes(List <AppCastItem> items, AppCastItem latestVersion, CancellationToken cancellationToken) { _sparkle.LogWriter.PrintMessage("Preparing to initialize release notes..."); StringBuilder sb = new StringBuilder(_initialHTML); foreach (AppCastItem castItem in items) { _sparkle.LogWriter.PrintMessage("Initializing release notes for {0}", castItem.Version); // TODO: could we optimize this by doing multiple downloads at once? var releaseNotes = await GetReleaseNotes(castItem, _sparkle, cancellationToken); sb.Append(string.Format(_separatorTemplate, castItem.Version, castItem.PublicationDate.ToString("D"), // was dd MMM yyyy releaseNotes, latestVersion.Version.Equals(castItem.Version) ? "#ABFF82" : "#AFD7FF")); } sb.Append("</body>"); _sparkle.LogWriter.PrintMessage("Done initializing release notes!"); return(sb.ToString()); }
/// <summary> /// Grab the release notes for the given item and return their release notes /// in HTML format so that they can be displayed to the user. /// </summary> /// <param name="item"><see cref="AppCastItem"/>item to download the release notes for</param> /// <param name="sparkle"><see cref="SparkleUpdater"/> that can be used for logging information /// about the release notes grabbing process (or its failures)</param> /// <param name="cancellationToken">token that can be used to cancel a release notes /// grabbing operation</param> /// <returns></returns> protected virtual async Task <string> GetReleaseNotes(AppCastItem item, SparkleUpdater sparkle, CancellationToken cancellationToken) { string criticalUpdate = item.IsCriticalUpdate ? "Critical Update" : ""; // at first try to use embedded description if (!string.IsNullOrEmpty(item.Description)) { // check for markdown Regex containsHtmlRegex = new Regex(@"<\s*([^ >]+)[^>]*>.*?<\s*/\s*\1\s*>"); if (containsHtmlRegex.IsMatch(item.Description)) { if (item.IsCriticalUpdate) { item.Description = "<p><em>" + criticalUpdate + "</em></p>" + "<br>" + item.Description; } return(item.Description); } else { var md = new Markdown(); if (item.IsCriticalUpdate) { item.Description = "*" + criticalUpdate + "*" + "\n\n" + item.Description; } var temp = md.Transform(item.Description); return(temp); } } // not embedded so try to release notes from the link if (string.IsNullOrEmpty(item.ReleaseNotesLink)) { return(null); } // download release notes sparkle.LogWriter.PrintMessage("Downloading release notes for {0} at {1}", item.Version, item.ReleaseNotesLink); string notes = await DownloadReleaseNotes(item.ReleaseNotesLink, cancellationToken, sparkle); sparkle.LogWriter.PrintMessage("Done downloading release notes for {0}", item.Version); if (string.IsNullOrEmpty(notes)) { return(null); } // check dsa of release notes if (!string.IsNullOrEmpty(item.ReleaseNotesSignature)) { if (ChecksReleaseNotesSignature && _sparkle.SignatureVerifier != null && Utilities.IsSignatureNeeded(_sparkle.SignatureVerifier.SecurityMode, _sparkle.SignatureVerifier.HasValidKeyInformation()) && sparkle.SignatureVerifier.VerifySignatureOfString(item.ReleaseNotesSignature, notes) == ValidationResult.Invalid) { return(null); } } // process release notes var extension = Path.GetExtension(item.ReleaseNotesLink); if (extension != null && MarkdownExtensions.Contains(extension.ToLower())) { try { var md = new Markdown(); if (item.IsCriticalUpdate) { notes = "*" + criticalUpdate + "*" + "\n\n" + notes; } notes = md.Transform(notes); } catch (Exception ex) { sparkle.LogWriter.PrintMessage("Error parsing Markdown syntax: {0}", ex.Message); } } return(notes); }
public UpdateAvailableDialog(SparkleUpdater sparkle, List <AppCastItem> items, Icon applicationIcon = null, bool isUpdateAlreadyDownloaded = false, string separatorTemplate = "", string headAddition = "") { _sparkle = sparkle; _updates = items; _releaseNotesGrabber = new ReleaseNotesGrabber(separatorTemplate, headAddition, sparkle); InitializeComponent(); // init ui try { wbChangeLog.AllowWebBrowserDrop = false; wbChangeLog.AllowNavigation = false; } catch (Exception ex) { _sparkle.LogWriter.PrintMessage("Error in browser init: {0}", ex.Message); } AppCastItem item = items.FirstOrDefault(); var downloadInstallText = isUpdateAlreadyDownloaded ? "install" : "download"; klblHeader.Text = $"A new version of { item.AppName } is available!"; if (item != null) { var versionString = ""; try { // Use try/catch since Version constructor can throw an exception and we don't want to // die just because the user has a malformed version string Version versionObj = new Version(item.AppVersionInstalled); versionString = Utilities.GetVersionString(versionObj); } catch { versionString = "?"; } klblInfo.Text = string.Format("{0} {3} is now available (you have {1}). Would you like to {2} it now?", item.AppName, versionString, downloadInstallText, item.Version); } else { // TODO: string translations (even though I guess this window should never be called with 0 app cast items...) klblInfo.Text = string.Format("Would you like to {0} it now?", downloadInstallText); } bool isUserMissingCriticalUpdate = items.Any(x => x.IsCriticalUpdate); kbtnRemindLater.Enabled = isUserMissingCriticalUpdate == false; kbtnSkip.Enabled = isUserMissingCriticalUpdate == false; //if (isUserMissingCriticalUpdate) //{ // FormClosing += UpdateAvailableWindow_FormClosing; // no closing a critical update! //} if (applicationIcon != null) { using (Icon icon = new Icon(applicationIcon, new Size(48, 48))) { pbxApplicationIcon.Image = icon.ToBitmap(); } Icon = applicationIcon; } _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; wbChangeLog.DocumentText = _releaseNotesGrabber.GetLoadingText(); EnsureDialogShown(); LoadReleaseNotes(items); }