Skip to content

Commit

Permalink
Added public/private editing
Browse files Browse the repository at this point in the history
  • Loading branch information
Nate Koenig committed May 5, 2020
1 parent 6b8f4f5 commit 556c687
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 111 deletions.
23 changes: 23 additions & 0 deletions include/ignition/fuel_tools/FuelClient.hh
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ namespace ignition
public: Result ModelDetails(const ModelIdentifier &_id,
ModelIdentifier &_model) const;

/// \brief Fetch the details of a model.
/// \param[in] _id a partially filled out identifier used to fetch models
/// \remarks Fulfills Get-One requirement
/// \param[out] _model The requested model
/// \return Result of the fetch operation.
public: Result ModelDetails(const ModelIdentifier &_id,
ModelIdentifier &_model,
const std::vector<std::string> &_headers) const;


/// \brief Returns an iterator that can return names of models
/// \remarks Fulfills Get-All requirement
/// \remarks an iterator instead of a list of names is returned in case
Expand Down Expand Up @@ -293,6 +303,19 @@ namespace ignition
WorldIdentifier &_id,
std::string &_filePath);

/// \brief Update a model using a PATCH request.
///
/// Model fields that are patched by this function:
/// * private
///
/// \param[in] _model The model to patch. The contents of this model
/// will be sent in the PATCH request.
/// \param[in] _headers Headers to set on the HTTP request.
/// \return Result of the patch operation.
public: Result PatchModel(
const ignition::fuel_tools::ModelIdentifier &_model,
const std::vector<std::string> &_headers);

/// \brief PIMPL
private: std::unique_ptr<FuelClientPrivate> dataPtr;
};
Expand Down
10 changes: 10 additions & 0 deletions include/ignition/fuel_tools/ModelIdentifier.hh
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ namespace ignition
/// \return The collection of tags.
public: std::vector<std::string> Tags() const;

/// \brief Returns the privacy setting of the model.
/// \return True if the model is private, false if the model is
/// public.
public: bool Private() const;

/// \brief Set the privacy setting of the model.
/// \param[in] True indicates the model is private, false indicates the
/// model is public.
public: void SetPrivate(bool _private) const;

/// \brief Set the description of the model.
/// \param[in] _desc The description
/// \return True if successful.
Expand Down
5 changes: 4 additions & 1 deletion include/ignition/fuel_tools/RestClient.hh
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ namespace ignition
PATCH,

/// \brief Post form method.
POST_FORM
POST_FORM,

/// \brief Patch form method.
PATCH_FORM
};

/// \brief A helper class for making REST requests.
Expand Down
8 changes: 7 additions & 1 deletion include/ignition/fuel_tools/Result.hh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ namespace ignition

/// \brief Upload failed. Other errors.
/// \sa ReadableResult
UPLOAD_ERROR
UPLOAD_ERROR,

/// \brief Patch failed.
PATCH_ERROR,

/// \brief Patch successful.
PATCH,
};

/// \brief Class describing a result of an operation.
Expand Down
36 changes: 35 additions & 1 deletion src/FuelClient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ ClientConfig &FuelClient::Config()
//////////////////////////////////////////////////
Result FuelClient::ModelDetails(const ModelIdentifier &_id,
ModelIdentifier &_model) const
{
return this->ModelDetails(_id, _model, {});
}

//////////////////////////////////////////////////
Result FuelClient::ModelDetails(const ModelIdentifier &_id,
ModelIdentifier &_model, const std::vector<std::string> &_headers) const
{
ignition::fuel_tools::Rest rest;
RestResponse resp;
Expand All @@ -212,7 +219,7 @@ Result FuelClient::ModelDetails(const ModelIdentifier &_id,
path = path / _id.Owner() / "models" / _id.Name();

resp = rest.Request(HttpMethod::GET, serverUrl, version,
path.Str(), {}, {}, "");
path.Str(), {}, _headers, "");
if (resp.statusCode != 200)
return Result(ResultType::FETCH_ERROR);

Expand Down Expand Up @@ -1108,6 +1115,33 @@ Result FuelClient::CachedWorldFile(const common::URI &_fileUrl,
return Result(ResultType::FETCH_ERROR);
}

//////////////////////////////////////////////////
Result FuelClient::PatchModel(
const ignition::fuel_tools::ModelIdentifier &_model,
const std::vector<std::string> &_headers)
{
ignition::fuel_tools::Rest rest;
RestResponse resp;

auto serverUrl = _model.Server().Url().Str();
auto version = _model.Server().Version();
common::URIPath path;
path = path / _model.Owner() / "models" / _model.Name();

std::multimap<std::string, std::string> form =
{
{"private", _model.Private() ? "1" : "0"}
};

resp = rest.Request(HttpMethod::PATCH_FORM, serverUrl, version,
path.Str(), {}, _headers, "", form);

if (resp.statusCode != 200)
return Result(ResultType::PATCH_ERROR);

return Result(ResultType::PATCH);
}

//////////////////////////////////////////////////
void FuelClientPrivate::AllFiles(const std::string &_path,
std::vector<std::string> &_files) const
Expand Down
16 changes: 16 additions & 0 deletions src/ModelIdentifier.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ class ignition::fuel_tools::ModelIdentifierPrivate

/// \brief Model version. Valid versions start from 1, 0 means the tip.
public: unsigned int version{0};

/// \brief True indicates the model is private, false indicates the
/// model is public.
public: bool privacy{false};
};

//////////////////////////////////////////////////
Expand Down Expand Up @@ -484,3 +488,15 @@ std::string ModelIdentifier::AsPrettyString(const std::string &_prefix) const
<< this->Server().AsPrettyString(_prefix + " ");
return out.str();
}

//////////////////////////////////////////////////
bool ModelIdentifier::Private() const
{
return this->dataPtr->privacy;
}

//////////////////////////////////////////////////
void ModelIdentifier::SetPrivate(bool _private) const
{
this->dataPtr->privacy = _private;
}
4 changes: 4 additions & 0 deletions src/ModelIdentifier_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ TEST(ModelIdentifier, SetFields)
std::time(&d2);
id.SetUploadDate(d2);

EXPECT_FALSE(id.Private());
id.SetPrivate(true);
EXPECT_TRUE(id.Private());

EXPECT_EQ(std::string("hello"), id.Name());
EXPECT_EQ(std::string("acai"), id.Owner());
EXPECT_EQ(6u, id.Version());
Expand Down
145 changes: 80 additions & 65 deletions src/RestClient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,79 @@ size_t RestWriteMemoryCallback(void *_buffer, size_t _size, size_t _nmemb,
return _size;
}

/////////////////////////////////////////////////
struct curl_httppost *BuildFormPost(
const std::multimap<std::string, std::string> &_form)
{
struct curl_httppost *formpost = nullptr;
struct curl_httppost *lastptr = nullptr;
for (const std::pair<std::string, std::string> &it : _form)
{
std::string key = it.first;
std::string value = it.second;

// follow same convention as curl cmdline tool
// field starting with @ indicates path to file to upload
// others are standard fields to describe the file
if (!value.empty() && value[0] == '@')
{
// Default file path
std::string path = value.substr(1);

// Default upload filename
std::string uploadFilename = ignition::common::basename(path);

// If the value has a semicolon, then use the string preceding the
// semicolon as the local filesystem path and the string following
// the semicolon as the upload filename.
if (value.substr(1).find(";") != std::string::npos)
{
path = value.substr(1, value.find(";") - 1);
uploadFilename = value.substr(value.find(";") + 1);
}

std::string basename = ignition::common::basename(path);
std::string contentType = "application/octet-stream";

// Figure out the content type based on the file extension.
std::string::size_type dotIdx = basename.rfind('.');
if (dotIdx != std::string::npos)
{
std::string extension =
ignition::common::lowercase(basename.substr(dotIdx));
if (kContentTypes.find(extension) != kContentTypes.end())
{
contentType = kContentTypes.at(extension);
}
else
{
ignwarn << "Unknown mime type for file[" << path
<< "]. The mime type '" << contentType << "' will be used.\n";
}
}

curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, key.c_str(),
CURLFORM_FILENAME, uploadFilename.c_str(),
CURLFORM_FILE, path.c_str(),
CURLFORM_CONTENTTYPE, contentType.c_str(),
CURLFORM_END);
}
else
{
// standard key:value fields
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, key.c_str(),
CURLFORM_COPYCONTENTS, value.c_str(),
CURLFORM_END);
}
}

return formpost;
}

/////////////////////////////////////////////////
RestResponse Rest::Request(HttpMethod _method,
const std::string &_url, const std::string &_version,
Expand Down Expand Up @@ -204,78 +277,20 @@ RestResponse Rest::Request(HttpMethod _method,
{
// no need to do anything
}
else if (_method == HttpMethod::PATCH_FORM)
{
formpost = BuildFormPost(_form);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
}
else if (_method == HttpMethod::POST)
{
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, _data.c_str());
}
else if (_method == HttpMethod::POST_FORM)
{
struct curl_httppost *lastptr = nullptr;
for (const std::pair<std::string, std::string> &it : _form)
{
std::string key = it.first;
std::string value = it.second;

// follow same convention as curl cmdline tool
// field starting with @ indicates path to file to upload
// others are standard fields to describe the file
if (!value.empty() && value[0] == '@')
{
// Default file path
std::string path = value.substr(1);

// Default upload filename
std::string uploadFilename = ignition::common::basename(path);

// If the value has a semicolon, then use the string preceding the
// semicolon as the local filesystem path and the string following
// the semicolon as the upload filename.
if (value.substr(1).find(";") != std::string::npos)
{
path = value.substr(1, value.find(";") - 1);
uploadFilename = value.substr(value.find(";") + 1);
}

std::string basename = ignition::common::basename(path);
std::string contentType = "application/octet-stream";

// Figure out the content type based on the file extension.
std::string::size_type dotIdx = basename.rfind('.');
if (dotIdx != std::string::npos)
{
std::string extension =
ignition::common::lowercase(basename.substr(dotIdx));
if (kContentTypes.find(extension) != kContentTypes.end())
{
contentType = kContentTypes.at(extension);
}
else
{
ignwarn << "Unknown mime type for file[" << path
<< "]. The mime type '" << contentType << "' will be used.\n";
}
}

curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, key.c_str(),
CURLFORM_FILENAME, uploadFilename.c_str(),
CURLFORM_FILE, path.c_str(),
CURLFORM_CONTENTTYPE, contentType.c_str(),
CURLFORM_END);
}
else
{
// standard key:value fields
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, key.c_str(),
CURLFORM_COPYCONTENTS, value.c_str(),
CURLFORM_END);
}
}

formpost = BuildFormPost(_form);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
}
else if (_method == HttpMethod::DELETE)
Expand Down
5 changes: 5 additions & 0 deletions src/Result.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Result::operator bool() const
case ResultType::DELETE:
case ResultType::FETCH:
case ResultType::FETCH_ALREADY_EXISTS:
case ResultType::PATCH:
case ResultType::UPLOAD:
return true;
default:
Expand Down Expand Up @@ -92,6 +93,10 @@ std::string Result::ReadableResult() const
return "Model already exists";
case ResultType::UPLOAD_ERROR:
return "Upload failed. Other errors";
case ResultType::PATCH_ERROR:
return "Patch failed.";
case ResultType::PATCH:
return "Successfully sent patch request to the server";
case ResultType::UNKNOWN:
default:
return "Unknown result";
Expand Down
Loading

0 comments on commit 556c687

Please sign in to comment.