markus / MarkusAutoUpdate / src / NetSparkle / AppCastItem.cs @ d8f5045e
이력 | 보기 | 이력해설 | 다운로드 (18.4 KB)
1 | d8f5045e | taeseongkim | using NetSparkleUpdater.AppCastHandlers; |
---|---|---|---|
2 | using NetSparkleUpdater.Interfaces; |
||
3 | using System; |
||
4 | using System.Globalization; |
||
5 | using System.Xml.Linq; |
||
6 | |||
7 | namespace NetSparkleUpdater |
||
8 | { |
||
9 | /// <summary> |
||
10 | /// Item from a Sparkle AppCast file |
||
11 | /// </summary> |
||
12 | [Serializable] |
||
13 | public class AppCastItem : IComparable<AppCastItem> |
||
14 | { |
||
15 | /// <summary> |
||
16 | /// The application name |
||
17 | /// </summary> |
||
18 | public string AppName { get; set; } |
||
19 | /// <summary> |
||
20 | /// The installed version |
||
21 | /// </summary> |
||
22 | public string AppVersionInstalled { get; set; } |
||
23 | /// <summary> |
||
24 | /// The item title |
||
25 | /// </summary> |
||
26 | public string Title { get; set; } |
||
27 | /// <summary> |
||
28 | /// The available version |
||
29 | /// </summary> |
||
30 | public string Version { get; set; } |
||
31 | /// <summary> |
||
32 | /// Shortened version |
||
33 | /// </summary> |
||
34 | public string ShortVersion { get; set; } |
||
35 | /// <summary> |
||
36 | /// The release notes link |
||
37 | /// </summary> |
||
38 | public string ReleaseNotesLink { get; set; } |
||
39 | /// <summary> |
||
40 | /// The signature of the Release Notes file |
||
41 | /// </summary> |
||
42 | public string ReleaseNotesSignature { get; set; } |
||
43 | /// <summary> |
||
44 | /// The embedded description |
||
45 | /// </summary> |
||
46 | public string Description { get; set; } |
||
47 | /// <summary> |
||
48 | /// The download link |
||
49 | /// </summary> |
||
50 | public string DownloadLink { get; set; } |
||
51 | /// <summary> |
||
52 | /// The signature of the download file |
||
53 | /// </summary> |
||
54 | public string DownloadSignature { get; set; } |
||
55 | /// <summary> |
||
56 | /// Date item was published |
||
57 | /// </summary> |
||
58 | public DateTime PublicationDate { get; set; } |
||
59 | /// <summary> |
||
60 | /// Whether the update was marked critical or not via sparkle:critical |
||
61 | /// </summary> |
||
62 | public bool IsCriticalUpdate { get; set; } |
||
63 | /// <summary> |
||
64 | /// Length of update set via sparkle:length |
||
65 | /// </summary> |
||
66 | public long UpdateSize { get; set; } |
||
67 | |||
68 | /// <summary> |
||
69 | /// Operating system that this update applies to |
||
70 | /// </summary> |
||
71 | public string OperatingSystemString { get; set; } |
||
72 | |||
73 | /// <summary> |
||
74 | /// True if this update is a windows update; false otherwise. |
||
75 | /// Acceptable OS strings are: "win" or "windows" (this is |
||
76 | /// checked with a case-insensitive check). If not specified, |
||
77 | /// assumed to be a Windows update. |
||
78 | /// </summary> |
||
79 | public bool IsWindowsUpdate |
||
80 | { |
||
81 | get |
||
82 | { |
||
83 | if (OperatingSystemString != null) |
||
84 | { |
||
85 | var lowercasedOS = OperatingSystemString.ToLower(); |
||
86 | if (lowercasedOS == "win" || lowercasedOS == "windows") |
||
87 | { |
||
88 | return true; |
||
89 | } |
||
90 | return false; |
||
91 | } |
||
92 | return true; |
||
93 | } |
||
94 | } |
||
95 | |||
96 | /// <summary> |
||
97 | /// True if this update is a macOS update; false otherwise. |
||
98 | /// Acceptable OS strings are: "mac", "osx", or "macos" (this is |
||
99 | /// checked with a case-insensitive check). If not specified, |
||
100 | /// assumed to be a Windows update. |
||
101 | /// </summary> |
||
102 | public bool IsMacOSUpdate |
||
103 | { |
||
104 | get |
||
105 | { |
||
106 | if (OperatingSystemString != null) |
||
107 | { |
||
108 | var lowercasedOS = OperatingSystemString.ToLower(); |
||
109 | if (lowercasedOS == "mac" || lowercasedOS == "macos" || lowercasedOS == "osx") |
||
110 | { |
||
111 | return true; |
||
112 | } |
||
113 | } |
||
114 | return false; |
||
115 | } |
||
116 | } |
||
117 | |||
118 | /// <summary> |
||
119 | /// True if this update is a macOS update; false otherwise. |
||
120 | /// Acceptable OS strings are: "linux" (this is |
||
121 | /// checked with a case-insensitive check). If not specified, |
||
122 | /// assumed to be a Linux update. |
||
123 | /// </summary> |
||
124 | public bool IsLinuxUpdate |
||
125 | { |
||
126 | get |
||
127 | { |
||
128 | if (OperatingSystemString != null) |
||
129 | { |
||
130 | var lowercasedOS = OperatingSystemString.ToLower(); |
||
131 | if (lowercasedOS == "linux") |
||
132 | { |
||
133 | return true; |
||
134 | } |
||
135 | } |
||
136 | return false; |
||
137 | } |
||
138 | } |
||
139 | |||
140 | /// <summary> |
||
141 | /// MIME type for file as specified in the closure tag. Defaults to "application/octet-stream". |
||
142 | /// </summary> |
||
143 | public string MIMEType { get; set; } |
||
144 | |||
145 | #region XML |
||
146 | |||
147 | private const string _itemNode = "item"; |
||
148 | private const string _titleNode = "title"; |
||
149 | private const string _enclosureNode = "enclosure"; |
||
150 | private const string _releaseNotesLinkNode = "releaseNotesLink"; |
||
151 | private const string _descriptionNode = "description"; |
||
152 | private const string _versionAttribute = "version"; |
||
153 | private const string _shortVersionAttribute = "shortVersionString"; |
||
154 | private const string _dsaSignatureAttribute = "dsaSignature"; |
||
155 | private const string _ed25519SignatureAttribute = "edSignature"; |
||
156 | private const string _signatureAttribute = "signature"; |
||
157 | private const string _criticalAttribute = "criticalUpdate"; |
||
158 | private const string _operatingSystemAttribute = "os"; |
||
159 | private const string _lengthAttribute = "length"; |
||
160 | private const string _typeAttribute = "type"; |
||
161 | private const string _urlAttribute = "url"; |
||
162 | private const string _pubDateNode = "pubDate"; |
||
163 | private const string _defaultOperatingSystem = "windows"; |
||
164 | private const string _defaultType = "application/octet-stream"; |
||
165 | |||
166 | /// <summary> |
||
167 | /// Parse item Xml Node to AppCastItem |
||
168 | /// </summary> |
||
169 | /// <param name="installedVersion">Currently installed version</param> |
||
170 | /// <param name="applicationName">Application name</param> |
||
171 | /// <param name="castUrl">The url of the appcast</param> |
||
172 | /// <param name="item">The item XML node</param> |
||
173 | /// <param name="logWriter">logwriter instance</param> |
||
174 | /// <returns>AppCastItem from Xml Node</returns> |
||
175 | public static AppCastItem Parse(string installedVersion, string applicationName, string castUrl, XElement item, ILogger logWriter) |
||
176 | { |
||
177 | |||
178 | var newAppCastItem = new AppCastItem() |
||
179 | { |
||
180 | AppVersionInstalled = installedVersion, |
||
181 | AppName = applicationName, |
||
182 | UpdateSize = 0, |
||
183 | IsCriticalUpdate = false, |
||
184 | OperatingSystemString = _defaultOperatingSystem, |
||
185 | MIMEType = _defaultType |
||
186 | }; |
||
187 | |||
188 | //title |
||
189 | newAppCastItem.Title = item.Element(_titleNode)?.Value ?? string.Empty; |
||
190 | |||
191 | //release notes |
||
192 | var releaseNotesElement = item.Element(XMLAppCast.SparkleNamespace + _releaseNotesLinkNode); |
||
193 | newAppCastItem.ReleaseNotesSignature = releaseNotesElement?.Attribute(XMLAppCast.SparkleNamespace + _signatureAttribute)?.Value ?? string.Empty; |
||
194 | if (newAppCastItem.ReleaseNotesSignature == string.Empty) |
||
195 | { |
||
196 | newAppCastItem.ReleaseNotesSignature = releaseNotesElement?.Attribute(XMLAppCast.SparkleNamespace + _dsaSignatureAttribute)?.Value ?? string.Empty; |
||
197 | } |
||
198 | if (newAppCastItem.ReleaseNotesSignature == string.Empty) |
||
199 | { |
||
200 | newAppCastItem.ReleaseNotesSignature = releaseNotesElement?.Attribute(XMLAppCast.SparkleNamespace + _ed25519SignatureAttribute)?.Value ?? string.Empty; |
||
201 | } |
||
202 | newAppCastItem.ReleaseNotesLink = releaseNotesElement?.Value.Trim() ?? string.Empty; |
||
203 | |||
204 | //description |
||
205 | newAppCastItem.Description = item.Element(_descriptionNode)?.Value.Trim() ?? string.Empty; |
||
206 | |||
207 | //enclosure |
||
208 | var enclosureElement = item.Element(_enclosureNode) ?? item.Element(XMLAppCast.SparkleNamespace + _enclosureNode); |
||
209 | |||
210 | newAppCastItem.Version = enclosureElement?.Attribute(XMLAppCast.SparkleNamespace + _versionAttribute)?.Value ?? string.Empty; |
||
211 | newAppCastItem.ShortVersion = enclosureElement?.Attribute(XMLAppCast.SparkleNamespace + _shortVersionAttribute)?.Value ?? string.Empty; |
||
212 | newAppCastItem.DownloadLink = enclosureElement?.Attribute(_urlAttribute)?.Value ?? string.Empty; |
||
213 | if (!string.IsNullOrEmpty(newAppCastItem.DownloadLink) && !newAppCastItem.DownloadLink.Contains("/")) |
||
214 | { |
||
215 | // Download link contains only the filename -> complete with _castUrl |
||
216 | newAppCastItem.DownloadLink = castUrl.Substring(0, castUrl.LastIndexOf('/') + 1) + newAppCastItem.DownloadLink; |
||
217 | } |
||
218 | |||
219 | newAppCastItem.DownloadSignature = enclosureElement?.Attribute(XMLAppCast.SparkleNamespace + _signatureAttribute)?.Value ?? string.Empty; |
||
220 | if (newAppCastItem.DownloadSignature == string.Empty) |
||
221 | { |
||
222 | newAppCastItem.DownloadSignature = enclosureElement?.Attribute(XMLAppCast.SparkleNamespace + _dsaSignatureAttribute)?.Value ?? string.Empty; |
||
223 | } |
||
224 | if (newAppCastItem.DownloadSignature == string.Empty) |
||
225 | { |
||
226 | newAppCastItem.DownloadSignature = enclosureElement?.Attribute(XMLAppCast.SparkleNamespace + _ed25519SignatureAttribute)?.Value ?? string.Empty; |
||
227 | } |
||
228 | string length = enclosureElement?.Attribute(_lengthAttribute)?.Value ?? string.Empty; |
||
229 | if (length != null) |
||
230 | { |
||
231 | if (long.TryParse(length, out var size)) |
||
232 | { |
||
233 | newAppCastItem.UpdateSize = size; |
||
234 | } |
||
235 | else |
||
236 | { |
||
237 | newAppCastItem.UpdateSize = 0; |
||
238 | } |
||
239 | } |
||
240 | bool isCritical = false; |
||
241 | string critical = enclosureElement?.Attribute(XMLAppCast.SparkleNamespace + _criticalAttribute)?.Value ?? string.Empty; |
||
242 | if (critical != null && critical == "true" || critical == "1") |
||
243 | { |
||
244 | isCritical = true; |
||
245 | } |
||
246 | newAppCastItem.IsCriticalUpdate = isCritical; |
||
247 | |||
248 | newAppCastItem.OperatingSystemString = enclosureElement?.Attribute(XMLAppCast.SparkleNamespace + _operatingSystemAttribute)?.Value ?? _defaultOperatingSystem; |
||
249 | |||
250 | newAppCastItem.MIMEType = enclosureElement?.Attribute(_typeAttribute)?.Value ?? _defaultType; |
||
251 | |||
252 | //pub date |
||
253 | var pubDateElement = item.Element(_pubDateNode); |
||
254 | if (pubDateElement != null) |
||
255 | { |
||
256 | // "ddd, dd MMM yyyy HH:mm:ss zzz" => Standard date format |
||
257 | // e.g. "Sat, 26 Oct 2019 22:05:11 -05:00" |
||
258 | // "ddd, dd MMM yyyy HH:mm:ss Z" => Check for MS AppCenter Sparkle date format which ends with GMT |
||
259 | // e.g. "Sat, 26 Oct 2019 22:05:11 GMT" |
||
260 | // "ddd, dd MMM yyyy HH:mm:ss" => Standard date format with no timezone (fallback) |
||
261 | // e.g. "Sat, 26 Oct 2019 22:05:11" |
||
262 | 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" }; |
||
263 | string dt = pubDateElement.Value.Trim(); |
||
264 | if (DateTime.TryParseExact(dt, formats, System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateValue)) |
||
265 | { |
||
266 | logWriter?.PrintMessage("While parsing app cast item, converted '{0}' to {1}.", dt, dateValue); |
||
267 | newAppCastItem.PublicationDate = dateValue; |
||
268 | } |
||
269 | else |
||
270 | { |
||
271 | logWriter?.PrintMessage("Cannot parse item's DateTime: {0}", dt); |
||
272 | } |
||
273 | } |
||
274 | |||
275 | return newAppCastItem; |
||
276 | } |
||
277 | |||
278 | /// <summary> |
||
279 | /// Create Xml node from this instance of AppCastItem |
||
280 | /// </summary> |
||
281 | /// <returns>An XML node</returns> |
||
282 | public XElement GetXElement() |
||
283 | { |
||
284 | var item = new XElement(_itemNode); |
||
285 | |||
286 | item.Add(new XElement(_titleNode) { Value = Title }); |
||
287 | |||
288 | if (!string.IsNullOrEmpty(ReleaseNotesLink)) |
||
289 | { |
||
290 | var releaseNotes = new XElement(XMLAppCast.SparkleNamespace + _releaseNotesLinkNode) { Value = ReleaseNotesLink }; |
||
291 | if (!string.IsNullOrEmpty(ReleaseNotesSignature)) |
||
292 | { |
||
293 | releaseNotes.Add(new XAttribute(XMLAppCast.SparkleNamespace + _signatureAttribute, ReleaseNotesSignature)); |
||
294 | } |
||
295 | item.Add(releaseNotes); |
||
296 | } |
||
297 | |||
298 | if (!string.IsNullOrEmpty(Description)) |
||
299 | { |
||
300 | item.Add(new XElement(_descriptionNode) { Value = Description }); |
||
301 | } |
||
302 | |||
303 | if (PublicationDate != DateTime.MinValue && PublicationDate != DateTime.MaxValue) |
||
304 | { |
||
305 | item.Add(new XElement(_pubDateNode) { Value = PublicationDate.ToString("ddd, dd MMM yyyy HH:mm:ss zzz", System.Globalization.CultureInfo.InvariantCulture) }); |
||
306 | } |
||
307 | |||
308 | if (!string.IsNullOrEmpty(DownloadLink)) |
||
309 | { |
||
310 | var enclosure = new XElement(_enclosureNode); |
||
311 | enclosure.Add(new XAttribute(_urlAttribute, DownloadLink)); |
||
312 | enclosure.Add(new XAttribute(XMLAppCast.SparkleNamespace + _versionAttribute, Version)); |
||
313 | |||
314 | if (!string.IsNullOrEmpty(ShortVersion)) |
||
315 | { |
||
316 | enclosure.Add(new XAttribute(XMLAppCast.SparkleNamespace + _shortVersionAttribute, ShortVersion)); |
||
317 | } |
||
318 | |||
319 | enclosure.Add(new XAttribute(_lengthAttribute, UpdateSize)); |
||
320 | enclosure.Add(new XAttribute(XMLAppCast.SparkleNamespace + _operatingSystemAttribute, OperatingSystemString ?? _defaultOperatingSystem)); |
||
321 | enclosure.Add(new XAttribute(_typeAttribute, MIMEType ?? _defaultType)); |
||
322 | |||
323 | if (!string.IsNullOrEmpty(DownloadSignature)) |
||
324 | { |
||
325 | enclosure.Add(new XAttribute(XMLAppCast.SparkleNamespace + _signatureAttribute, DownloadSignature)); |
||
326 | } |
||
327 | item.Add(enclosure); |
||
328 | } |
||
329 | return item; |
||
330 | } |
||
331 | |||
332 | #endregion |
||
333 | |||
334 | #region IComparable<AppCastItem> Members |
||
335 | |||
336 | /// <summary> |
||
337 | /// Compares this instance to another |
||
338 | /// </summary> |
||
339 | /// <param name="other">the other instance</param> |
||
340 | /// <returns>-1, 0, 1 if this instance is less than, equal to, or greater than the <paramref name="other"/></returns> |
||
341 | public int CompareTo(AppCastItem other) |
||
342 | { |
||
343 | if (!Version.Contains(".") || !other.Version.Contains(".")) |
||
344 | { |
||
345 | return 0; |
||
346 | } |
||
347 | Version v1 = new Version(Version); |
||
348 | Version v2 = new Version(other.Version); |
||
349 | return v1.CompareTo(v2); |
||
350 | } |
||
351 | |||
352 | /// <summary> |
||
353 | /// Equality check to another instance |
||
354 | /// </summary> |
||
355 | /// <param name="obj">the instance to compare to</param> |
||
356 | /// <returns></returns> |
||
357 | public override bool Equals(object obj) |
||
358 | { |
||
359 | if (!(obj is AppCastItem item)) |
||
360 | { |
||
361 | return false; |
||
362 | } |
||
363 | if (ReferenceEquals(this, item)) |
||
364 | { |
||
365 | return true; |
||
366 | } |
||
367 | return AppName.Equals(item.AppName) && CompareTo(item) == 0; |
||
368 | } |
||
369 | |||
370 | /// <summary> |
||
371 | /// Derive hashcode from immutable variables |
||
372 | /// </summary> |
||
373 | /// <returns></returns> |
||
374 | public override int GetHashCode() |
||
375 | { |
||
376 | return Version.GetHashCode() * 17 + AppName.GetHashCode(); |
||
377 | } |
||
378 | |||
379 | /// <summary> |
||
380 | /// Check equality of two AppCastItem instances |
||
381 | /// </summary> |
||
382 | /// <param name="left">AppCastItem to compare</param> |
||
383 | /// <param name="right">AppCastItem to compare</param> |
||
384 | /// <returns>True if items are the same</returns> |
||
385 | public static bool operator ==(AppCastItem left, AppCastItem right) |
||
386 | { |
||
387 | if (left is null) |
||
388 | { |
||
389 | return right is null; |
||
390 | } |
||
391 | return left.Equals(right); |
||
392 | } |
||
393 | |||
394 | /// <summary> |
||
395 | /// Check if two AppCastItem instances are different |
||
396 | /// </summary> |
||
397 | /// <param name="left">AppCastItem to compare</param> |
||
398 | /// <param name="right">AppCastItem to compare</param> |
||
399 | /// <returns>True if items are different</returns> |
||
400 | public static bool operator !=(AppCastItem left, AppCastItem right) |
||
401 | { |
||
402 | return !(left == right); |
||
403 | } |
||
404 | |||
405 | /// <summary> |
||
406 | /// Less than comparison of version between two AppCastItem instances |
||
407 | /// </summary> |
||
408 | /// <param name="left">AppCastItem to compare</param> |
||
409 | /// <param name="right">AppCastItem to compare</param> |
||
410 | /// <returns>True if left version is less than right version</returns> |
||
411 | public static bool operator <(AppCastItem left, AppCastItem right) |
||
412 | { |
||
413 | return left is null ? !(right is null) : left.CompareTo(right) < 0; |
||
414 | } |
||
415 | |||
416 | /// <summary> |
||
417 | /// Less than or equal to comparison of version between two AppCastItem instances |
||
418 | /// </summary> |
||
419 | /// <param name="left">AppCastItem to compare</param> |
||
420 | /// <param name="right">AppCastItem to compare</param> |
||
421 | /// <returns>True if left version is less than or equal to right version</returns> |
||
422 | public static bool operator <=(AppCastItem left, AppCastItem right) |
||
423 | { |
||
424 | return left is null || left.CompareTo(right) <= 0; |
||
425 | } |
||
426 | |||
427 | /// <summary> |
||
428 | /// Greater than comparison of version between two AppCastItem instances |
||
429 | /// </summary> |
||
430 | /// <param name="left">AppCastItem to compare</param> |
||
431 | /// <param name="right">AppCastItem to compare</param> |
||
432 | /// <returns>True if left version is greater than right version</returns> |
||
433 | public static bool operator >(AppCastItem left, AppCastItem right) |
||
434 | { |
||
435 | return !(left is null) && left.CompareTo(right) > 0; |
||
436 | } |
||
437 | |||
438 | /// <summary> |
||
439 | /// Greater than or equal to comparison of version between two AppCastItem instances |
||
440 | /// </summary> |
||
441 | /// <param name="left">AppCastItem to compare</param> |
||
442 | /// <param name="right">AppCastItem to compare</param> |
||
443 | /// <returns>True if left version is greater than or equal to right version</returns> |
||
444 | public static bool operator >=(AppCastItem left, AppCastItem right) |
||
445 | { |
||
446 | return left is null ? right is null : left.CompareTo(right) >= 0; |
||
447 | } |
||
448 | |||
449 | #endregion |
||
450 | } |
||
451 | } |