markus / MarkusAutoUpdate / src / NetSparkle / ReleaseNotesGrabber.cs @ d8f5045e
이력 | 보기 | 이력해설 | 다운로드 (10.5 KB)
1 | d8f5045e | taeseongkim | using System; |
---|---|---|---|
2 | using System.Collections.Generic; |
||
3 | using System.Linq; |
||
4 | using System.Text; |
||
5 | using System.Threading.Tasks; |
||
6 | using System.Text.RegularExpressions; |
||
7 | using System.Threading; |
||
8 | using System.IO; |
||
9 | using NetSparkleUpdater.Enums; |
||
10 | using System.Net; |
||
11 | |||
12 | namespace NetSparkleUpdater |
||
13 | { |
||
14 | /// <summary> |
||
15 | /// Grabs release notes formatted as Markdown from the server and allows you to view them as HTML |
||
16 | /// </summary> |
||
17 | public class ReleaseNotesGrabber |
||
18 | { |
||
19 | /// <summary> |
||
20 | /// The HTML template to use between each changelog for every update between the |
||
21 | /// most current update and the one that the user is going to install |
||
22 | /// </summary> |
||
23 | protected string _separatorTemplate; |
||
24 | /// <summary> |
||
25 | /// The initial HTML to use for the changelog. This is everything before the |
||
26 | /// body tag and includes the html and head elements/tags. |
||
27 | /// </summary> |
||
28 | protected string _initialHTML; |
||
29 | |||
30 | /// <summary> |
||
31 | /// The <see cref="SparkleUpdater"/> for this ReleaseNotesGrabber. Mostly |
||
32 | /// used for logging via <see cref="LogWriter"/>, but also can be used |
||
33 | /// to grab other information about updates, etc. |
||
34 | /// </summary> |
||
35 | protected SparkleUpdater _sparkle; |
||
36 | |||
37 | /// <summary> |
||
38 | /// List of supported extensions for markdown files (.md, .mkdn, .mkd, .markdown) |
||
39 | /// </summary> |
||
40 | public static readonly List<string> MarkdownExtensions = new List<string> { ".md", ".mkdn", ".mkd", ".markdown" }; |
||
41 | |||
42 | /// <summary> |
||
43 | /// Whether or not to check the signature of the release notes |
||
44 | /// after they've been downloaded. Defaults to false. |
||
45 | /// </summary> |
||
46 | public bool ChecksReleaseNotesSignature { get; set; } |
||
47 | |||
48 | /// <summary> |
||
49 | /// Base constructor for ReleaseNotesGrabber |
||
50 | /// </summary> |
||
51 | /// <param name="separatorTemplate">Template to use for separating each item in the HTML</param> |
||
52 | /// <param name="htmlHeadAddition">Any additional header information to stick in the HTML that will show up in the release notes</param> |
||
53 | /// <param name="sparkle">Sparkle updater being used</param> |
||
54 | public ReleaseNotesGrabber(string separatorTemplate, string htmlHeadAddition, SparkleUpdater sparkle) |
||
55 | { |
||
56 | _separatorTemplate = |
||
57 | !string.IsNullOrEmpty(separatorTemplate) ? |
||
58 | separatorTemplate : |
||
59 | "<div style=\"border: #ccc 1px solid;\"><div style=\"background: {3}; padding: 5px;\"><span style=\"float: right;\">" + |
||
60 | "{1}</span>{0}</div><div style=\"padding: 5px;\">{2}</div></div><br/>"; |
||
61 | _initialHTML = "<!DOCTYPE html><html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8'/><title>Sparkle</title>" + |
||
62 | htmlHeadAddition + "</head><body>"; |
||
63 | _sparkle = sparkle; |
||
64 | ChecksReleaseNotesSignature = false; |
||
65 | } |
||
66 | |||
67 | /// <summary> |
||
68 | /// Generates the text to display while release notes are loading |
||
69 | /// </summary> |
||
70 | /// <returns>HTML to show to the user while release notes are loading</returns> |
||
71 | public virtual string GetLoadingText() |
||
72 | { |
||
73 | return _initialHTML + "<p><em>Loading release notes...</em></p></body></html>"; ; |
||
74 | } |
||
75 | |||
76 | /// <summary> |
||
77 | /// Download all of the release notes provided to this function and convert them to HTML |
||
78 | /// </summary> |
||
79 | /// <param name="items">List of items that you want to display in the release notes</param> |
||
80 | /// <param name="latestVersion">The latest version (most current version) of your releases</param> |
||
81 | /// <param name="cancellationToken">Token to cancel the async download requests</param> |
||
82 | /// <returns></returns> |
||
83 | public virtual async Task<string> DownloadAllReleaseNotes(List<AppCastItem> items, AppCastItem latestVersion, CancellationToken cancellationToken) |
||
84 | { |
||
85 | _sparkle.LogWriter.PrintMessage("Preparing to initialize release notes..."); |
||
86 | StringBuilder sb = new StringBuilder(_initialHTML); |
||
87 | foreach (AppCastItem castItem in items) |
||
88 | { |
||
89 | _sparkle.LogWriter.PrintMessage("Initializing release notes for {0}", castItem.Version); |
||
90 | // TODO: could we optimize this by doing multiple downloads at once? |
||
91 | var releaseNotes = await GetReleaseNotes(castItem, _sparkle, cancellationToken); |
||
92 | sb.Append(string.Format(_separatorTemplate, |
||
93 | castItem.Version, |
||
94 | castItem.PublicationDate.ToString("D"), // was dd MMM yyyy |
||
95 | releaseNotes, |
||
96 | latestVersion.Version.Equals(castItem.Version) ? "#ABFF82" : "#AFD7FF")); |
||
97 | } |
||
98 | sb.Append("</body></html>"); |
||
99 | |||
100 | _sparkle.LogWriter.PrintMessage("Done initializing release notes!"); |
||
101 | return sb.ToString(); |
||
102 | } |
||
103 | |||
104 | /// <summary> |
||
105 | /// Grab the release notes for the given item and return their release notes |
||
106 | /// in HTML format so that they can be displayed to the user. |
||
107 | /// </summary> |
||
108 | /// <param name="item"><see cref="AppCastItem"/>item to download the release notes for</param> |
||
109 | /// <param name="sparkle"><see cref="SparkleUpdater"/> that can be used for logging information |
||
110 | /// about the release notes grabbing process (or its failures)</param> |
||
111 | /// <param name="cancellationToken">token that can be used to cancel a release notes |
||
112 | /// grabbing operation</param> |
||
113 | /// <returns></returns> |
||
114 | protected virtual async Task<string> GetReleaseNotes(AppCastItem item, SparkleUpdater sparkle, CancellationToken cancellationToken) |
||
115 | { |
||
116 | string criticalUpdate = item.IsCriticalUpdate ? "Critical Update" : ""; |
||
117 | // at first try to use embedded description |
||
118 | if (!string.IsNullOrEmpty(item.Description)) |
||
119 | { |
||
120 | // check for markdown |
||
121 | Regex containsHtmlRegex = new Regex(@"<\s*([^ >]+)[^>]*>.*?<\s*/\s*\1\s*>"); |
||
122 | if (containsHtmlRegex.IsMatch(item.Description)) |
||
123 | { |
||
124 | if (item.IsCriticalUpdate) |
||
125 | { |
||
126 | item.Description = "<p><em>" + criticalUpdate + "</em></p>" + "<br/>" + item.Description; |
||
127 | } |
||
128 | return item.Description; |
||
129 | } |
||
130 | else |
||
131 | { |
||
132 | var md = new MarkdownSharp.Markdown(); |
||
133 | if (item.IsCriticalUpdate) |
||
134 | { |
||
135 | item.Description = "*" + criticalUpdate + "*" + "\n\n" + item.Description; |
||
136 | } |
||
137 | var temp = md.Transform(item.Description); |
||
138 | return temp; |
||
139 | } |
||
140 | } |
||
141 | |||
142 | // not embedded so try to release notes from the link |
||
143 | if (string.IsNullOrEmpty(item.ReleaseNotesLink)) |
||
144 | { |
||
145 | return null; |
||
146 | } |
||
147 | |||
148 | // download release notes |
||
149 | sparkle.LogWriter.PrintMessage("Downloading release notes for {0} at {1}", item.Version, item.ReleaseNotesLink); |
||
150 | string notes = await DownloadReleaseNotes(item.ReleaseNotesLink, cancellationToken, sparkle); |
||
151 | sparkle.LogWriter.PrintMessage("Done downloading release notes for {0}", item.Version); |
||
152 | if (string.IsNullOrEmpty(notes)) |
||
153 | { |
||
154 | return null; |
||
155 | } |
||
156 | |||
157 | // check dsa of release notes |
||
158 | if (!string.IsNullOrEmpty(item.ReleaseNotesSignature)) |
||
159 | { |
||
160 | if (ChecksReleaseNotesSignature && |
||
161 | _sparkle.SignatureVerifier != null && |
||
162 | Utilities.IsSignatureNeeded(_sparkle.SignatureVerifier.SecurityMode, _sparkle.SignatureVerifier.HasValidKeyInformation(), false) && |
||
163 | sparkle.SignatureVerifier.VerifySignatureOfString(item.ReleaseNotesSignature, notes) == ValidationResult.Invalid) |
||
164 | { |
||
165 | return null; |
||
166 | } |
||
167 | } |
||
168 | |||
169 | // process release notes |
||
170 | var extension = Path.GetExtension(item.ReleaseNotesLink); |
||
171 | if (extension != null && MarkdownExtensions.Contains(extension.ToLower())) |
||
172 | { |
||
173 | try |
||
174 | { |
||
175 | var md = new MarkdownSharp.Markdown(); |
||
176 | if (item.IsCriticalUpdate) |
||
177 | { |
||
178 | notes = "*" + criticalUpdate + "*" + "\n\n" + notes; |
||
179 | } |
||
180 | notes = md.Transform(notes); |
||
181 | } |
||
182 | catch (Exception ex) |
||
183 | { |
||
184 | sparkle.LogWriter.PrintMessage("Error parsing Markdown syntax: {0}", ex.Message); |
||
185 | } |
||
186 | } |
||
187 | return notes; |
||
188 | } |
||
189 | |||
190 | /// <summary> |
||
191 | /// Download the release notes at the given link. Does not do anything else |
||
192 | /// for the release notes (verification, display, etc.) -- just downloads the |
||
193 | /// release notes and passes them back as a string. |
||
194 | /// </summary> |
||
195 | /// <param name="link">string URL to the release notes to download</param> |
||
196 | /// <param name="cancellationToken">token that can be used to cancel a download operation</param> |
||
197 | /// <param name="sparkle"><see cref="SparkleUpdater"/> that can be used for logging information |
||
198 | /// about the download process (or its failures)</param> |
||
199 | /// <returns></returns> |
||
200 | protected virtual async Task<string> DownloadReleaseNotes(string link, CancellationToken cancellationToken, SparkleUpdater sparkle) |
||
201 | { |
||
202 | try |
||
203 | { |
||
204 | using (var webClient = new WebClient()) |
||
205 | { |
||
206 | webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials; |
||
207 | webClient.Encoding = Encoding.UTF8; |
||
208 | if (cancellationToken != null) |
||
209 | { |
||
210 | using (cancellationToken.Register(() => webClient.CancelAsync())) |
||
211 | { |
||
212 | return await webClient.DownloadStringTaskAsync(Utilities.GetAbsoluteURL(link, sparkle.AppCastUrl)); |
||
213 | } |
||
214 | } |
||
215 | return await webClient.DownloadStringTaskAsync(Utilities.GetAbsoluteURL(link, sparkle.AppCastUrl)); |
||
216 | } |
||
217 | } |
||
218 | catch (WebException ex) |
||
219 | { |
||
220 | sparkle.LogWriter.PrintMessage("Cannot download release notes from {0} because {1}", link, ex.Message); |
||
221 | return ""; |
||
222 | } |
||
223 | } |
||
224 | } |
||
225 | } |