Skip to content

Commit bd22901

Browse files
authored
Merge pull request #9 from darrarski/feature/get-metadata
GetMetadata
2 parents 2ec723e + b8333da commit bd22901

File tree

5 files changed

+373
-81
lines changed

5 files changed

+373
-81
lines changed

Example/DropboxClientExampleApp/Dependencies.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ extension DropboxClient.Client: DependencyKey {
8383
serverModified: Date(),
8484
isDownloadable: true
8585
)
86+
},
87+
getMetadata: .init { params in
88+
Metadata(
89+
tag: .file,
90+
id: "id:preview-uploaded",
91+
name: "Preview-uploaded.txt",
92+
pathDisplay: "/Preview-uploaded.txt",
93+
pathLower: "/preview-uploaded.txt",
94+
clientModified: Date(),
95+
serverModified: Date(),
96+
isDownloadable: true
97+
)
8698
}
8799
)
88100
}()

Example/DropboxClientExampleApp/ExampleView.swift

Lines changed: 104 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import Logging
44
import SwiftUI
55

66
struct ExampleView: View {
7+
struct Alert: Equatable {
8+
var title: String
9+
var message: String
10+
}
11+
712
@Dependency(\.dropboxClient) var client
813
let log = Logger(label: Bundle.main.bundleIdentifier!)
914
@State var isSignedIn = false
1015
@State var list: ListFolder.Result?
11-
@State var fileContentAlert: String?
16+
@State var alert: Alert?
1217

1318
var body: some View {
1419
Form {
@@ -35,18 +40,18 @@ struct ExampleView: View {
3540
}
3641
}
3742
.alert(
38-
"File content",
43+
alert?.title ?? "",
3944
isPresented: Binding(
40-
get: { fileContentAlert != nil },
45+
get: { alert != nil },
4146
set: { isPresented in
4247
if !isPresented {
43-
fileContentAlert = nil
48+
alert = nil
4449
}
4550
}
4651
),
47-
presenting: fileContentAlert,
52+
presenting: alert,
4853
actions: { _ in Button("OK") {} },
49-
message: { Text($0) }
54+
message: { Text($0.message) }
5055
)
5156
}
5257

@@ -130,98 +135,117 @@ struct ExampleView: View {
130135

131136
func listEntrySection(_ entry: Metadata) -> some View {
132137
Section {
133-
VStack(alignment: .leading) {
134-
Text("Tag").font(.caption).foregroundColor(.secondary)
135-
Text(entry.tag.rawValue)
136-
}
138+
Group {
139+
VStack(alignment: .leading) {
140+
Text("Tag").font(.caption).foregroundColor(.secondary)
141+
Text(entry.tag.rawValue)
142+
}
137143

138-
VStack(alignment: .leading) {
139-
Text("ID").font(.caption).foregroundColor(.secondary)
140-
Text(entry.id)
141-
}
144+
VStack(alignment: .leading) {
145+
Text("ID").font(.caption).foregroundColor(.secondary)
146+
Text(entry.id)
147+
}
142148

143-
VStack(alignment: .leading) {
144-
Text("Name").font(.caption).foregroundColor(.secondary)
145-
Text(entry.name)
146-
}
149+
VStack(alignment: .leading) {
150+
Text("Name").font(.caption).foregroundColor(.secondary)
151+
Text(entry.name)
152+
}
147153

148-
VStack(alignment: .leading) {
149-
Text("Path (display)").font(.caption).foregroundColor(.secondary)
150-
Text(entry.pathDisplay)
151-
}
154+
VStack(alignment: .leading) {
155+
Text("Path (display)").font(.caption).foregroundColor(.secondary)
156+
Text(entry.pathDisplay)
157+
}
152158

153-
VStack(alignment: .leading) {
154-
Text("Path (lower)").font(.caption).foregroundColor(.secondary)
155-
Text(entry.pathLower)
156-
}
159+
VStack(alignment: .leading) {
160+
Text("Path (lower)").font(.caption).foregroundColor(.secondary)
161+
Text(entry.pathLower)
162+
}
157163

158-
VStack(alignment: .leading) {
159-
Text("Client modified").font(.caption).foregroundColor(.secondary)
160-
Text(entry.clientModified.formatted(date: .complete, time: .complete))
161-
}
164+
VStack(alignment: .leading) {
165+
Text("Client modified").font(.caption).foregroundColor(.secondary)
166+
Text(entry.clientModified.formatted(date: .complete, time: .complete))
167+
}
162168

163-
VStack(alignment: .leading) {
164-
Text("Server modified").font(.caption).foregroundColor(.secondary)
165-
Text(entry.serverModified.formatted(date: .complete, time: .complete))
169+
VStack(alignment: .leading) {
170+
Text("Server modified").font(.caption).foregroundColor(.secondary)
171+
Text(entry.serverModified.formatted(date: .complete, time: .complete))
172+
}
166173
}
167174

168-
Button {
169-
Task<Void, Never> {
170-
do {
171-
let data = try await client.downloadFile(path: entry.id)
172-
if let string = String(data: data, encoding: .utf8) {
173-
fileContentAlert = string
174-
} else {
175-
fileContentAlert = data.base64EncodedString()
175+
Group {
176+
Button {
177+
Task<Void, Never> {
178+
do {
179+
let metadata = try await client.getMetadata(path: entry.pathDisplay)
180+
alert = Alert(title: "Metadata", message: String(describing: metadata))
181+
} catch {
182+
log.error("GetMetadata failure", metadata: [
183+
"error": "\(error)",
184+
"localizedDescription": "\(error.localizedDescription)"
185+
])
176186
}
177-
} catch {
178-
log.error("DownloadFile failure", metadata: [
179-
"error": "\(error)",
180-
"localizedDescription": "\(error.localizedDescription)"
181-
])
182187
}
188+
} label: {
189+
Text("Get Metadata")
183190
}
184-
} label: {
185-
Text("Download File")
186-
}
187191

188-
Button {
189-
Task<Void, Never> {
190-
do {
191-
_ = try await client.uploadFile(
192-
path: entry.pathDisplay,
193-
data: "Uploaded with overwrite at \(Date().formatted(date: .complete, time: .complete))"
194-
.data(using: .utf8)!,
195-
mode: .overwrite,
196-
autorename: false
197-
)
198-
} catch {
199-
log.error("UploadFile failure", metadata: [
200-
"error": "\(error)",
201-
"localizedDescription": "\(error.localizedDescription)"
202-
])
192+
Button {
193+
Task<Void, Never> {
194+
do {
195+
let data = try await client.downloadFile(path: entry.id)
196+
alert = Alert(
197+
title: "Content",
198+
message: String(data: data, encoding: .utf8) ?? data.base64EncodedString()
199+
)
200+
} catch {
201+
log.error("DownloadFile failure", metadata: [
202+
"error": "\(error)",
203+
"localizedDescription": "\(error.localizedDescription)"
204+
])
205+
}
203206
}
207+
} label: {
208+
Text("Download File")
204209
}
205-
} label: {
206-
Text("Upload file (overwrite)")
207-
}
208210

209-
Button(role: .destructive) {
210-
Task<Void, Never> {
211-
do {
212-
_ = try await client.deleteFile(path: entry.pathDisplay)
213-
if let entries = list?.entries {
214-
list?.entries = entries.filter { $0.pathDisplay != entry.pathDisplay }
211+
Button {
212+
Task<Void, Never> {
213+
do {
214+
_ = try await client.uploadFile(
215+
path: entry.pathDisplay,
216+
data: "Uploaded with overwrite at \(Date().formatted(date: .complete, time: .complete))"
217+
.data(using: .utf8)!,
218+
mode: .overwrite,
219+
autorename: false
220+
)
221+
} catch {
222+
log.error("UploadFile failure", metadata: [
223+
"error": "\(error)",
224+
"localizedDescription": "\(error.localizedDescription)"
225+
])
226+
}
227+
}
228+
} label: {
229+
Text("Upload file (overwrite)")
230+
}
231+
232+
Button(role: .destructive) {
233+
Task<Void, Never> {
234+
do {
235+
_ = try await client.deleteFile(path: entry.pathDisplay)
236+
if let entries = list?.entries {
237+
list?.entries = entries.filter { $0.pathDisplay != entry.pathDisplay }
238+
}
239+
} catch {
240+
log.error("DeleteFile failure", metadata: [
241+
"error": "\(error)",
242+
"localizedDescription": "\(error.localizedDescription)"
243+
])
215244
}
216-
} catch {
217-
log.error("DeleteFile failure", metadata: [
218-
"error": "\(error)",
219-
"localizedDescription": "\(error.localizedDescription)"
220-
])
221245
}
246+
} label: {
247+
Text("Delete File")
222248
}
223-
} label: {
224-
Text("Delete File")
225249
}
226250
}
227251
}

Sources/DropboxClient/Client.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ public struct Client: Sendable {
44
listFolder: ListFolder,
55
downloadFile: DownloadFile,
66
deleteFile: DeleteFile,
7-
uploadFile: UploadFile
7+
uploadFile: UploadFile,
8+
getMetadata: GetMetadata
89
) {
910
self.auth = auth
1011
self.listFolder = listFolder
1112
self.downloadFile = downloadFile
1213
self.deleteFile = deleteFile
1314
self.uploadFile = uploadFile
15+
self.getMetadata = getMetadata
1416
}
1517

1618
public var auth: Auth
1719
public var listFolder: ListFolder
1820
public var downloadFile: DownloadFile
1921
public var deleteFile: DeleteFile
2022
public var uploadFile: UploadFile
23+
public var getMetadata: GetMetadata
2124
}
2225

2326
extension Client {
@@ -59,6 +62,11 @@ extension Client {
5962
auth: auth,
6063
keychain: keychain,
6164
httpClient: httpClient
65+
),
66+
getMetadata: .live(
67+
auth: auth,
68+
keychain: keychain,
69+
httpClient: httpClient
6270
)
6371
)
6472
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import Foundation
2+
3+
public struct GetMetadata: Sendable {
4+
public struct Params: Sendable, Equatable, Encodable {
5+
public init(
6+
path: String,
7+
includeMediaInfo: Bool? = nil,
8+
includeDeleted: Bool? = nil
9+
) {
10+
self.path = path
11+
self.includeMediaInfo = includeMediaInfo
12+
self.includeDeleted = includeDeleted
13+
}
14+
15+
public var path: String
16+
public var includeMediaInfo: Bool?
17+
public var includeDeleted: Bool?
18+
}
19+
20+
public enum Error: Swift.Error, Sendable, Equatable {
21+
case notAuthorized
22+
case response(statusCode: Int?, data: Data)
23+
}
24+
25+
public typealias Run = @Sendable (Params) async throws -> Metadata
26+
27+
public init(run: @escaping Run) {
28+
self.run = run
29+
}
30+
31+
public var run: Run
32+
33+
public func callAsFunction(_ params: Params) async throws -> Metadata {
34+
try await run(params)
35+
}
36+
37+
public func callAsFunction(
38+
path: String
39+
) async throws -> Metadata {
40+
try await run(.init(
41+
path: path
42+
))
43+
}
44+
}
45+
46+
extension GetMetadata {
47+
public static func live(
48+
auth: Auth,
49+
keychain: Keychain,
50+
httpClient: HTTPClient
51+
) -> GetMetadata {
52+
GetMetadata { params in
53+
try await auth.refreshToken()
54+
55+
guard let credentials = await keychain.loadCredentials() else {
56+
throw Error.notAuthorized
57+
}
58+
59+
let request: URLRequest = try {
60+
var components = URLComponents()
61+
components.scheme = "https"
62+
components.host = "api.dropboxapi.com"
63+
components.path = "/2/files/get_metadata"
64+
65+
var request = URLRequest(url: components.url!)
66+
request.httpMethod = "POST"
67+
request.setValue(
68+
"\(credentials.tokenType) \(credentials.accessToken)",
69+
forHTTPHeaderField: "Authorization"
70+
)
71+
request.setValue(
72+
"application/json",
73+
forHTTPHeaderField: "Content-Type"
74+
)
75+
request.httpBody = try JSONEncoder.api.encode(params)
76+
77+
return request
78+
}()
79+
80+
let (responseData, response) = try await httpClient.data(for: request)
81+
let statusCode = (response as? HTTPURLResponse)?.statusCode
82+
83+
guard let statusCode, (200..<300).contains(statusCode) else {
84+
throw Error.response(statusCode: statusCode, data: responseData)
85+
}
86+
87+
let metadata = try JSONDecoder.api.decode(Metadata.self, from: responseData)
88+
return metadata
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)