Skip to content

Commit

Permalink
License handling (#69)
Browse files Browse the repository at this point in the history
* Added improved license handling to model upload

Signed-off-by: Nate Koenig <nate@openrobotics.org>
Co-authored-by: Louise Poubel <louise@openrobotics.org>
  • Loading branch information
nkoenig and chapulina authored Jun 11, 2020
1 parent 507d0fa commit fb39d3f
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ build_*
# OS generated files
.DS_Store
*.swp
*.orig
*.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://github.com/ignitionrobotics/ign-fuel-tools/pull/69)

1. Added `edit` subcommand to the `ign fuel`. The edit command currently
supports editing a model's privacy.
* [Pull request 67](https://github.com/ignitionrobotics/ign-fuel-tools/pull/67)
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 @@ -124,7 +124,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 @@ -133,7 +133,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 @@ -253,7 +253,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 @@ -265,7 +265,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 @@ -277,7 +277,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 @@ -290,7 +290,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 @@ -303,6 +303,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 Update a model using a PATCH request.
///
/// Model fields that are patched by this function:
Expand Down
11 changes: 11 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 Down
80 changes: 76 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 @@ -417,10 +422,57 @@ 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 we have legal, then attempt to fill in the correct license information.
if (meta.has_legal())
{
// Attempt to retrieve the available licenses, if we have no available
// licenses.
if (this->dataPtr->licenses.empty())
{
this->PopulateLicenses(_id.Server());
// Fail if a license has been requested, but we couldn't get the
// available licenses.
if (this->dataPtr->licenses.empty())
{
return Result(ResultType::UPLOAD_ERROR);
}
}

// 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 (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);
}
}
// If there is no license information, then default to
// "Creative Commons - Public Domain"
else
{
form.emplace("license", "1");
}

// Add tags
std::string tags;
Expand Down Expand Up @@ -1159,3 +1211,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";
}
}
72 changes: 72 additions & 0 deletions src/JSONParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,45 @@ std::time_t ParseDateTime(const std::string &_datetime)
return timegm(&tm);
}

/////////////////////////////////////////////////
/// \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
bool 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;
}

/////////////////////////////////////////////////
std::vector<std::string> JSONParser::ParseTags(const Json::Value &_json)
{
Expand Down Expand Up @@ -313,3 +352,36 @@ 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;
}
Loading

0 comments on commit fb39d3f

Please sign in to comment.