Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

License handling #69

Merged
merged 7 commits into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ build_*
# OS generated files
.DS_Store
*.swp
*.bak
*.orig

# server files
test-server/node_modules
Expand Down
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

### Ignition Fuel Tools 4.X.X (20xx-xx-xx)

1. Set license information based on licenses available from a Fuel server
and `legal` information in a `metadata.pbtxt` file.
* [Pull request 69](https://bitbucket.org/ignitionrobotics/ign-fuel-tools/pull-requests/69)

1. Alphabetical listing of subcommands.
* [BitBucket pull request 65](https://github.com/ignitionrobotics/ign-fuel-tools/pull/65)

Expand Down
24 changes: 18 additions & 6 deletions include/ignition/fuel_tools/FuelClient.hh
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ namespace ignition
/// \param[in] _id a partially filled out identifier used to fetch models
/// \remarks Fulfills Get-One requirement
/// \remarks It's not yet clear if model names are unique, so this API
/// allows the posibility of getting multiple models with the
/// allows the possibility of getting multiple models with the
/// same name.
/// \return An iterator of models with names matching the criteria
public: ModelIter Models(const ModelIdentifier &_id);
Expand All @@ -123,7 +123,7 @@ namespace ignition
/// \param[in] _id a partially filled out identifier used to fetch models
/// \remarks Fulfills Get-One requirement
/// \remarks It's not yet clear if model names are unique, so this API
/// allows the posibility of getting multiple models with the
/// allows the possibility of getting multiple models with the
/// same name.
/// \return An iterator of models with names matching the criteria
public: ModelIter Models(const ModelIdentifier &_id) const;
Expand Down Expand Up @@ -243,7 +243,7 @@ namespace ignition
public: Result CachedWorldFile(const common::URI &_fileUrl,
std::string &_path);

/// \brief Parse model identifer from model URL or unique name.
/// \brief Parse model identifier from model URL or unique name.
/// \param[in] _modelUrl The unique URL of a model. It may also be a
/// unique name, which is a URL without the server version.
/// \param[out] _id The model identifier. It may contain incomplete
Expand All @@ -255,7 +255,7 @@ namespace ignition
public: bool ParseModelUrl(const common::URI &_modelUrl,
ModelIdentifier &_id);

/// \brief Parse world identifer from world URL or unique name.
/// \brief Parse world identifier from world URL or unique name.
/// \param[in] _worldUrl The unique URL of a world. It may also be a
/// unique name, which is a URL without the server version.
/// \param[out] _id The world identifier. It may contain incomplete
Expand All @@ -267,7 +267,7 @@ namespace ignition
public: bool ParseWorldUrl(const common::URI &_worldUrl,
WorldIdentifier &_id);

/// \brief Parse model file identifer from model file URL.
/// \brief Parse model file identifier from model file URL.
/// \param[in] _modelFileUrl The unique URL of a model file. It may also
/// be a unique name, which is a URL without the server version.
/// \param[out] _id The model identifier. It may contain incomplete
Expand All @@ -280,7 +280,7 @@ namespace ignition
ModelIdentifier &_id,
std::string &_filePath);

/// \brief Parse world file identifer from world file URL.
/// \brief Parse world file identifier from world file URL.
/// \param[in] _worldFileUrl The unique URL of a world file. It may also
/// be a unique name, which is a URL without the server version.
/// \param[out] _id The world identifier. It may contain incomplete
Expand All @@ -293,6 +293,18 @@ namespace ignition
WorldIdentifier &_id,
std::string &_filePath);

/// \brief This function requests the available licenses from the
/// Fuel server and stores this information locally.
///
/// The UploadModel function can use this information to set
/// appropriate license information based on a model's metadata.pbtxt
/// file. If license information is not available, then the
/// UploadModel function will default to the
/// "Creative Commons - Public Domain" license.
/// \param[in] _server Information about the server that provides
/// license information.
public: void PopulateLicenses(const ServerConfig &_server);

/// \brief PIMPL
private: std::unique_ptr<FuelClientPrivate> dataPtr;
};
Expand Down
20 changes: 20 additions & 0 deletions include/ignition/fuel_tools/JSONParser.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#ifndef IGNITION_FUEL_TOOLS_JSONPARSER_HH_
#define IGNITION_FUEL_TOOLS_JSONPARSER_HH_

#include <map>
#include <string>
#include <utility>
#include <vector>

#include "ignition/fuel_tools/ModelIdentifier.hh"
Expand Down Expand Up @@ -86,6 +88,15 @@ namespace ignition
/// \return A JSON string representing a single world
public: static std::string BuildWorld(WorldIter _worldIt);

/// \brief Parse a license array JSON string and return a map of
/// licenses.
/// \param[in] _json JSON string containing an array of models
/// \param[out] _licenses License information where the keys are license
/// names and values are license id's on the Fuel server.
/// \return True on success, false otherwise.
public: static bool ParseLicenses(const std::string &_json,
std::map<std::string, unsigned int> &_licenses);

/// \brief Parse a json object as a model.
/// \param[in] _json JSON object containing a single model
/// \param[out] _model a model identifier after parsing the JSON
Expand All @@ -105,6 +116,15 @@ namespace ignition
/// \return The list of tags.
private: static std::vector<std::string> ParseTags(
const Json::Value &_json);

/// \brief Parse a JSON object as a license.
/// \param[in] _json JSON object containing a single license.
/// \param[out] _license License information where the first value in
/// the pair is the name of the license and the second value is
/// the license id on the Fuel server.
/// \return True if parsing succeeded or false otherwise
private: static bool ParseLicenseImpl(const Json::Value &_json,
std::pair<std::string, unsigned int> &_license);
};
}
}
Expand Down
69 changes: 65 additions & 4 deletions src/FuelClient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ class ignition::fuel_tools::FuelClientPrivate

/// \brief Regex to parse Ignition Fuel world file URLs.
public: std::unique_ptr<std::regex> urlWorldFileRegex;

/// \brief The set of licenses where the key is the name of the license
/// and the value is the license ID on a Fuel server. See the
/// PopulateLicenses function.
public: std::map<std::string, unsigned int> licenses;
};

//////////////////////////////////////////////////
Expand Down Expand Up @@ -410,10 +415,46 @@ Result FuelClient::UploadModel(const std::string &_pathToModelDir,
// \todo(nkoenig) The ign-fuelserver expects an integer number for the
// license information. The fuelserver should be modified to accept
// a string. Otherwise, we have to bake into each client a mapping of
// license name to integer. For now, we are making a model
// "Creative Commons - Public Domain
// if (meta.has_legal()) {....}
form.emplace("license", "1");
// license name to integer.
//
// If there is no license information, then default to
// "Creative Commons - Public Domain"
if (this->dataPtr->licenses.empty() || !meta.has_legal())
{
form.emplace("license", "1");
}
// Otherwise, we have license information (see PopulateLicenses) and
// legal information
else
{
// Find the license by name.
std::map<std::string, unsigned int>::const_iterator licenseIt =
this->dataPtr->licenses.find(meta.legal().license());
if (licenseIt != this->dataPtr->licenses.end())
{
form.emplace("license", std::to_string(licenseIt->second));
}
// No license found, print an error and return.
else
{
std::string validLicenseNames;
auto end = this->dataPtr->licenses.end();
std::advance(end, -1);
//for (size_t i = 0; i < this->dataPtr->licenses.size(); ++i)
for (licenseIt = this->dataPtr->licenses.begin();
licenseIt != end; ++licenseIt)
{
validLicenseNames += " " + licenseIt->first + "\n";
}
validLicenseNames += " " + licenseIt->first;

ignerr << "Invalid license[" << meta.legal().license() << "].\n"
<< " Valid licenses include:\n"
<< validLicenseNames << std::endl;

return Result(ResultType::UPLOAD_ERROR);
}
}

// Add tags
std::string tags;
Expand Down Expand Up @@ -1125,3 +1166,23 @@ void FuelClientPrivate::AllFiles(const std::string &_path,
}
}


//////////////////////////////////////////////////
void FuelClient::PopulateLicenses(const ServerConfig &_server)
{
ignition::fuel_tools::Rest rest;
RestResponse resp;

// Send the request.
resp = rest.Request(HttpMethod::GET, _server.Url().Str(),
_server.Version(), "licenses", {}, {}, "");
if (resp.statusCode != 200)
{
ignerr << "Failed to get license information from "
<< _server.Url().Str() << "/" << _server.Version() << std::endl;
}
else if (!JSONParser::ParseLicenses(resp.data, this->dataPtr->licenses))
{
ignerr << "Failed to parse license information[" << resp.data << "]\n";
}
}
66 changes: 66 additions & 0 deletions src/JSONParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,69 @@ std::string JSONParser::BuildWorld(WorldIter _worldIt)
Json::StreamWriterBuilder builder;
return Json::writeString(builder, value);
}

/////////////////////////////////////////////////
bool JSONParser::ParseLicenses(const std::string &_json,
std::map<std::string, unsigned int> &_licenses)
{
Json::CharReaderBuilder reader;
Json::Value licenses;
std::istringstream iss(_json);
JSONCPP_STRING errs;
Json::parseFromStream(reader, iss, &licenses, &errs);

if (!licenses.isArray())
{
ignerr << "JSON response is not an array.\n";
return false;
}

for (auto licenseIt = licenses.begin();
licenseIt != licenses.end(); ++licenseIt)
{
Json::Value licenseJson = *licenseIt;
std::pair<std::string, unsigned int> license;
if (!ParseLicenseImpl(licenseJson, license))
{
ignerr << "License isn't a json object!\n";
continue;
}

_licenses.insert(license);
}

return true;
}

/////////////////////////////////////////////////
bool JSONParser::ParseLicenseImpl(const Json::Value &_json,
std::pair<std::string, unsigned int> &_license)
{
try
{
if (!_json.isObject())
{
ignerr << "License isn't a json object!\n";
return false;
}

if (_json.isMember("name"))
_license.first = _json["name"].asString();
if (_json.isMember("ID"))
_license.second = _json["ID"].asUInt();
}
#if JSONCPP_VERSION_MAJOR < 1 && JSONCPP_VERSION_MINOR < 10
catch (...)
{
std::string what;
#else
catch (const Json::LogicError &error)
{
std::string what = ": [" + std::string(error.what()) + "]";
#endif
ignerr << "Bad response from server" << what << "\n";
return false;
}

return true;
}
Loading