-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathGithubReleasesZsyncUpdateInformation.cpp
More file actions
133 lines (108 loc) · 5.09 KB
/
GithubReleasesZsyncUpdateInformation.cpp
File metadata and controls
133 lines (108 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include "GithubReleasesZsyncUpdateInformation.h"
namespace appimage::update::updateinformation {
GithubReleasesUpdateInformation::GithubReleasesUpdateInformation(
const std::vector<std::string>& updateInformationComponents) :
AbstractUpdateInformation(updateInformationComponents, ZSYNC_GITHUB_RELEASES)
{
// validation
assertParameterCount(_updateInformationComponents, 5);
}
std::string GithubReleasesUpdateInformation::buildUrl(const StatusMessageCallback& issueStatusMessage) const {
auto username = _updateInformationComponents[1];
auto repository = _updateInformationComponents[2];
auto tag = _updateInformationComponents[3];
auto filename = _updateInformationComponents[4];
std::stringstream url;
url << "https://api.github.com/repos/" << username << "/" << repository << "/releases";
bool parseListResponse = false;
bool usePrereleases = false;
bool useReleases = true;
// TODO: this snippet does not support pagination
// it is more reliable for "known" releases ("latest" and named ones, e.g., "continuous") to query them directly
// we expect paginated responses to be very unlikely
if (tag == "latest-pre") {
usePrereleases = true;
useReleases = false;
parseListResponse = true;
} else if (tag == "latest-all") {
usePrereleases = true;
parseListResponse = true;
} else if (tag == "latest") {
issueStatusMessage("Fetching latest release information from GitHub API");
url << "/latest";
} else {
std::ostringstream oss;
oss << "Fetching release information for tag \"" << tag << "\" from GitHub API.";
issueStatusMessage(oss.str());
url << "/tags/" << tag;
}
if (parseListResponse) {
issueStatusMessage("Fetching releases list from GitHub API");
}
auto urlStr = url.str();
auto response = cpr::Get(cpr::Url{urlStr});
nlohmann::json json;
try {
json = nlohmann::json::parse(response.text);
} catch (const std::exception& e) {
throw UpdateInformationError(std::string("Failed to parse GitHub response: ") + e.what());
}
// continue only if request worked
if (response.error.code != cpr::ErrorCode::OK || response.status_code < 200 || response.status_code >= 300) {
std::ostringstream oss;
oss << "GitHub API request failed: HTTP status " << std::to_string(response.status_code)
<< ", CURL error: " << response.error.message;
throw UpdateInformationError(oss.str());
}
if (parseListResponse) {
bool found = false;
for (auto& item : json) {
if (item["prerelease"].get<bool>() && usePrereleases) {
json = item;
found = true;
break;
}
if (!useReleases) {
continue;
}
json = item;
found = true;
break;
}
if (!found) {
throw UpdateInformationError(std::string("Failed to find suitable release"));
}
issueStatusMessage("Found matching release: " + json["name"].get<std::string>());
}
// not ideal, but allows for returning a match for the entire line
auto pattern = "*" + filename;
const auto& assets = json["assets"];
if (assets.empty()) {
std::ostringstream oss;
oss << "Could not find any artifacts in release data. "
<< "Please contact the author of the AppImage and tell them the files are missing "
<< "on the releases page.";
throw UpdateInformationError(oss.str());
}
std::vector<std::string> matchingUrls;
for (const auto& asset : assets) {
const auto browserDownloadUrl = asset["browser_download_url"].get<std::string>();
const auto name = asset["name"].get<std::string>();
if (fnmatch(pattern.c_str(), name.c_str(), 0) == 0) {
matchingUrls.emplace_back(browserDownloadUrl);
}
}
if (matchingUrls.empty()) {
std::ostringstream oss;
oss << "None of the artifacts matched the pattern in the update information. "
<< "The pattern is most likely invalid, e.g., due to changes in the filenames of "
<< "the AppImages. Please contact the author of the AppImage and ask them to "
<< "revise the update information.";
throw UpdateInformationError(oss.str());
}
// this _should_ ensure the first entry in the vector is the latest release in case there is more than one)
// (this of course depends on the stability of the naming pattern used by the AppImage vendors)
std::sort(matchingUrls.begin(), matchingUrls.end(), std::greater<>());
return matchingUrls[0];
}
}