Skip to content

Commit b8d15a0

Browse files
grdsdevclaude
andcommitted
refactor(storage): improve resumable upload implementation
- Remove unnecessary public modifiers for internal types - Add DiskResumableCache as default cache implementation - Simplify Fingerprint interface by removing CustomStringConvertible - Make cancel() method synchronous for better usability - Switch default cache from memory to disk-based storage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 52bd57c commit b8d15a0

File tree

6 files changed

+70
-48
lines changed

6 files changed

+70
-48
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Foundation
2+
3+
final class DiskResumableCache: ResumableCache, @unchecked Sendable {
4+
private let storage: FileManager
5+
6+
init(storage: FileManager) {
7+
self.storage = storage
8+
}
9+
10+
func set(fingerprint: Fingerprint, entry: ResumableCacheEntry) async throws {
11+
let data = try JSONEncoder().encode(entry)
12+
storage.createFile(atPath: fingerprint.value, contents: data)
13+
}
14+
15+
func get(fingerprint: Fingerprint) async throws -> ResumableCacheEntry? {
16+
let data = storage.contents(atPath: fingerprint.value)
17+
guard let data = data else {
18+
return nil
19+
}
20+
return try JSONDecoder().decode(ResumableCacheEntry.self, from: data)
21+
}
22+
23+
func remove(fingerprint: Fingerprint) async throws {
24+
try storage.removeItem(atPath: fingerprint.value)
25+
}
26+
27+
func clear() async throws {
28+
try storage.removeItem(atPath: storage.currentDirectoryPath)
29+
}
30+
31+
func entries() async throws -> [CachePair] {
32+
let files = try storage.contentsOfDirectory(atPath: storage.currentDirectoryPath)
33+
return try files.compactMap { file -> CachePair? in
34+
let data = storage.contents(atPath: file)
35+
guard let data = data else {
36+
return nil
37+
}
38+
return (
39+
Fingerprint(value: file)!,
40+
try JSONDecoder().decode(ResumableCacheEntry.self, from: data)
41+
)
42+
}
43+
}
44+
}

Sources/Storage/Resumable/Fingerprint.swift

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Foundation
22

33
public struct Fingerprint: Hashable, Sendable {
4-
public let value: String
4+
let value: String
55

66
private static let fingerprintSeparator = "::"
77
private static let fingerprintParts = 2
@@ -10,19 +10,19 @@ public struct Fingerprint: Hashable, Sendable {
1010
value.components(separatedBy: Self.fingerprintSeparator)
1111
}
1212

13-
public var source: String {
13+
var source: String {
1414
parts[0]
1515
}
1616

17-
public var size: Int64 {
17+
var size: Int64 {
1818
Int64(parts[1]) ?? 0
1919
}
2020

21-
public init(source: String, size: Int64) {
21+
init(source: String, size: Int64) {
2222
self.value = "\(source)\(Self.fingerprintSeparator)\(size)"
2323
}
2424

25-
public init?(value: String) {
25+
init?(value: String) {
2626
let parts = value.components(separatedBy: Self.fingerprintSeparator)
2727
guard parts.count == Self.fingerprintParts else { return nil }
2828
self.init(source: parts[0], size: Int64(parts[1]) ?? 0)
@@ -48,10 +48,4 @@ extension Fingerprint: Codable {
4848
var container = encoder.singleValueContainer()
4949
try container.encode(value)
5050
}
51-
}
52-
53-
extension Fingerprint: CustomStringConvertible {
54-
public var description: String {
55-
value
56-
}
5751
}

Sources/Storage/Resumable/MemoryResumableCache.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
import Foundation
22

3-
public actor MemoryResumableCache: ResumableCache {
3+
actor MemoryResumableCache: ResumableCache {
44
private var storage: [String: Data] = [:]
55

6-
public init() {}
6+
init() {}
77

8-
public func set(fingerprint: Fingerprint, entry: ResumableCacheEntry) async throws {
8+
func set(fingerprint: Fingerprint, entry: ResumableCacheEntry) async throws {
99
let data = try JSONEncoder().encode(entry)
1010
storage[fingerprint.value] = data
1111
}
1212

13-
public func get(fingerprint: Fingerprint) async throws -> ResumableCacheEntry? {
13+
func get(fingerprint: Fingerprint) async throws -> ResumableCacheEntry? {
1414
guard let data = storage[fingerprint.value] else {
1515
return nil
1616
}
1717
return try JSONDecoder().decode(ResumableCacheEntry.self, from: data)
1818
}
1919

20-
public func remove(fingerprint: Fingerprint) async throws {
20+
func remove(fingerprint: Fingerprint) async throws {
2121
storage.removeValue(forKey: fingerprint.value)
2222
}
2323

24-
public func clear() async throws {
24+
func clear() async throws {
2525
storage.removeAll()
2626
}
2727

28-
public func entries() async throws -> [CachePair] {
28+
func entries() async throws -> [CachePair] {
2929
var pairs: [CachePair] = []
3030

3131
for (key, data) in storage {
Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
11
import Foundation
22

3-
public struct ResumableCacheEntry: Codable, Sendable {
4-
public let uploadURL: String
5-
public let path: String
6-
public let bucketId: String
7-
public let expiration: Date
8-
public let upsert: Bool
9-
public let contentType: String?
10-
11-
public init(
12-
uploadURL: String,
13-
path: String,
14-
bucketId: String,
15-
expiration: Date,
16-
upsert: Bool,
17-
contentType: String? = nil
18-
) {
19-
self.uploadURL = uploadURL
20-
self.path = path
21-
self.bucketId = bucketId
22-
self.expiration = expiration
23-
self.upsert = upsert
24-
self.contentType = contentType
25-
}
3+
struct ResumableCacheEntry: Codable, Sendable {
4+
let uploadURL: String
5+
let path: String
6+
let bucketId: String
7+
let expiration: Date
8+
let upsert: Bool
9+
let contentType: String?
2610

2711
enum CodingKeys: String, CodingKey {
2812
case uploadURL = "upload_url"
@@ -34,16 +18,16 @@ public struct ResumableCacheEntry: Codable, Sendable {
3418
}
3519
}
3620

37-
public typealias CachePair = (Fingerprint, ResumableCacheEntry)
21+
typealias CachePair = (Fingerprint, ResumableCacheEntry)
3822

39-
public protocol ResumableCache: Sendable {
23+
protocol ResumableCache: Sendable {
4024
func set(fingerprint: Fingerprint, entry: ResumableCacheEntry) async throws
4125
func get(fingerprint: Fingerprint) async throws -> ResumableCacheEntry?
4226
func remove(fingerprint: Fingerprint) async throws
4327
func clear() async throws
4428
func entries() async throws -> [CachePair]
4529
}
4630

47-
public func createDefaultResumableCache() -> some ResumableCache {
48-
MemoryResumableCache()
49-
}
31+
func createDefaultResumableCache() -> some ResumableCache {
32+
DiskResumableCache(storage: FileManager.default)
33+
}

Sources/Storage/Resumable/ResumableUpload.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public actor ResumableUpload {
6161
isPaused = true
6262
}
6363

64-
public func cancel() async {
64+
public func cancel() {
6565
isCancelled = true
6666
stateContinuation.finish()
6767
}

Sources/Storage/Resumable/ResumableUploadState.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public struct ResumableUploadState: Sendable {
3333
return Float(status.totalBytesSent) / Float(status.contentLength)
3434
}
3535

36-
public init(
36+
init(
3737
fingerprint: Fingerprint,
3838
cacheEntry: ResumableCacheEntry,
3939
status: UploadStatus,

0 commit comments

Comments
 (0)