Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FirebaseStorage module #65

Merged
merged 10 commits into from
May 15, 2024
Merged
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
19 changes: 19 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,25 @@ target_link_libraries(FirebaseFunctions PRIVATE
ssl
zlibstatic)

add_library(FirebaseStorage SHARED
Sources/FirebaseStorage/StorageErrorCode.swift
Sources/FirebaseStorage/Storage+Swift.swift
Sources/FirebaseStorage/StorageMetadata+Swift.swift
Sources/FirebaseStorage/StorageReference+Swift.swift)
target_compile_options(FirebaseStorage PRIVATE
-cxx-interoperability-mode=default)
target_link_libraries(FirebaseStorage PUBLIC
firebase
firebase_storage
FirebaseCore)
target_link_libraries(FirebaseStorage PRIVATE
crypto
firebase_rest_lib
flatbuffers
libcurl
ssl
zlibstatic)

if(SWIFT_FIREBASE_BUILD_EXAMPLES)
FetchContent_Declare(SwiftWin32
GIT_REPOSITORY https://github.com/compnerd/swift-win32
Expand Down
13 changes: 13 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ let SwiftFirebase =
.library(name: "FirebaseAuth", targets: ["FirebaseAuth"]),
.library(name: "FirebaseFirestore", targets: ["FirebaseFirestore"]),
.library(name: "FirebaseFunctions", targets: ["FirebaseFunctions"]),
.library(name: "FirebaseStorage", targets: ["FirebaseStorage"]),
.executable(name: "FireBaseUI", targets: ["FireBaseUI"]),
],
dependencies: [
Expand Down Expand Up @@ -193,6 +194,18 @@ let SwiftFirebase =
.interoperabilityMode(.Cxx),
.unsafeFlags(["-Xcc", "-I\(include)"]),
]),
.target(name: "FirebaseStorage",
dependencies: ["firebase", "FirebaseCore"],
cxxSettings: [
.define("INTERNAL_EXPERIMENTAL"),
.define("_CRT_SECURE_NO_WARNINGS",
.when(platforms: [.windows])),
.headerSearchPath("../../third_party/firebase-development/usr/include"),
],
swiftSettings: [
.interoperabilityMode(.Cxx),
.unsafeFlags(["-Xcc", "-I\(include)"]),
]),
.executableTarget(name: "FireBaseUI",
dependencies: [
"FirebaseCore",
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebaseFunctions/HTTPSCallable+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class HTTPSCallable {
callImpl(data: data) { result, error in
if let error {
continuation.resume(throwing: error)
} else{
} else {
continuation.resume(returning: result ?? .init())
}
}
Expand Down
28 changes: 28 additions & 0 deletions Sources/FirebaseStorage/Storage+Swift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase
@_spi(FirebaseInternal)
import FirebaseCore

import CxxShim

public class Storage {
let impl: swift_firebase.swift_cxx_shims.firebase.storage.StorageRef

init(_ impl: swift_firebase.swift_cxx_shims.firebase.storage.StorageRef) {
self.impl = impl
}

public static func storage(app: FirebaseApp) -> Storage {
let instance = swift_firebase.swift_cxx_shims.firebase.storage.storage_get_instance(app)
guard swift_firebase.swift_cxx_shims.firebase.storage.storage_is_valid(instance) else {
fatalError("Invalid Storage Instance")
}
return .init(instance)
}

public func reference(withPath path: String) -> StorageReference {
.init(swift_firebase.swift_cxx_shims.firebase.storage.storage_get_reference(impl, path))
}
}
77 changes: 77 additions & 0 deletions Sources/FirebaseStorage/StorageErrorCode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase
@_spi(FirebaseInternal)
import FirebaseCore

public struct StorageErrorCode: Error {
public let rawValue: Int
public let localizedDescription: String

internal init(_ params: (code: Int32, message: String)) {
self.rawValue = Int(params.code)
localizedDescription = params.message
}

private init(_ error: firebase.storage.Error) {
self.init(rawValue: Int(error.rawValue))
}
}

extension StorageErrorCode: RawRepresentable {
public typealias RawValue = Int

public init(rawValue: Int) {
self.rawValue = rawValue
localizedDescription = "\(rawValue)"
}
}

extension StorageErrorCode {
init(_ error: firebase.storage.Error, errorMessage: String?) {
self.init((code: error.rawValue, message: errorMessage ?? "\(error.rawValue)"))
}

init?(_ error: firebase.storage.Error?, errorMessage: UnsafePointer<CChar>?) {
guard let actualError = error, actualError.rawValue != 0 else { return nil }
var errorMessageString: String?
if let errorMessage {
errorMessageString = .init(cString: errorMessage)
}
self.init(actualError, errorMessage: errorMessageString)
}
}

extension StorageErrorCode {
public static var none: Self { .init(firebase.storage.kErrorNone) }
public static var unknown: Self { .init(firebase.storage.kErrorUnknown) }
public static var objectNotFound: Self { .init(firebase.storage.kErrorObjectNotFound) }
public static var bucketNotFound: Self { .init(firebase.storage.kErrorBucketNotFound) }
public static var projectNotFound: Self { .init(firebase.storage.kErrorProjectNotFound) }
public static var quotaExceeded: Self { .init(firebase.storage.kErrorQuotaExceeded) }
public static var unauthenticated: Self { .init(firebase.storage.kErrorUnauthenticated) }
public static var unauthorized: Self { .init(firebase.storage.kErrorUnauthorized) }
public static var retryLimitExceeded: Self { .init(firebase.storage.kErrorRetryLimitExceeded) }
public static var nonMatchingChecksum: Self { .init(firebase.storage.kErrorNonMatchingChecksum) }
public static var downloadSizeExceeded: Self { .init(firebase.storage.kErrorDownloadSizeExceeded) }
public static var cancelled: Self { .init(firebase.storage.kErrorCancelled) }
}

extension StorageErrorCode: Equatable {}

extension StorageErrorCode {
// The Obj C API provides this type as well, so provide it here for consistency.
public typealias Code = StorageErrorCode

// This allows us to re-expose self as a code similarly
// to what the Firebase SDK does when it creates the
// underlying NSErrors on iOS/macOS.
public var code: Code {
return self
}

public init(_ code: Code) {
self.init(rawValue: code.rawValue)
}
}
53 changes: 53 additions & 0 deletions Sources/FirebaseStorage/StorageMetadata+Swift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase
@_spi(FirebaseInternal)
import FirebaseCore

import CxxShim

public class StorageMetadata {
let impl: firebase.storage.Metadata

init(_ impl: firebase.storage.Metadata) {
self.impl = impl
}

public init() {
self.impl = .init()
}

public var customMetadata: [String: String]? {
get {
let map = swift_firebase.swift_cxx_shims.firebase.storage.metadata_get_custom_metadata(impl)
return map.toDict()
}
set {
swift_firebase.swift_cxx_shims.firebase.storage.metadata_clear_custom_metadata(impl)
guard let newValue else { return }
for (key, value) in newValue {
swift_firebase.swift_cxx_shims.firebase.storage.metadata_insert_custom_metadata(
impl, std.string(key), std.string(value)
)
}
}
}
}

// Workaround for https://github.com/apple/swift/issues/69711
private extension swift_firebase.swift_cxx_shims.firebase.storage.CustomMetadata {
borrowing func toDict() -> [String: String] {
darinf marked this conversation as resolved.
Show resolved Hide resolved
var result = [String: String]()
var iterator = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_begin(self)
let endIterator = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_end(self)

while !swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_iterators_equal(iterator, endIterator) {
let key = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_iterator_first(iterator)
let value = swift_firebase.swift_cxx_shims.firebase.storage.custom_metadata_iterator_second(iterator)
result[String(key.pointee)] = String(value.pointee)
iterator = iterator.successor()
}
return result
}
}
91 changes: 91 additions & 0 deletions Sources/FirebaseStorage/StorageReference+Swift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase
@_spi(FirebaseInternal)
import FirebaseCore

import CxxShim
import Foundation

public class StorageReference {
let impl: firebase.storage.StorageReference

init(_ impl: firebase.storage.StorageReference) {
self.impl = impl
}

public func child(_ path: String) -> StorageReference {
.init(impl.Child(path))
}

public func downloadURL(completion: @escaping (URL?, Error?) -> Void) {
downloadURLImpl() { result, error in
DispatchQueue.main.async {
completion(result, error)
}
}
}

public func downloadURL() async throws -> URL {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<URL, any Error>) in
downloadURLImpl() { result, error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: result!)
}
}
}
}

private func downloadURLImpl(completion: @escaping (URL?, Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.storage.storage_reference_get_download_url(impl)
future.setCompletion({
let (result, error) = future.resultAndError { StorageErrorCode($0) }
completion(result.flatMap { .init(string: String($0)) }, error)
})
}

public func putDataAsync(
_ uploadData: Data,
metadata: StorageMetadata? = nil,
onProgress: ((Progress?) -> Void)? = nil
) async throws -> StorageMetadata {
// TODO(PRENG-63978): Add support for `onProgress` callback.
assert(onProgress == nil, "Missing support for non-nil onProgress")
let controller = ControllerRef()
return try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<StorageMetadata, any Error>) in
let future = uploadData.withUnsafeBytes { ptr in
let bytes = ptr.baseAddress!.assumingMemoryBound(to: UInt8.self)
let numBytes = uploadData.count
if let metadata {
return swift_firebase.swift_cxx_shims.firebase.storage.storage_reference_put_bytes(
self.impl, bytes, numBytes, metadata.impl, &controller.impl
)
} else {
return swift_firebase.swift_cxx_shims.firebase.storage.storage_reference_put_bytes(
self.impl, bytes, numBytes, &controller.impl
)
}
}
future.setCompletion({
let (result, error) = future.resultAndError { StorageErrorCode($0) }
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: result.map { .init($0) } ?? .init())
}
})
}
} onCancel: {
controller.impl.Cancel()
}
}
}

// The underlying `firebase.storage.Controller` type is thread-safe.
private class ControllerRef: @unchecked Sendable {
var impl: firebase.storage.Controller = .init()
}
Loading
Loading