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)