Skip to content

Commit 48697c7

Browse files
committed
Feat: storage add in list command
1 parent 5f348b8 commit 48697c7

15 files changed

Lines changed: 688 additions & 0 deletions

storage/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ set(common_SRCS
1919
src/common/common.cc
2020
src/common/controller.cc
2121
src/common/listener.cc
22+
src/common/list_result.cc
2223
src/common/metadata.cc
2324
src/common/storage.cc
2425
src/common/storage_reference.cc

storage/integration_test/src/integration_test.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,71 @@ TEST_F(FirebaseStorageTest, TestDeleteFile) {
744744
firebase::storage::kErrorObjectNotFound);
745745
}
746746

747+
TEST_F(FirebaseStorageTest, TestList) {
748+
SignIn();
749+
750+
firebase::storage::StorageReference ref = CreateFolder().Child("list_test");
751+
752+
// Upload a few files
753+
const char* file_contents[] = {"file0", "file1", "file2"};
754+
for (int i = 0; i < 3; ++i) {
755+
char file_name[16];
756+
snprintf(file_name, sizeof(file_name), "file_%d.txt", i);
757+
firebase::storage::StorageReference file_ref = ref.Child(file_name);
758+
firebase::Future<firebase::storage::Metadata> put_future =
759+
file_ref.PutBytes(file_contents[i], strlen(file_contents[i]));
760+
WaitForCompletion(put_future, "PutBytes");
761+
EXPECT_EQ(put_future.error(), firebase::storage::kErrorNone);
762+
cleanup_files_.push_back(file_ref);
763+
}
764+
765+
// Upload to a subfolder to test prefixes
766+
firebase::storage::StorageReference subfolder_ref = ref.Child("subfolder/sub_file.txt");
767+
firebase::Future<firebase::storage::Metadata> subfolder_put_future =
768+
subfolder_ref.PutBytes("sub", 3);
769+
WaitForCompletion(subfolder_put_future, "PutBytes subfolder");
770+
EXPECT_EQ(subfolder_put_future.error(), firebase::storage::kErrorNone);
771+
cleanup_files_.push_back(subfolder_ref);
772+
773+
// Test List
774+
LogDebug("Testing List");
775+
firebase::Future<firebase::storage::StorageListResult> list_future = ref.List();
776+
WaitForCompletionAnyResult(list_future, "List");
777+
778+
if (list_future.error() != firebase::storage::kErrorNone) {
779+
if (list_future.error() == firebase::storage::kErrorUnknown) {
780+
LogWarning("Skipping TestList as the test project is likely using an older rules_version.");
781+
return;
782+
}
783+
EXPECT_EQ(list_future.error(), firebase::storage::kErrorNone);
784+
}
785+
786+
if (list_future.error() != firebase::storage::kErrorNone) return;
787+
788+
firebase::storage::StorageListResult result = *list_future.result();
789+
EXPECT_EQ(result.items().size(), 3);
790+
EXPECT_EQ(result.prefixes().size(), 1);
791+
EXPECT_EQ(result.next_page_token(), "");
792+
793+
// Test Pagination
794+
LogDebug("Testing List Pagination");
795+
firebase::Future<firebase::storage::StorageListResult> page1_future = ref.List(2);
796+
WaitForCompletionAnyResult(page1_future, "List Page 1");
797+
EXPECT_EQ(page1_future.error(), firebase::storage::kErrorNone);
798+
799+
firebase::storage::StorageListResult page1_result = *page1_future.result();
800+
EXPECT_TRUE(page1_result.items().size() + page1_result.prefixes().size() == 2);
801+
EXPECT_NE(page1_result.next_page_token(), "");
802+
803+
firebase::Future<firebase::storage::StorageListResult> page2_future = ref.List(2, page1_result.next_page_token().c_str());
804+
WaitForCompletionAnyResult(page2_future, "List Page 2");
805+
EXPECT_EQ(page2_future.error(), firebase::storage::kErrorNone);
806+
807+
firebase::storage::StorageListResult page2_result = *page2_future.result();
808+
EXPECT_TRUE(page2_result.items().size() + page2_result.prefixes().size() == 2);
809+
EXPECT_EQ(page2_result.next_page_token(), "");
810+
}
811+
747812
// Only test retries on desktop since Android and iOS don't have an option
748813
// to retry file-not-found errors and just pass-through to native
749814
// implementations.

storage/src/android/storage_reference_android.cc

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ namespace internal {
8686
"Lcom/google/firebase/storage/FileDownloadTask;"), \
8787
X(Delete, "delete", \
8888
"()Lcom/google/android/gms/tasks/Task;"), \
89+
X(List, "list", \
90+
"(I)Lcom/google/android/gms/tasks/Task;"), \
91+
X(ListWithToken, "list", \
92+
"(ILjava/lang/String;)Lcom/google/android/gms/tasks/Task;"), \
8993
X(ToString, "toString", \
9094
"()Ljava/lang/String;")
9195
// clang-format on
@@ -96,6 +100,19 @@ METHOD_LOOKUP_DEFINITION(storage_reference,
96100
"com/google/firebase/storage/StorageReference",
97101
STORAGE_REFERENCE_METHODS)
98102

103+
// clang-format off
104+
#define LIST_RESULT_METHODS(X) \
105+
X(GetItems, "getItems", "()Ljava/util/List;"), \
106+
X(GetPrefixes, "getPrefixes", "()Ljava/util/List;"), \
107+
X(GetPageToken, "getPageToken", "()Ljava/lang/String;")
108+
// clang-format on
109+
110+
METHOD_LOOKUP_DECLARATION(list_result, LIST_RESULT_METHODS)
111+
METHOD_LOOKUP_DEFINITION(list_result,
112+
PROGUARD_KEEP_CLASS
113+
"com/google/firebase/storage/ListResult",
114+
LIST_RESULT_METHODS)
115+
99116
enum StorageReferenceFn {
100117
kStorageReferenceFnDelete = 0,
101118
kStorageReferenceFnGetBytes,
@@ -105,6 +122,7 @@ enum StorageReferenceFn {
105122
kStorageReferenceFnUpdateMetadata,
106123
kStorageReferenceFnPutBytes,
107124
kStorageReferenceFnPutFile,
125+
kStorageReferenceFnList,
108126
kStorageReferenceFnCount,
109127
};
110128

@@ -309,6 +327,49 @@ void StorageReferenceInternal::FutureCallback(JNIEnv* env, jobject result,
309327
file_download_task_task_snapshot::kGetBytesTransferred));
310328
data->impl->Complete<size_t>(data->handle, kErrorNone, status_message,
311329
[bytes](size_t* size) { *size = bytes; });
330+
} else if (result && env->IsInstanceOf(result, list_result::GetClass())) {
331+
LogDebug("FutureCallback: Completing a Future from a ListResult.");
332+
// Complete a Future<StorageListResult> from a Java ListResult object.
333+
jobject prefixes_list = env->CallObjectMethod(
334+
result, list_result::GetMethodId(list_result::kGetPrefixes));
335+
jobject items_list = env->CallObjectMethod(
336+
result, list_result::GetMethodId(list_result::kGetItems));
337+
jstring page_token_jstr = static_cast<jstring>(env->CallObjectMethod(
338+
result, list_result::GetMethodId(list_result::kGetPageToken)));
339+
340+
std::vector<StorageReference> prefixes;
341+
std::vector<StorageReference> items;
342+
std::string page_token = util::JStringToString(env, page_token_jstr);
343+
344+
if (prefixes_list) {
345+
int size = env->CallIntMethod(prefixes_list, util::list::GetMethodId(util::list::kSize));
346+
for (int i = 0; i < size; ++i) {
347+
jobject ref_obj = env->CallObjectMethod(prefixes_list, util::list::GetMethodId(util::list::kGet), i);
348+
prefixes.push_back(StorageReference(new StorageReferenceInternal(data->storage, ref_obj)));
349+
env->DeleteLocalRef(ref_obj);
350+
}
351+
env->DeleteLocalRef(prefixes_list);
352+
}
353+
354+
if (items_list) {
355+
int size = env->CallIntMethod(items_list, util::list::GetMethodId(util::list::kSize));
356+
for (int i = 0; i < size; ++i) {
357+
jobject ref_obj = env->CallObjectMethod(items_list, util::list::GetMethodId(util::list::kGet), i);
358+
items.push_back(StorageReference(new StorageReferenceInternal(data->storage, ref_obj)));
359+
env->DeleteLocalRef(ref_obj);
360+
}
361+
env->DeleteLocalRef(items_list);
362+
}
363+
364+
if (page_token_jstr) env->DeleteLocalRef(page_token_jstr);
365+
366+
data->impl->Complete<StorageListResult>(
367+
data->handle, kErrorNone, status_message,
368+
[prefixes, items, page_token](StorageListResult* list_result) {
369+
internal::StorageListResultInternal* list_result_internal = new internal::StorageListResultInternal(
370+
prefixes, items, page_token);
371+
*list_result = internal::StorageListResultInternal::AsStorageListResult(list_result_internal);
372+
});
312373
} else {
313374
LogDebug("FutureCallback: Completing a Future from a default result.");
314375
// Unknown or null result type, treat this as a Future<void> and just
@@ -541,6 +602,49 @@ Future<Metadata> StorageReferenceInternal::UpdateMetadataLastResult() {
541602
future()->LastResult(kStorageReferenceFnUpdateMetadata));
542603
}
543604

605+
Future<StorageListResult> StorageReferenceInternal::List(
606+
int max_results_per_page, const char* page_token) {
607+
ReferenceCountedFutureImpl* future_impl = future();
608+
FutureHandle handle = future_impl->Alloc<StorageListResult>(kStorageReferenceFnList);
609+
610+
JNIEnv* env = storage_->app()->GetJNIEnv();
611+
jstring page_token_string =
612+
page_token ? env->NewStringUTF(page_token) : nullptr;
613+
jobject task = nullptr;
614+
615+
if (page_token_string) {
616+
task = env->CallObjectMethod(
617+
obj_, storage_reference::GetMethodId(storage_reference::kListWithToken),
618+
(jint)max_results_per_page, page_token_string);
619+
env->DeleteLocalRef(page_token_string);
620+
} else {
621+
task = env->CallObjectMethod(
622+
obj_, storage_reference::GetMethodId(storage_reference::kList),
623+
(jint)max_results_per_page);
624+
}
625+
626+
if (util::LogException(env, kLogLevelWarning,
627+
"StorageReference::List(): Call to list() failed.")) {
628+
future_impl->Complete(handle, kErrorUnknown, "Call to list() failed.");
629+
} else {
630+
util::RegisterCallbackOnTask(
631+
env, task,
632+
reinterpret_cast<util::TaskFinishedCallback>(
633+
StorageReferenceInternal::FutureCallback),
634+
new FutureCallbackData(handle, future_impl, storage_,
635+
kStorageReferenceFnList),
636+
storage_resources::GetClass(storage_resources::kApiExecutor));
637+
}
638+
env->DeleteLocalRef(task);
639+
640+
return ListLastResult();
641+
}
642+
643+
Future<StorageListResult> StorageReferenceInternal::ListLastResult() {
644+
return static_cast<const Future<StorageListResult>&>(
645+
future()->LastResult(kStorageReferenceFnList));
646+
}
647+
544648
std::string StorageReferenceInternal::name() {
545649
JNIEnv* env = storage_->app()->GetJNIEnv();
546650
jstring name_jstring = static_cast<jstring>(env->CallObjectMethod(

storage/src/android/storage_reference_android.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "app/src/reference_counted_future_impl.h"
2424
#include "app/src/util_android.h"
2525
#include "storage/src/android/storage_android.h"
26+
#include "storage/src/include/firebase/storage/list_result.h"
2627
#include "storage/src/include/firebase/storage/storage_reference.h"
2728

2829
namespace firebase {
@@ -129,6 +130,13 @@ class StorageReferenceInternal {
129130
// Returns the result of the most recent call to PutFile();
130131
Future<Metadata> PutFileLastResult();
131132

133+
// List items (files) and prefixes (folders) under this StorageReference.
134+
Future<StorageListResult> List(int max_results_per_page,
135+
const char* page_token);
136+
137+
// Returns the result of the most recent call to List();
138+
Future<StorageListResult> ListLastResult();
139+
132140
// Initialize JNI bindings for this class.
133141
static bool Initialize(App* app);
134142
static void Terminate(App* app);

storage/src/common/list_result.cc

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "firebase/storage/list_result.h"
16+
#include "list_result_internal.h"
17+
18+
namespace firebase {
19+
namespace storage {
20+
21+
StorageListResult::StorageListResult() : internal_(nullptr) {}
22+
23+
StorageListResult::~StorageListResult() {
24+
delete internal_;
25+
internal_ = nullptr;
26+
}
27+
28+
StorageListResult::StorageListResult(const StorageListResult& other)
29+
: internal_(other.internal_ ? new internal::StorageListResultInternal(*other.internal_)
30+
: nullptr) {}
31+
32+
StorageListResult& StorageListResult::operator=(const StorageListResult& other) {
33+
if (this != &other) {
34+
delete internal_;
35+
internal_ = other.internal_ ? new internal::StorageListResultInternal(*other.internal_)
36+
: nullptr;
37+
}
38+
return *this;
39+
}
40+
41+
#if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN)
42+
StorageListResult::StorageListResult(StorageListResult&& other)
43+
: internal_(other.internal_) {
44+
other.internal_ = nullptr;
45+
}
46+
47+
StorageListResult& StorageListResult::operator=(StorageListResult&& other) {
48+
if (this != &other) {
49+
delete internal_;
50+
internal_ = other.internal_;
51+
other.internal_ = nullptr;
52+
}
53+
return *this;
54+
}
55+
#endif // defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN)
56+
57+
const std::vector<StorageReference>& StorageListResult::prefixes() const {
58+
static const std::vector<StorageReference> empty_vector;
59+
return internal_ ? internal_->prefixes_ : empty_vector;
60+
}
61+
62+
const std::vector<StorageReference>& StorageListResult::items() const {
63+
static const std::vector<StorageReference> empty_vector;
64+
return internal_ ? internal_->items_ : empty_vector;
65+
}
66+
67+
const std::string& StorageListResult::next_page_token() const {
68+
static const std::string empty_string;
69+
return internal_ ? internal_->next_page_token_ : empty_string;
70+
}
71+
72+
bool StorageListResult::is_valid() const { return internal_ != nullptr; }
73+
74+
} // namespace storage
75+
} // namespace firebase
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef FIREBASE_STORAGE_SRC_COMMON_LIST_RESULT_INTERNAL_H_
16+
#define FIREBASE_STORAGE_SRC_COMMON_LIST_RESULT_INTERNAL_H_
17+
18+
#include <string>
19+
#include <vector>
20+
21+
#include "firebase/storage/storage_reference.h"
22+
23+
namespace firebase {
24+
namespace storage {
25+
namespace internal {
26+
27+
class StorageListResultInternal {
28+
public:
29+
StorageListResultInternal() {}
30+
31+
StorageListResultInternal(const std::vector<StorageReference>& prefixes,
32+
const std::vector<StorageReference>& items,
33+
const std::string& next_page_token)
34+
: prefixes_(prefixes),
35+
items_(items),
36+
next_page_token_(next_page_token) {}
37+
38+
static StorageListResult AsStorageListResult(
39+
StorageListResultInternal* internal) {
40+
StorageListResult result;
41+
result.internal_ = internal;
42+
return result;
43+
}
44+
45+
std::vector<StorageReference> prefixes_;
46+
std::vector<StorageReference> items_;
47+
std::string next_page_token_;
48+
};
49+
50+
} // namespace internal
51+
} // namespace storage
52+
} // namespace firebase
53+
54+
#endif // FIREBASE_STORAGE_SRC_COMMON_LIST_RESULT_INTERNAL_H_

storage/src/common/storage_reference.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,16 @@ Future<Metadata> StorageReference::PutFileLastResult() {
246246
return internal_ ? internal_->PutFileLastResult() : Future<Metadata>();
247247
}
248248

249+
Future<StorageListResult> StorageReference::List(int max_results_per_page,
250+
const char* page_token) {
251+
return internal_ ? internal_->List(max_results_per_page, page_token)
252+
: Future<StorageListResult>();
253+
}
254+
255+
Future<StorageListResult> StorageReference::ListLastResult() {
256+
return internal_ ? internal_->ListLastResult() : Future<StorageListResult>();
257+
}
258+
249259
bool StorageReference::is_valid() const { return internal_ != nullptr; }
250260

251261
} // namespace storage

0 commit comments

Comments
 (0)