Skip to content

Commit 7788dab

Browse files
authored
Storage and StorageReference to Swift (#10077)
1 parent 5cd2df1 commit 7788dab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1088
-2334
lines changed

FirebaseStorage/Sources/Internal/StorageDeleteTask.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import Foundation
1616

17-
import FirebaseStorageInternal
1817
#if COCOAPODS
1918
import GTMSessionFetcher
2019
#else
@@ -29,7 +28,7 @@ internal class StorageDeleteTask: StorageTask, StorageTaskManagement {
2928
private var fetcherCompletion: ((Data?, NSError?) -> Void)?
3029
private var taskCompletion: ((_ error: Error?) -> Void)?
3130

32-
internal init(reference: FIRIMPLStorageReference,
31+
internal init(reference: StorageReference,
3332
fetcherService: GTMSessionFetcherService,
3433
queue: DispatchQueue,
3534
completion: ((_: Error?) -> Void)?) {

FirebaseStorage/Sources/Internal/StorageGetDownloadURLTask.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import Foundation
1616

17-
import FirebaseStorageInternal
1817
#if COCOAPODS
1918
import GTMSessionFetcher
2019
#else
@@ -29,7 +28,7 @@ internal class StorageGetDownloadURLTask: StorageTask, StorageTaskManagement {
2928
private var fetcherCompletion: ((Data?, NSError?) -> Void)?
3029
private var taskCompletion: ((_ downloadURL: URL?, _: Error?) -> Void)?
3130

32-
internal init(reference: FIRIMPLStorageReference,
31+
internal init(reference: StorageReference,
3332
fetcherService: GTMSessionFetcherService,
3433
queue: DispatchQueue,
3534
completion: ((_: URL?, _: Error?) -> Void)?) {

FirebaseStorage/Sources/Internal/StorageGetMetadataTask.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import Foundation
1616

17-
import FirebaseStorageInternal
1817
#if COCOAPODS
1918
import GTMSessionFetcher
2019
#else
@@ -29,7 +28,7 @@ internal class StorageGetMetadataTask: StorageTask, StorageTaskManagement {
2928
private var fetcherCompletion: ((Data?, NSError?) -> Void)?
3029
private var taskCompletion: ((_ metadata: StorageMetadata?, _: Error?) -> Void)?
3130

32-
internal init(reference: FIRIMPLStorageReference,
31+
internal init(reference: StorageReference,
3332
fetcherService: GTMSessionFetcherService,
3433
queue: DispatchQueue,
3534
completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {

FirebaseStorage/Sources/Internal/StorageListTask.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import Foundation
1616

17-
import FirebaseStorageInternal
1817
#if COCOAPODS
1918
import GTMSessionFetcher
2019
#else
@@ -44,7 +43,7 @@ internal class StorageListTask: StorageTask, StorageTaskManagement {
4443
* @param previousPageToken An optional pageToken, used to resume a previous invocation.
4544
* @param completion The completion handler to be called with the FIRIMPLStorageListResult.
4645
*/
47-
internal init(reference: FIRIMPLStorageReference,
46+
internal init(reference: StorageReference,
4847
fetcherService: GTMSessionFetcherService,
4948
queue: DispatchQueue,
5049
pageSize: Int64?,
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright 2022 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+
import Foundation
16+
17+
enum StoragePathError: Error {
18+
case storagePathError(String)
19+
}
20+
21+
/**
22+
* Represents a path in GCS, which can be represented as: gs://bucket/path/to/object
23+
* or http[s]://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object?token=<12345>
24+
* This class also includes helper methods to parse those URI/Ls, as well as to
25+
* add and remove path segments.
26+
*/
27+
internal class StoragePath: NSCopying, Equatable {
28+
// MARK: Class methods
29+
30+
/**
31+
* Parses a generic string (representing some URI or URL) and returns the appropriate path.
32+
* @param string String which is parsed into a path.
33+
* @return Returns an instance of StoragePath.
34+
* @throws Throws an error if the string is not a valid gs:// URI or http[s]:// URL.
35+
*/
36+
static func path(string: String) throws -> StoragePath {
37+
if string.hasPrefix("gs://") {
38+
// "gs://bucket/path/to/object"
39+
return try path(GSURI: string)
40+
} else if string.hasPrefix("http://") || string.hasPrefix("https://") {
41+
// "http[s]://firebasestorage.googleapis.com/bucket/path/to/object?signed_url_params"
42+
return try path(HTTPURL: string)
43+
} else {
44+
// Invalid scheme, throw an error!
45+
throw StoragePathError.storagePathError("Internal error: URL scheme must be one" +
46+
"of gs://, http://, or https://")
47+
}
48+
}
49+
50+
/**
51+
* Parses a gs://bucket/path/to/object URI into a GCS path.
52+
* @param aURIString gs:// URI which is parsed into a path.
53+
* @return Returns an instance of StoragePath or nil if one can't be created.
54+
* @throws Throws an error if the string is not a valid gs:// URI.
55+
*/
56+
internal static func path(GSURI aURIString: String) throws -> StoragePath {
57+
if aURIString.starts(with: "gs://") {
58+
let bucketObject = aURIString.dropFirst("gs://".count)
59+
if bucketObject.contains("/") {
60+
let splitStringArray = bucketObject.split(separator: "/", maxSplits: 1).map(String.init)
61+
let object = splitStringArray.count == 2 ? splitStringArray[1] : nil
62+
return StoragePath(with: splitStringArray[0], object: object)
63+
} else if bucketObject.count > 0 {
64+
return StoragePath(with: String(bucketObject))
65+
}
66+
}
67+
throw StoragePathError
68+
.storagePathError("Internal error: URI must be in the form of " +
69+
"gs://<bucket>/<path/to/object>")
70+
}
71+
72+
/**
73+
* Parses a http[s]://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object...?token=<12345>
74+
* URL into a GCS path.
75+
* @param aURLString http[s]:// URL which is parsed into a path.
76+
* string which is parsed into a path.
77+
* @return Returns an instance of StoragePath or nil if one can't be created.
78+
* @throws Throws an error if the string is not a valid http[s]:// URL.
79+
*/
80+
private static func path(HTTPURL aURLString: String) throws -> StoragePath {
81+
let httpsURL = URL(string: aURLString)
82+
let pathComponents = httpsURL?.pathComponents
83+
guard let pathComponents = pathComponents,
84+
pathComponents.count >= 4,
85+
pathComponents[1] == "v0",
86+
pathComponents[2] == "b" else {
87+
throw StoragePathError.storagePathError("Internal error: URL must be in the form of " +
88+
"http[s]://<host>/v0/b/<bucket>/o/<path/to/object>[?token=signed_url_params]")
89+
}
90+
let bucketName = pathComponents[3]
91+
92+
guard pathComponents.count > 4 else {
93+
return StoragePath(with: bucketName)
94+
}
95+
// Construct object name
96+
var objectName = pathComponents[5]
97+
for i in 6 ..< pathComponents.count {
98+
objectName = "\(objectName)/\(pathComponents[i])"
99+
}
100+
return StoragePath(with: bucketName, object: objectName)
101+
}
102+
103+
// Removes leading and trailing slashes, and compresses multiple slashes
104+
// to create a canonical representation.
105+
// Example: /foo//bar///baz//// -> foo/bar/baz
106+
private static func standardizedPathForString(_ string: String) -> String {
107+
var output = string
108+
while true {
109+
let newOutput = output.replacingOccurrences(of: "//", with: "/")
110+
if newOutput == output {
111+
break
112+
}
113+
output = newOutput
114+
}
115+
return output.trimmingCharacters(in: ["/"])
116+
}
117+
118+
// MARK: - Internal Implementations
119+
120+
/**
121+
* The GCS bucket in the path.
122+
*/
123+
internal let bucket: String
124+
125+
/**
126+
* The GCS object in the path.
127+
*/
128+
internal let object: String?
129+
130+
/**
131+
* Constructs an StoragePath object that represents the given bucket and object.
132+
* @param bucket The name of the bucket.
133+
* @param object The name of the object.
134+
* @return An instance of StoragePath representing the @a bucket and @a object.
135+
*/
136+
internal init(with bucket: String,
137+
object: String? = nil) {
138+
self.bucket = bucket
139+
if let object = object {
140+
self.object = StoragePath.standardizedPathForString(object)
141+
} else {
142+
self.object = nil
143+
}
144+
}
145+
146+
static func == (lhs: StoragePath, rhs: StoragePath) -> Bool {
147+
return lhs.bucket == rhs.bucket && lhs.object == rhs.object
148+
}
149+
150+
internal func copy(with zone: NSZone? = nil) -> Any {
151+
return StoragePath(with: bucket, object: object)
152+
}
153+
154+
/**
155+
* Creates a new path based off of the current path and a string appended to it.
156+
* Note that all slashes are compressed to a single slash, and leading and trailing slashes
157+
* are removed.
158+
* @param path String to append to the current path.
159+
* @return Returns a new instance of StoragePath with the new path appended.
160+
*/
161+
internal func child(_ path: String) -> StoragePath {
162+
if path.count == 0 {
163+
return copy() as! StoragePath
164+
}
165+
var childObject: String
166+
if let object = object as? NSString {
167+
childObject = object.appendingPathComponent(path)
168+
} else {
169+
childObject = path
170+
}
171+
return StoragePath(with: bucket, object: childObject)
172+
}
173+
174+
/**
175+
* Creates a new path based off of the current path with the last path segment removed.
176+
* @return Returns a new instance of StoragePath pointing to the parent path,
177+
* or nil if the current path points to the root.
178+
*/
179+
internal func parent() -> StoragePath? {
180+
guard let object = object,
181+
object.count > 0 else {
182+
return nil
183+
}
184+
let parentObject = (object as NSString).deletingLastPathComponent
185+
return StoragePath(with: bucket, object: parentObject)
186+
}
187+
188+
/**
189+
* Creates a new path based off of the root of the bucket.
190+
* @return Returns a new instance of StoragePath pointing to the root of the bucket.
191+
*/
192+
internal func root() -> StoragePath {
193+
return StoragePath(with: bucket)
194+
}
195+
196+
/**
197+
* Returns a GS URI representing the current path.
198+
* @return Returns a gs://bucket/path/to/object URI representing the current path.
199+
*/
200+
internal func stringValue() -> String {
201+
return "gs://\(bucket)/\(object ?? "")"
202+
}
203+
}

FirebaseStorage/Sources/Internal/StorageUpdateMetadataTask.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import Foundation
1616

17-
import FirebaseStorageInternal
1817
#if COCOAPODS
1918
import GTMSessionFetcher
2019
#else
@@ -30,7 +29,7 @@ internal class StorageUpdateMetadataTask: StorageTask, StorageTaskManagement {
3029
private var taskCompletion: ((_ metadata: StorageMetadata?, _: Error?) -> Void)?
3130
private var updateMetadata: StorageMetadata
3231

33-
internal init(reference: FIRIMPLStorageReference,
32+
internal init(reference: StorageReference,
3433
fetcherService: GTMSessionFetcherService,
3534
queue: DispatchQueue,
3635
metadata: StorageMetadata,

FirebaseStorage/Sources/Internal/StorageUtils.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import FirebaseStorageInternal
15+
import Foundation
1616
#if os(iOS) || os(tvOS)
1717
import MobileCoreServices
1818
#elseif os(macOS) || os(watchOS)
1919
import CoreServices
2020
#endif
2121

2222
class StorageUtils {
23-
internal class func defaultRequestForReference(reference: FIRIMPLStorageReference,
23+
internal class func defaultRequestForReference(reference: StorageReference,
2424
queryParams: [String: String]? = nil)
2525
-> URLRequest {
2626
var components = URLComponents()
@@ -42,12 +42,13 @@ class StorageUtils {
4242
with: "%2B"
4343
)
4444
}
45+
// TODO: review the force unwraps below.
4546
let encodedPath = encodedURL(for: reference.path)
4647
components.percentEncodedPath = encodedPath
4748
return URLRequest(url: components.url!)
4849
}
4950

50-
internal class func encodedURL(for path: FIRStoragePath) -> String {
51+
internal class func encodedURL(for path: StoragePath) -> String {
5152
let bucketString = "/b/\(GCSEscapedString(path.bucket))"
5253
var objectString: String
5354
if let objectName = path.object {

0 commit comments

Comments
 (0)