#include "../include/Package.hpp" #include "../include/curl.hpp" #include "../include/VersionParser.hpp" #include "../external/rapidjson/error/en.h" #ifdef GetObject #undef GetObject #endif using namespace std; using namespace rapidjson; static Document *packageList = nullptr; namespace sibs { static vector getPlatformsByNames(const vector &platformNames) { vector result; result.reserve(platformNames.size()); for(const string &platformName : platformNames) { result.push_back(getPlatformByName(StringView(platformName.data(), platformName.size()))); } return result; } static Result getPackageMetadata(Value::ConstObject jsonObj) { const auto &description = jsonObj.FindMember("description"); if(description == jsonObj.MemberEnd() || !description->value.IsString()) return Result::Err("Expected description to be a string"); const auto &version = jsonObj.FindMember("version"); if(version == jsonObj.MemberEnd() || !version->value.IsString()) return Result::Err("Expected version to be a string"); const auto &platforms = jsonObj.FindMember("platforms"); if(platforms == jsonObj.MemberEnd() || !platforms->value.IsArray() || platforms->value.GetArray().Empty()) return Result::Err("Expected platforms to be an array of strings"); const auto &urls = jsonObj.FindMember("urls"); if(urls == jsonObj.MemberEnd() || !urls->value.IsArray() || urls->value.GetArray().Empty()) return Result::Err("Expected urls to be an array of string"); PackageMetadata packageMetadata; packageMetadata.description.assign(description->value.GetString(), description->value.GetStringLength()); int versionStrSize = 0; Result versionResult = parsePackageVersion({ version->value.GetString(), version->value.GetStringLength() }, &versionStrSize); if(!versionResult) return Result::Err("package version is in wrong fromat, error: " + versionResult.getErrMsg()); if(versionStrSize != (int)version->value.GetStringLength()) return Result::Err("package version is in wrong format, expected only numbers and dots (version: " + string(version->value.GetString(), version->value.GetStringLength()) + ")"); packageMetadata.version = versionResult.unwrap(); const auto &platformsArray = platforms->value.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->value.GetArray(); if(urlsArray.Empty()) return Result::Err("Expected url list to not be empty"); 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); } static Result isPackageUsableForPlatform(const PackageMetadata &packageMetadata, const char *packageName, const PackageVersionRange &versionRange, Platform platform) { if(!versionRange.isInRange(packageMetadata.version)) { string errMsg = "Package \""; errMsg += packageName; errMsg += "\" does not exist for version range \""; errMsg += versionRange.toString(); errMsg += "\""; return Result::Err(errMsg); } if(!containsPlatform(getPlatformsByNames(packageMetadata.platforms), platform)) { string errMsg = "Package \""; errMsg += packageName; errMsg += "\" with version \""; errMsg += packageMetadata.version.toString(); errMsg += "\" does not support platform \""; errMsg += platform; errMsg += "\""; return Result::Err(errMsg); } return Result::Ok(true); } // 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 ~/.cache/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::getPackage(const char *packageName, const PackageVersionRange &versionRange, Platform platform) { Result packageList = Package::getPackageList("https://gitlab.com/DEC05EBA/sibs_packages/raw/master/packages.json"); if(!packageList) return Result::Err(packageList); const Document &packageDoc = *packageList.unwrap(); auto packageMetaDataJsonIt = packageDoc.FindMember(packageName); if (packageMetaDataJsonIt == packageDoc.MemberEnd()) { string errMsg = "No package with the name \""; errMsg += packageName; errMsg += "\" was found"; return Result::Err(errMsg); } if(packageMetaDataJsonIt->value.IsObject()) { Result packageMetadataResult = getPackageMetadata(packageMetaDataJsonIt->value.GetObject()); if(!packageMetadataResult) return packageMetadataResult; Result packageUsableResult = isPackageUsableForPlatform(packageMetadataResult.unwrap(), packageName, versionRange, platform); if(packageUsableResult) return Result::Ok(packageMetadataResult.unwrap()); else return Result::Err(packageUsableResult); } else if(packageMetaDataJsonIt->value.IsArray()) { int i = 0; for(const auto &packageData : packageMetaDataJsonIt->value.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(packageData.GetObject()); if(!packageMetadataResult) return packageMetadataResult; if(isPackageUsableForPlatform(packageMetadataResult.unwrap(), packageName, versionRange, platform)) return Result::Ok(packageMetadataResult.unwrap()); ++i; } string errMsg = "Package \""; errMsg += packageName; errMsg += "\" in version range \""; errMsg += versionRange.toString(); errMsg += "\" does not exist or does not exist for platform \""; errMsg += asString(platform); errMsg += "\""; return Result::Err(errMsg); } else { string errMsg = "No package with the name \""; errMsg += packageName; errMsg += "\" was found"; return Result::Err(errMsg); } } }