Skip to content

Commit cfd3c10

Browse files
darinfcompnerd
authored andcommitted
Add FirebaseStorage module (#65)
Adds the subset of `FirebaseStorage` needed by Arc: - `Storage` (partial) - `StorageReference` (partial) - `StorageMetadata` (partial) - `StorageErrorCode` The implementation of `StorageMetadata.customMetadata` required some extra thunking through the `CxxShims` lib.
1 parent d4b4d68 commit cfd3c10

File tree

9 files changed

+386
-1
lines changed

9 files changed

+386
-1
lines changed

CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,25 @@ target_link_libraries(FirebaseFunctions PRIVATE
218218
ssl
219219
zlibstatic)
220220

221+
add_library(FirebaseStorage SHARED
222+
Sources/FirebaseStorage/StorageErrorCode.swift
223+
Sources/FirebaseStorage/Storage+Swift.swift
224+
Sources/FirebaseStorage/StorageMetadata+Swift.swift
225+
Sources/FirebaseStorage/StorageReference+Swift.swift)
226+
target_compile_options(FirebaseStorage PRIVATE
227+
-cxx-interoperability-mode=default)
228+
target_link_libraries(FirebaseStorage PUBLIC
229+
firebase
230+
firebase_storage
231+
FirebaseCore)
232+
target_link_libraries(FirebaseStorage PRIVATE
233+
crypto
234+
firebase_rest_lib
235+
flatbuffers
236+
libcurl
237+
ssl
238+
zlibstatic)
239+
221240
if(SWIFT_FIREBASE_BUILD_EXAMPLES)
222241
FetchContent_Declare(SwiftWin32
223242
GIT_REPOSITORY https://github.com/compnerd/swift-win32

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ let SwiftFirebase =
2828
.library(name: "FirebaseAuth", targets: ["FirebaseAuth"]),
2929
.library(name: "FirebaseFirestore", targets: ["FirebaseFirestore"]),
3030
.library(name: "FirebaseFunctions", targets: ["FirebaseFunctions"]),
31+
.library(name: "FirebaseStorage", targets: ["FirebaseStorage"]),
3132
.executable(name: "FireBaseUI", targets: ["FireBaseUI"]),
3233
],
3334
dependencies: [
@@ -193,6 +194,18 @@ let SwiftFirebase =
193194
.interoperabilityMode(.Cxx),
194195
.unsafeFlags(["-Xcc", "-I\(include)"]),
195196
]),
197+
.target(name: "FirebaseStorage",
198+
dependencies: ["firebase", "FirebaseCore"],
199+
cxxSettings: [
200+
.define("INTERNAL_EXPERIMENTAL"),
201+
.define("_CRT_SECURE_NO_WARNINGS",
202+
.when(platforms: [.windows])),
203+
.headerSearchPath("../../third_party/firebase-development/usr/include"),
204+
],
205+
swiftSettings: [
206+
.interoperabilityMode(.Cxx),
207+
.unsafeFlags(["-Xcc", "-I\(include)"]),
208+
]),
196209
.executableTarget(name: "FireBaseUI",
197210
dependencies: [
198211
"FirebaseCore",

Sources/FirebaseFunctions/HTTPSCallable+Swift.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class HTTPSCallable {
2828
callImpl(data: data) { result, error in
2929
if let error {
3030
continuation.resume(throwing: error)
31-
} else{
31+
} else {
3232
continuation.resume(returning: result ?? .init())
3333
}
3434
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
3+
@_exported
4+
import firebase
5+
@_spi(FirebaseInternal)
6+
import FirebaseCore
7+
8+
import CxxShim
9+
10+
public class Storage {
11+
let impl: swift_firebase.swift_cxx_shims.firebase.storage.StorageRef
12+
13+
init(_ impl: swift_firebase.swift_cxx_shims.firebase.storage.StorageRef) {
14+
self.impl = impl
15+
}
16+
17+
public static func storage(app: FirebaseApp) -> Storage {
18+
let instance = swift_firebase.swift_cxx_shims.firebase.storage.storage_get_instance(app)
19+
guard swift_firebase.swift_cxx_shims.firebase.storage.storage_is_valid(instance) else {
20+
fatalError("Invalid Storage Instance")
21+
}
22+
return .init(instance)
23+
}
24+
25+
public func reference(withPath path: String) -> StorageReference {
26+
.init(swift_firebase.swift_cxx_shims.firebase.storage.storage_get_reference(impl, path))
27+
}
28+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
3+
@_exported
4+
import firebase
5+
@_spi(FirebaseInternal)
6+
import FirebaseCore
7+
8+
public struct StorageErrorCode: Error {
9+
public let rawValue: Int
10+
public let localizedDescription: String
11+
12+
internal init(_ params: (code: Int32, message: String)) {
13+
self.rawValue = Int(params.code)
14+
localizedDescription = params.message
15+
}
16+
17+
private init(_ error: firebase.storage.Error) {
18+
self.init(rawValue: Int(error.rawValue))
19+
}
20+
}
21+
22+
extension StorageErrorCode: RawRepresentable {
23+
public typealias RawValue = Int
24+
25+
public init(rawValue: Int) {
26+
self.rawValue = rawValue
27+
localizedDescription = "\(rawValue)"
28+
}
29+
}
30+
31+
extension StorageErrorCode {
32+
init(_ error: firebase.storage.Error, errorMessage: String?) {
33+
self.init((code: error.rawValue, message: errorMessage ?? "\(error.rawValue)"))
34+
}
35+
36+
init?(_ error: firebase.storage.Error?, errorMessage: UnsafePointer<CChar>?) {
37+
guard let actualError = error, actualError.rawValue != 0 else { return nil }
38+
var errorMessageString: String?
39+
if let errorMessage {
40+
errorMessageString = .init(cString: errorMessage)
41+
}
42+
self.init(actualError, errorMessage: errorMessageString)
43+
}
44+
}
45+
46+
extension StorageErrorCode {
47+
public static var none: Self { .init(firebase.storage.kErrorNone) }
48+
public static var unknown: Self { .init(firebase.storage.kErrorUnknown) }
49+
public static var objectNotFound: Self { .init(firebase.storage.kErrorObjectNotFound) }
50+
public static var bucketNotFound: Self { .init(firebase.storage.kErrorBucketNotFound) }
51+
public static var projectNotFound: Self { .init(firebase.storage.kErrorProjectNotFound) }
52+
public static var quotaExceeded: Self { .init(firebase.storage.kErrorQuotaExceeded) }
53+
public static var unauthenticated: Self { .init(firebase.storage.kErrorUnauthenticated) }
54+
public static var unauthorized: Self { .init(firebase.storage.kErrorUnauthorized) }
55+
public static var retryLimitExceeded: Self { .init(firebase.storage.kErrorRetryLimitExceeded) }
56+
public static var nonMatchingChecksum: Self { .init(firebase.storage.kErrorNonMatchingChecksum) }
57+
public static var downloadSizeExceeded: Self { .init(firebase.storage.kErrorDownloadSizeExceeded) }
58+
public static var cancelled: Self { .init(firebase.storage.kErrorCancelled) }
59+
}
60+
61+
extension StorageErrorCode: Equatable {}
62+
63+
extension StorageErrorCode {
64+
// The Obj C API provides this type as well, so provide it here for consistency.
65+
public typealias Code = StorageErrorCode
66+
67+
// This allows us to re-expose self as a code similarly
68+
// to what the Firebase SDK does when it creates the
69+
// underlying NSErrors on iOS/macOS.
70+
public var code: Code {
71+
return self
72+
}
73+
74+
public init(_ code: Code) {
75+
self.init(rawValue: code.rawValue)
76+
}
77+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
3+
@_exported
4+
import firebase
5+
@_spi(FirebaseInternal)
6+
import FirebaseCore
7+
8+
import CxxShim
9+
10+
public class StorageMetadata {
11+
let impl: firebase.storage.Metadata
12+
13+
init(_ impl: firebase.storage.Metadata) {
14+
self.impl = impl
15+
}
16+
17+
public init() {
18+
self.impl = .init()
19+
}
20+
21+
public var customMetadata: [String: String]? {
22+
get {
23+
let map = swift_firebase.swift_cxx_shims.firebase.storage.metadata_get_custom_metadata(impl)
24+
return map.toDict()
25+
}
26+
set {
27+
swift_firebase.swift_cxx_shims.firebase.storage.metadata_clear_custom_metadata(impl)
28+
guard let newValue else { return }
29+
for (key, value) in newValue {
30+
swift_firebase.swift_cxx_shims.firebase.storage.metadata_insert_custom_metadata(
31+
impl, std.string(key), std.string(value)
32+
)
33+
}
34+
}
35+
}
36+
}
37+
38+
// Workaround for https://github.com/apple/swift/issues/69711
39+
private extension swift_firebase.swift_cxx_shims.firebase.storage.CustomMetadata {
40+
borrowing func toDict() -> [String: String] {
41+
var result = [String: String]()
42+
var iterator = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_begin(self)
43+
let endIterator = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_end(self)
44+
45+
while !swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_iterators_equal(iterator, endIterator) {
46+
let key = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_iterator_first(iterator)
47+
let value = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_iterator_second(iterator)
48+
result[String(key.pointee)] = String(value.pointee)
49+
iterator = iterator.successor()
50+
}
51+
return result
52+
}
53+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
3+
@_exported
4+
import firebase
5+
@_spi(FirebaseInternal)
6+
import FirebaseCore
7+
8+
import CxxShim
9+
import Foundation
10+
11+
public class StorageReference {
12+
let impl: firebase.storage.StorageReference
13+
14+
init(_ impl: firebase.storage.StorageReference) {
15+
self.impl = impl
16+
}
17+
18+
public func child(_ path: String) -> StorageReference {
19+
.init(impl.Child(path))
20+
}
21+
22+
public func downloadURL(completion: @escaping (URL?, Error?) -> Void) {
23+
downloadURLImpl() { result, error in
24+
DispatchQueue.main.async {
25+
completion(result, error)
26+
}
27+
}
28+
}
29+
30+
public func downloadURL() async throws -> URL {
31+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<URL, any Error>) in
32+
downloadURLImpl() { result, error in
33+
if let error {
34+
continuation.resume(throwing: error)
35+
} else {
36+
continuation.resume(returning: result!)
37+
}
38+
}
39+
}
40+
}
41+
42+
private func downloadURLImpl(completion: @escaping (URL?, Error?) -> Void) {
43+
let future = swift_firebase.swift_cxx_shims.firebase.storage.storage_reference_get_download_url(impl)
44+
future.setCompletion({
45+
let (result, error) = future.resultAndError { StorageErrorCode($0) }
46+
completion(result.flatMap { .init(string: String($0)) }, error)
47+
})
48+
}
49+
50+
public func putDataAsync(
51+
_ uploadData: Data,
52+
metadata: StorageMetadata? = nil,
53+
onProgress: ((Progress?) -> Void)? = nil
54+
) async throws -> StorageMetadata {
55+
// TODO(PRENG-63978): Add support for `onProgress` callback.
56+
assert(onProgress == nil, "Missing support for non-nil onProgress")
57+
let controller = ControllerRef()
58+
return try await withTaskCancellationHandler {
59+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<StorageMetadata, any Error>) in
60+
let future = uploadData.withUnsafeBytes { ptr in
61+
let bytes = ptr.baseAddress!.assumingMemoryBound(to: UInt8.self)
62+
let numBytes = uploadData.count
63+
if let metadata {
64+
return swift_firebase.swift_cxx_shims.firebase.storage.storage_reference_put_bytes(
65+
self.impl, bytes, numBytes, metadata.impl, &controller.impl
66+
)
67+
} else {
68+
return swift_firebase.swift_cxx_shims.firebase.storage.storage_reference_put_bytes(
69+
self.impl, bytes, numBytes, &controller.impl
70+
)
71+
}
72+
}
73+
future.setCompletion({
74+
let (result, error) = future.resultAndError { StorageErrorCode($0) }
75+
if let error {
76+
continuation.resume(throwing: error)
77+
} else {
78+
continuation.resume(returning: result.map { .init($0) } ?? .init())
79+
}
80+
})
81+
}
82+
} onCancel: {
83+
controller.impl.Cancel()
84+
}
85+
}
86+
}
87+
88+
// The underlying `firebase.storage.Controller` type is thread-safe.
89+
private class ControllerRef: @unchecked Sendable {
90+
var impl: firebase.storage.Controller = .init()
91+
}

0 commit comments

Comments
 (0)