diff --git a/CCDB/include/CCDB/CcdbApi.h b/CCDB/include/CCDB/CcdbApi.h index 207644bd1b8f9..3586003822849 100644 --- a/CCDB/include/CCDB/CcdbApi.h +++ b/CCDB/include/CCDB/CcdbApi.h @@ -180,6 +180,15 @@ class CcdbApi //: public DatabaseInterface */ void deleteObject(std::string const& path, long timestamp = -1) const; + /** + * Update the metadata of the object defined by the provided timestamp, and id if provided. + * @param path Path to the object to update + * @param metadata The metadata to update + * @param timestamp The timestamp to select the object + * @param id The id, if any, to select the object + */ + void updateMetadata(std::string const& path, std::map const& metadata, long timestamp, std::string const& id = ""); + /** * Return the listing of objects, and in some cases subfolders, matching this path. * The path can contain sql patterns (correctly encoded) or regexps. diff --git a/CCDB/src/CcdbApi.cxx b/CCDB/src/CcdbApi.cxx index 14a1924769f5e..fe47eb968684e 100644 --- a/CCDB/src/CcdbApi.cxx +++ b/CCDB/src/CcdbApi.cxx @@ -1339,5 +1339,44 @@ TClass* CcdbApi::tinfo2TClass(std::type_info const& tinfo) return cl; } +void CcdbApi::updateMetadata(std::string const& path, std::map const& metadata, long timestamp, std::string const& id) +{ + CURL* curl; + CURLcode res; + stringstream fullUrl; + fullUrl << mUrl << "/" << path << "/" << timestamp; + if (!id.empty()) { + fullUrl << "/" << id; + } + fullUrl << "?"; + + curl = curl_easy_init(); + + for (auto& kv : metadata) { + string mfirst = kv.first; + string msecond = kv.second; + // same trick for the metadata as for the object type + char* mfirstEncoded = curl_easy_escape(curl, mfirst.c_str(), mfirst.size()); + char* msecondEncoded = curl_easy_escape(curl, msecond.c_str(), msecond.size()); + fullUrl << string(mfirstEncoded) + "=" + string(msecondEncoded) + "&"; + curl_free(mfirstEncoded); + curl_free(msecondEncoded); + } + + if (curl != nullptr) { + curl_easy_setopt(curl, CURLOPT_URL, fullUrl.str().c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); // make sure we use PUT + + curlSetSSLOptions(curl); + + // Perform the request, res will get the return code + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } + curl_easy_cleanup(curl); + } +} + } // namespace ccdb } // namespace o2 diff --git a/CCDB/test/testCcdbApi.cxx b/CCDB/test/testCcdbApi.cxx index b9ffe59c19500..fa3f839281f6c 100644 --- a/CCDB/test/testCcdbApi.cxx +++ b/CCDB/test/testCcdbApi.cxx @@ -24,22 +24,14 @@ #include "CCDB/CCDBTimeStampUtils.h" #include #include -#include -#include #include -#include -#include -#include -#include #include #include #include -#include #include #include #include #include -#include #include #include @@ -479,3 +471,66 @@ BOOST_AUTO_TEST_CASE(TestRetrieveHeaders, *utf::precondition(if_reachable())) } BOOST_CHECK_EQUAL(headers.size(), 0); } + +BOOST_AUTO_TEST_CASE(TestUpdateMetadata, *utf::precondition(if_reachable())) +{ + test_fixture f; + + // upload an object + TH1F h1("object1", "object1", 100, 0, 99); + cout << "storing object 1 in " << basePath << "Test" << endl; + map metadata; + metadata["custom"] = "whatever"; + metadata["id"] = "first"; + f.api.storeAsTFile(&h1, basePath + "Test", metadata); + + // retrieve the headers just to be sure + std::map headers = f.api.retrieveHeaders(basePath + "Test", metadata); + BOOST_CHECK(headers.count("custom") > 0); + BOOST_CHECK(headers.at("custom") == "whatever"); + string firstID = headers.at("ETag"); + firstID.erase(std::remove(firstID.begin(), firstID.end(), '"'), firstID.end()); + + map newMetadata; + newMetadata["custom"] = "somethingelse"; + + // update the metadata and check + f.api.updateMetadata(basePath + "Test", newMetadata, o2::ccdb::getCurrentTimestamp()); + headers = f.api.retrieveHeaders(basePath + "Test", newMetadata); + BOOST_CHECK(headers.count("custom") > 0); + BOOST_CHECK(headers.at("custom") == "somethingelse"); + + // add a second object + cout << "storing object 2 in " << basePath << "Test" << endl; + metadata.clear(); + metadata["custom"] = "whatever"; + metadata["id"] = "second"; + f.api.storeAsTFile(&h1, basePath + "Test", metadata); + + // get id + cout << "get id" << endl; + headers = f.api.retrieveHeaders(basePath + "Test", metadata); + string secondID = headers.at("ETag"); + secondID.erase(std::remove(secondID.begin(), secondID.end(), '"'), secondID.end()); + + // update the metadata by id + cout << "update the metadata by id" << endl; + newMetadata.clear(); + newMetadata["custom"] = "first"; + f.api.updateMetadata(basePath + "Test", newMetadata, o2::ccdb::getCurrentTimestamp(), firstID); + newMetadata.clear(); + newMetadata["custom"] = "second"; + f.api.updateMetadata(basePath + "Test", newMetadata, o2::ccdb::getCurrentTimestamp(), secondID); + + // check + metadata.clear(); + metadata["id"] = "first"; + headers = f.api.retrieveHeaders(basePath + "Test", metadata); + BOOST_CHECK(headers.count("custom") > 0); + BOOST_CHECK(headers.at("custom") == "first"); + metadata.clear(); + metadata["id"] = "second"; + headers = f.api.retrieveHeaders(basePath + "Test", metadata); + BOOST_CHECK(headers.count("custom") > 0); + BOOST_CHECK(headers.at("custom") == "second"); +}