1 /++ dub.sdl:
2 dependency "requests" version="*"
3 +/
4 
5 module update_downloader;
6 
7 import requests;
8 
9 import fs = std.file;
10 import std.algorithm;
11 import std.array;
12 import std.digest;
13 import std.digest.sha;
14 import std.exception;
15 import std.json;
16 import std.process;
17 import std.stdio;
18 import std.string;
19 import std.traits;
20 
21 enum ArchSuffix
22 {
23 	linux_x86,
24 	osx_x86,
25 	osx_aarch64,
26 	windows_x86,
27 }
28 
29 void main()
30 {
31 	auto rq = Request();
32 	rq.addHeaders(["User-Agent": "[update_downloader.d] https://github.com/rorm-orm/dorm"]);
33 
34 	auto output = File("download-map.txt", "w");
35 	output.write("# rorm-cli - ");
36 	putDownloads(rq, "https://api.github.com/repos/rorm-orm/rorm-cli/releases", output);
37 	output.writeln();
38 	output.write("# rorm-lib - ");
39 	putDownloads(rq, "https://api.github.com/repos/rorm-orm/rorm-lib/releases", output);
40 }
41 
42 void putDownloads(ref Request rq, string url, ref File output)
43 {
44 	auto rormReleases = rq.get(url).responseBody.toString.parseJSON;
45 
46 	ArchSuffix[] allSuffixes = [EnumMembers!ArchSuffix];
47 
48 	ArchSuffix[] missingAssetSuffixes = allSuffixes.dup;
49 
50 	string[ArchSuffix] downloadURLs;
51 
52 	auto release = rormReleases.array[0];
53 
54 	output.writeln(release["tag_name"].str);
55 	foreach (asset; release["assets"].array)
56 	{
57 		string assetName = asset["name"].str;
58 		string downloadUrl = asset["browser_download_url"].str;
59 		foreach_reverse (i, missingSuffix; missingAssetSuffixes)
60 		{
61 			if (matchesAsset(assetName, missingSuffix))
62 			{
63 				downloadURLs[missingSuffix] = downloadUrl;
64 				missingAssetSuffixes = missingAssetSuffixes.remove(i);
65 			}
66 		}
67 	}
68 
69 	foreach (suffix; allSuffixes)
70 	{
71 		if (auto downloadUrl = suffix in downloadURLs)
72 		{
73 			string sigFilePath = "/tmp/verify-test.bin.sig";
74 			download(rq, *downloadUrl ~ ".sig", sigFilePath);
75 
76 			string downloadFilePath = "/tmp/verify-test.bin";
77 			auto downloadFile = File(downloadFilePath, "wb");
78 			SHA512 sha512;
79 			sha512.start();
80 			auto data = rq.get(*downloadUrl).responseBody.data;
81 			downloadFile.rawWrite(data);
82 			sha512.put(data);
83 			downloadFile.close();
84 
85 			checkSignature(downloadFilePath, sigFilePath);
86 
87 			output.writeln(suffix, "\t", *downloadUrl, "\t", sha512.finish.toHexString);
88 		}
89 		else
90 		{
91 			stderr.writeln("Warning: no pre-built release for ", suffix, " - user will be prompted to compile manually!");
92 		}
93 	}
94 }
95 
96 bool matchesAsset(string asset, ArchSuffix suffix)
97 {
98 	final switch (suffix)
99 	{
100 		case ArchSuffix.linux_x86:
101 			return !!asset.endsWith("linux-x86_64.tar.gz");
102 		case ArchSuffix.osx_x86:
103 			return !!asset.endsWith("apple-x86_64.tar.gz");
104 		case ArchSuffix.osx_aarch64:
105 			return !!asset.endsWith("apple-aarch64.tar.gz");
106 		case ArchSuffix.windows_x86:
107 			return !!asset.endsWith("windows-x86_64.zip");
108 	}
109 }
110 
111 void download(ref Request rq, string url, string output)
112 {
113 	auto rs = rq.get(url);
114 	fs.write(output, rs.responseBody.data);
115 }
116 
117 void checkSignature(string file, string sigFile)
118 {
119 	enforce(spawnProcess([
120 		"gpg", "--verify", sigFile, file
121 	]).wait == 0, "Failed to verify " ~ file ~ " with signature " ~ sigFile);
122 }