diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt index e2fd88e72f..d768e8503a 100644 --- a/storage/CMakeLists.txt +++ b/storage/CMakeLists.txt @@ -38,7 +38,8 @@ set(android_SRCS src/android/controller_android.cc src/android/metadata_android.cc src/android/storage_android.cc - src/android/storage_reference_android.cc) + src/android/storage_reference_android.cc + src/android/list_result_android.cc) # Source files used by the iOS implementation. set(ios_SRCS @@ -47,7 +48,8 @@ set(ios_SRCS src/ios/metadata_ios.mm src/ios/storage_ios.mm src/ios/storage_reference_ios.mm - src/ios/util_ios.mm) + src/ios/util_ios.mm + src/ios/list_result_ios.mm) # Source files used by the desktop implementation. set(desktop_SRCS @@ -58,7 +60,8 @@ set(desktop_SRCS src/desktop/rest_operation.cc src/desktop/storage_desktop.cc src/desktop/storage_path.cc - src/desktop/storage_reference_desktop.cc) + src/desktop/storage_reference_desktop.cc + src/desktop/list_result_desktop.cc) if(ANDROID) set(storage_platform_SRCS diff --git a/storage/integration_test/src/integration_test.cc b/storage/integration_test/src/integration_test.cc index f430de37de..dd86c32fb0 100644 --- a/storage/integration_test/src/integration_test.cc +++ b/storage/integration_test/src/integration_test.cc @@ -26,6 +26,7 @@ #include "firebase/auth.h" #include "firebase/internal/platform.h" #include "firebase/storage.h" +#include "firebase/storage/list_result.h" #include "firebase/util.h" #include "firebase_test_framework.h" // NOLINT @@ -1622,4 +1623,78 @@ TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingApp) { InitializeAppAndAuth(); } +// Test the StorageReference::ListAll() method. +// This test currently only verifies that the stubbed method returns an empty +// result. +TEST_F(FirebaseStorageTest, TestListAll) { + if (skip_tests_) return; + + firebase::storage::Storage* storage = storage_; // Use the member variable + firebase::storage::StorageReference reference = storage->GetReference(); + + firebase::Future future = reference.ListAll(); + WaitForCompletion(future, "ListAll"); + + ASSERT_EQ(future.status(), firebase::kFutureStatusComplete); + ASSERT_EQ(future.error(), firebase::storage::kErrorNone); + + const firebase::storage::ListResult* result = future.result(); + ASSERT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_TRUE(result->items.empty()); + EXPECT_TRUE(result->prefixes.empty()); + EXPECT_TRUE(result->page_token.empty()); + } +} + +// Test the StorageReference::List() method with no page token. +// This test currently only verifies that the stubbed method returns an empty +// result. +TEST_F(FirebaseStorageTest, TestListNoPageToken) { + if (skip_tests_) return; + + firebase::storage::Storage* storage = storage_; // Use the member variable + firebase::storage::StorageReference reference = storage->GetReference(); + + firebase::Future future = reference.List(); + WaitForCompletion(future, "List (no page token)"); + + ASSERT_EQ(future.status(), firebase::kFutureStatusComplete); + ASSERT_EQ(future.error(), firebase::storage::kErrorNone); + + const firebase::storage::ListResult* result = future.result(); + ASSERT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_TRUE(result->items.empty()); + EXPECT_TRUE(result->prefixes.empty()); + EXPECT_TRUE(result->page_token.empty()); + } +} + +// Test the StorageReference::List() method with a page token. +// This test currently only verifies that the stubbed method returns an empty +// result and that the page token is passed (though not used by the stub). +TEST_F(FirebaseStorageTest, TestListWithPageToken) { + if (skip_tests_) return; + + firebase::storage::Storage* storage = storage_; // Use the member variable + firebase::storage::StorageReference reference = storage->GetReference(); + const char* page_token = "test_page_token"; + + firebase::Future future = + reference.List(page_token); + WaitForCompletion(future, "List (with page token)"); + + ASSERT_EQ(future.status(), firebase::kFutureStatusComplete); + ASSERT_EQ(future.error(), firebase::storage::kErrorNone); + + const firebase::storage::ListResult* result = future.result(); + ASSERT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_TRUE(result->items.empty()); + EXPECT_TRUE(result->prefixes.empty()); + EXPECT_TRUE(result->page_token.empty()); + } +} + } // namespace firebase_testapp_automated diff --git a/storage/src/android/list_result_android.cc b/storage/src/android/list_result_android.cc new file mode 100644 index 0000000000..9115c75de7 --- /dev/null +++ b/storage/src/android/list_result_android.cc @@ -0,0 +1,21 @@ +// File: storage/src/android/list_result_android.cc +#include "storage/src/android/list_result_android.h" + +namespace firebase { +namespace storage { +namespace internal { + +ListResultInternal::ListResultInternal( + StorageReferenceInternal* platform_sri, + const ListResultInternal* other_to_copy_from) + : platform_sri_(platform_sri) { + if (other_to_copy_from) { + items_ = other_to_copy_from->items_; + prefixes_ = other_to_copy_from->prefixes_; + page_token_ = other_to_copy_from->page_token_; + } +} + +} // namespace internal +} // namespace storage +} // namespace firebase diff --git a/storage/src/android/list_result_android.h b/storage/src/android/list_result_android.h new file mode 100644 index 0000000000..751fb3207f --- /dev/null +++ b/storage/src/android/list_result_android.h @@ -0,0 +1,46 @@ +// File: storage/src/android/list_result_android.h +#ifndef FIREBASE_STORAGE_CLIENT_CPP_SRC_ANDROID_LIST_RESULT_ANDROID_H_ +#define FIREBASE_STORAGE_CLIENT_CPP_SRC_ANDROID_LIST_RESULT_ANDROID_H_ + +#include +#include + +#include "firebase/storage/storage_reference.h" +#include "storage/src/android/storage_internal_android.h" +#include "storage/src/android/storage_reference_android.h" + +namespace firebase { +namespace storage { +namespace internal { + +class ListResultInternal { + public: + explicit ListResultInternal( + StorageReferenceInternal* platform_sri, + const ListResultInternal* other_to_copy_from = nullptr); + + const std::vector& items() const { return items_; } + const std::vector& prefixes() const { return prefixes_; } + const std::string& page_token() const { return page_token_; } + + StorageReferenceInternal* storage_reference_internal() const { + return platform_sri_; + } + StorageInternal* associated_storage_internal() const { + return platform_sri_ ? platform_sri_->storage_internal() : nullptr; + } + + private: + ListResultInternal& operator=(const ListResultInternal&); + + StorageReferenceInternal* platform_sri_; + std::vector items_; + std::vector prefixes_; + std::string page_token_; +}; + +} // namespace internal +} // namespace storage +} // namespace firebase + +#endif // FIREBASE_STORAGE_CLIENT_CPP_SRC_ANDROID_LIST_RESULT_ANDROID_H_ diff --git a/storage/src/android/storage_reference_android.cc b/storage/src/android/storage_reference_android.cc index 99b9d40280..d47334c2a1 100644 --- a/storage/src/android/storage_reference_android.cc +++ b/storage/src/android/storage_reference_android.cc @@ -23,6 +23,7 @@ #include "app/src/include/firebase/app.h" #include "app/src/util_android.h" #include "storage/src/android/controller_android.h" +#include "storage/src/android/list_result_android.h" #include "storage/src/android/metadata_android.h" #include "storage/src/android/storage_android.h" #include "storage/src/include/firebase/storage.h" @@ -734,6 +735,37 @@ jint StorageReferenceInternal::CppByteUploaderReadBytes( return data_read; } +Future StorageReferenceInternal::ListAll() { + StorageReference self(this); // Public self for future context + ReferenceCountedFutureImpl* ref_future = + future()->Alloc(kStorageReferenceFnCount); + Future future = MakeFuture(ref_future, self); + + internal::ListResultInternal* list_pimpl = new internal::ListResultInternal( + this, nullptr); // 'this' is StorageReferenceInternal* (Android) + + ListResult result_to_complete(list_pimpl); + + ref_future->Complete(self.AsHandle(), kErrorNone, "", result_to_complete); + return future; +} + +Future StorageReferenceInternal::List(const char* page_token) { + StorageReference self(this); // Public self for future context + ReferenceCountedFutureImpl* ref_future = + future()->Alloc(kStorageReferenceFnCount); + Future future = MakeFuture(ref_future, self); + + // page_token is ignored for stub. + internal::ListResultInternal* list_pimpl = + new internal::ListResultInternal(this, nullptr); + + ListResult result_to_complete(list_pimpl); + + ref_future->Complete(self.AsHandle(), kErrorNone, "", result_to_complete); + return future; +} + } // namespace internal } // namespace storage } // namespace firebase diff --git a/storage/src/android/storage_reference_android.h b/storage/src/android/storage_reference_android.h index 6643a4d8bd..cfb389e3d9 100644 --- a/storage/src/android/storage_reference_android.h +++ b/storage/src/android/storage_reference_android.h @@ -22,6 +22,7 @@ #include "app/src/include/firebase/internal/common.h" #include "app/src/reference_counted_future_impl.h" #include "app/src/util_android.h" +#include "firebase/storage/list_result.h" #include "storage/src/android/storage_android.h" #include "storage/src/include/firebase/storage/storage_reference.h" @@ -153,6 +154,17 @@ class StorageReferenceInternal { // StorageInternal instance we are associated with. StorageInternal* storage_internal() const { return storage_; } + /// @brief Lists all items and prefixes under this reference (Android + /// implementation). + /// @return A Future that will be resolved with a ListResult. + virtual Future ListAll(); + + /// @brief Lists items and prefixes under this reference, with pagination + /// (Android implementation). + /// @param[in] page_token Token for the page of results to return. + /// @return A Future that will be resolved with a ListResult. + virtual Future List(const char* page_token); + private: static void FutureCallback(JNIEnv* env, jobject result, util::FutureResult result_code, diff --git a/storage/src/common/list_result.cc b/storage/src/common/list_result.cc new file mode 100644 index 0000000000..43788d7469 --- /dev/null +++ b/storage/src/common/list_result.cc @@ -0,0 +1,207 @@ +// File: storage/src/common/list_result.cc + +#include "firebase/storage/list_result.h" + +#include "app/src/cleanup_notifier.h" +#include "app/src/include/firebase/internal/platform.h" +#include "app/src/log.h" // For LogWarning, LogDebug (used sparingly) +#include "firebase/storage/storage_reference.h" + +// Platform-specific headers that define internal::ListResultInternal (the PIMPL +// class) and internal::StorageInternal (for CleanupNotifier context). +#if FIREBASE_PLATFORM_ANDROID +#include "storage/src/android/list_result_android.h" +#include "storage/src/android/storage_internal_android.h" +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +#include "storage/src/ios/list_result_ios.h" +#include "storage/src/ios/storage_internal_ios.h" +#else // Desktop +#include "storage/src/desktop/list_result_desktop.h" +#include "storage/src/desktop/storage_internal_desktop.h" +#endif + +namespace firebase { +namespace storage { + +// Forward declaration of the PIMPL class and related internal types. +namespace internal { +class ListResultInternal; +class StorageReferenceInternal; +class StorageInternal; +} // namespace internal + +namespace internal { + +// ListResultInternalCommon: Provides static helper methods for managing the +// lifecycle of the ListResultInternal PIMPL object. +class ListResultInternalCommon { + public: + // Retrieves the StorageInternal context from the ListResultInternal object. + static StorageInternal* GetStorageInternalContext( + ListResultInternal* pimpl_obj) { + if (!pimpl_obj) return nullptr; + StorageInternal* storage_ctx = pimpl_obj->associated_storage_internal(); + if (storage_ctx == nullptr) { + LogWarning( + "ListResultInternal %p has no associated StorageInternal for " + "cleanup context.", + pimpl_obj); + } + return storage_ctx; + } + + // Callback for CleanupNotifier, invoked when the App is being destroyed. + static void CleanupPublicListResultObject(void* public_obj_void) { + ListResult* public_obj = reinterpret_cast(public_obj_void); + if (public_obj) { + LogDebug( + "CleanupNotifier: Cleaning up ListResult %p due to App shutdown.", + public_obj); + DeleteInternal(public_obj); // Ensure PIMPL is deleted. + } else { + LogWarning( + "CleanupNotifier: CleanupPublicListResultObject called with null " + "object."); + } + } + + static void RegisterForCleanup(ListResult* public_obj, + ListResultInternal* pimpl_obj) { + FIREBASE_ASSERT(public_obj != nullptr); + if (!pimpl_obj) return; + StorageInternal* storage_ctx = GetStorageInternalContext(pimpl_obj); + if (storage_ctx) { + storage_ctx->cleanup().RegisterObject(public_obj, + CleanupPublicListResultObject); + } else { + LogWarning( // Kept this warning as it indicates a potential issue. + "Could not register ListResult %p for cleanup: no StorageInternal " + "context.", + public_obj); + } + } + + static void UnregisterFromCleanup(ListResult* public_obj, + ListResultInternal* pimpl_obj) { + FIREBASE_ASSERT(public_obj != nullptr); + if (!pimpl_obj) + return; // If no PIMPL, it couldn't have been registered with its + // context. + StorageInternal* storage_ctx = GetStorageInternalContext(pimpl_obj); + if (storage_ctx) { + storage_ctx->cleanup().UnregisterObject(public_obj); + } + } + + // Deletes the PIMPL object, unregisters from cleanup, and nulls the pointer + // in public_obj. + static void DeleteInternal(ListResult* public_obj) { + FIREBASE_ASSERT(public_obj != nullptr); + if (!public_obj->internal_) return; + + ListResultInternal* pimpl_to_delete = public_obj->internal_; + // Unregister the public object using the context from the PIMPL about to be + // deleted. + UnregisterFromCleanup(public_obj, pimpl_to_delete); + public_obj->internal_ = nullptr; + delete pimpl_to_delete; + } +}; + +} // namespace internal + +// --- Public ListResult Method Implementations --- + +const std::vector ListResult::s_empty_items_; +const std::vector ListResult::s_empty_prefixes_; +const std::string ListResult::s_empty_page_token_; + +ListResult::ListResult() : internal_(nullptr) {} + +ListResult::ListResult(internal::ListResultInternal* internal_pimpl) + : internal_(internal_pimpl) { + if (internal_) { + internal::ListResultInternalCommon::RegisterForCleanup(this, internal_); + } +} + +ListResult::~ListResult() { + internal::ListResultInternalCommon::DeleteInternal(this); +} + +ListResult::ListResult(const ListResult& other) : internal_(nullptr) { + if (other.internal_) { + internal::StorageReferenceInternal* sri_context = + other.internal_->storage_reference_internal(); + internal_ = new internal::ListResultInternal(sri_context, other.internal_); + internal::ListResultInternalCommon::RegisterForCleanup(this, internal_); + } +} + +ListResult& ListResult::operator=(const ListResult& other) { + if (this == &other) { + return *this; + } + internal::ListResultInternalCommon::DeleteInternal(this); + + if (other.internal_) { + internal::StorageReferenceInternal* sri_context = + other.internal_->storage_reference_internal(); + internal_ = new internal::ListResultInternal(sri_context, other.internal_); + internal::ListResultInternalCommon::RegisterForCleanup(this, internal_); + } + return *this; +} + +#if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) +ListResult::ListResult(ListResult&& other) : internal_(other.internal_) { + other.internal_ = nullptr; + if (internal_) { + // The new public object (this) takes ownership of the PIMPL. + // Unregister the old public object (other) from cleanup. + internal::ListResultInternalCommon::UnregisterFromCleanup(&other, + internal_); + // Register the new public object (this) for cleanup. + internal::ListResultInternalCommon::RegisterForCleanup(this, internal_); + } +} + +ListResult& ListResult::operator=(ListResult&& other) { + if (this == &other) { + return *this; + } + internal::ListResultInternalCommon::DeleteInternal( + this); // Clean up current state. + + internal_ = other.internal_; + other.internal_ = nullptr; + + if (internal_) { + // Similar to move constructor logic. + internal::ListResultInternalCommon::UnregisterFromCleanup(&other, + internal_); + internal::ListResultInternalCommon::RegisterForCleanup(this, internal_); + } + return *this; +} +#endif // defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) + +const std::vector& ListResult::items() const { + if (!is_valid()) return s_empty_items_; + return internal_->items(); +} + +const std::vector& ListResult::prefixes() const { + if (!is_valid()) return s_empty_prefixes_; + return internal_->prefixes(); +} + +const std::string& ListResult::page_token() const { + if (!is_valid()) return s_empty_page_token_; + return internal_->page_token(); +} + +bool ListResult::is_valid() const { return internal_ != nullptr; } + +} // namespace storage +} // namespace firebase diff --git a/storage/src/common/storage_reference.cc b/storage/src/common/storage_reference.cc index 54bc6983b7..1c363d1648 100644 --- a/storage/src/common/storage_reference.cc +++ b/storage/src/common/storage_reference.cc @@ -15,9 +15,17 @@ #include "storage/src/include/firebase/storage/storage_reference.h" #include "app/src/assert.h" +#include "app/src/future_manager.h" +#include "app/src/include/firebase/internal/platform.h" +#include "firebase/storage/list_result.h" + +// Platform-specific ListResultInternal definition is no longer needed here, +// as StorageReferenceInternal (platform-specific) now handles ListResult +// creation. #ifdef __APPLE__ #include "TargetConditionals.h" +// Platform-specific StorageReferenceInternal is included below. #endif // __APPLE__ // StorageReference is defined in these 3 files, one implementation for each OS. @@ -248,5 +256,20 @@ Future StorageReference::PutFileLastResult() { bool StorageReference::is_valid() const { return internal_ != nullptr; } +Future StorageReference::ListAll() { + FIREBASE_ASSERT_RETURN(Future(), internal_->is_valid()); + return internal_->ListAll(); +} + +Future StorageReference::List(const char* page_token) { + FIREBASE_ASSERT_RETURN(Future(), internal_->is_valid()); + return internal_->List(page_token); +} + +Future StorageReference::List() { + // Simply call the List method that takes a page_token, with a nullptr. + return List(nullptr); +} + } // namespace storage } // namespace firebase diff --git a/storage/src/desktop/list_result_desktop.cc b/storage/src/desktop/list_result_desktop.cc new file mode 100644 index 0000000000..1f0d08c415 --- /dev/null +++ b/storage/src/desktop/list_result_desktop.cc @@ -0,0 +1,25 @@ +// File: storage/src/desktop/list_result_desktop.cc +#include "storage/src/desktop/list_result_desktop.h" + +namespace firebase { +namespace storage { +namespace internal { + +ListResultInternal::ListResultInternal( + StorageReferenceInternal* platform_sri, + const ListResultInternal* other_to_copy_from) + : platform_sri_(platform_sri) { + if (other_to_copy_from) { + items_ = other_to_copy_from->items_; + prefixes_ = other_to_copy_from->prefixes_; + page_token_ = other_to_copy_from->page_token_; + } + // If other_to_copy_from is null, members are default-initialized. +} + +// Note: Lifecycle of this PIMPL object is managed by the public ListResult +// class. + +} // namespace internal +} // namespace storage +} // namespace firebase diff --git a/storage/src/desktop/list_result_desktop.h b/storage/src/desktop/list_result_desktop.h new file mode 100644 index 0000000000..0bab1ba589 --- /dev/null +++ b/storage/src/desktop/list_result_desktop.h @@ -0,0 +1,65 @@ +// File: storage/src/desktop/list_result_desktop.h +#ifndef FIREBASE_STORAGE_CLIENT_CPP_SRC_DESKTOP_LIST_RESULT_DESKTOP_H_ +#define FIREBASE_STORAGE_CLIENT_CPP_SRC_DESKTOP_LIST_RESULT_DESKTOP_H_ + +#include +#include + +#include "firebase/storage/storage_reference.h" +#include "storage/src/desktop/storage_internal_desktop.h" +#include "storage/src/desktop/storage_reference_desktop.h" + +namespace firebase { +namespace storage { +namespace internal { + +/// Desktop platform's internal implementation for ListResult. +/// This class holds the data for a list operation specific to the desktop +/// platform. Its lifecycle is managed by the public ListResult class via static +/// helpers. +class ListResultInternal { + public: + /// Constructor. + /// @param[in] platform_sri The desktop StorageReferenceInternal this list + /// result + /// is associated with; used for context. + /// @param[in] other_to_copy_from If not null, initializes this instance by + /// copying data from other_to_copy_from. + explicit ListResultInternal( + StorageReferenceInternal* platform_sri, + const ListResultInternal* other_to_copy_from = nullptr); + + const std::vector& items() const { return items_; } + const std::vector& prefixes() const { return prefixes_; } + const std::string& page_token() const { return page_token_; } + + /// Provides access to the StorageReferenceInternal this object is associated + /// with. + StorageReferenceInternal* storage_reference_internal() const { + return platform_sri_; + } + + /// Provides access to the StorageInternal context, typically for cleanup + /// registration. + StorageInternal* associated_storage_internal() const { + return platform_sri_ ? platform_sri_->storage_internal() : nullptr; + } + + private: + // Disallow copy assignment; copy construction is handled via the constructor. + ListResultInternal& operator=(const ListResultInternal&); + + StorageReferenceInternal* + platform_sri_; // Associated StorageReference, not owned. + + // Data for list results (stubs are empty). + std::vector items_; + std::vector prefixes_; + std::string page_token_; +}; + +} // namespace internal +} // namespace storage +} // namespace firebase + +#endif // FIREBASE_STORAGE_CLIENT_CPP_SRC_DESKTOP_LIST_RESULT_DESKTOP_H_ diff --git a/storage/src/desktop/storage_reference_desktop.cc b/storage/src/desktop/storage_reference_desktop.cc index aa99863e3b..c7599b43cb 100644 --- a/storage/src/desktop/storage_reference_desktop.cc +++ b/storage/src/desktop/storage_reference_desktop.cc @@ -30,6 +30,7 @@ #include "app/src/thread.h" #include "storage/src/common/common_internal.h" #include "storage/src/desktop/controller_desktop.h" +#include "storage/src/desktop/list_result_desktop.h" #include "storage/src/desktop/metadata_desktop.h" #include "storage/src/desktop/storage_desktop.h" #include "storage/src/include/firebase/storage.h" @@ -698,6 +699,37 @@ ReferenceCountedFutureImpl* StorageReferenceInternal::future() { return storage_->future_manager().GetFutureApi(this); } +Future StorageReferenceInternal::ListAll() { + StorageReference self(this); + ReferenceCountedFutureImpl* ref_future = + future()->Alloc(kStorageReferenceFnCount); + Future future = MakeFuture(ref_future, self); + + internal::ListResultInternal* list_pimpl = + new internal::ListResultInternal(this, nullptr); + + ListResult result_to_complete(list_pimpl); + + ref_future->Complete(self.AsHandle(), kErrorNone, "", result_to_complete); + return future; +} + +Future StorageReferenceInternal::List(const char* page_token) { + StorageReference self(this); + ReferenceCountedFutureImpl* ref_future = + future()->Alloc(kStorageReferenceFnCount); + Future future = MakeFuture(ref_future, self); + + // page_token is ignored for stub. + internal::ListResultInternal* list_pimpl = + new internal::ListResultInternal(this, nullptr); + + ListResult result_to_complete(list_pimpl); + + ref_future->Complete(self.AsHandle(), kErrorNone, "", result_to_complete); + return future; +} + } // namespace internal } // namespace storage } // namespace firebase diff --git a/storage/src/desktop/storage_reference_desktop.h b/storage/src/desktop/storage_reference_desktop.h index bbda95d342..556b7c1079 100644 --- a/storage/src/desktop/storage_reference_desktop.h +++ b/storage/src/desktop/storage_reference_desktop.h @@ -152,6 +152,17 @@ class StorageReferenceInternal { // Exposed for testing. StorageReference AsStorageReference() const; + /// @brief Lists all items and prefixes under this reference (Desktop + /// implementation). + /// @return A Future that will be resolved with a ListResult. + virtual Future ListAll(); + + /// @brief Lists items and prefixes under this reference, with pagination + /// (Desktop implementation). + /// @param[in] page_token Token for the page of results to return. + /// @return A Future that will be resolved with a ListResult. + virtual Future List(const char* page_token); + private: // Function type that sends a Rest Request and returns the BlockingResponse. typedef std::function SendRequestFunct; diff --git a/storage/src/include/firebase/storage/list_result.h b/storage/src/include/firebase/storage/list_result.h new file mode 100644 index 0000000000..b03e30878b --- /dev/null +++ b/storage/src/include/firebase/storage/list_result.h @@ -0,0 +1,119 @@ +// File: storage/src/include/firebase/storage/list_result.h +#ifndef FIREBASE_STORAGE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_STORAGE_LIST_RESULT_H_ +#define FIREBASE_STORAGE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_STORAGE_LIST_RESULT_H_ + +#include +#include + +#include "firebase/internal/common.h" +#include "firebase/storage/common.h" + +namespace firebase { +namespace storage { + +// Forward declarations for internal classes. +namespace internal { +class ListResultInternal; +class ListResultInternalCommon; +class StorageReferenceInternal; +} // namespace internal + +class StorageReference; // Forward declaration + +/// @brief Holds the results of a list operation, such as +/// StorageReference::List() or StorageReference::ListAll(). +/// +/// This class provides access to the items (files) and prefixes (directories) +/// found under a given StorageReference, as well as a page token for pagination +/// if the results are not complete. +class ListResult { + public: + /// @brief Default constructor. Creates an empty and invalid ListResult. + /// + /// A valid ListResult is typically obtained from the Future returned by + /// StorageReference::List() or StorageReference::ListAll(). + ListResult(); + + /// @brief Copy constructor. + /// @param[in] other ListResult to copy the contents from. + ListResult(const ListResult& other); + + /// @brief Copy assignment operator. + /// @param[in] other ListResult to copy the contents from. + /// @return Reference to this ListResult. + ListResult& operator=(const ListResult& other); + +#if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) + /// @brief Move constructor. + /// @param[in] other ListResult to move the contents from. `other` is left in + /// a valid but unspecified state. + ListResult(ListResult&& other); + + /// @brief Move assignment operator. + /// @param[in] other ListResult to move the contents from. `other` is left in + /// a valid but unspecified state. + /// @return Reference to this ListResult. + ListResult& operator=(ListResult&& other); +#endif // defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) + + /// @brief Destructor. + ~ListResult(); + + /// @brief Gets the individual items (files) found in this result. + /// + /// @return Vector of StorageReferences to the items. Will be an empty + /// vector if no items are found or if this ListResult is invalid. + const std::vector& items() const; + + /// @brief Gets the prefixes (directories) found in this result. + /// These can be used to further "navigate" the storage hierarchy by calling + /// List() or ListAll() on them. + /// + /// @return Vector of StorageReferences to the prefixes. Will be an empty + /// vector if no prefixes are found or if this ListResult is invalid. + const std::vector& prefixes() const; + + /// @brief Gets the page token for the next page of results. + /// + /// If the string is empty, it indicates that there are no more results + /// for the current list operation (i.e., this is the last page). This token + /// can be passed to StorageReference::List() to retrieve the next page. + /// + /// @return Page token string. Empty if no more results or if this + /// ListResult is invalid. + const std::string& page_token() const; + + /// @brief Returns true if this ListResult object is valid, false otherwise. + /// + /// An invalid ListResult is typically one that was default-constructed + /// and not subsequently assigned a valid result from a list operation, + /// or one that has been moved from. Operations on an invalid ListResult + /// will typically return default values (e.g., empty vectors or strings). + /// + /// @return true if this ListResult is valid and represents a result from + /// a list operation (even if that result is empty); false otherwise. + bool is_valid() const; + + private: + friend class StorageReference; + friend class internal::StorageReferenceInternal; + // Allows ListResultInternalCommon to manage the lifecycle of internal_. + friend class internal::ListResultInternalCommon; + + // Private constructor for creating a ListResult with an PIMPL object. + // Takes ownership of the provided internal_pimpl. + explicit ListResult(internal::ListResultInternal* internal_pimpl); + + internal::ListResultInternal* + internal_; // Pointer to the internal implementation. + + // Static empty results for returning from an invalid ListResult. + static const std::vector s_empty_items_; + static const std::vector s_empty_prefixes_; + static const std::string s_empty_page_token_; +}; + +} // namespace storage +} // namespace firebase + +#endif // FIREBASE_STORAGE_CLIENT_CPP_SRC_INCLUDE_FIREBASE_STORAGE_LIST_RESULT_H_ diff --git a/storage/src/include/firebase/storage/storage_reference.h b/storage/src/include/firebase/storage/storage_reference.h index e5c7c2f85a..a39e9959c2 100644 --- a/storage/src/include/firebase/storage/storage_reference.h +++ b/storage/src/include/firebase/storage/storage_reference.h @@ -227,6 +227,44 @@ class StorageReference { /// @returns The result of the most recent call to UpdateMetadata(); Future UpdateMetadataLastResult(); + // Lists the items and prefixes under this StorageReference. + /// @brief Lists all items (files) and prefixes (directories) under this + /// StorageReference. + /// + /// This is a helper method for calling List() repeatedly until there are + /// no more results. Consistency of the result is not guaranteed if objects + /// are updated while this operation is running. All results are buffered in + /// memory. + /// + /// @return A Future that will be resolved with a ListResult containing all + /// items and prefixes under the current StorageReference. For stubs, this + /// will be an empty ListResult. + Future ListAll(); + + /// @brief Lists items (files) and prefixes (directories) under this + /// StorageReference. + /// + /// This method allows for pagination. + /// + /// @param[in] page_token A token indicating the page of results to return. + /// If nullptr or an empty string, the first page of results will be returned. + /// The token is obtained from a previous ListResult::page_token(). + /// + /// @return A Future that will be resolved with a ListResult containing + /// items and prefixes for the specified page. For stubs, this will be an + /// empty ListResult. + Future List(const char* page_token); + + /// @brief Lists the first page of items (files) and prefixes (directories) + /// under this StorageReference. + /// + /// This is an overload of List() that omits the page_token, effectively + /// requesting the first page of results. + /// + /// @return A Future that will be resolved with a ListResult. For stubs, + /// this will be an empty ListResult. + Future List(); + /// @brief Returns the short name of this object. /// /// @returns the short name of this object. diff --git a/storage/src/ios/list_result_ios.h b/storage/src/ios/list_result_ios.h new file mode 100644 index 0000000000..1544a0aa11 --- /dev/null +++ b/storage/src/ios/list_result_ios.h @@ -0,0 +1,46 @@ +// File: storage/src/ios/list_result_ios.h +#ifndef FIREBASE_STORAGE_CLIENT_CPP_SRC_IOS_LIST_RESULT_IOS_H_ +#define FIREBASE_STORAGE_CLIENT_CPP_SRC_IOS_LIST_RESULT_IOS_H_ + +#include +#include + +#include "firebase/storage/storage_reference.h" +#include "storage/src/ios/storage_reference_ios.h" +#include "storage/src/ios/storage_internal_ios.h" + +namespace firebase { +namespace storage { +namespace internal { + +class ListResultInternal { + public: + explicit ListResultInternal( + StorageReferenceInternal* platform_sri, + const ListResultInternal* other_to_copy_from = nullptr); + + const std::vector& items() const { return items_; } + const std::vector& prefixes() const { return prefixes_; } + const std::string& page_token() const { return page_token_; } + + StorageReferenceInternal* storage_reference_internal() const { + return platform_sri_; + } + StorageInternal* associated_storage_internal() const { + return platform_sri_ ? platform_sri_->storage_internal() : nullptr; + } + + private: + ListResultInternal& operator=(const ListResultInternal&); + + StorageReferenceInternal* platform_sri_; + std::vector items_; + std::vector prefixes_; + std::string page_token_; +}; + +} // namespace internal +} // namespace storage +} // namespace firebase + +#endif // FIREBASE_STORAGE_CLIENT_CPP_SRC_IOS_LIST_RESULT_IOS_H_ diff --git a/storage/src/ios/list_result_ios.mm b/storage/src/ios/list_result_ios.mm new file mode 100644 index 0000000000..0fcbfe6eb0 --- /dev/null +++ b/storage/src/ios/list_result_ios.mm @@ -0,0 +1,23 @@ +// File: storage/src/ios/list_result_ios.mm +#import "storage/src/ios/list_result_ios.h" + +// Other necessary Objective-C or C++ includes for iOS if any. + +namespace firebase { +namespace storage { +namespace internal { + +ListResultInternal::ListResultInternal( + StorageReferenceInternal* platform_sri, + const ListResultInternal* other_to_copy_from) + : platform_sri_(platform_sri) { + if (other_to_copy_from) { + items_ = other_to_copy_from->items_; + prefixes_ = other_to_copy_from->prefixes_; + page_token_ = other_to_copy_from->page_token_; + } +} + +} // namespace internal +} // namespace storage +} // namespace firebase diff --git a/storage/src/ios/storage_reference_ios.h b/storage/src/ios/storage_reference_ios.h index c8b39cd54c..e852bdd96c 100644 --- a/storage/src/ios/storage_reference_ios.h +++ b/storage/src/ios/storage_reference_ios.h @@ -21,6 +21,7 @@ #include "app/src/reference_counted_future_impl.h" #include "app/src/util_ios.h" #include "storage/src/ios/storage_ios.h" +#include "firebase/storage/list_result.h" #ifdef __OBJC__ #import "FirebaseStorage-Swift.h" @@ -163,6 +164,15 @@ class StorageReferenceInternal { // StorageInternal instance we are associated with. StorageInternal* _Nullable storage_internal() const { return storage_; } + /// @brief Lists all items and prefixes under this reference (iOS implementation). + /// @return A Future that will be resolved with a ListResult. + virtual Future ListAll(); + + /// @brief Lists items and prefixes under this reference, with pagination (iOS implementation). + /// @param[in] page_token Token for the page of results to return. + /// @return A Future that will be resolved with a ListResult. + virtual Future List(const char* page_token); + private: #ifdef __OBJC__ void AssignListener( diff --git a/storage/src/ios/storage_reference_ios.mm b/storage/src/ios/storage_reference_ios.mm index d2260e3416..6b4f6fe48e 100644 --- a/storage/src/ios/storage_reference_ios.mm +++ b/storage/src/ios/storage_reference_ios.mm @@ -27,6 +27,7 @@ #include "storage/src/ios/metadata_ios.h" #include "storage/src/ios/storage_ios.h" #include "storage/src/ios/util_ios.h" +#import "storage/src/ios/list_result_ios.h" namespace firebase { namespace storage { @@ -442,6 +443,37 @@ return storage_->future_manager().GetFutureApi(this); } +Future StorageReferenceInternal::ListAll() { + StorageReference self(this); // Public self for future context + ReferenceCountedFutureImpl* ref_future = + future()->Alloc(kStorageReferenceFnCount); + Future future = MakeFuture(ref_future, self); + + internal::ListResultInternal* list_pimpl = + new internal::ListResultInternal(this, nullptr); // 'this' is StorageReferenceInternal* (iOS) + + ListResult result_to_complete(list_pimpl); + + ref_future->Complete(self.AsHandle(), kErrorNone, "", result_to_complete); + return future; +} + +Future StorageReferenceInternal::List(const char* page_token) { + StorageReference self(this); // Public self for future context + ReferenceCountedFutureImpl* ref_future = + future()->Alloc(kStorageReferenceFnCount); + Future future = MakeFuture(ref_future, self); + + // page_token is ignored for stub. + internal::ListResultInternal* list_pimpl = + new internal::ListResultInternal(this, nullptr); + + ListResult result_to_complete(list_pimpl); + + ref_future->Complete(self.AsHandle(), kErrorNone, "", result_to_complete); + return future; +} + } // namespace internal } // namespace storage } // namespace firebase