Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions release_build_files/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@ workflow use only during the development of your app, not for publicly shipping
code.

## Release Notes
### Upcoming
- Changes
- Storage: Added `List` API across all platforms.

### 13.5.0
- Changes
- General (Android): Update to Firebase Android BoM version 34.10.0.
Expand Down
1 change: 1 addition & 0 deletions storage/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ set(common_SRCS
src/common/common.cc
src/common/controller.cc
src/common/listener.cc
src/common/list_result.cc
src/common/metadata.cc
src/common/storage.cc
src/common/storage_reference.cc
Expand Down
72 changes: 72 additions & 0 deletions storage/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,78 @@ TEST_F(FirebaseStorageTest, TestDeleteFile) {
firebase::storage::kErrorObjectNotFound);
}

TEST_F(FirebaseStorageTest, TestList) {
SignIn();

firebase::storage::StorageReference ref = CreateFolder().Child("list_test");

// Upload a few files
const char* file_contents[] = {"file0", "file1", "file2"};
for (int i = 0; i < 3; ++i) {
char file_name[16];
snprintf(file_name, sizeof(file_name), "file_%d.txt", i);
firebase::storage::StorageReference file_ref = ref.Child(file_name);
firebase::Future<firebase::storage::Metadata> put_future =
file_ref.PutBytes(file_contents[i], strlen(file_contents[i]));
WaitForCompletion(put_future, "PutBytes");
EXPECT_EQ(put_future.error(), firebase::storage::kErrorNone);
cleanup_files_.push_back(file_ref);
}

// Upload to a subfolder to test prefixes
firebase::storage::StorageReference subfolder_ref =
ref.Child("subfolder/sub_file.txt");
firebase::Future<firebase::storage::Metadata> subfolder_put_future =
subfolder_ref.PutBytes("sub", 3);
WaitForCompletion(subfolder_put_future, "PutBytes subfolder");
EXPECT_EQ(subfolder_put_future.error(), firebase::storage::kErrorNone);
cleanup_files_.push_back(subfolder_ref);

// Test List
LogDebug("Testing List");
firebase::Future<firebase::storage::StorageListResult> list_future =
ref.List();
WaitForCompletionAnyResult(list_future, "List");

if (list_future.error() != firebase::storage::kErrorNone) {
if (list_future.error() == firebase::storage::kErrorUnknown) {
LogWarning(
"Skipping TestList as the test project is likely using an older "
"rules_version.");
return;
}
EXPECT_EQ(list_future.error(), firebase::storage::kErrorNone);
return;
}

firebase::storage::StorageListResult result = *list_future.result();
EXPECT_EQ(result.items().size(), 3);
EXPECT_EQ(result.prefixes().size(), 1);
EXPECT_EQ(result.next_page_token(), "");

// Test Pagination
LogDebug("Testing List Pagination");
firebase::Future<firebase::storage::StorageListResult> page1_future =
ref.List(2);
WaitForCompletionAnyResult(page1_future, "List Page 1");
EXPECT_EQ(page1_future.error(), firebase::storage::kErrorNone);

firebase::storage::StorageListResult page1_result = *page1_future.result();
EXPECT_TRUE(page1_result.items().size() + page1_result.prefixes().size() ==
2);
EXPECT_NE(page1_result.next_page_token(), "");

firebase::Future<firebase::storage::StorageListResult> page2_future =
ref.List(2, page1_result.next_page_token().c_str());
WaitForCompletionAnyResult(page2_future, "List Page 2");
EXPECT_EQ(page2_future.error(), firebase::storage::kErrorNone);

firebase::storage::StorageListResult page2_result = *page2_future.result();
EXPECT_TRUE(page2_result.items().size() + page2_result.prefixes().size() ==
2);
EXPECT_EQ(page2_result.next_page_token(), "");
}

// Only test retries on desktop since Android and iOS don't have an option
// to retry file-not-found errors and just pass-through to native
// implementations.
Expand Down
111 changes: 110 additions & 1 deletion storage/src/android/storage_reference_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "storage/src/android/controller_android.h"
#include "storage/src/android/metadata_android.h"
#include "storage/src/android/storage_android.h"
#include "storage/src/common/list_result_internal.h"
#include "storage/src/include/firebase/storage.h"
#include "storage/src/include/firebase/storage/common.h"
#include "storage/storage_resources.h"
Expand Down Expand Up @@ -86,6 +87,10 @@ namespace internal {
"Lcom/google/firebase/storage/FileDownloadTask;"), \
X(Delete, "delete", \
"()Lcom/google/android/gms/tasks/Task;"), \
X(List, "list", \
"(I)Lcom/google/android/gms/tasks/Task;"), \
X(ListWithToken, "list", \
"(ILjava/lang/String;)Lcom/google/android/gms/tasks/Task;"), \
X(ToString, "toString", \
"()Ljava/lang/String;")
// clang-format on
Expand All @@ -96,6 +101,19 @@ METHOD_LOOKUP_DEFINITION(storage_reference,
"com/google/firebase/storage/StorageReference",
STORAGE_REFERENCE_METHODS)

// clang-format off
#define LIST_RESULT_METHODS(X) \
X(GetItems, "getItems", "()Ljava/util/List;"), \
X(GetPrefixes, "getPrefixes", "()Ljava/util/List;"), \
X(GetPageToken, "getPageToken", "()Ljava/lang/String;")
// clang-format on

METHOD_LOOKUP_DECLARATION(list_result, LIST_RESULT_METHODS)
METHOD_LOOKUP_DEFINITION(list_result,
PROGUARD_KEEP_CLASS
"com/google/firebase/storage/ListResult",
LIST_RESULT_METHODS)

enum StorageReferenceFn {
kStorageReferenceFnDelete = 0,
kStorageReferenceFnGetBytes,
Expand All @@ -105,18 +123,21 @@ enum StorageReferenceFn {
kStorageReferenceFnUpdateMetadata,
kStorageReferenceFnPutBytes,
kStorageReferenceFnPutFile,
kStorageReferenceFnList,
kStorageReferenceFnCount,
};

bool StorageReferenceInternal::Initialize(App* app) {
JNIEnv* env = app->GetJNIEnv();
jobject activity = app->activity();
return storage_reference::CacheMethodIds(env, activity);
return storage_reference::CacheMethodIds(env, activity) &&
list_result::CacheMethodIds(env, activity);
}

void StorageReferenceInternal::Terminate(App* app) {
JNIEnv* env = app->GetJNIEnv();
storage_reference::ReleaseClass(env);
list_result::ReleaseClass(env);
util::CheckAndClearJniExceptions(env);
}

Expand Down Expand Up @@ -309,6 +330,51 @@ void StorageReferenceInternal::FutureCallback(JNIEnv* env, jobject result,
file_download_task_task_snapshot::kGetBytesTransferred));
data->impl->Complete<size_t>(data->handle, kErrorNone, status_message,
[bytes](size_t* size) { *size = bytes; });
} else if (result && env->IsInstanceOf(result, list_result::GetClass())) {
LogDebug("FutureCallback: Completing a Future from a ListResult.");
// Complete a Future<StorageListResult> from a Java ListResult object.
jobject prefixes_list = env->CallObjectMethod(
result, list_result::GetMethodId(list_result::kGetPrefixes));
jobject items_list = env->CallObjectMethod(
result, list_result::GetMethodId(list_result::kGetItems));
jstring page_token_jstr = static_cast<jstring>(env->CallObjectMethod(
result, list_result::GetMethodId(list_result::kGetPageToken)));

std::vector<StorageReference> prefixes;
std::vector<StorageReference> items;
std::string page_token = util::JStringToString(env, page_token_jstr);

auto process_list = [&](jobject list_obj,
std::vector<StorageReference>& out_vec) {
if (list_obj) {
int size = env->CallIntMethod(
list_obj, util::list::GetMethodId(util::list::kSize));
for (int i = 0; i < size; ++i) {
jobject ref_obj = env->CallObjectMethod(
list_obj, util::list::GetMethodId(util::list::kGet), i);
out_vec.push_back(StorageReference(
new StorageReferenceInternal(data->storage, ref_obj)));
env->DeleteLocalRef(ref_obj);
}
env->DeleteLocalRef(list_obj);
}
};

process_list(prefixes_list, prefixes);
process_list(items_list, items);

if (page_token_jstr) env->DeleteLocalRef(page_token_jstr);

data->impl->Complete<StorageListResult>(
data->handle, kErrorNone, status_message,
[prefixes, items, page_token](StorageListResult* list_result) {
internal::StorageListResultInternal* list_result_internal =
new internal::StorageListResultInternal(prefixes, items,
page_token);
*list_result =
internal::StorageListResultInternal::AsStorageListResult(
list_result_internal);
});
} else {
LogDebug("FutureCallback: Completing a Future from a default result.");
// Unknown or null result type, treat this as a Future<void> and just
Expand Down Expand Up @@ -541,6 +607,49 @@ Future<Metadata> StorageReferenceInternal::UpdateMetadataLastResult() {
future()->LastResult(kStorageReferenceFnUpdateMetadata));
}

Future<StorageListResult> StorageReferenceInternal::List(
int max_results_per_page, const char* page_token) {
ReferenceCountedFutureImpl* future_impl = future();
FutureHandle handle =
future_impl->Alloc<StorageListResult>(kStorageReferenceFnList);

JNIEnv* env = storage_->app()->GetJNIEnv();
jstring page_token_string =
page_token ? env->NewStringUTF(page_token) : nullptr;
jobject task = nullptr;

if (page_token_string) {
task = env->CallObjectMethod(
obj_, storage_reference::GetMethodId(storage_reference::kListWithToken),
(jint)max_results_per_page, page_token_string);
env->DeleteLocalRef(page_token_string);
} else {
task = env->CallObjectMethod(
obj_, storage_reference::GetMethodId(storage_reference::kList),
(jint)max_results_per_page);
}

if (util::LogException(env, kLogLevelWarning,
"StorageReference::List(): Call to list() failed.")) {
future_impl->Complete(handle, kErrorUnknown, "Call to list() failed.");
} else {
util::RegisterCallbackOnTask(
env, task, FutureCallback,
reinterpret_cast<void*>(new FutureCallbackData(
handle, future_impl, storage_, kStorageReferenceFnList)),
storage_->jni_task_id());
}
util::CheckAndClearJniExceptions(env);
env->DeleteLocalRef(task);

return ListLastResult();
}

Future<StorageListResult> StorageReferenceInternal::ListLastResult() {
return static_cast<const Future<StorageListResult>&>(
future()->LastResult(kStorageReferenceFnList));
}

std::string StorageReferenceInternal::name() {
JNIEnv* env = storage_->app()->GetJNIEnv();
jstring name_jstring = static_cast<jstring>(env->CallObjectMethod(
Expand Down
8 changes: 8 additions & 0 deletions storage/src/android/storage_reference_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "app/src/reference_counted_future_impl.h"
#include "app/src/util_android.h"
#include "storage/src/android/storage_android.h"
#include "storage/src/include/firebase/storage/list_result.h"
#include "storage/src/include/firebase/storage/storage_reference.h"

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

// List items (files) and prefixes (folders) under this StorageReference.
Future<StorageListResult> List(int max_results_per_page,
const char* page_token);

// Returns the result of the most recent call to List();
Future<StorageListResult> ListLastResult();

// Initialize JNI bindings for this class.
static bool Initialize(App* app);
static void Terminate(App* app);
Expand Down
79 changes: 79 additions & 0 deletions storage/src/common/list_result.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "firebase/storage/list_result.h"

#include "list_result_internal.h"

namespace firebase {
namespace storage {

StorageListResult::StorageListResult() : internal_(nullptr) {}

StorageListResult::~StorageListResult() {
delete internal_;
internal_ = nullptr;
}

StorageListResult::StorageListResult(const StorageListResult& other)
: internal_(other.internal_
? new internal::StorageListResultInternal(*other.internal_)
: nullptr) {}

StorageListResult& StorageListResult::operator=(
const StorageListResult& other) {
if (this != &other) {
delete internal_;
internal_ = other.internal_
? new internal::StorageListResultInternal(*other.internal_)
: nullptr;
}
return *this;
}

#if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN)
StorageListResult::StorageListResult(StorageListResult&& other)
: internal_(other.internal_) {
other.internal_ = nullptr;
}

StorageListResult& StorageListResult::operator=(StorageListResult&& other) {
if (this != &other) {
delete internal_;
internal_ = other.internal_;
other.internal_ = nullptr;
}
return *this;
}
#endif // defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN)

const std::vector<StorageReference>& StorageListResult::prefixes() const {
static const std::vector<StorageReference> empty_vector;
return internal_ ? internal_->prefixes_ : empty_vector;
}

const std::vector<StorageReference>& StorageListResult::items() const {
static const std::vector<StorageReference> empty_vector;
return internal_ ? internal_->items_ : empty_vector;
}

const std::string& StorageListResult::next_page_token() const {
static const std::string empty_string;
return internal_ ? internal_->next_page_token_ : empty_string;
}

bool StorageListResult::is_valid() const { return internal_ != nullptr; }

} // namespace storage
} // namespace firebase
Loading
Loading