private async void downloadAndDisplayAllReleaseNotes(AppCastItem[] items, AppCastItem latestVersion, string initialHTML) { _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); 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>"); string fullHTML = sb.ToString(); ReleaseNotesBrowser.Invoke((MethodInvoker) delegate { // see https://stackoverflow.com/a/15209861/3938401 ReleaseNotesBrowser.Navigate("about:blank"); ReleaseNotesBrowser.Document.OpenNew(true); ReleaseNotesBrowser.Document.Write(fullHTML); ReleaseNotesBrowser.DocumentText = fullHTML; }); _sparkle.LogWriter.PrintMessage("Done initializing release notes!"); }
/// <summary> /// Constructor /// </summary> /// <param name="item">The appcast item to use</param> /// <param name="applicationIcon">Your application Icon</param> public DownloadProgressWindow(AppCastItem item, Icon applicationIcon) { InitializeComponent(); imgAppIcon.Image = applicationIcon.ToBitmap(); Icon = applicationIcon; // init ui btnInstallAndReLaunch.Visible = false; lblHeader.Text = lblHeader.Text.Replace("APP", item.AppName + " " + item.Version); downloadProgressLbl.Text = ""; progressDownload.Maximum = 100; progressDownload.Minimum = 0; progressDownload.Step = 1; FormClosing += DownloadProgressWindow_FormClosing; }
/// <summary> /// Create download progress window /// </summary> /// <param name="item">Appcast item to download</param> /// <param name="applicationIcon">Application icon to use</param> public virtual IDownloadProgress CreateProgressWindow(AppCastItem item, Icon applicationIcon) { return(new DownloadProgressWindow(item, applicationIcon)); }
/// <summary> /// Form constructor for showing release notes. /// </summary> /// <param name="sparkle">The <see cref="Sparkle"/> instance to use</param> /// <param name="items">List of updates to show. Should contain at least one item.</param> /// <param name="applicationIcon">The icon to display</param> /// <param name="isUpdateAlreadyDownloaded">If true, make sure UI text shows that the user is about to install the file instead of download it.</param> /// <param name="separatorTemplate">HTML template for every single note. Use {0} = Version. {1} = Date. {2} = Note Body</param> /// <param name="headAddition">Additional text they will inserted into HTML Head. For Stylesheets.</param> public UpdateAvailableWindow(Sparkle sparkle, AppCastItem[] items, Icon applicationIcon = null, bool isUpdateAlreadyDownloaded = false, string separatorTemplate = "", string headAddition = "") { _sparkle = sparkle; _updates = items; _separatorTemplate = !string.IsNullOrEmpty(separatorTemplate) ? separatorTemplate : "<div style=\"border: #ccc 1px solid;\"><div style=\"background: {3}; padding: 5px;\"><span style=\"float: right; display:float;\">" + "{1}</span>{0}</div><div style=\"padding: 5px;\">{2}</div></div><br>"; InitializeComponent(); // init ui try { ReleaseNotesBrowser.AllowWebBrowserDrop = false; ReleaseNotesBrowser.AllowNavigation = false; } catch (Exception ex) { _sparkle.LogWriter.PrintMessage("Error in browser init: {0}", ex.Message); } AppCastItem item = items.FirstOrDefault(); lblHeader.Text = lblHeader.Text.Replace("APP", item != null ? item.AppName : "the application"); if (item != null) { lblInfoText.Text = lblInfoText.Text.Replace("APP", item.AppName + " " + item.Version); 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 = ""; } lblInfoText.Text = lblInfoText.Text.Replace("OLDVERSION", versionString); } else { // TODO: string translations (even though I guess this window should never be called with 0 app cast items...) lblInfoText.Text = "Would you like to [DOWNLOAD] it now?"; } lblInfoText.Text = lblInfoText.Text.Replace("[DOWNLOAD]", isUpdateAlreadyDownloaded ? "install" : "download"); AppCastItem latestVersion = items.OrderByDescending(p => p.Version).FirstOrDefault(); string initialHTML = "<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>" + headAddition + "</head><body>"; ReleaseNotesBrowser.DocumentText = initialHTML + "<p><em>Loading release notes...</em></p></body></html>"; bool isUserMissingCriticalUpdate = false; foreach (AppCastItem castItem in items) { isUserMissingCriticalUpdate = isUserMissingCriticalUpdate | castItem.IsCriticalUpdate; } buttonRemind.Enabled = isUserMissingCriticalUpdate == false; skipButton.Enabled = isUserMissingCriticalUpdate == false; //if (isUserMissingCriticalUpdate) //{ // FormClosing += UpdateAvailableWindow_FormClosing; // no closing a critical update! //} if (applicationIcon != null) { imgAppIcon.Image = new Icon(applicationIcon, new Size(48, 48)).ToBitmap(); Icon = applicationIcon; } EnsureDialogShown(); _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; downloadAndDisplayAllReleaseNotes(items, latestVersion, initialHTML); }
private async Task <string> GetReleaseNotes(AppCastItem item) { 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 MarkdownSharp.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.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.ReleaseNotesDSASignature)) { if (_sparkle.DSAChecker.VerifyDSASignatureOfString(item.ReleaseNotesDSASignature, notes) == ValidationResult.Invalid) { return(null); } } // process release notes var extension = Path.GetExtension(item.ReleaseNotesLink); if (extension != null && MarkDownExtension.Contains(extension.ToLower())) { try { var md = new MarkdownSharp.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); }
private void Parse(XmlReader reader) { const string itemNode = "item"; const string enclosureNode = "enclosure"; const string sparkleEnclosureNode = "sparkle:enclosure"; const string releaseNotesLinkNode = "sparkle:releaseNotesLink"; const string descriptionNode = "description"; const string versionAttribute = "sparkle:version"; const string dsaSignature = "sparkle:dsaSignature"; const string criticalAttribute = "sparkle:criticalUpdate"; const string operatingSystemAttribute = "sparkle:os"; const string lengthAttribute = "length"; const string typeAttribute = "type"; const string urlAttribute = "url"; const string pubDateNode = "pubDate"; AppCastItem currentItem = null; while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case itemNode: currentItem = new AppCastItem() { AppVersionInstalled = _config.InstalledVersion, AppName = _config.ApplicationName, UpdateSize = 0, IsCriticalUpdate = false, OperatingSystemString = "windows", MIMEType = "application/octet-stream" }; break; case releaseNotesLinkNode: if (currentItem != null) { currentItem.ReleaseNotesDSASignature = reader.GetAttribute(dsaSignature); currentItem.ReleaseNotesLink = reader.ReadString().Trim(); } break; case descriptionNode: if (currentItem != null) { currentItem.Description = reader.ReadString().Trim(); } break; case enclosureNode: case sparkleEnclosureNode: if (currentItem != null) { currentItem.Version = reader.GetAttribute(versionAttribute); currentItem.DownloadLink = reader.GetAttribute(urlAttribute); currentItem.DownloadDSASignature = reader.GetAttribute(dsaSignature); string length = reader.GetAttribute(lengthAttribute); if (length != null) { int size = 0; if (int.TryParse(length, out size)) { currentItem.UpdateSize = size; } else { currentItem.UpdateSize = 0; } } bool isCritical = false; string critical = reader.GetAttribute(criticalAttribute); if (critical != null && critical == "true" || critical == "1") { isCritical = true; } currentItem.IsCriticalUpdate = isCritical; string operatingSystem = reader.GetAttribute(operatingSystemAttribute); if (operatingSystem != null && operatingSystem != "") { currentItem.OperatingSystemString = operatingSystem; } string mimeType = reader.GetAttribute(typeAttribute); if (mimeType != null && mimeType != "") { currentItem.MIMEType = mimeType; } } break; case pubDateNode: if (currentItem != null) { string dt = reader.ReadString().Trim(); try { currentItem.PublicationDate = DateTime.ParseExact(dt, "ddd, dd MMM yyyy HH:mm:ss zzz", System.Globalization.CultureInfo.InvariantCulture); } catch (FormatException ex) { _logWriter.PrintMessage("Cannot parse item datetime {0} with message {1}", dt, ex.Message); } } break; } } else if (reader.NodeType == XmlNodeType.EndElement) { switch (reader.Name) { case itemNode: _items.Add(currentItem); break; } } } // sort versions in reverse order _items.Sort((item1, item2) => - 1 * item1.CompareTo(item2)); }
private void Parse(XmlReader reader) { const string itemNode = "item"; const string enclosureNode = "enclosure"; const string sparkleEnclosureNode = "sparkle:enclosure"; const string releaseNotesLinkNode = "sparkle:releaseNotesLink"; const string descriptionNode = "description"; const string versionAttribute = "sparkle:version"; const string dsaSignature = "sparkle:dsaSignature"; const string criticalAttribute = "sparkle:criticalUpdate"; const string operatingSystemAttribute = "sparkle:os"; const string lengthAttribute = "length"; const string typeAttribute = "type"; const string urlAttribute = "url"; const string pubDateNode = "pubDate"; AppCastItem currentItem = null; while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case itemNode: currentItem = new AppCastItem() { AppVersionInstalled = _config.InstalledVersion, AppName = _config.ApplicationName, UpdateSize = 0, IsCriticalUpdate = false, OperatingSystemString = "windows", MIMEType = "application/octet-stream" }; break; case releaseNotesLinkNode: if (currentItem != null) { currentItem.ReleaseNotesDSASignature = reader.GetAttribute(dsaSignature); currentItem.ReleaseNotesLink = reader.ReadString().Trim(); } break; case descriptionNode: if (currentItem != null) { currentItem.Description = reader.ReadString().Trim(); } break; case enclosureNode: case sparkleEnclosureNode: if (currentItem != null) { currentItem.Version = reader.GetAttribute(versionAttribute); currentItem.DownloadLink = reader.GetAttribute(urlAttribute); if (!string.IsNullOrEmpty(currentItem.DownloadLink) && !currentItem.DownloadLink.Contains("/")) { // Download link contains only the filename -> complete with _castUrl currentItem.DownloadLink = _castUrl.Substring(0, _castUrl.LastIndexOf('/') + 1) + currentItem.DownloadLink; } currentItem.DownloadDSASignature = reader.GetAttribute(dsaSignature); string length = reader.GetAttribute(lengthAttribute); if (length != null) { int size = 0; if (int.TryParse(length, out size)) { currentItem.UpdateSize = size; } else { currentItem.UpdateSize = 0; } } bool isCritical = false; string critical = reader.GetAttribute(criticalAttribute); if (critical != null && critical == "true" || critical == "1") { isCritical = true; } currentItem.IsCriticalUpdate = isCritical; string operatingSystem = reader.GetAttribute(operatingSystemAttribute); if (operatingSystem != null && operatingSystem != "") { currentItem.OperatingSystemString = operatingSystem; } string mimeType = reader.GetAttribute(typeAttribute); if (mimeType != null && mimeType != "") { currentItem.MIMEType = mimeType; } } break; case pubDateNode: if (currentItem != null) { // "ddd, dd MMM yyyy HH:mm:ss zzz" => Standard date format // e.g. "Sat, 26 Oct 2019 22:05:11 -05:00" // "ddd, dd MMM yyyy HH:mm:ss Z" => Check for MS AppCenter Sparkle date format which ends with GMT // e.g. "Sat, 26 Oct 2019 22:05:11 GMT" // "ddd, dd MMM yyyy HH:mm:ss" => Standard date format with no timezone (fallback) // e.g. "Sat, 26 Oct 2019 22:05:11" string[] formats = { "ddd, dd MMM yyyy HH:mm:ss zzz", "ddd, dd MMM yyyy HH:mm:ss Z", "ddd, dd MMM yyyy HH:mm:ss" }; string dt = reader.ReadString().Trim(); if (DateTime.TryParseExact(dt, formats, System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateValue)) { _logWriter.PrintMessage("Converted '{0}' to {1}.", dt, dateValue); currentItem.PublicationDate = dateValue; } else { _logWriter.PrintMessage("Cannot parse item datetime {0}", dt); } } break; } } else if (reader.NodeType == XmlNodeType.EndElement) { switch (reader.Name) { case itemNode: _items.Add(currentItem); break; } } } // sort versions in reverse order _items.Sort((item1, item2) => - 1 * item1.CompareTo(item2)); }
/// <summary> /// Form constructor /// </summary> /// <param name="sparkle">The <see cref="Sparkle"/> instance to use</param> /// <param name="items">List of updates to show</param> /// <param name="applicationIcon">The icon to display</param> /// <param name="isUpdateAlreadyDownloaded">If true, make sure UI text shows that the user is about to install the file instead of download it.</param> /// <param name="separatorTemplate">HTML template for every single note. Use {0} = Version. {1} = Date. {2} = Note Body</param> /// <param name="headAddition">Additional text they will inserted into HTML Head. For Stylesheets.</param> public UpdateAvailableWindow(Sparkle sparkle, AppCastItem[] items, Icon applicationIcon = null, bool isUpdateAlreadyDownloaded = false, string separatorTemplate = "", string headAddition = "") { _sparkle = sparkle; _updates = items; SeparatorTemplate = !string.IsNullOrEmpty(separatorTemplate) ? separatorTemplate : "<div style=\"border: #ccc 1px solid;\"><div style=\"background: {3}; padding: 5px;\"><span style=\"float: right; display:float;\">" + "{1}</span>{0}</div><div style=\"padding: 5px;\">{2}</div></div><br>"; InitializeComponent(); // init ui try { ReleaseNotesBrowser.AllowWebBrowserDrop = false; ReleaseNotesBrowser.AllowNavigation = false; } catch (Exception ex) { _sparkle.LogWriter.PrintMessage("Error in browser init: {0}", ex.Message); } AppCastItem item = items[0]; lblHeader.Text = lblHeader.Text.Replace("APP", item.AppName); lblInfoText.Text = lblInfoText.Text.Replace("APP", item.AppName + " " + item.Version); lblInfoText.Text = lblInfoText.Text.Replace("OLDVERSION", getVersion(new Version(item.AppVersionInstalled))); lblInfoText.Text = lblInfoText.Text.Replace("[DOWNLOAD]", isUpdateAlreadyDownloaded ? "install" : "download"); if (items.Length == 0) { RemoveReleaseNotesControls(); } else { AppCastItem latestVersion = _updates.OrderByDescending(p => p.Version).FirstOrDefault(); StringBuilder sb = new StringBuilder("<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>" + headAddition + "</head><body>"); bool isUserMissingCriticalUpdate = false; foreach (AppCastItem castItem in items) { isUserMissingCriticalUpdate = isUserMissingCriticalUpdate | castItem.IsCriticalUpdate; sb.Append(string.Format(SeparatorTemplate, castItem.Version, castItem.PublicationDate.ToString("dd MMM yyyy"), GetReleaseNotes(castItem), latestVersion.Version.Equals(castItem.Version) ? "#ABFF82" : "#AFD7FF")); } sb.Append("</body>"); string releaseNotes = sb.ToString(); ReleaseNotesBrowser.DocumentText = releaseNotes; buttonRemind.Enabled = isUserMissingCriticalUpdate == false; skipButton.Enabled = isUserMissingCriticalUpdate == false; //if (isUserMissingCriticalUpdate) //{ // FormClosing += UpdateAvailableWindow_FormClosing; // no closing a critical update! //} } if (applicationIcon != null) { imgAppIcon.Image = new Icon(applicationIcon, new Size(48, 48)).ToBitmap(); Icon = applicationIcon; } EnsureDialogShown(); }