Skip to content

Commit

Permalink
Merge pull request #6 from darrarski/feature/upload-file
Browse files Browse the repository at this point in the history
Upload file
  • Loading branch information
darrarski authored Jul 9, 2023
2 parents 3ae6cbd + 1eff35a commit 324481f
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 2 deletions.
11 changes: 11 additions & 0 deletions Example/DropboxClientExampleApp/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ extension DropboxClient.Client: DependencyKey {
return entry
}
return DeleteFile.Result(metadata: entry)
},
uploadFile: .init { params in
FileMetadata(
id: "id:preview-uploaded",
name: "Preview-uploaded.txt",
pathDisplay: "/Preview-uploaded.txt",
pathLower: "/preview-uploaded.txt",
clientModified: Date(),
serverModified: Date(),
isDownloadable: true
)
}
)
}()
Expand Down
41 changes: 41 additions & 0 deletions Example/DropboxClientExampleApp/ExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ struct ExampleView: View {
} label: {
Text("List Folder")
}

Button {
Task<Void, Never> {
do {
_ = try await client.uploadFile(
path: "/example-upload.txt",
data: "swift-dropbox-client example uploaded file".data(using: .utf8)!,
mode: .add,
autorename: true
)
} catch {
log.error("UploadFile failure", metadata: [
"error": "\(error)",
"localizedDescription": "\(error.localizedDescription)"
])
}
}
} label: {
Text("Upload file")
}
}

if let list {
Expand Down Expand Up @@ -165,6 +185,27 @@ struct ExampleView: View {
Text("Download File")
}

Button {
Task<Void, Never> {
do {
_ = try await client.uploadFile(
path: entry.pathDisplay,
data: "Uploaded with overwrite at \(Date().formatted(date: .complete, time: .complete))"
.data(using: .utf8)!,
mode: .overwrite,
autorename: false
)
} catch {
log.error("UploadFile failure", metadata: [
"error": "\(error)",
"localizedDescription": "\(error.localizedDescription)"
])
}
}
} label: {
Text("Upload file (overwrite)")
}

Button(role: .destructive) {
Task<Void, Never> {
do {
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ Basic Dropbox HTTP API client that does not depend on Dropbox's SDK. No external

- Authorize access
- List folder
- Upload file
- Download file
- ...
- Delete file

## 📖 Usage

Expand Down
9 changes: 8 additions & 1 deletion Sources/DropboxClient/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ public struct Client: Sendable {
auth: Auth,
listFolder: ListFolder,
downloadFile: DownloadFile,
deleteFile: DeleteFile
deleteFile: DeleteFile,
uploadFile: UploadFile
) {
self.auth = auth
self.listFolder = listFolder
self.downloadFile = downloadFile
self.deleteFile = deleteFile
self.uploadFile = uploadFile
}

public var auth: Auth
public var listFolder: ListFolder
public var downloadFile: DownloadFile
public var deleteFile: DeleteFile
public var uploadFile: UploadFile
}

extension Client {
Expand Down Expand Up @@ -46,6 +49,10 @@ extension Client {
deleteFile: .live(
keychain: keychain,
httpClient: httpClient
),
uploadFile: .live(
keychain: keychain,
httpClient: httpClient
)
)
}
Expand Down
29 changes: 29 additions & 0 deletions Sources/DropboxClient/FileMetadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

public struct FileMetadata: Sendable, Equatable, Identifiable, Decodable {
public init(
id: String,
name: String,
pathDisplay: String,
pathLower: String,
clientModified: Date,
serverModified: Date,
isDownloadable: Bool
) {
self.id = id
self.name = name
self.pathDisplay = pathDisplay
self.pathLower = pathLower
self.clientModified = clientModified
self.serverModified = serverModified
self.isDownloadable = isDownloadable
}

public var id: String
public var name: String
public var pathDisplay: String
public var pathLower: String
public var clientModified: Date
public var serverModified: Date
public var isDownloadable: Bool
}
119 changes: 119 additions & 0 deletions Sources/DropboxClient/UploadFile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Foundation

public struct UploadFile: Sendable {
public struct Params: Sendable, Equatable, Encodable {
public enum Mode: String, Sendable, Equatable, Encodable {
case add, overwrite
}

public init(
path: String,
data: Data,
mode: Mode? = nil,
autorename: Bool? = nil
) {
self.path = path
self.data = data
self.mode = mode
self.autorename = autorename
}

public var path: String
public var data: Data
public var mode: Mode?
public var autorename: Bool?
}

public enum Error: Swift.Error, Sendable, Equatable {
case notAuthorized
case response(statusCode: Int?, data: Data)
}

public typealias Run = @Sendable (Params) async throws -> FileMetadata

public init(run: @escaping Run) {
self.run = run
}

public var run: Run

public func callAsFunction(_ params: Params) async throws -> FileMetadata {
try await run(params)
}

public func callAsFunction(
path: String,
data: Data,
mode: Params.Mode? = nil,
autorename: Bool? = nil
) async throws -> FileMetadata {
try await run(.init(
path: path,
data: data,
mode: mode,
autorename: autorename
))
}
}

extension UploadFile {
public static func live(
keychain: Keychain,
httpClient: HTTPClient
) -> UploadFile {
UploadFile { params in
guard let credentials = await keychain.loadCredentials() else {
throw Error.notAuthorized
}

let request: URLRequest = try {
var components = URLComponents()
components.scheme = "https"
components.host = "content.dropboxapi.com"
components.path = "/2/files/upload"

var request = URLRequest(url: components.url!)
request.httpMethod = "POST"
request.setValue(
"\(credentials.tokenType) \(credentials.accessToken)",
forHTTPHeaderField: "Authorization"
)

struct Args: Encodable {
var path: String
var mode: String?
var autorename: Bool?
}
let args = Args(
path: params.path,
mode: params.mode?.rawValue,
autorename: params.autorename
)
let argsData = try JSONEncoder.api.encode(args)
let argsString = String(data: argsData, encoding: .utf8)

request.setValue(
argsString,
forHTTPHeaderField: "Dropbox-API-Arg"
)
request.setValue(
"application/octet-stream",
forHTTPHeaderField: "Content-Type"
)
request.httpBody = params.data

return request
}()

let (responseData, response) = try await httpClient.data(for: request)
let statusCode = (response as? HTTPURLResponse)?.statusCode

guard let statusCode, (200..<300).contains(statusCode) else {
throw Error.response(statusCode: statusCode, data: responseData)
}

let metadata = try JSONDecoder.api.decode(FileMetadata.self, from: responseData)
return metadata
}
}
}
Loading

0 comments on commit 324481f

Please sign in to comment.