Skip to content

Commit 63496cd

Browse files
Add List() support (#458)
1 parent cb61522 commit 63496cd

File tree

20 files changed

+2361
-15
lines changed

20 files changed

+2361
-15
lines changed

firebase-storage/CHANGELOG.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
# Unreleased
1+
# Unreleased (17.1.0)
2+
- [feature] Added `StorageReference.list()` and `StorageReference.listAll()`,
3+
which allows developers to list the files and folders under the given
4+
StorageReference.
5+
- [changed] Added validation to `StorageReference.getDownloadUrl()` and
6+
`StorageReference.getMetadata()` to return an error if the reference is the
7+
root of the bucket.
8+
9+
# 17.0.0
10+
- [internal] Updated the SDK initialization process and removed usages of
11+
deprecated methods.
212
- [changed] Added `@RestrictTo` annotations to discourage the use of APIs that
313
are not public. This affects internal APIs that were previously obfuscated
414
and are not mentioned in our documentation.
5-
- [internal] Updated the SDK initialization process and removed usages of
6-
deprecated methods.
7-
- [changed] Added validation to `StorageReference.getDownloadUrl` and
8-
`StorageReference.getMetadata` to return an error if the reference is the
9-
root of the bucket.

firebase-storage/firebase-storage.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ android {
4545
sourceSets {
4646
androidTest {
4747
java {
48-
srcDir 'src/androidTest'
49-
srcDir 'src/testUtil'
48+
srcDir 'src/androidTest/java'
49+
srcDir 'src/testUtil/java'
5050
//We need this since FirebaseApp rule is externalized
5151
//TODO(ashwinraghav) b/113079738
5252
srcDir '../firebase-common/src/testUtil'

firebase-storage/src/androidTest/java/com/google/firebase/storage/IntegrationTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public void before() throws ExecutionException, InterruptedException {
5858

5959
Tasks.await(getReference("metadata.dat").putBytes(new byte[0]));
6060
Tasks.await(getReference("download.dat").putBytes(new byte[LARGE_FILE_SIZE_BYTES]));
61+
Tasks.await(getReference("prefix/empty.dat").putBytes(new byte[0]));
6162
}
6263
}
6364

@@ -130,6 +131,35 @@ public void updateMetadata() throws ExecutionException, InterruptedException {
130131
.isEqualTo(randomMetadata.getCustomMetadata("rand"));
131132
}
132133

134+
@Test
135+
public void pagedListFiles() throws ExecutionException, InterruptedException {
136+
Task<ListResult> listTask = storageClient.getReference(randomPrefix).list(2);
137+
ListResult listResult = Tasks.await(listTask);
138+
139+
assertThat(listResult.getItems())
140+
.containsExactly(getReference("download.dat"), getReference("metadata.dat"));
141+
assertThat(listResult.getPrefixes()).isEmpty();
142+
assertThat(listResult.getPageToken()).isNotEmpty();
143+
144+
listTask = storageClient.getReference(randomPrefix).list(2, listResult.getPageToken());
145+
listResult = Tasks.await(listTask);
146+
147+
assertThat(listResult.getItems()).isEmpty();
148+
assertThat(listResult.getPrefixes()).containsExactly(getReference("prefix"));
149+
assertThat(listResult.getPageToken()).isNull();
150+
}
151+
152+
@Test
153+
public void listAllFiles() throws ExecutionException, InterruptedException {
154+
Task<ListResult> listTask = storageClient.getReference(randomPrefix).listAll();
155+
ListResult listResult = Tasks.await(listTask);
156+
157+
assertThat(listResult.getPrefixes()).containsExactly(getReference("prefix"));
158+
assertThat(listResult.getItems())
159+
.containsExactly(getReference("metadata.dat"), getReference("download.dat"));
160+
assertThat(listResult.getPageToken()).isNull();
161+
}
162+
133163
@NonNull
134164
private StorageReference getReference(String filename) {
135165
return storageClient.getReference(randomPrefix + "/" + filename);
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
package com.google.firebase.storage;
16+
17+
import android.support.annotation.NonNull;
18+
import android.support.annotation.Nullable;
19+
import com.google.firebase.annotations.PublicApi;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import org.json.JSONArray;
23+
import org.json.JSONException;
24+
import org.json.JSONObject;
25+
26+
/** Contains the prefixes and items returned by a {@link StorageReference#list} call. */
27+
@PublicApi
28+
public final class ListResult {
29+
private static final String ITEMS_KEY = "items";
30+
private static final String NAME_KEY = "name";
31+
private static final String PAGE_TOKEN_KEY = "nextPageToken";
32+
private static final String PREFIXES_KEY = "prefixes";
33+
34+
private final List<StorageReference> prefixes;
35+
private final List<StorageReference> items;
36+
@Nullable private final String pageToken;
37+
38+
ListResult(
39+
List<StorageReference> prefixes, List<StorageReference> items, @Nullable String pageToken) {
40+
this.prefixes = prefixes;
41+
this.items = items;
42+
this.pageToken = pageToken;
43+
}
44+
45+
static ListResult fromJSON(FirebaseStorage storage, JSONObject resultBody) throws JSONException {
46+
List<StorageReference> prefixes = new ArrayList<>();
47+
List<StorageReference> items = new ArrayList<>();
48+
49+
if (resultBody.has(PREFIXES_KEY)) {
50+
JSONArray prefixEntries = resultBody.getJSONArray(PREFIXES_KEY);
51+
for (int i = 0; i < prefixEntries.length(); ++i) {
52+
String pathWithoutTrailingSlash = prefixEntries.getString(i);
53+
if (pathWithoutTrailingSlash.endsWith("/")) {
54+
pathWithoutTrailingSlash =
55+
pathWithoutTrailingSlash.substring(0, pathWithoutTrailingSlash.length() - 1);
56+
}
57+
prefixes.add(storage.getReference(pathWithoutTrailingSlash));
58+
}
59+
}
60+
61+
if (resultBody.has(ITEMS_KEY)) {
62+
JSONArray itemEntries = resultBody.getJSONArray(ITEMS_KEY);
63+
for (int i = 0; i < itemEntries.length(); ++i) {
64+
JSONObject metadata = itemEntries.getJSONObject(i);
65+
items.add(storage.getReference(metadata.getString(NAME_KEY)));
66+
}
67+
}
68+
69+
String pageToken = resultBody.optString(PAGE_TOKEN_KEY, /* defaultValue= */ null);
70+
return new ListResult(prefixes, items, pageToken);
71+
}
72+
73+
/**
74+
* The prefixes (folders) returned by the {@code list()} operation.
75+
*
76+
* @return A list of prefixes (folders).
77+
*/
78+
@NonNull
79+
@PublicApi
80+
public List<StorageReference> getPrefixes() {
81+
return prefixes;
82+
}
83+
84+
/**
85+
* The items (files) returned by the {@code list()} operation.
86+
*
87+
* @return A list of items (files).
88+
*/
89+
@NonNull
90+
@PublicApi
91+
public List<StorageReference> getItems() {
92+
return items;
93+
}
94+
95+
/**
96+
* Returns a token that can be used to resume a previous {@code list()} operation. `null`
97+
* indicates that there are no more results.
98+
*
99+
* @return A page token if more results are avaible.
100+
*/
101+
@Nullable
102+
@PublicApi
103+
public String getPageToken() {
104+
return pageToken;
105+
}
106+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
package com.google.firebase.storage;
16+
17+
import android.support.annotation.NonNull;
18+
import android.support.annotation.Nullable;
19+
import android.util.Log;
20+
import com.google.android.gms.common.internal.Preconditions;
21+
import com.google.android.gms.tasks.TaskCompletionSource;
22+
import com.google.firebase.storage.internal.ExponentialBackoffSender;
23+
import com.google.firebase.storage.network.ListNetworkRequest;
24+
import com.google.firebase.storage.network.NetworkRequest;
25+
import org.json.JSONException;
26+
27+
/** A Task that lists the entries under a {@link StorageReference} */
28+
class ListTask implements Runnable {
29+
private static final String TAG = "ListTask";
30+
31+
private final StorageReference storageRef;
32+
private final TaskCompletionSource<ListResult> pendingResult;
33+
private final ExponentialBackoffSender sender;
34+
@Nullable private final String pageToken;
35+
@Nullable private final Integer maxResults;
36+
37+
ListTask(
38+
@NonNull StorageReference storageRef,
39+
@Nullable Integer maxResults,
40+
@Nullable String pageToken,
41+
@NonNull TaskCompletionSource<ListResult> pendingResult) {
42+
Preconditions.checkNotNull(storageRef);
43+
Preconditions.checkNotNull(pendingResult);
44+
45+
this.storageRef = storageRef;
46+
this.maxResults = maxResults;
47+
this.pageToken = pageToken;
48+
this.pendingResult = pendingResult;
49+
50+
FirebaseStorage storage = this.storageRef.getStorage();
51+
sender =
52+
new ExponentialBackoffSender(
53+
storage.getApp().getApplicationContext(),
54+
storage.getAuthProvider(),
55+
storage.getMaxDownloadRetryTimeMillis());
56+
}
57+
58+
@Override
59+
public void run() {
60+
final NetworkRequest request =
61+
new ListNetworkRequest(
62+
storageRef.getStorageUri(), storageRef.getApp(), maxResults, pageToken);
63+
64+
sender.sendWithExponentialBackoff(request);
65+
66+
ListResult listResult = null;
67+
68+
if (request.isResultSuccess()) {
69+
try {
70+
listResult = ListResult.fromJSON(storageRef.getStorage(), request.getResultBody());
71+
} catch (final JSONException e) {
72+
Log.e(TAG, "Unable to parse response body. " + request.getRawResult(), e);
73+
74+
pendingResult.setException(StorageException.fromException(e));
75+
return;
76+
}
77+
}
78+
79+
if (pendingResult != null) {
80+
request.completeTask(pendingResult, listResult);
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)