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

feat(storage): add info, exists, custom metadata, and methods for uploading file URL #510

Merged
merged 10 commits into from
Sep 25, 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
9 changes: 9 additions & 0 deletions .swiftpm/configuration/Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "multipartformdata",
"kind" : "remoteSourceControl",
"location" : "https://github.com/grdsdev/MultipartFormData",
"state" : {
"revision" : "ed7abea9cfc6c3b5e77a73fe6842c57a372d2017",
"version" : "0.1.0"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
Expand Down
9 changes: 9 additions & 0 deletions Examples/Examples/Storage/FileObjectDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ struct FileObjectDetailView: View {
} catch {}
}
}

Button("Get info") {
Task {
do {
let info = try await api.info(path: fileObject.name)
lastActionResult = ("info", info)
} catch {}
}
}
}

if let lastActionResult {
Expand Down
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ let package = Package(
.library(name: "Supabase", targets: ["Supabase", "Functions", "PostgREST", "Auth", "Realtime", "Storage"]),
],
dependencies: [
.package(url: "https://github.com/grdsdev/MultipartFormData", from: "0.1.0"),
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0"),
.package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.1.0"),
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"),
Expand Down Expand Up @@ -127,7 +128,13 @@ let package = Package(
"TestHelpers",
]
),
.target(name: "Storage", dependencies: ["Helpers"]),
.target(
name: "Storage",
dependencies: [
"MultipartFormData",
"Helpers",
]
),
.testTarget(
name: "StorageTests",
dependencies: [
Expand Down
105 changes: 105 additions & 0 deletions Sources/Storage/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,109 @@ extension StorageFileApi {
) async throws -> String {
try await uploadToSignedURL(path: path, token: token, file: file, options: options).fullPath
}

@available(*, deprecated, renamed: "upload(_:data:options:)")
@discardableResult
public func upload(
path: String,
file: Data,
options: FileOptions = FileOptions()
) async throws -> FileUploadResponse {
try await upload(path, data: file, options: options)
}

@available(*, deprecated, renamed: "update(_:data:options:)")
@discardableResult
public func update(
path: String,
file: Data,
options: FileOptions = FileOptions()
) async throws -> FileUploadResponse {
try await update(path, data: file, options: options)
}

@available(*, deprecated, renamed: "updateToSignedURL(_:token:data:options:)")
@discardableResult
public func uploadToSignedURL(
path: String,
token: String,
file: Data,
options: FileOptions = FileOptions()
) async throws -> SignedURLUploadResponse {
try await uploadToSignedURL(path, token: token, data: file, options: options)
}
}

@available(
*,
deprecated,
message: "File was deprecated and it isn't used in the package anymore, if you're using it on your application, consider replacing it as it will be removed on the next major release."
)
public struct File: Hashable, Equatable {
public var name: String
public var data: Data
public var fileName: String?
public var contentType: String?

public init(name: String, data: Data, fileName: String?, contentType: String?) {
self.name = name
self.data = data
self.fileName = fileName
self.contentType = contentType
}
}

@available(
*,
deprecated,
renamed: "MultipartFormData",
message: "FormData was deprecated in favor of MultipartFormData, and it isn't used in the package anymore, if you're using it on your application, consider replacing it as it will be removed on the next major release."
)
public class FormData {
var files: [File] = []
var boundary: String

public init(boundary: String = UUID().uuidString) {
self.boundary = boundary
}

public func append(file: File) {
files.append(file)
}

public var contentType: String {
"multipart/form-data; boundary=\(boundary)"
}

public var data: Data {
var data = Data()

for file in files {
data.append("--\(boundary)\r\n")
data.append("Content-Disposition: form-data; name=\"\(file.name)\"")
if let filename = file.fileName?.replacingOccurrences(of: "\"", with: "_") {
data.append("; filename=\"\(filename)\"")
}
data.append("\r\n")
if let contentType = file.contentType {
data.append("Content-Type: \(contentType)\r\n")
}
data.append("\r\n")
data.append(file.data)
data.append("\r\n")
}

data.append("--\(boundary)--\r\n")
return data
}
}

extension Data {
mutating func append(_ string: String) {
let data = string.data(
using: String.Encoding.utf8,
allowLossyConversion: true
)
append(data!)
}
}
77 changes: 53 additions & 24 deletions Sources/Storage/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,67 @@

import Foundation

#if canImport(CoreServices)
#if canImport(MobileCoreServices)
import MobileCoreServices
#elseif canImport(CoreServices)
import CoreServices
#endif

#if canImport(UniformTypeIdentifiers)
import UniformTypeIdentifiers
#endif

#if os(Linux) || os(Windows)
/// On Linux or Windows this method always returns `application/octet-stream`.
func mimeTypeForExtension(_: String) -> String {
"application/octet-stream"
func mimeType(forPathExtension pathExtension: String) -> String {
#if swift(>=5.9)
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, visionOS 1, *) {
return UTType(filenameExtension: pathExtension)?.preferredMIMEType
?? "application/octet-stream"
} else {
if let id = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, pathExtension as CFString, nil
)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
.takeRetainedValue()
{
return contentType as String
}

return "application/octet-stream"
}
#else
if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, *) {
return UTType(filenameExtension: pathExtension)?.preferredMIMEType
?? "application/octet-stream"
} else {
if let id = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, pathExtension as CFString, nil
)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
.takeRetainedValue()
{
return contentType as String
}

return "application/octet-stream"
}
#endif
}
#else
func mimeTypeForExtension(_ fileExtension: String) -> String {
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, visionOS 1.0, *) {
return UTType(filenameExtension: fileExtension)?.preferredMIMEType ?? "application/octet-stream"
} else {
guard
let type = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
fileExtension as NSString,
nil
)?.takeUnretainedValue(),
let mimeType = UTTypeCopyPreferredTagWithClass(
type,
kUTTagClassMIMEType
)?.takeUnretainedValue()
else { return "application/octet-stream" }

return mimeType as String
}

// MARK: - Private - Mime Type

func mimeType(forPathExtension pathExtension: String) -> String {
#if canImport(CoreServices) || canImport(MobileCoreServices)
if let id = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, pathExtension as CFString, nil
)?.takeRetainedValue(),
let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?
.takeRetainedValue()
{
return contentType as String
}
#endif

return "application/octet-stream"
}
#endif

Expand Down
64 changes: 0 additions & 64 deletions Sources/Storage/MultipartFile.swift

This file was deleted.

14 changes: 9 additions & 5 deletions Sources/Storage/StorageApi.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import Helpers
import class MultipartFormData.MultipartFormData

#if canImport(FoundationNetworking)
import FoundationNetworking
Expand Down Expand Up @@ -36,7 +37,10 @@ public class StorageApi: @unchecked Sendable {
let response = try await http.send(request)

guard (200 ..< 300).contains(response.statusCode) else {
if let error = try? configuration.decoder.decode(StorageError.self, from: response.data) {
if let error = try? configuration.decoder.decode(
StorageError.self,
from: response.data
) {
throw error
}

Expand All @@ -52,23 +56,23 @@ extension HTTPRequest {
url: URL,
method: HTTPMethod,
query: [URLQueryItem],
formData: FormData,
formData: MultipartFormData,
options: FileOptions,
headers: HTTPHeaders = [:]
) {
) throws {
var headers = headers
if headers["Content-Type"] == nil {
headers["Content-Type"] = formData.contentType
}
if headers["Cache-Control"] == nil {
headers["Cache-Control"] = "max-age=\(options.cacheControl)"
}
self.init(
try self.init(
url: url,
method: method,
query: query,
headers: headers,
body: formData.data
body: formData.encode()
)
}
}
2 changes: 1 addition & 1 deletion Sources/Storage/StorageBucketApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Helpers
#endif

/// Storage Bucket API
public class StorageBucketApi: StorageApi {
public class StorageBucketApi: StorageApi, @unchecked Sendable {
/// Retrieves the details of all Storage buckets within an existing product.
public func listBuckets() async throws -> [Bucket] {
try await execute(
Expand Down
Loading
Loading