Skip to content

Commit

Permalink
feat(storage): add info, exists, custom metadata, and methods for upl…
Browse files Browse the repository at this point in the history
…oading file URL (#510)
  • Loading branch information
grdsdev authored Sep 25, 2024
1 parent 6522826 commit d9ba673
Show file tree
Hide file tree
Showing 13 changed files with 496 additions and 163 deletions.
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

0 comments on commit d9ba673

Please sign in to comment.