프로젝트

일반

사용자정보

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

markus / MarkusAutoUpdate / src / NetSparkle / AppCastItem.cs @ d8f5045e

이력 | 보기 | 이력해설 | 다운로드 (18.4 KB)

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