Skip to content

Commit 989eb79

Browse files
I've addressed the review comments for the Storage List API.
Here's a summary of the changes: - Integration Tests: - I removed `SKIP_TEST_ON_ANDROID_EMULATOR` from the `ListAllBasic` and `ListPaginated` tests. - I refactored the list tests so they create their own unique root folders instead of using a shared one in the test fixture. This improves test isolation. - Code Style: - I removed unnecessary comments that were explaining header includes. - I removed placeholder comments from public headers. - I updated the copyright year to 2025 in all newly added files. - Android Performance: - I optimized Android's `ListResultInternal` to cache converted items, prefixes, and the page token. This will avoid repeated JNI calls on subsequent accesses. - Cleanup Mechanism: - I simplified the `ListResult` cleanup by removing the `ListResultInternalCommon` class. `ListResult` now directly manages the cleanup registration of its `internal_` member, similar to `StorageReference`.
1 parent 5e9fc6b commit 989eb79

File tree

8 files changed

+211
-269
lines changed

8 files changed

+211
-269
lines changed

storage/integration_test/src/integration_test.cc

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,6 @@ class FirebaseStorageTest : public FirebaseTest {
101101
void TearDown() override;
102102

103103
protected:
104-
// Root reference for list tests.
105-
firebase::storage::StorageReference list_test_root_;
106-
107104
// Initialize Firebase App and Firebase Auth.
108105
static void InitializeAppAndAuth();
109106
// Shut down Firebase App and Firebase Auth.
@@ -230,16 +227,7 @@ void FirebaseStorageTest::TerminateAppAndAuth() {
230227
void FirebaseStorageTest::SetUp() {
231228
FirebaseTest::SetUp();
232229
InitializeStorage();
233-
if (storage_ != nullptr && storage_->GetReference().is_valid()) {
234-
list_test_root_ = CreateFolder().Child("list_tests_root");
235-
// list_test_root_ itself doesn't need to be in cleanup_files_ if its parent from CreateFolder() is.
236-
// However, specific files/folders created under list_test_root_ for each test *will* be added
237-
// via UploadStringAsFile or by explicitly adding the parent of a set of files for that test.
238-
} else {
239-
// Handle cases where storage might not be initialized (e.g. if InitializeStorage fails)
240-
// by providing a default, invalid reference.
241-
list_test_root_ = firebase::storage::StorageReference();
242-
}
230+
// list_test_root_ removed from SetUp
243231
}
244232

245233
void FirebaseStorageTest::TearDown() {
@@ -1707,23 +1695,21 @@ TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingApp) {
17071695
}
17081696

17091697
TEST_F(FirebaseStorageTest, ListAllBasic) {
1710-
SKIP_TEST_ON_ANDROID_EMULATOR; // List tests can be slow on emulators or have quota issues.
1698+
// SKIP_TEST_ON_ANDROID_EMULATOR; // Removed
17111699
SignIn();
1712-
ASSERT_TRUE(list_test_root_.is_valid()) << "List test root is not valid.";
1700+
firebase::storage::StorageReference test_root = CreateFolder().Child("list_all_basic_root");
1701+
ASSERT_TRUE(test_root.is_valid()) << "Test root for ListAllBasic is not valid.";
17131702

1714-
firebase::storage::StorageReference list_all_base =
1715-
list_test_root_.Child("list_all_basic_test");
1716-
// cleanup_files_.push_back(list_all_base); // Not a file, its contents are files.
17171703

1718-
UploadStringAsFile(list_all_base.Child("file_a.txt"), "content_a");
1719-
UploadStringAsFile(list_all_base.Child("file_b.txt"), "content_b");
1720-
UploadStringAsFile(list_all_base.Child("prefix1/file_c.txt"), "content_c_in_prefix1");
1721-
UploadStringAsFile(list_all_base.Child("prefix2/file_e.txt"), "content_e_in_prefix2");
1704+
UploadStringAsFile(test_root.Child("file_a.txt"), "content_a");
1705+
UploadStringAsFile(test_root.Child("file_b.txt"), "content_b");
1706+
UploadStringAsFile(test_root.Child("prefix1/file_c.txt"), "content_c_in_prefix1");
1707+
UploadStringAsFile(test_root.Child("prefix2/file_e.txt"), "content_e_in_prefix2");
17221708

1723-
LogDebug("Calling ListAll() on gs://%s%s", list_all_base.bucket().c_str(),
1724-
list_all_base.full_path().c_str());
1709+
LogDebug("Calling ListAll() on gs://%s%s", test_root.bucket().c_str(),
1710+
test_root.full_path().c_str());
17251711
firebase::Future<firebase::storage::ListResult> future =
1726-
list_all_base.ListAll();
1712+
test_root.ListAll();
17271713
WaitForCompletion(future, "ListAllBasic");
17281714

17291715
ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
@@ -1737,20 +1723,18 @@ TEST_F(FirebaseStorageTest, ListAllBasic) {
17371723
}
17381724

17391725
TEST_F(FirebaseStorageTest, ListPaginated) {
1740-
SKIP_TEST_ON_ANDROID_EMULATOR;
1726+
// SKIP_TEST_ON_ANDROID_EMULATOR; // Removed
17411727
SignIn();
1742-
ASSERT_TRUE(list_test_root_.is_valid()) << "List test root is not valid.";
1728+
firebase::storage::StorageReference test_root = CreateFolder().Child("list_paginated_root");
1729+
ASSERT_TRUE(test_root.is_valid()) << "Test root for ListPaginated is not valid.";
17431730

1744-
firebase::storage::StorageReference list_paginated_base =
1745-
list_test_root_.Child("list_paginated_test");
1746-
// cleanup_files_.push_back(list_paginated_base);
17471731

17481732
// Expected total entries: file_aa.txt, file_bb.txt, file_ee.txt, prefix_x/, prefix_y/ (5 entries)
1749-
UploadStringAsFile(list_paginated_base.Child("file_aa.txt"), "content_aa");
1750-
UploadStringAsFile(list_paginated_base.Child("prefix_x/file_cc.txt"), "content_cc_in_prefix_x");
1751-
UploadStringAsFile(list_paginated_base.Child("file_bb.txt"), "content_bb");
1752-
UploadStringAsFile(list_paginated_base.Child("prefix_y/file_dd.txt"), "content_dd_in_prefix_y");
1753-
UploadStringAsFile(list_paginated_base.Child("file_ee.txt"), "content_ee");
1733+
UploadStringAsFile(test_root.Child("file_aa.txt"), "content_aa");
1734+
UploadStringAsFile(test_root.Child("prefix_x/file_cc.txt"), "content_cc_in_prefix_x");
1735+
UploadStringAsFile(test_root.Child("file_bb.txt"), "content_bb");
1736+
UploadStringAsFile(test_root.Child("prefix_y/file_dd.txt"), "content_dd_in_prefix_y");
1737+
UploadStringAsFile(test_root.Child("file_ee.txt"), "content_ee");
17541738

17551739

17561740
std::vector<std::string> all_item_names_collected;
@@ -1761,14 +1745,14 @@ TEST_F(FirebaseStorageTest, ListPaginated) {
17611745
const int max_pages = 5; // Safety break for loop
17621746

17631747
LogDebug("Starting paginated List() on gs://%s%s with page_size %d",
1764-
list_paginated_base.bucket().c_str(), list_paginated_base.full_path().c_str(), page_size);
1748+
test_root.bucket().c_str(), test_root.full_path().c_str(), page_size);
17651749

17661750
do {
17671751
page_count++;
17681752
LogDebug("Fetching page %d, token: '%s'", page_count, page_token.c_str());
17691753
firebase::Future<firebase::storage::ListResult> future =
1770-
page_token.empty() ? list_paginated_base.List(page_size)
1771-
: list_paginated_base.List(page_size, page_token.c_str());
1754+
page_token.empty() ? test_root.List(page_size)
1755+
: test_root.List(page_size, page_token.c_str());
17721756
WaitForCompletion(future, "ListPaginated - Page " + std::to_string(page_count));
17731757

17741758
ASSERT_EQ(future.error(), firebase::storage::kErrorNone) << future.error_message();
@@ -1823,19 +1807,17 @@ TEST_F(FirebaseStorageTest, ListPaginated) {
18231807

18241808

18251809
TEST_F(FirebaseStorageTest, ListEmpty) {
1826-
SKIP_TEST_ON_ANDROID_EMULATOR;
1810+
// SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed as it's a lightweight test.
18271811
SignIn();
1828-
ASSERT_TRUE(list_test_root_.is_valid()) << "List test root is not valid.";
1812+
firebase::storage::StorageReference test_root = CreateFolder().Child("list_empty_root");
1813+
ASSERT_TRUE(test_root.is_valid()) << "Test root for ListEmpty is not valid.";
18291814

1830-
firebase::storage::StorageReference list_empty_ref =
1831-
list_test_root_.Child("list_empty_folder_test");
1832-
// Do not upload anything to this reference.
1833-
// cleanup_files_.push_back(list_empty_ref); // Not a file
1815+
// Do not upload anything to test_root.
18341816

18351817
LogDebug("Calling ListAll() on empty folder: gs://%s%s",
1836-
list_empty_ref.bucket().c_str(), list_empty_ref.full_path().c_str());
1818+
test_root.bucket().c_str(), test_root.full_path().c_str());
18371819
firebase::Future<firebase::storage::ListResult> future =
1838-
list_empty_ref.ListAll();
1820+
test_root.ListAll();
18391821
WaitForCompletion(future, "ListEmpty");
18401822

18411823
ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
@@ -1848,22 +1830,20 @@ TEST_F(FirebaseStorageTest, ListEmpty) {
18481830
}
18491831

18501832
TEST_F(FirebaseStorageTest, ListWithMaxResultsGreaterThanActual) {
1851-
SKIP_TEST_ON_ANDROID_EMULATOR;
1833+
// SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed.
18521834
SignIn();
1853-
ASSERT_TRUE(list_test_root_.is_valid()) << "List test root is not valid.";
1835+
firebase::storage::StorageReference test_root = CreateFolder().Child("list_max_greater_root");
1836+
ASSERT_TRUE(test_root.is_valid()) << "Test root for ListWithMaxResultsGreaterThanActual is not valid.";
18541837

1855-
firebase::storage::StorageReference list_max_greater_base =
1856-
list_test_root_.Child("list_max_greater_test");
1857-
// cleanup_files_.push_back(list_max_greater_base);
18581838

1859-
UploadStringAsFile(list_max_greater_base.Child("only_file.txt"), "content_only");
1860-
UploadStringAsFile(list_max_greater_base.Child("only_prefix/another.txt"), "content_another_in_prefix");
1839+
UploadStringAsFile(test_root.Child("only_file.txt"), "content_only");
1840+
UploadStringAsFile(test_root.Child("only_prefix/another.txt"), "content_another_in_prefix");
18611841

18621842
LogDebug("Calling List(10) on gs://%s%s",
1863-
list_max_greater_base.bucket().c_str(),
1864-
list_max_greater_base.full_path().c_str());
1843+
test_root.bucket().c_str(),
1844+
test_root.full_path().c_str());
18651845
firebase::Future<firebase::storage::ListResult> future =
1866-
list_max_greater_base.List(10); // Max results (10) > actual (1 file + 1 prefix = 2)
1846+
test_root.List(10); // Max results (10) > actual (1 file + 1 prefix = 2)
18671847
WaitForCompletion(future, "ListWithMaxResultsGreaterThanActual");
18681848

18691849
ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
@@ -1876,19 +1856,20 @@ TEST_F(FirebaseStorageTest, ListWithMaxResultsGreaterThanActual) {
18761856
}
18771857

18781858
TEST_F(FirebaseStorageTest, ListNonExistentPath) {
1879-
SKIP_TEST_ON_ANDROID_EMULATOR;
1859+
// SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed.
18801860
SignIn();
1881-
ASSERT_TRUE(list_test_root_.is_valid()) << "List test root is not valid.";
1861+
firebase::storage::StorageReference test_root = CreateFolder().Child("list_non_existent_parent_root");
1862+
ASSERT_TRUE(test_root.is_valid()) << "Test root for ListNonExistentPath is not valid.";
18821863

1883-
firebase::storage::StorageReference list_non_existent_ref =
1884-
list_test_root_.Child("this_folder_does_not_exist_for_list_test");
1864+
firebase::storage::StorageReference non_existent_ref =
1865+
test_root.Child("this_folder_truly_does_not_exist");
18851866
// No cleanup needed as nothing is created.
18861867

18871868
LogDebug("Calling ListAll() on non-existent path: gs://%s%s",
1888-
list_non_existent_ref.bucket().c_str(),
1889-
list_non_existent_ref.full_path().c_str());
1869+
non_existent_ref.bucket().c_str(),
1870+
non_existent_ref.full_path().c_str());
18901871
firebase::Future<firebase::storage::ListResult> future =
1891-
list_non_existent_ref.ListAll();
1872+
non_existent_ref.ListAll();
18921873
WaitForCompletion(future, "ListNonExistentPath");
18931874

18941875
// Listing a non-existent path should not be an error, it's just an empty list.

storage/src/android/list_result_android.cc

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 Google LLC
1+
// Copyright 2025 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -17,8 +17,8 @@
1717
#include "app/src/include/firebase/app.h"
1818
#include "app/src/util_android.h"
1919
#include "storage/src/android/storage_android.h"
20-
#include "storage/src/android/storage_reference_android.h" // For StorageReferenceInternal constructor
21-
#include "storage/src/common/common_android.h" // For METHOD_LOOKUP_DECLARATION/DEFINITION
20+
#include "storage/src/android/storage_reference_android.h"
21+
#include "storage/src/common/common_android.h"
2222

2323
namespace firebase {
2424
namespace storage {
@@ -64,7 +64,11 @@ void ListResultInternal::Terminate(App* app) {
6464

6565
ListResultInternal::ListResultInternal(StorageInternal* storage_internal,
6666
jobject java_list_result)
67-
: storage_internal_(storage_internal), list_result_java_ref_(nullptr) {
67+
: storage_internal_(storage_internal),
68+
list_result_java_ref_(nullptr),
69+
items_converted_(false),
70+
prefixes_converted_(false),
71+
page_token_converted_(false) {
6872
FIREBASE_ASSERT(storage_internal != nullptr);
6973
FIREBASE_ASSERT(java_list_result != nullptr);
7074
JNIEnv* env = storage_internal_->app()->GetJNIEnv();
@@ -73,7 +77,13 @@ ListResultInternal::ListResultInternal(StorageInternal* storage_internal,
7377

7478
ListResultInternal::ListResultInternal(const ListResultInternal& other)
7579
: storage_internal_(other.storage_internal_),
76-
list_result_java_ref_(nullptr) {
80+
list_result_java_ref_(nullptr),
81+
items_cache_(other.items_cache_), // Copy cache
82+
prefixes_cache_(other.prefixes_cache_), // Copy cache
83+
page_token_cache_(other.page_token_cache_), // Copy cache
84+
items_converted_(other.items_converted_),
85+
prefixes_converted_(other.prefixes_converted_),
86+
page_token_converted_(other.page_token_converted_) {
7787
FIREBASE_ASSERT(storage_internal_ != nullptr);
7888
JNIEnv* env = storage_internal_->app()->GetJNIEnv();
7989
if (other.list_result_java_ref_ != nullptr) {
@@ -96,6 +106,13 @@ ListResultInternal& ListResultInternal::operator=(
96106
if (other.list_result_java_ref_ != nullptr) {
97107
list_result_java_ref_ = env->NewGlobalRef(other.list_result_java_ref_);
98108
}
109+
// Copy cache state
110+
items_cache_ = other.items_cache_;
111+
prefixes_cache_ = other.prefixes_cache_;
112+
page_token_cache_ = other.page_token_cache_;
113+
items_converted_ = other.items_converted_;
114+
prefixes_converted_ = other.prefixes_converted_;
115+
page_token_converted_ = other.page_token_converted_;
99116
return *this;
100117
}
101118

@@ -142,7 +159,10 @@ std::vector<StorageReference> ListResultInternal::ProcessJavaReferenceList(
142159
}
143160

144161
std::vector<StorageReference> ListResultInternal::items() const {
145-
if (!list_result_java_ref_) return {};
162+
if (!list_result_java_ref_) return items_cache_; // Return empty if no ref
163+
if (items_converted_) {
164+
return items_cache_;
165+
}
146166

147167
JNIEnv* env = storage_internal_->app()->GetJNIEnv();
148168
jobject java_items_list = env->CallObjectMethod(
@@ -151,17 +171,23 @@ std::vector<StorageReference> ListResultInternal::items() const {
151171
env->ExceptionClear();
152172
LogError("Failed to call getItems() on Java ListResult");
153173
if (java_items_list) env->DeleteLocalRef(java_items_list);
154-
return {};
174+
// In case of error, still mark as "converted" to avoid retrying JNI call,
175+
// return whatever might be in cache (empty at this point).
176+
items_converted_ = true;
177+
return items_cache_;
155178
}
156179

157-
std::vector<StorageReference> items_vector =
158-
ProcessJavaReferenceList(java_items_list);
180+
items_cache_ = ProcessJavaReferenceList(java_items_list);
159181
env->DeleteLocalRef(java_items_list);
160-
return items_vector;
182+
items_converted_ = true;
183+
return items_cache_;
161184
}
162185

163186
std::vector<StorageReference> ListResultInternal::prefixes() const {
164-
if (!list_result_java_ref_) return {};
187+
if (!list_result_java_ref_) return prefixes_cache_;
188+
if (prefixes_converted_) {
189+
return prefixes_cache_;
190+
}
165191

166192
JNIEnv* env = storage_internal_->app()->GetJNIEnv();
167193
jobject java_prefixes_list = env->CallObjectMethod(
@@ -170,17 +196,21 @@ std::vector<StorageReference> ListResultInternal::prefixes() const {
170196
env->ExceptionClear();
171197
LogError("Failed to call getPrefixes() on Java ListResult");
172198
if (java_prefixes_list) env->DeleteLocalRef(java_prefixes_list);
173-
return {};
199+
prefixes_converted_ = true;
200+
return prefixes_cache_;
174201
}
175202

176-
std::vector<StorageReference> prefixes_vector =
177-
ProcessJavaReferenceList(java_prefixes_list);
203+
prefixes_cache_ = ProcessJavaReferenceList(java_prefixes_list);
178204
env->DeleteLocalRef(java_prefixes_list);
179-
return prefixes_vector;
205+
prefixes_converted_ = true;
206+
return prefixes_cache_;
180207
}
181208

182209
std::string ListResultInternal::page_token() const {
183-
if (!list_result_java_ref_) return "";
210+
if (!list_result_java_ref_) return page_token_cache_;
211+
if (page_token_converted_) {
212+
return page_token_cache_;
213+
}
184214

185215
JNIEnv* env = storage_internal_->app()->GetJNIEnv();
186216
jstring page_token_jstring = static_cast<jstring>(env->CallObjectMethod(
@@ -189,12 +219,19 @@ std::string ListResultInternal::page_token() const {
189219
env->ExceptionClear();
190220
LogError("Failed to call getPageToken() on Java ListResult");
191221
if (page_token_jstring) env->DeleteLocalRef(page_token_jstring);
192-
return "";
222+
page_token_converted_ = true;
223+
return page_token_cache_; // Return empty if error
224+
}
225+
226+
if (page_token_jstring != nullptr) {
227+
page_token_cache_ = util::JniStringToString(env, page_token_jstring);
228+
env->DeleteLocalRef(page_token_jstring);
229+
} else {
230+
page_token_cache_ = ""; // Explicitly set to empty if Java string is null
193231
}
194232

195-
std::string page_token_std_string = util::JniStringToString(env, page_token_jstring);
196-
if (page_token_jstring) env->DeleteLocalRef(page_token_jstring);
197-
return page_token_std_string;
233+
page_token_converted_ = true;
234+
return page_token_cache_;
198235
}
199236

200237
} // namespace internal

storage/src/android/list_result_android.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 Google LLC
1+
// Copyright 2025 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -92,6 +92,14 @@ class ListResultInternal {
9292
StorageInternal* storage_internal_; // Not owned.
9393
// Global reference to Java com.google.firebase.storage.ListResult object.
9494
jobject list_result_java_ref_;
95+
96+
// Caches for converted data
97+
mutable std::vector<StorageReference> items_cache_;
98+
mutable std::vector<StorageReference> prefixes_cache_;
99+
mutable std::string page_token_cache_;
100+
mutable bool items_converted_;
101+
mutable bool prefixes_converted_;
102+
mutable bool page_token_converted_;
95103
};
96104

97105
} // namespace internal

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy