api-swift / Sources / IrdAPI / Flows / IpfsFlow.swift
470 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import Foundation

extension IPFSAPI {
    /// Grant → raw-byte POST to the node → poll until the upload settles. Returns the
    /// completed upload (with its `cid` and `pinId`); `failed`/`expired` throw
    /// `IrdError.uploadFailed`.
    public func addFile(data: Data, name: String? = nil, meta: [String: String]? = nil,
                        format: UploadFormat? = nil, options: PollOptions = .uploads) async throws -> Upload {
        let grant = try await grant(name: name, meta: meta, format: format)
        try await client.uploadBytes(to: grant.url, token: grant.token, data: data, format: format)
        return try await settle(grant, options: options)
    }

    /// Same, streaming the file from disk (`URLSession.upload(for:fromFile:)`), so a
    /// large file never sits in memory.
    public func addFile(url: URL, name: String? = nil, meta: [String: String]? = nil,
                        format: UploadFormat? = nil, options: PollOptions = .uploads) async throws -> Upload {
        let grant = try await grant(name: name ?? url.lastPathComponent, meta: meta, format: format)
        try await client.uploadBytes(to: grant.url, token: grant.token, file: url, format: format)
        return try await settle(grant, options: options)
    }

    private func grant(name: String?, meta: [String: String]?, format: UploadFormat?) async throws -> (url: URL, token: String?, id: String) {
        let body = (name == nil && meta == nil && format == nil) ? nil : StartUploadBody(name: name, meta: meta, format: format)
        let upload = try await startUpload(body: body).upload
        guard let url = URL(string: upload.url) else { throw IrdError.transport("upload grant carries an invalid URL: \(upload.url)") }
        return (url, upload.token, upload.id)
    }

    private func settle(_ grant: (url: URL, token: String?, id: String), options: PollOptions) async throws -> Upload {
        let upload = try await pollUntil(options, clock: client.configuration.clock,
                                         step: { try await getUpload(id: grant.id).upload },
                                         done: { [.completed, .failed, .expired].contains($0.status) })
        if upload.status != .completed { throw IrdError.uploadFailed(upload) }
        return upload
    }
}

extension IrdClient {
    /// POSTs raw bytes to an upload grant URL: `application/octet-stream`, the grant's own
    /// bearer when non-nil, `?format=car` for a CAR archive. Non-2xx answers throw `.api`.
    public func uploadBytes(to url: URL, token: String?, data: Data, format: UploadFormat? = nil) async throws {
        let request = try uploadRequest(to: url, token: token, format: format)
        var r = request
        r.httpBody = data
        r.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        let response = try await configuration.transport.send(r)
        _ = try ResponseDecoder.decode(status: response.status, body: response.body)
    }

    /// Same, streaming `file` from disk through the transport.
    public func uploadBytes(to url: URL, token: String?, file: URL, format: UploadFormat? = nil) async throws {
        var request = try uploadRequest(to: url, token: token, format: format)
        request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        let response = try await configuration.transport.upload(request, fromFile: file)
        _ = try ResponseDecoder.decode(status: response.status, body: response.body)
    }

    private func uploadRequest(to url: URL, token: String?, format: UploadFormat?) throws -> URLRequest {
        let query: [QueryItem] = format == .car ? [QueryItem("format", "car")] : []
        guard let full = URL(string: url.absoluteString + RequestBuilder.queryString(query)) else {
            throw IrdError.config("invalid upload URL \(url)")
        }
        return RequestBuilder.build(url: full, method: .post, token: token, body: nil,
                                    headers: configuration.headers, timeout: configuration.timeout)
    }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/api-swift.git