프로젝트

일반

사용자정보

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

markus / AutoUpdater / NetSparkle.Tools.AppCastGenerator / Program.cs @ master

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

1
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

    
19
namespace NetSparkleUpdater.Tools.AppCastGenerator
20
{
21
    internal class Program
22
    {
23
        public class Options
24
        {
25
            [Option('a', "appcast-output-directory", Required = false, HelpText = "Directory to write appcast.xml")]
26
            public string OutputDirectory { get; set; }
27

    
28
            [Option('e', "ext", SetName = "local", Required = false, HelpText = "Search for file extensions.", Default = "exe")]
29
            public string Extension { get; set; }
30

    
31
            [Option('b', "binaries", SetName = "local", Required = false, HelpText = "Directory containing binaries.", Default = ".")]
32
            public string SourceBinaryDirectory { get; set; }
33

    
34
            //[Option('g', "github-atom-feed", SetName = "github", Required = false, HelpText = "Generate from Github release atom feed (signatures not supported yet)")]
35
            //public string GithubAtomFeed { get; set; }
36

    
37
            [Option('f', "file-extract-version", SetName = "local", Required = false, HelpText = "Determine the version from the file name", Default = false)]
38
            public bool FileExtractVersion { get; set; }
39

    
40
            [Option('o', "os", Required = false, HelpText = "Operating System (windows,macos,linux)", Default = "windows")]
41
            public string OperatingSystem { get; set; }
42

    
43
            [Option('u', "base-url", SetName = "local", Required = false, HelpText = "Base URL for downloads", Default = null)]
44
            public Uri BaseUrl { get; set; }
45

    
46
            [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 = "")]
47
            public string ChangeLogUrl { get; set; }
48

    
49
            [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 = "")]
50
            public string ChangeLogPath { get; set; }
51

    
52
            [Option('n', "product-name", Required = false, HelpText = "Product Name", Default = "Application")]
53
            public string ProductName { get; set; }
54

    
55
            [Option('x', "url-prefix-version", SetName = "local", Required = false, HelpText = "Add the version as a prefix to the download url")]
56
            public bool PrefixVersion { get; set; }
57

    
58
            [Option('v', "Fixed version", SetName = "local", Required = false, HelpText = "Fixed Version (All files) Option",Default =null)]
59
            public string FixedVersion { get; set; }
60

    
61
            [Option("key-path", SetName = "local", Required = false, HelpText = "Path to NetSparkle_Ed25519.priv and NetSparkle_Ed25519.pub files")]
62
            public string PathToKeyFiles { get; set; }
63

    
64

    
65
            #region Key Generation
66

    
67
            [Option("generate-keys", SetName = "keys", Required = false, HelpText = "Generate keys")]
68
            public bool GenerateKeys { get; set; }
69

    
70
            [Option("force", SetName = "keys", Required = false, HelpText = "Force regeneration of keys")]
71
            public bool ForceRegeneration { get; set; }
72

    
73
            [Option("export", SetName = "keys", Required = false, HelpText = "Export keys")]
74
            public bool Export { get; set; }
75

    
76
            #endregion
77

    
78

    
79
            #region Getting Signatures for Binaries
80

    
81
            [Option("generate-signature", SetName = "signing", Required = false, HelpText = "Generate signature from binary")]
82
            public string BinaryToSign { get; set; }
83

    
84
            #endregion
85

    
86
            #region Verifying Binary Signatures
87

    
88
            [Option("verify", SetName = "verify", Required = false, HelpText = "Binary to verify")]
89
            public string BinaryToVerify { get; set; }
90

    
91
            [Option("signature", SetName = "verify", Required = false, HelpText = "Signature")]
92
            public string Signature { get; set; }
93

    
94
            #endregion
95

    
96
        }
97

    
98

    
99
        private static string[] _operatingSystems = new string[] { "windows", "mac", "linux" };
100
        private static SignatureManager _signatureManager = new SignatureManager();
101

    
102
        static void Main(string[] args)
103
        {
104
            Parser.Default.ParseArguments<Options>(args)
105
                .WithParsed(Run)
106
                .WithNotParsed(HandleParseError);
107
        }
108
        static void Run(Options opts)
109
        {
110
            /*
111
            if (opts.GithubAtomFeed != null)
112
            {
113
                GenerateFromAtom(opts);
114
                return;
115
            }*/
116

    
117
            if (!string.IsNullOrWhiteSpace(opts.PathToKeyFiles))
118
            {
119
                _signatureManager.SetStorageDirectory(opts.PathToKeyFiles);
120
            }
121

    
122
            if (opts.Export)
123
            {
124
                Console.WriteLine("Private Key:");
125
                Console.WriteLine(Convert.ToBase64String(_signatureManager.GetPrivateKey()));
126
                Console.WriteLine("Public Key:");
127
                Console.WriteLine(Convert.ToBase64String(_signatureManager.GetPublicKey()));
128
                return;
129
            }
130

    
131
            if (opts.GenerateKeys)
132
            {
133
                var didSucceed = _signatureManager.Generate(opts.ForceRegeneration);
134
                if (didSucceed)
135
                {
136
                    Console.WriteLine("Keys successfully generated", Color.Green);
137
                }
138
                else
139
                {
140
                    Console.WriteLine("Keys failed to generate", Color.Red);
141
                }
142
                return;
143
            }
144

    
145
            if (opts.BinaryToSign != null)
146
            {
147
                var signature = _signatureManager.GetSignatureForFile(new FileInfo(opts.BinaryToSign));
148

    
149
                Console.WriteLine($"Signature: {signature}", Color.Green);
150

    
151
                return;
152
            }
153

    
154
            if (opts.BinaryToVerify != null)
155
            {
156
                var result = _signatureManager.VerifySignature(new FileInfo(opts.BinaryToVerify), opts.Signature);
157

    
158
                if (result)
159
                {
160
                    Console.WriteLine($"Signature valid", Color.Green);
161
                } 
162
                else
163
                {
164
                    Console.WriteLine($"Signature invalid", Color.Red);
165
                }
166

    
167
                return;
168
            }
169

    
170

    
171
            var search = $"*.{opts.Extension}";
172

    
173
            if (opts.SourceBinaryDirectory == ".")
174
            {
175
                opts.SourceBinaryDirectory = Environment.CurrentDirectory;
176
            }
177

    
178
            var binaries = Directory.GetFiles(opts.SourceBinaryDirectory, search,SearchOption.AllDirectories);
179

    
180
            if (binaries.Length == 0)
181
            {
182
                Console.WriteLine($"No files founds matching {search} in {opts.SourceBinaryDirectory}", Color.Yellow);
183
                Environment.Exit(1);
184
            }
185

    
186
            if (!_operatingSystems.Any(opts.OperatingSystem.Contains))
187
            {
188
                Console.WriteLine($"Invalid operating system: {opts.OperatingSystem}", Color.Red);
189
                Console.WriteLine($"Valid options are : windows, macos or linux");
190
                Environment.Exit(1);
191
            }
192

    
193
            if (string.IsNullOrEmpty(opts.OutputDirectory))
194
            {
195
                opts.OutputDirectory = opts.SourceBinaryDirectory;
196
            }
197

    
198
            Console.WriteLine("");
199
            Console.WriteLine($"Operating System: {opts.OperatingSystem}", Color.Blue);
200
            Console.WriteLine($"Searching: {opts.SourceBinaryDirectory}", Color.Blue);
201
            Console.WriteLine($"Found {binaries.Count()} {opts.Extension} files(s)", Color.Blue);
202
            Console.WriteLine("");
203

    
204
            try
205
            {
206

    
207
                var productName = opts.ProductName;
208

    
209
                var items = new List<AppCastItem>();
210

    
211
                var usesChangelogs = !string.IsNullOrWhiteSpace(opts.ChangeLogPath) && Directory.Exists(opts.ChangeLogPath);
212

    
213
                foreach (var binary in binaries)
214
                {
215
                    string version = null;
216
                    var fileInfo = new FileInfo(binary);
217

    
218
                    if (!string.IsNullOrWhiteSpace(opts.FixedVersion))
219
                    {
220
                        version = opts.FixedVersion;
221
                    }
222
                    else
223
                    {
224
                        if (opts.FileExtractVersion)
225
                        {
226
                            version = GetVersionFromName(fileInfo);
227
                        }
228
                        else
229
                        {
230
                            version = GetVersionFromAssembly(fileInfo);
231

    
232
                        }
233

    
234
                        if (version == null)
235
                        {
236
                            Console.WriteLine($"Unable to determine version of binary {fileInfo.Name}, try -f parameter");
237
                            Environment.Exit(1);
238
                        }
239
                    }
240

    
241
                    var productVersion = version;
242
                    var itemTitle = string.IsNullOrWhiteSpace(productName) ? productVersion : productName + " " + productVersion;
243
                    
244
                    var remoteUpdateFilePath = $"{(opts.PrefixVersion ? $"{version}/" :"")}{HttpUtility.UrlEncode(fileInfo.Name)}";
245

    
246
                    var remoteUpdateFile = $"{opts.BaseUrl}/{remoteUpdateFilePath}";
247

    
248
                    // changelog stuff
249
                    var changelogFileName = productVersion + ".md";
250
                    var changelogPath = Path.Combine(opts.ChangeLogPath, changelogFileName);
251
                    var hasChangelogForFile = usesChangelogs && File.Exists(changelogPath);
252
                    var changelogSignature = "";
253

    
254
                    if (hasChangelogForFile)
255
                    {
256
                        changelogSignature = _signatureManager.GetSignatureForFile(changelogPath);
257
                    }
258

    
259
                    //
260
                    var item = new AppCastItem()
261
                    {
262
                        Title = itemTitle,
263
                        DownloadLink = remoteUpdateFile,
264
                        Version = productVersion,
265
                        ShortVersion = productVersion.Substring(0, productVersion.LastIndexOf('.')),
266
                        PublicationDate = fileInfo.CreationTime,
267
                        UpdateSize = fileInfo.Length,
268
                        Description = "",
269
                        DownloadSignature = _signatureManager.KeysExist() ? _signatureManager.GetSignatureForFile(fileInfo) : null,
270
                        OperatingSystemString = opts.OperatingSystem,
271
                        MIMEType = MimeTypes.GetMimeType(fileInfo.Name)
272
                    };
273

    
274
                    if (hasChangelogForFile)
275
                    {
276
                        if (!string.IsNullOrWhiteSpace(opts.ChangeLogUrl))
277
                        {
278
                            item.ReleaseNotesSignature = changelogSignature;
279
                            item.ReleaseNotesLink = opts.ChangeLogUrl + changelogFileName;
280
                        }
281
                        else
282
                        {
283
                            item.Description = File.ReadAllText(changelogPath);
284
                        }
285
                    }
286

    
287
                    items.Add(item);
288

    
289
                    // appcast  생성 폴더에 업데이트가 필요한 파일을 복사
290
                    Copy(fileInfo.FullName, Path.Combine(opts.OutputDirectory, remoteUpdateFilePath));
291
                }
292

    
293
                var appcastXmlDocument = XMLAppCast.GenerateAppCastXml(items, productName);
294

    
295
                var appcastFileName = Path.Combine(opts.OutputDirectory, "appcast.xml");
296

    
297
                var dirName = Path.GetDirectoryName(appcastFileName);
298

    
299
                if (!Directory.Exists(dirName))
300
                {
301
                    Console.WriteLine("Creating {0}", dirName);
302
                    Directory.CreateDirectory(dirName);
303
                }
304

    
305
                Console.WriteLine("Writing appcast to {0}", appcastFileName);
306

    
307
                using (var w = XmlWriter.Create(appcastFileName, new XmlWriterSettings { NewLineChars = "\n", Encoding = new UTF8Encoding(false) }))
308
                {
309
                    appcastXmlDocument.Save(w);
310
                }
311

    
312
                if (_signatureManager.KeysExist())
313
                {
314
                    var appcastFile = new FileInfo(appcastFileName);
315
                    var signatureFile = appcastFileName + ".signature";
316
                    var signature = _signatureManager.GetSignatureForFile(appcastFile);
317

    
318
                    var result = _signatureManager.VerifySignature(appcastFile, signature);
319

    
320
                    if (result)
321
                    {
322
                        
323
                        File.WriteAllText(signatureFile, signature);
324
                        Console.WriteLine($"Wrote {signatureFile}", Color.Green);
325
                    }
326
                    else
327
                    {
328
                        Console.WriteLine($"Failed to verify {signatureFile}", Color.Red);
329
                    }
330

    
331
                } 
332
                else
333
                {
334
                    Console.WriteLine("Skipped generating signature.  Generate keys with --generate-keys", Color.Red);
335
                    Environment.Exit(1);
336
                }
337
            }
338
            catch (Exception e)
339
            {
340
                Console.WriteLine(e.Message);
341
                Console.WriteLine();
342
                Environment.Exit(1);
343
            }
344
        }
345

    
346
        static private void Copy(string sourceFileName, string destFileName)
347
        {
348
            try
349
            {
350
                var dirName = Path.GetDirectoryName(destFileName);
351

    
352
                if (!Directory.Exists(dirName))
353
                {
354
                    Console.WriteLine("Creating {0}", dirName);
355
                    Directory.CreateDirectory(dirName);
356
                }
357

    
358
                File.Copy(sourceFileName, destFileName);
359
            }
360
            catch (Exception e)
361
            {
362
                Console.WriteLine(e.Message);
363
                Console.WriteLine();
364
                Environment.Exit(1);
365
            }
366
        }
367

    
368
        static void HandleParseError(IEnumerable<Error> errs)
369
        {
370
            errs.Output();
371
        }
372

    
373
        static string GetVersionFromName(FileInfo fileInfo)
374
        {
375
            var regexPattern = @"\d+(\.\d+)+";
376
            var regex = new Regex(regexPattern);
377

    
378
            var match = regex.Match(fileInfo.FullName);
379

    
380
            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
381
        }
382

    
383
        static string GetVersionFromAssembly(FileInfo fileInfo)
384
        {
385
            return FileVersionInfo.GetVersionInfo(fileInfo.FullName).ProductVersion;
386
        }
387

    
388

    
389
    }
390
}
클립보드 이미지 추가 (최대 크기: 500 MB)