프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

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
}
클립보드 이미지 추가 (최대 크기: 500 MB)