From 037b717e609e2f7fc916ef7ed0b7625d04b8f981 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 20 Jun 2022 00:00:34 -0400 Subject: [PATCH] Include filename extension for attachments Fixes posting attachments on pleroma resulting in them served as application/octet-stream, even though we're sending the mime type as well --- Tusker/Models/CompositionAttachmentData.swift | 18 +++++++++--------- Tusker/Services/PostService.swift | 13 +++++++------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Tusker/Models/CompositionAttachmentData.swift b/Tusker/Models/CompositionAttachmentData.swift index 71223c02..96cdaa85 100644 --- a/Tusker/Models/CompositionAttachmentData.swift +++ b/Tusker/Models/CompositionAttachmentData.swift @@ -48,13 +48,13 @@ enum CompositionAttachmentData { } } - func getData(completion: @escaping (Result<(Data, String), Error>) -> Void) { + func getData(completion: @escaping (Result<(Data, UTType), Error>) -> Void) { switch self { case let .image(image): // Export as JPEG instead of PNG, otherweise photos straight from the camera are too large // for Mastodon in its default configuration (max of 10MB). // The quality of 0.8 was chosen completely arbitrarily, it may need to be tuned in the future. - completion(.success((image.jpegData(compressionQuality: 0.8)!, "image/jpeg"))) + completion(.success((image.jpegData(compressionQuality: 0.8)!, .jpeg))) case let .asset(asset): if asset.mediaType == .image { let options = PHImageRequestOptions() @@ -68,19 +68,19 @@ enum CompositionAttachmentData { return } - let mimeType: String + let utType: UTType if dataUTI == "public.heic" { // neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG let image = CIImage(data: data)! let context = CIContext() let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)! data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])! - mimeType = "image/jpeg" + utType = .jpeg } else { - mimeType = UTType(dataUTI)!.preferredMIMEType! + utType = UTType(dataUTI)! } - completion(.success((data, mimeType))) + completion(.success((data, utType))) } } else if asset.mediaType == .video { let options = PHVideoRequestOptions() @@ -109,11 +109,11 @@ enum CompositionAttachmentData { case let .drawing(drawing): let image = drawing.imageInLightMode(from: drawing.bounds, scale: 1) - completion(.success((image.pngData()!, "image/png"))) + completion(.success((image.pngData()!, .png))) } } - private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Result<(Data, String), Error>) -> Void) { + private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Result<(Data, UTType), Error>) -> Void) { session.outputFileType = .mp4 session.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("exported_video_\(UUID())").appendingPathExtension("mp4") session.exportAsynchronously { @@ -123,7 +123,7 @@ enum CompositionAttachmentData { } do { let data = try Data(contentsOf: session.outputURL!) - completion(.success((data, "video/mp4"))) + completion(.success((data, .mpeg4Movie))) } catch { completion(.failure(.videoExport(error))) } diff --git a/Tusker/Services/PostService.swift b/Tusker/Services/PostService.swift index 37bd9ed8..c952e99e 100644 --- a/Tusker/Services/PostService.swift +++ b/Tusker/Services/PostService.swift @@ -8,6 +8,7 @@ import Foundation import Pachyderm +import UniformTypeIdentifiers class PostService: ObservableObject { private let mastodonController: MastodonController @@ -66,15 +67,15 @@ class PostService: ObservableObject { attachments.reserveCapacity(draft.attachments.count) for (index, attachment) in draft.attachments.enumerated() { let data: Data - let mimeType: String + let utType: UTType do { - (data, mimeType) = try await getData(for: attachment) + (data, utType) = try await getData(for: attachment) currentStep += 1 } catch let error as CompositionAttachmentData.Error { throw Error.attachmentData(index: index, cause: error) } do { - let uploaded = try await uploadAttachment(data: data, mimeType: mimeType, description: attachment.attachmentDescription) + let uploaded = try await uploadAttachment(data: data, utType: utType, description: attachment.attachmentDescription) attachments.append(uploaded) currentStep += 1 } catch let error as Client.Error { @@ -84,7 +85,7 @@ class PostService: ObservableObject { return attachments } - private func getData(for attachment: CompositionAttachment) async throws -> (Data, String) { + private func getData(for attachment: CompositionAttachment) async throws -> (Data, UTType) { return try await withCheckedThrowingContinuation { continuation in attachment.data.getData { result in switch result { @@ -97,8 +98,8 @@ class PostService: ObservableObject { } } - private func uploadAttachment(data: Data, mimeType: String, description: String?) async throws -> Attachment { - let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file") + private func uploadAttachment(data: Data, utType: UTType, description: String?) async throws -> Attachment { + let formAttachment = FormAttachment(mimeType: utType.preferredMIMEType!, data: data, fileName: "file.\(utType.preferredFilenameExtension!)") let req = Client.upload(attachment: formAttachment, description: description) return try await mastodonController.run(req).0 }