Skip to content

Commit

Permalink
Merge pull request #9 from darrarski/feature/get-metadata
Browse files Browse the repository at this point in the history
GetMetadata
  • Loading branch information
darrarski authored Jul 11, 2023
2 parents 2ec723e + b8333da commit bd22901
Show file tree
Hide file tree
Showing 5 changed files with 373 additions and 81 deletions.
12 changes: 12 additions & 0 deletions Example/DropboxClientExampleApp/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ extension DropboxClient.Client: DependencyKey {
serverModified: Date(),
isDownloadable: true
)
},
getMetadata: .init { params in
Metadata(
tag: .file,
id: "id:preview-uploaded",
name: "Preview-uploaded.txt",
pathDisplay: "/Preview-uploaded.txt",
pathLower: "/preview-uploaded.txt",
clientModified: Date(),
serverModified: Date(),
isDownloadable: true
)
}
)
}()
Expand Down
184 changes: 104 additions & 80 deletions Example/DropboxClientExampleApp/ExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import Logging
import SwiftUI

struct ExampleView: View {
struct Alert: Equatable {
var title: String
var message: String
}

@Dependency(\.dropboxClient) var client
let log = Logger(label: Bundle.main.bundleIdentifier!)
@State var isSignedIn = false
@State var list: ListFolder.Result?
@State var fileContentAlert: String?
@State var alert: Alert?

var body: some View {
Form {
Expand All @@ -35,18 +40,18 @@ struct ExampleView: View {
}
}
.alert(
"File content",
alert?.title ?? "",
isPresented: Binding(
get: { fileContentAlert != nil },
get: { alert != nil },
set: { isPresented in
if !isPresented {
fileContentAlert = nil
alert = nil
}
}
),
presenting: fileContentAlert,
presenting: alert,
actions: { _ in Button("OK") {} },
message: { Text($0) }
message: { Text($0.message) }
)
}

Expand Down Expand Up @@ -130,98 +135,117 @@ struct ExampleView: View {

func listEntrySection(_ entry: Metadata) -> some View {
Section {
VStack(alignment: .leading) {
Text("Tag").font(.caption).foregroundColor(.secondary)
Text(entry.tag.rawValue)
}
Group {
VStack(alignment: .leading) {
Text("Tag").font(.caption).foregroundColor(.secondary)
Text(entry.tag.rawValue)
}

VStack(alignment: .leading) {
Text("ID").font(.caption).foregroundColor(.secondary)
Text(entry.id)
}
VStack(alignment: .leading) {
Text("ID").font(.caption).foregroundColor(.secondary)
Text(entry.id)
}

VStack(alignment: .leading) {
Text("Name").font(.caption).foregroundColor(.secondary)
Text(entry.name)
}
VStack(alignment: .leading) {
Text("Name").font(.caption).foregroundColor(.secondary)
Text(entry.name)
}

VStack(alignment: .leading) {
Text("Path (display)").font(.caption).foregroundColor(.secondary)
Text(entry.pathDisplay)
}
VStack(alignment: .leading) {
Text("Path (display)").font(.caption).foregroundColor(.secondary)
Text(entry.pathDisplay)
}

VStack(alignment: .leading) {
Text("Path (lower)").font(.caption).foregroundColor(.secondary)
Text(entry.pathLower)
}
VStack(alignment: .leading) {
Text("Path (lower)").font(.caption).foregroundColor(.secondary)
Text(entry.pathLower)
}

VStack(alignment: .leading) {
Text("Client modified").font(.caption).foregroundColor(.secondary)
Text(entry.clientModified.formatted(date: .complete, time: .complete))
}
VStack(alignment: .leading) {
Text("Client modified").font(.caption).foregroundColor(.secondary)
Text(entry.clientModified.formatted(date: .complete, time: .complete))
}

VStack(alignment: .leading) {
Text("Server modified").font(.caption).foregroundColor(.secondary)
Text(entry.serverModified.formatted(date: .complete, time: .complete))
VStack(alignment: .leading) {
Text("Server modified").font(.caption).foregroundColor(.secondary)
Text(entry.serverModified.formatted(date: .complete, time: .complete))
}
}

Button {
Task<Void, Never> {
do {
let data = try await client.downloadFile(path: entry.id)
if let string = String(data: data, encoding: .utf8) {
fileContentAlert = string
} else {
fileContentAlert = data.base64EncodedString()
Group {
Button {
Task<Void, Never> {
do {
let metadata = try await client.getMetadata(path: entry.pathDisplay)
alert = Alert(title: "Metadata", message: String(describing: metadata))
} catch {
log.error("GetMetadata failure", metadata: [
"error": "\(error)",
"localizedDescription": "\(error.localizedDescription)"
])
}
} catch {
log.error("DownloadFile failure", metadata: [
"error": "\(error)",
"localizedDescription": "\(error.localizedDescription)"
])
}
} label: {
Text("Get Metadata")
}
} label: {
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)"
])
Button {
Task<Void, Never> {
do {
let data = try await client.downloadFile(path: entry.id)
alert = Alert(
title: "Content",
message: String(data: data, encoding: .utf8) ?? data.base64EncodedString()
)
} catch {
log.error("DownloadFile failure", metadata: [
"error": "\(error)",
"localizedDescription": "\(error.localizedDescription)"
])
}
}
} label: {
Text("Download File")
}
} label: {
Text("Upload file (overwrite)")
}

Button(role: .destructive) {
Task<Void, Never> {
do {
_ = try await client.deleteFile(path: entry.pathDisplay)
if let entries = list?.entries {
list?.entries = entries.filter { $0.pathDisplay != entry.pathDisplay }
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 {
_ = try await client.deleteFile(path: entry.pathDisplay)
if let entries = list?.entries {
list?.entries = entries.filter { $0.pathDisplay != entry.pathDisplay }
}
} catch {
log.error("DeleteFile failure", metadata: [
"error": "\(error)",
"localizedDescription": "\(error.localizedDescription)"
])
}
} catch {
log.error("DeleteFile failure", metadata: [
"error": "\(error)",
"localizedDescription": "\(error.localizedDescription)"
])
}
} label: {
Text("Delete File")
}
} label: {
Text("Delete File")
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion Sources/DropboxClient/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ public struct Client: Sendable {
listFolder: ListFolder,
downloadFile: DownloadFile,
deleteFile: DeleteFile,
uploadFile: UploadFile
uploadFile: UploadFile,
getMetadata: GetMetadata
) {
self.auth = auth
self.listFolder = listFolder
self.downloadFile = downloadFile
self.deleteFile = deleteFile
self.uploadFile = uploadFile
self.getMetadata = getMetadata
}

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

extension Client {
Expand Down Expand Up @@ -59,6 +62,11 @@ extension Client {
auth: auth,
keychain: keychain,
httpClient: httpClient
),
getMetadata: .live(
auth: auth,
keychain: keychain,
httpClient: httpClient
)
)
}
Expand Down
91 changes: 91 additions & 0 deletions Sources/DropboxClient/GetMetadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Foundation

public struct GetMetadata: Sendable {
public struct Params: Sendable, Equatable, Encodable {
public init(
path: String,
includeMediaInfo: Bool? = nil,
includeDeleted: Bool? = nil
) {
self.path = path
self.includeMediaInfo = includeMediaInfo
self.includeDeleted = includeDeleted
}

public var path: String
public var includeMediaInfo: Bool?
public var includeDeleted: Bool?
}

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

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

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

public var run: Run

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

public func callAsFunction(
path: String
) async throws -> Metadata {
try await run(.init(
path: path
))
}
}

extension GetMetadata {
public static func live(
auth: Auth,
keychain: Keychain,
httpClient: HTTPClient
) -> GetMetadata {
GetMetadata { params in
try await auth.refreshToken()

guard let credentials = await keychain.loadCredentials() else {
throw Error.notAuthorized
}

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

var request = URLRequest(url: components.url!)
request.httpMethod = "POST"
request.setValue(
"\(credentials.tokenType) \(credentials.accessToken)",
forHTTPHeaderField: "Authorization"
)
request.setValue(
"application/json",
forHTTPHeaderField: "Content-Type"
)
request.httpBody = try JSONEncoder.api.encode(params)

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(Metadata.self, from: responseData)
return metadata
}
}
}
Loading

0 comments on commit bd22901

Please sign in to comment.