markus / MarkusAutoUpdate / src / NetSparkle / ReleaseNotesGrabber.cs @ d8f5045e
이력 | 보기 | 이력해설 | 다운로드 (10.5 KB)
1 |
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 |
} |