프로젝트

일반

사용자정보

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

markus / MarkusAutoUpdate / src / NetSparkle.Tools.AppCastGenerator / Program.cs @ 77cdac33

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

1 d8f5045e taeseongkim
using NetSparkleUpdater;
2
using System;
3
using System.Diagnostics;
4
using System.IO;
5
using System.Collections.Generic;
6
using System.Xml;
7
using NetSparkleUpdater.AppCastHandlers;
8
using System.Text;
9
using CommandLine;
10
using System.Text.RegularExpressions;
11
using System.Linq;
12
using Console = Colorful.Console;
13
using System.Drawing;
14
using NetSparkleUpdater.AppCastGenerator;
15
using System.Web;
16
using System.Net.Http.Headers;
17
using System.Threading;
18
using System.Net;
19
using System.IO.Compression;
20
21
namespace NetSparkleUpdater.Tools.AppCastGenerator
22
{
23
    internal class Program
24
    {
25
        public class Options
26
        {
27
            [Option('a', "appcast-output-directory", Required = false, HelpText = "Directory to write appcast.xml")]
28
            public string OutputDirectory { get; set; }
29
30
            [Option('e', "ext", SetName = "local", Required = false, HelpText = "Search for file extensions.", Default = "exe")]
31
            public string Extension { get; set; }
32
33
            [Option('b', "binaries", SetName = "local", Required = false, HelpText = "Directory containing binaries.", Default = ".")]
34
            public string SourceBinaryDirectory { get; set; }
35
36
            //[Option('g', "github-atom-feed", SetName = "github", Required = false, HelpText = "Generate from Github release atom feed (signatures not supported yet)")]
37
            //public string GithubAtomFeed { get; set; }
38
39
            [Option('f', "file-extract-version", SetName = "local", Required = false, HelpText = "Determine the version from the file name", Default = false)]
40
            public bool FileExtractVersion { get; set; }
41
42
            [Option('o', "os", Required = false, HelpText = "Operating System (windows,macos,linux)", Default = "windows")]
43
            public string OperatingSystem { get; set; }
44
45
            [Option('u', "base-url", SetName = "local", Required = false, HelpText = "Base URL for downloads", Default = null)]
46
            public Uri BaseUrl { get; set; }
47
48
            [Option('l', "change-log-url", SetName = "local", Required = false, HelpText = "Base URL to the location for your changelog files on some server for downloading", Default = "")]
49
            public string ChangeLogUrl { get; set; }
50
51
            [Option('p', "change-log-path", SetName = "local", Required = false, HelpText = "File path to Markdown changelog files (expected extension: .md; version must match AssemblyVersion, e.g. MyApp 1.0.0.md).", Default = "")]
52
            public string ChangeLogPath { get; set; }
53
54
            [Option('n', "product-name", Required = false, HelpText = "Product Name", Default = "Application")]
55
            public string ProductName { get; set; }
56
57
            [Option('x', "url-prefix-version", SetName = "local", Required = false, HelpText = "Add the version as a prefix to the download url")]
58
            public bool PrefixVersion { get; set; }
59
60
            [Option('v', "Fixed version", SetName = "local", Required = false, HelpText = "Fixed Version (All files) Option",Default =null)]
61
            public string FixedVersion { get; set; }
62
63
            [Option("key-path", SetName = "local", Required = false, HelpText = "Path to NetSparkle_Ed25519.priv and NetSparkle_Ed25519.pub files")]
64
            public string PathToKeyFiles { get; set; }
65
66
67
            #region Key Generation
68
69 77cdac33 taeseongkim
            [Option("generate-keys", SetName = "local", Required = false, HelpText = "Generate keys")]
70 d8f5045e taeseongkim
            public bool GenerateKeys { get; set; }
71
72
            [Option("force", SetName = "keys", Required = false, HelpText = "Force regeneration of keys")]
73
            public bool ForceRegeneration { get; set; }
74
75
            [Option("export", SetName = "keys", Required = false, HelpText = "Export keys")]
76
            public bool Export { get; set; }
77
78
            #endregion
79
80
81
            #region Getting Signatures for Binaries
82
83
            [Option("generate-signature", SetName = "signing", Required = false, HelpText = "Generate signature from binary")]
84
            public string BinaryToSign { get; set; }
85
86
            #endregion
87
88
            #region Verifying Binary Signatures
89
90
            [Option("verify", SetName = "verify", Required = false, HelpText = "Binary to verify")]
91
            public string BinaryToVerify { get; set; }
92
93
            [Option("signature", SetName = "verify", Required = false, HelpText = "Signature")]
94
            public string Signature { get; set; }
95
96
            #endregion
97
98
        }
99
100
101
        private static string[] _operatingSystems = new string[] { "windows", "mac", "linux" };
102
        private static SignatureManager _signatureManager = new SignatureManager();
103
104
        static void Main(string[] args)
105
        {
106
            Parser.Default.ParseArguments<Options>(args)
107
                .WithParsed(Run)
108
                .WithNotParsed(HandleParseError);
109
        }
110
        static void Run(Options opts)
111
        {
112
            /*
113
            if (opts.GithubAtomFeed != null)
114
            {
115
                GenerateFromAtom(opts);
116
                return;
117
            }*/
118
119
            if (!string.IsNullOrWhiteSpace(opts.PathToKeyFiles))
120
            {
121
                _signatureManager.SetStorageDirectory(opts.PathToKeyFiles);
122
            }
123
124
            if (opts.Export)
125
            {
126
                Console.WriteLine("Private Key:");
127
                Console.WriteLine(Convert.ToBase64String(_signatureManager.GetPrivateKey()));
128
                Console.WriteLine("Public Key:");
129
                Console.WriteLine(Convert.ToBase64String(_signatureManager.GetPublicKey()));
130
                return;
131
            }
132
133
            if (opts.GenerateKeys)
134
            {
135
                var didSucceed = _signatureManager.Generate(opts.ForceRegeneration);
136
                if (didSucceed)
137
                {
138
                    Console.WriteLine("Keys successfully generated", Color.Green);
139
                }
140
                else
141
                {
142
                    Console.WriteLine("Keys failed to generate", Color.Red);
143
                }
144
                return;
145
            }
146
147
            if (opts.BinaryToSign != null)
148
            {
149
                var signature = _signatureManager.GetSignatureForFile(new FileInfo(opts.BinaryToSign));
150
151
                Console.WriteLine($"Signature: {signature}", Color.Green);
152
153
                return;
154
            }
155
156
            if (opts.BinaryToVerify != null)
157
            {
158
                var result = _signatureManager.VerifySignature(new FileInfo(opts.BinaryToVerify), opts.Signature);
159
160
                if (result)
161
                {
162
                    Console.WriteLine($"Signature valid", Color.Green);
163
                } 
164
                else
165
                {
166
                    Console.WriteLine($"Signature invalid", Color.Red);
167
                }
168
169
                return;
170
            }
171
172
173
            var search = $"*.{opts.Extension}";
174
175
            if (opts.SourceBinaryDirectory == ".")
176
            {
177
                opts.SourceBinaryDirectory = Environment.CurrentDirectory;
178
            }
179
180
            var binaries = Directory.GetFiles(opts.SourceBinaryDirectory, search,SearchOption.AllDirectories);
181
182
            if (binaries.Length == 0)
183
            {
184
                Console.WriteLine($"No files founds matching {search} in {opts.SourceBinaryDirectory}", Color.Yellow);
185
                Environment.Exit(1);
186
            }
187
188
            if (!_operatingSystems.Any(opts.OperatingSystem.Contains))
189
            {
190
                Console.WriteLine($"Invalid operating system: {opts.OperatingSystem}", Color.Red);
191
                Console.WriteLine($"Valid options are : windows, macos or linux");
192
                Environment.Exit(1);
193
            }
194
195
            if (string.IsNullOrEmpty(opts.OutputDirectory))
196
            {
197
                opts.OutputDirectory = opts.SourceBinaryDirectory;
198
            }
199
200
            Console.WriteLine("");
201
            Console.WriteLine($"Operating System: {opts.OperatingSystem}", Color.Blue);
202
            Console.WriteLine($"Searching: {opts.SourceBinaryDirectory}", Color.Blue);
203
            Console.WriteLine($"Found {binaries.Count()} {opts.Extension} files(s)", Color.Blue);
204
            Console.WriteLine("");
205
206
            try
207
            {
208
209
                var productName = opts.ProductName;
210
211
                var items = new List<AppCastItem>();
212
213
                var usesChangelogs = !string.IsNullOrWhiteSpace(opts.ChangeLogPath) && Directory.Exists(opts.ChangeLogPath);
214
215
                if(string.IsNullOrWhiteSpace(opts.FixedVersion))
216
                {
217
                    Console.WriteLine("FixedVersion Null. setting -v x.x.x", Color.Red);
218
                    Environment.Exit(1);
219
                }
220
221
                string tempfile = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
222
            
223
                CreateZip(opts.SourceBinaryDirectory, tempfile);
224
225
                var fileInfo = new FileInfo(tempfile);
226
227
                var fileVersion = FileVersion(fileInfo,opts.FixedVersion,opts.FileExtractVersion);
228
229
                var productVersion = fileVersion;
230
                var itemTitle = string.IsNullOrWhiteSpace(productName) ? productVersion : productName + " " + productVersion;
231
232
                var remoteUpdateFileName = $"{productName}_{opts.FixedVersion}.zip";
233
234
                var remoteUpdateFilePath = $"{(opts.PrefixVersion ? $"{fileVersion}/" :"")}{HttpUtility.UrlEncode(remoteUpdateFileName)}";
235
236
                Copy(tempfile, Path.Combine(opts.OutputDirectory, remoteUpdateFilePath.Replace('/', '\\')));
237
238
                var remoteUpdateFile = $"{opts.BaseUrl}{remoteUpdateFilePath}";
239
240
                // changelog stuff
241
                var changelogFileName = productVersion + ".md";
242
                var changelogPath = Path.Combine(opts.ChangeLogPath, changelogFileName);
243
                var hasChangelogForFile = usesChangelogs && File.Exists(changelogPath);
244
                var changelogSignature = "";
245
246
                if (hasChangelogForFile)
247
                {
248
                    changelogSignature = _signatureManager.GetSignatureForFile(changelogPath);
249
                }
250
251
                //
252
                var item = new AppCastItem()
253
                {
254
                    Title = itemTitle,
255
                    DownloadLink = remoteUpdateFile,
256
                    Version = productVersion,
257
                    ShortVersion = productVersion.Substring(0, productVersion.LastIndexOf('.')),
258
                    PublicationDate = fileInfo.CreationTime,
259
                    UpdateSize = fileInfo.Length,
260
                    Description = "",
261
                    DownloadSignature = _signatureManager.KeysExist() ? _signatureManager.GetSignatureForFile(fileInfo) : null,
262
                    OperatingSystemString = opts.OperatingSystem,
263
                    MIMEType = MimeTypes.GetMimeType(fileInfo.Name)
264
                };
265
266
                if (hasChangelogForFile)
267
                {
268
                    if (!string.IsNullOrWhiteSpace(opts.ChangeLogUrl))
269
                    {
270
                        item.ReleaseNotesSignature = changelogSignature;
271
                        item.ReleaseNotesLink = opts.ChangeLogUrl + changelogFileName;
272
                    }
273
                    else
274
                    {
275
                        item.Description = File.ReadAllText(changelogPath);
276
                    }
277
                }
278
279
                items.Add(item);
280
281
                // appcast  생성 폴더에 업데이트가 필요한 파일을 복사
282
                
283
             
284
285
                var appcastXmlDocument = XMLAppCast.GenerateAppCastXml(items, productName);
286
287
                var appcastFileName = Path.Combine(opts.OutputDirectory, "appcast.xml");
288
289
                var dirName = Path.GetDirectoryName(appcastFileName);
290
291
                if (!Directory.Exists(dirName))
292
                {
293
                    Console.WriteLine("Creating {0}", dirName);
294
                    Directory.CreateDirectory(dirName);
295
                }
296
                else
297
                {
298
                    if(File.Exists(appcastFileName))
299
                    {
300
                        File.Copy(appcastFileName, $"{ appcastFileName}{ opts.FixedVersion}", true);
301
                    }
302
                }
303
304
                Console.WriteLine("Writing appcast to {0}", appcastFileName);
305
306
                using (var w = XmlWriter.Create(appcastFileName, new XmlWriterSettings { NewLineChars = "\n", Encoding = new UTF8Encoding(false) }))
307
                {
308
                    appcastXmlDocument.Save(w);
309
                }
310
311
                if (_signatureManager.KeysExist())
312
                {
313
                    var appcastFile = new FileInfo(appcastFileName);
314
                    var signatureFile = appcastFileName + ".signature";
315
316
                    if (File.Exists(signatureFile))
317
                    {
318
                        File.Copy(signatureFile, $"{ signatureFile}{ opts.FixedVersion}",true);
319
                    }
320
321
                    var signature = _signatureManager.GetSignatureForFile(appcastFile);
322
323
                    var result = _signatureManager.VerifySignature(appcastFile, signature);
324
325
                    if (result)
326
                    {
327
                        File.WriteAllText(signatureFile, signature);
328
                        Console.WriteLine($"Wrote {signatureFile}", Color.Green);
329
                    }
330
                    else
331
                    {
332
                        Console.WriteLine($"Failed to verify {signatureFile}", Color.Red);
333
                    }
334
                } 
335
                else
336
                {
337
                    Console.WriteLine("Skipped generating signature.  Generate keys with --generate-keys", Color.Red);
338
                    Environment.Exit(1);
339
                }
340
            }
341
            catch (Exception e)
342
            {
343
                Console.WriteLine(e.Message);
344
                Console.WriteLine();
345
                Environment.Exit(1);
346
            }
347
        }
348
349
        private static string[] CreateZip(string sourceBinaryDirectory,string targetFile)
350
        {
351
            ZipFile.CreateFromDirectory(sourceBinaryDirectory, targetFile);
352
353
            return new[] { targetFile };
354
        }
355
356
        private static string FileVersion(FileInfo fileInfo,string fixedVersion,bool fileExtractVersion)
357
        {
358
            string version = "";
359
360
            if (!string.IsNullOrWhiteSpace(fixedVersion))
361
            {
362
                version = fixedVersion;
363
            }
364
            else
365
            {
366
                if (fileExtractVersion)
367
                {
368
                    version = GetVersionFromName(fileInfo);
369
                }
370
                else
371
                {
372
                    version = GetVersionFromAssembly(fileInfo);
373
374
                }
375
            }
376
377
            return version;
378
        }
379
380
381
        static private void Copy(string sourceFileName, string destFileName)
382
        {
383
            try
384
            {
385
                var dirName = Path.GetDirectoryName(destFileName);
386
387
                if (!Directory.Exists(dirName))
388
                {
389
                    Console.WriteLine("Creating {0}", dirName);
390
                    Directory.CreateDirectory(dirName);
391
                }
392
393
                File.Copy(sourceFileName, destFileName,true);
394
            }
395
            catch (Exception e)
396
            {
397
                Console.WriteLine(e.Message);
398
                Console.WriteLine();
399
                Environment.Exit(1);
400
            }
401
        }
402
403
        static void HandleParseError(IEnumerable<Error> errs)
404
        {
405
            errs.Output();
406
        }
407
408
        static string GetVersionFromName(FileInfo fileInfo)
409
        {
410
            var regexPattern = @"\d+(\.\d+)+";
411
            var regex = new Regex(regexPattern);
412
413
            var match = regex.Match(fileInfo.FullName);
414
415
            return match.Captures[match.Captures.Count - 1].Value; // get the numbers at the end of the string incase the app is something like 1.0application1.0.0.dmg
416
        }
417
418
        static string GetVersionFromAssembly(FileInfo fileInfo)
419
        {
420
            return FileVersionInfo.GetVersionInfo(fileInfo.FullName).ProductVersion;
421
        }
422
423
424
    }
425
}
클립보드 이미지 추가 (최대 크기: 500 MB)