// // Attachment.swift // Pachyderm // // Created by Shadowfacts on 9/9/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import Foundation public class Attachment: Decodable { public let id: String public let kind: Kind public let url: URL public let remoteURL: URL? public let previewURL: URL public let textURL: URL? public let meta: Metadata? public let description: String? public static func update(_ attachment: Attachment, focus: (Float, Float)?, description: String?) -> Request { return Request(method: .put, path: "/api/v1/media/\(attachment.id)", body: .formData([ "description" => (description ?? attachment.description), "focus" => focus ], nil)) } required public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(String.self, forKey: .id) self.kind = try container.decode(Kind.self, forKey: .kind) self.url = URL(lenient: try container.decode(String.self, forKey: .url))! if let remote = try? container.decode(String.self, forKey: .remoteURL) { self.remoteURL = URL(lenient: remote.replacingOccurrences(of: " ", with: "%20")) } else { self.remoteURL = nil } self.previewURL = URL(lenient: try container.decode(String.self, forKey: .previewURL).replacingOccurrences(of: " ", with: "%20"))! if let text = try? container.decode(String.self, forKey: .textURL) { self.textURL = URL(lenient: text.replacingOccurrences(of: " ", with: "%20")) } else { self.textURL = nil } self.meta = try? container.decode(Metadata.self, forKey: .meta) self.description = try? container.decode(String.self, forKey: .description) } private enum CodingKeys: String, CodingKey { case id case kind = "type" case url case remoteURL = "remote_url" case previewURL = "preview_url" case textURL = "text_url" case meta case description } } extension Attachment { public enum Kind: String, Decodable { case image case video case gifv case audio case unknown // we need a custom decoder, because all API-compatible implementations don't return some data in the same format public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let str = try container.decode(String.self) if let kind = Kind(rawValue: str.lowercased()) { self = kind } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Attachment type must be one of image, video, gifv, audio, or unknown.") } } } } extension Attachment { public class Metadata: Decodable { public let length: String? public let duration: Float? public let audioEncoding: String? public let audioBitrate: String? public let audioChannels: String? public let fps: Float? public let width: Int? public let height: Int? public let size: String? public let aspect: Float? public let small: ImageMetadata? public let original: ImageMetadata? private enum CodingKeys: String, CodingKey { case length case duration case audioEncoding = "audio_encode" case audioBitrate = "audio_bitrate" case audioChannels = "audio_channels" case fps case width case height case size case aspect case small case original } } public class ImageMetadata: Decodable { public let width: Int? public let height: Int? public let size: String? public let aspect: Float? private enum CodingKeys: String, CodingKey { case width case height case size case aspect } } } fileprivate extension URL { private static let allowedChars = CharacterSet.urlHostAllowed.union(.urlPathAllowed).union(.urlQueryAllowed) init?(lenient string: String) { guard let escaped = string.addingPercentEncoding(withAllowedCharacters: URL.allowedChars) else { return nil } self.init(string: escaped) } }