Skip to content

Commit

Permalink
chore: Use our multipart/form-data implementation for uploading image
Browse files Browse the repository at this point in the history
  • Loading branch information
rinsuki committed Jan 20, 2024
1 parent 395f060 commit 37dda5d
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 49 deletions.
85 changes: 84 additions & 1 deletion Sources/Core/Mastodon/API/MastodonAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import Foundation

public struct MastodonAttachment: Codable, Sendable {
public struct MastodonAttachment: Codable, Sendable, MastodonEndpointResponse {
public let id: MastodonID
public let type: MediaType
public let url: String
Expand Down Expand Up @@ -58,3 +58,86 @@ public struct MastodonAttachment: Codable, Sendable {
}

}

public protocol MultipartEndpointProtocol {
func multipartBody() throws -> [(name: String, file: (name: String, contentType: String)?, data: Data)]
}

extension MastodonEndpointProtocol where Self: MultipartEndpointProtocol {
public func body() throws -> (Data, contentType: String)? {
var estimatedLength = 0
let contents = try multipartBody()

let boundary: String = {
while true {
let uuid = UUID().uuidString
let uuidData = uuid.data(using: .ascii)!
var notCapable = false
for content in contents {
if content.data.range(of: uuidData) != nil {
notCapable = true
}
}
if !notCapable {
return uuid
}
}
}()
let boundaryInBody = "--\(boundary)\r\n".data(using: .ascii)!

let headerCount = boundaryInBody.count + 45

for content in contents {
estimatedLength += content.name.count
estimatedLength += content.data.count
if let file = content.file {
estimatedLength += file.name.count + file.contentType.count + 29
}
}
estimatedLength += headerCount * contents.count
estimatedLength += boundaryInBody.count + 2

var buffer = Data(capacity: estimatedLength)
for content in contents {
buffer.append(boundaryInBody)
buffer.append("Content-Disposition: form-data; name=\"\(content.name)\"".data(using: .ascii)!)
if let file = content.file {
buffer.append("; filename=\"\(file.name)\"\r\nContent-Type: \(file.contentType)".data(using: .ascii)!)
}
buffer.append("\r\n\r\n".data(using: .ascii)!)
buffer.append(content.data)
buffer.append("\r\n".data(using: .ascii)!)
}
buffer.append("--\(boundary)--\r\n".data(using: .ascii)!)

#if DEBUG
print("estimatedLength diff", estimatedLength, buffer.count, estimatedLength - buffer.count)
#endif

return (buffer, "multipart/form-data; boundary=\(boundary)")
}
}

extension MastodonEndpoint {
public struct UploadMediaV1: MastodonEndpointProtocol, MultipartEndpointProtocol {
public typealias Response = MastodonAttachment

public var endpoint: String { "/api/v1/media" }
public var method: String { "POST" }

public let file: Data
public let mimeType: String
public let fileName: String = "imast_upload_file"

public init(file: Data, mimeType: String) {
self.file = file
self.mimeType = mimeType
}

public func multipartBody() throws -> [(name: String, file: (name: String, contentType: String)?, data: Data)] {
return [
(name: "file", file: (name: fileName, contentType: mimeType), data: file),
]
}
}
}
45 changes: 0 additions & 45 deletions Sources/Core/Mastodon/Model/MastodonUserToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
// limitations under the License.

import Foundation
import Alamofire
import Hydra
import GRDB
import KeychainAccess
Expand Down Expand Up @@ -251,50 +250,6 @@ public class MastodonUserToken: Equatable, @unchecked Sendable {
)
}

public func upload(file: Data, mimetype: String, filename: String = "imast_upload_file") async throws -> MastodonAttachment {
let request = try await withCheckedThrowingContinuation { continuation in
Alamofire.upload(
multipartFormData: { (multipartFormData) in
multipartFormData.append(file, withName: "file", fileName: filename, mimeType: mimetype)
},
to: "https://\(self.app.instance.hostName)/api/v1/media",
method: .post,
headers: self.getHeader()
) { encodingResult in
switch encodingResult {
case .success(let request, _, _):
continuation.resume(returning: request)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
let (data, urlRes): (Data, HTTPURLResponse) = try await withCheckedThrowingContinuation { continuation in
request.responseData { res in
switch res.result {
case .success(let data):
let urlRes = res.response!
continuation.resume(returning: (data, urlRes))
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
if urlRes.statusCode < 300 {
do {
return try JSONDecoder.forMastodonAPI.decode(MastodonAttachment.self, from: data)
} catch {
throw error
}
} else {
if let error = try? JSONDecoder().decode(MastodonErrorResponse.self, from: data).error {
throw APIError.errorReturned(errorMessage: error, errorHttpCode: urlRes.statusCode)
} else {
throw APIError.unknownResponse(errorHttpCode: urlRes.statusCode, errorString: .init(data: data, encoding: .utf8))
}
}
}

public static func == (lhs: MastodonUserToken, rhs: MastodonUserToken) -> Bool {
return lhs.id == rhs.id
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/iOS/App/Screens/NewPost/NewPostViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import UIKit
import Hydra
import MediaPlayer
import Alamofire
import iMastiOSCore

// YOU PROBABLY WANT TO ALSO MODIFY ShareNewPostViewController, which is subset of NewPostViewController.
Expand Down Expand Up @@ -208,7 +207,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
await MainActor.run {
alert.message = baseMessage + L10n.NewPost.Alerts.Sending.Steps.mediaUpload(index+1, self.media.count)
}
let response = try await self.userToken.upload(file: medium.toUploadableData(), mimetype: medium.getMimeType())
let response = try await MastodonEndpoint.UploadMediaV1(file: medium.toUploadableData(), mimeType: medium.getMimeType()).request(with: self.userToken)
media.append(response)
}
await MainActor.run {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ class ShareNewPostViewController: UIViewController, Instantiatable, UITextViewDe
await MainActor.run {
alert.message = baseMessage + L10n.NewPost.Alerts.Sending.Steps.mediaUpload(index+1, self.media.count)
}
let response = try await self.environment.upload(file: medium.toUploadableData(), mimetype: medium.getMimeType())
let response = try await MastodonEndpoint.UploadMediaV1(
file: medium.toUploadableData(),
mimeType: medium.getMimeType()
).request(with: self.environment)
media.append(response)
}
await MainActor.run {
Expand Down

0 comments on commit 37dda5d

Please sign in to comment.