#include "../include/Package.hpp" #include "../include/curl.hpp" #include "../external/rapidjson/error/en.h" using namespace std; using namespace rapidjson; static Document *packageList = nullptr; namespace sibs { // TODO: Use containsPlatform in Conf.hpp instead? dont need a vector of string when we can use vector of enum bool containsPlatform(const vector &supportedPlatforms, const char *platform) { for(const string &supportedPlatform : supportedPlatforms) { if(strcmp(supportedPlatform.c_str(), platform) == 0 || strcmp(supportedPlatform.c_str(), "any") == 0) return true; } return false; } Result getPackageMetadata(Value::ConstObject jsonObj) { const auto &description = jsonObj["description"]; if(!description.IsString()) return Result::Err("Expected description to be a string"); const auto &version = jsonObj["version"]; if(!version.IsString()) return Result::Err("Expected version to be a string"); const auto &platforms = jsonObj["platforms"]; if(!platforms.IsArray() || platforms.GetArray().Empty()) return Result::Err("Expected platforms to be an array of strings"); const auto &urls = jsonObj["urls"]; if(!urls.IsArray() || urls.GetArray().Empty()) return Result::Err("Expected urls to be an array of string"); PackageMetadata packageMetadata; packageMetadata.description.assign(description.GetString(), description.GetStringLength()); packageMetadata.version.assign(version.GetString(), version.GetStringLength()); const auto &platformsArray = platforms.GetArray(); packageMetadata.platforms.reserve(platformsArray.Size()); for(int i = 0; i < platformsArray.Size(); ++i) { const auto &platformElement = platformsArray[i]; if(!platformElement.IsString()) return Result::Err("Expected platforms to only contain strings"); packageMetadata.platforms.push_back(platformElement.GetString()); } const auto &urlsArray = urls.GetArray(); packageMetadata.urls.reserve(urlsArray.Size()); for(int i = 0; i < urlsArray.Size(); ++i) { const auto &urlElement = urlsArray[i]; if(!urlElement.IsString()) return Result::Err("Expected urls to only contain strings"); packageMetadata.urls.push_back(urlElement.GetString()); } return Result::Ok(packageMetadata); } Result getPackageUrl(const PackageMetadata &packageMetadata, const char *packageName, const char *packageVersion, const char *platform) { if(strcmp(packageMetadata.version.c_str(), packageVersion) != 0) { string errMsg = "Package \""; errMsg += packageName; errMsg += "\" does not exist for version \""; errMsg += packageVersion; errMsg += "\""; return Result::Err(errMsg); } if(!containsPlatform(packageMetadata.platforms, platform)) { string errMsg = "Package \""; errMsg += packageName; errMsg += "\" with version \""; errMsg += packageVersion; errMsg += "\" does not support platform \""; errMsg += platform; errMsg += "\""; return Result::Err(errMsg); } return Result::Ok(packageMetadata.urls[0]); } // TODO: Always downloading is fine right now because the package list is small. This should later be modified to use local cache. // The package file should be stored locally (at ~/.sibs/packages.json) and the version file should also be stored. // First we check if the version is incorrect and if it, we download the new packages.json file. // We should only check if the package list is up to date once every 10 minute or so (make it configurable in a config file?) // to improve build performance and reduce server load. // Or maybe we dont have to do that at all.... Result Package::getPackageList(const char *url) { if(packageList) return Result::Ok(packageList); HttpResult httpResult = curl::get(url); if(!httpResult.success) return Result::Err(httpResult.str, httpResult.httpCode); Document *doc = new Document(); ParseResult parseResult = doc->Parse(httpResult.str.c_str()); if(!parseResult) { string errMsg = "Failed to parse package list json file: "; errMsg += GetParseError_En(parseResult.Code()); errMsg += " ("; errMsg += to_string(parseResult.Offset()); errMsg += ")"; return Result::Err(errMsg); } packageList = doc; return Result::Ok(packageList); } Result Package::getPackageUrl(const char *packageName, const char *packageVersion, const char *platform) { Result packageList = Package::getPackageList("https://raw.githubusercontent.com/DEC05EBA/sibs_packages/master/packages.json"); if(!packageList) return Result::Err(packageList); const Document &packageDoc = *packageList.unwrap(); const Value &packageMetaDataJson = packageDoc[packageName]; if(packageMetaDataJson.IsObject()) { Result packageMetadataResult = getPackageMetadata(packageMetaDataJson.GetObject()); if(!packageMetadataResult) return Result::Err(packageMetadataResult); return ::sibs::getPackageUrl(packageMetadataResult.unwrap(), packageName, packageVersion, platform); } else if(packageMetaDataJson.IsArray()) { int i = 0; for(const auto &packageData : packageMetaDataJson.GetArray()) { if(!packageData.IsObject()) { string errMsg = "Package file is corrupt. "; errMsg += packageName; errMsg += "["; errMsg += to_string(i); errMsg += "] is not an object"; return Result::Err(errMsg); } Result packageMetadataResult = getPackageMetadata(packageMetaDataJson.GetObject()); if(packageMetadataResult) { Result packageUrlResult = ::sibs::getPackageUrl(packageMetadataResult.unwrap(), packageName, packageVersion, platform); if(packageUrlResult) return packageUrlResult; } ++i; } string errMsg = "Package \""; errMsg += packageName; errMsg += "\" with version \""; errMsg += packageVersion; errMsg += "\" does not exist or does not exist for platform \""; errMsg += platform; errMsg += "\""; return Result::Err(errMsg); } else { string errMsg = "No package with the name \""; errMsg += packageName; errMsg += "\" was found"; return Result::Err(errMsg); } } }