diff --git a/google/cloud/storage/client.h b/google/cloud/storage/client.h index 84cc2dfe0c38e..9cfc912099608 100644 --- a/google/cloud/storage/client.h +++ b/google/cloud/storage/client.h @@ -1051,13 +1051,17 @@ class Client { * Valid types for this operation include * `IfMetagenerationMatch`, `IfMetagenerationNotMatch`, `UserProject`, * `Projection`, `Prefix`, `Delimiter`, `IncludeTrailingDelimiter`, - * `StartOffset`, `EndOffset`, `MatchGlob`, and `Versions`. + * `IncludeFoldersAsPrefixes`, `StartOffset`, `EndOffset`, `MatchGlob`, + * `SoftDeleted`, and `Versions`. * * @par Idempotency * This is a read-only operation and is always idempotent. * * @par Example * @snippet storage_object_samples.cc list objects and prefixes + * + * @par Example + * @snippet storage_object_samples.cc list-objects-and-folders */ template ListObjectsAndPrefixesReader ListObjectsAndPrefixes( diff --git a/google/cloud/storage/examples/storage_object_samples.cc b/google/cloud/storage/examples/storage_object_samples.cc index 7703960c4905e..358bdeac6d6a7 100644 --- a/google/cloud/storage/examples/storage_object_samples.cc +++ b/google/cloud/storage/examples/storage_object_samples.cc @@ -99,6 +99,29 @@ void ListObjectsAndPrefixes(google::cloud::storage::Client client, (std::move(client), argv.at(0), argv.at(1)); } +void ListObjectsAndFolders(google::cloud::storage::Client client, + std::vector const& argv) { + //! [list-objects-and-folders] + namespace gcs = ::google::cloud::storage; + [](gcs::Client client, std::string const& bucket_name, + std::string const& bucket_prefix) { + for (auto&& item : client.ListObjectsAndPrefixes( + bucket_name, gcs::Prefix(bucket_prefix), gcs::Delimiter("/"), + gcs::IncludeFoldersAsPrefixes(true))) { + if (!item) throw std::move(item).status(); + auto result = *std::move(item); + if (absl::holds_alternative(result)) { + std::cout << "object_name=" + << absl::get(result).name() << "\n"; + } else if (absl::holds_alternative(result)) { + std::cout << "prefix =" << absl::get(result) << "\n"; + } + } + } + //! [list-objects-and-folders] + (std::move(client), argv.at(0), argv.at(1)); +} + void InsertObject(google::cloud::storage::Client client, std::vector const& argv) { //! [insert object] @@ -664,6 +687,11 @@ void RunAll(std::vector const& argv) { std::cout << "\nRunning ListObjectsAndPrefixes() example" << std::endl; ListObjectsAndPrefixes(client, {bucket_name, bucket_prefix}); + if (!examples::UsingEmulator()) { + std::cout << "\nRunning ListObjectsAndFolders() example" << std::endl; + ListObjectsAndFolders(client, {bucket_name, bucket_prefix}); + } + // Cleanup the objects so the bucket can be deleted client.DeleteObject(bucket_name, bucket_prefix + "/foo/bar"); client.DeleteObject(bucket_name, bucket_prefix + "/qux/bar"); @@ -770,6 +798,8 @@ int main(int argc, char* argv[]) { make_entry("list-versioned-objects", {}, ListVersionedObjects), make_entry("list-objects-and-prefixes", {""}, ListObjectsAndPrefixes), + make_entry("list-objects-and-folders", {""}, + ListObjectsAndFolders), make_entry("insert-object", {"", ""}, InsertObject), make_entry("insert-object-strict-idempotency", diff --git a/google/cloud/storage/google_cloud_cpp_storage.bzl b/google/cloud/storage/google_cloud_cpp_storage.bzl index 3bc41b4822b34..3189f092c98eb 100644 --- a/google/cloud/storage/google_cloud_cpp_storage.bzl +++ b/google/cloud/storage/google_cloud_cpp_storage.bzl @@ -42,6 +42,7 @@ google_cloud_cpp_storage_hdrs = [ "hmac_key_metadata.h", "iam_policy.h", "idempotency_policy.h", + "include_folders_as_prefixes.h", "internal/access_control_common.h", "internal/access_control_common_parser.h", "internal/access_token_credentials.h", diff --git a/google/cloud/storage/google_cloud_cpp_storage.cmake b/google/cloud/storage/google_cloud_cpp_storage.cmake index afad645944c33..8e69ad26b6e75 100644 --- a/google/cloud/storage/google_cloud_cpp_storage.cmake +++ b/google/cloud/storage/google_cloud_cpp_storage.cmake @@ -61,6 +61,7 @@ add_library( iam_policy.h idempotency_policy.cc idempotency_policy.h + include_folders_as_prefixes.h internal/access_control_common.h internal/access_control_common_parser.cc internal/access_control_common_parser.h diff --git a/google/cloud/storage/include_folders_as_prefixes.h b/google/cloud/storage/include_folders_as_prefixes.h new file mode 100644 index 0000000000000..ad5337538db23 --- /dev/null +++ b/google/cloud/storage/include_folders_as_prefixes.h @@ -0,0 +1,43 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INCLUDE_FOLDERS_AS_PREFIXES_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INCLUDE_FOLDERS_AS_PREFIXES_H + +#include "google/cloud/storage/internal/well_known_parameters_impl.h" +#include "google/cloud/storage/version.h" + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/** + * Set this parameter to `true` to include folders in the + * `Client::ListObjectsAndPrefixes()` calls. + */ +struct IncludeFoldersAsPrefixes + : public internal::WellKnownParameter { + using WellKnownParameter::WellKnownParameter; + static char const* well_known_parameter_name() { + return "includeFoldersAsPrefixes"; + } +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INCLUDE_FOLDERS_AS_PREFIXES_H diff --git a/google/cloud/storage/internal/object_requests.h b/google/cloud/storage/internal/object_requests.h index 9acc719447733..5a23c3ceeed6d 100644 --- a/google/cloud/storage/internal/object_requests.h +++ b/google/cloud/storage/internal/object_requests.h @@ -18,6 +18,7 @@ #include "google/cloud/storage/auto_finalize.h" #include "google/cloud/storage/download_options.h" #include "google/cloud/storage/hashing_options.h" +#include "google/cloud/storage/include_folders_as_prefixes.h" #include "google/cloud/storage/internal/const_buffer.h" #include "google/cloud/storage/internal/generic_object_request.h" #include "google/cloud/storage/internal/hash_function.h" @@ -47,9 +48,9 @@ namespace internal { */ class ListObjectsRequest : public GenericRequest { + IncludeFoldersAsPrefixes, IncludeTrailingDelimiter, + StartOffset, EndOffset, MatchGlob, Projection, + SoftDeleted, UserProject, Versions> { public: ListObjectsRequest() = default; explicit ListObjectsRequest(std::string bucket_name) diff --git a/google/cloud/storage/tests/object_integration_test.cc b/google/cloud/storage/tests/object_integration_test.cc index 769be90a6799a..cfcdd31b05519 100644 --- a/google/cloud/storage/tests/object_integration_test.cc +++ b/google/cloud/storage/tests/object_integration_test.cc @@ -176,6 +176,43 @@ TEST_F(ObjectIntegrationTest, ListObjectsAndPrefixes) { object_prefix + "/foo")); } +TEST_F(ObjectIntegrationTest, ListObjectsAndPrefixesWithFolders) { + if (UsingEmulator()) GTEST_SKIP(); + + StatusOr client = MakeIntegrationTestClient(); + ASSERT_STATUS_OK(client); + + auto object_prefix = MakeRandomObjectName(); + for (auto const* suffix : + {"/foo", "/foo/bar", "/foo/baz", "/qux/quux", "/something"}) { + auto meta = + client->InsertObject(bucket_name_, object_prefix + suffix, LoremIpsum(), + storage::IfGenerationMatch(0)); + ASSERT_STATUS_OK(meta); + ScheduleForDelete(*meta); + } + + auto reader = client->ListObjectsAndPrefixes( + bucket_name_, IncludeFoldersAsPrefixes(true), Prefix(object_prefix + "/"), + Delimiter("/")); + std::vector prefixes; + std::vector objects; + for (auto& it : reader) { + auto const& result = it.value(); + if (absl::holds_alternative(result)) { + prefixes.push_back(absl::get(result)); + } else { + auto const& meta = absl::get(result); + EXPECT_EQ(bucket_name_, meta.bucket()); + objects.push_back(meta.name()); + } + } + EXPECT_THAT(prefixes, UnorderedElementsAre(object_prefix + "/qux/", + object_prefix + "/foo/")); + EXPECT_THAT(objects, UnorderedElementsAre(object_prefix + "/something", + object_prefix + "/foo")); +} + TEST_F(ObjectIntegrationTest, ListObjectsStartEndOffset) { StatusOr client = MakeIntegrationTestClient(); ASSERT_STATUS_OK(client);