Skip to content

Instantly share code, notes, and snippets.

@saroar
Last active February 25, 2023 11:48
Show Gist options
  • Select an option

  • Save saroar/e9a769cd8a42b3c226525192d0cf9dbc to your computer and use it in GitHub Desktop.

Select an option

Save saroar/e9a769cd8a42b3c226525192d0cf9dbc to your computer and use it in GitHub Desktop.
//
// AttachmentClient.swift
//
//
// Created by Saroar Khandoker on 27.01.2021.
//
import Combine
import Foundation
import AddaSharedModels
import UIKit
import InfoPlist
import KeychainClient
import SotoS3
import Dependencies
public struct AttachmentS3Client {
public static let bucket = "adda"
public static var bucketWithEndpoint = "https://adda.nyc3.digitaloceanspaces.com/"
static public let client = AWSClient(
credentialProvider: .static(
accessKeyId: EnvironmentKeys.accessKeyId,
secretAccessKey: EnvironmentKeys.secretAccessKey
),
httpClientProvider: .createNew
)
public static let awsS3 = S3(
client: client,
region: .useast1,
endpoint: "https://nyc3.digitaloceanspaces.com"
)
public typealias UploadImageToS3Handler = @Sendable (UIImage, String?, String?) async throws -> String
public let uploadImageToS3: UploadImageToS3Handler
public init(uploadImageToS3: @escaping UploadImageToS3Handler) {
self.uploadImageToS3 = uploadImageToS3
}
}
extension AttachmentS3Client {
static public func buildImageKey(
conversationId: String? = nil,
userId: String? = nil,
imageFormat: String
) -> String {
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
var imageKey = String(format: "%ld", currentTime)
if let conversationId = conversationId {
imageKey = "uploads/images/\(conversationId)/\(imageKey).\(imageFormat)"
} else if let userId = userId {
imageKey = "uploads/images/\(userId)/\(imageKey).\(imageFormat)"
}
return imageKey
}
// upload image to DigitalOcen Spaces
static public func uploadImage(
image: UIImage,
conversationId: String? = nil,
userId: String? = nil
) async throws -> String {
return try await withCheckedThrowingContinuation { continuation in
let data = image.compressImage(conversationId == nil ? .highest : .medium)
let imageFormat = data.1
guard let imageData = data.0 else {
return continuation.resume(throwing: "Data compressImage error")
}
let imageKey = buildImageKey(conversationId: conversationId, userId: userId, imageFormat: imageFormat)
let body = AWSPayload.data(imageData)
// Put an Object
let putObjectRequest = S3.PutObjectRequest(
acl: .publicRead,
body: body,
bucket: bucket,
contentLength: Int64(imageData.count),
key: imageKey
)
let futureOutput = awsS3.putObject(putObjectRequest)
futureOutput.whenSuccess { response in
print(#line, self, response, imageKey)
let finalURL = bucketWithEndpoint + imageKey
return continuation.resume(returning: finalURL)
}
futureOutput.whenFailure { error in
return continuation.resume(throwing: error.localizedDescription)
}
}
}
}
extension AttachmentS3Client {
public static var live: AttachmentS3Client =
.init(
uploadImageToS3: { image, conversationId, userId in
return try await AttachmentS3Client.uploadImage(
image: image,
conversationId: conversationId,
userId: userId
)
}
)
}
public enum AttachmentS3ClientKey: TestDependencyKey {
public static let testValue = AttachmentS3Client.happyPath
}
extension AttachmentS3ClientKey: DependencyKey {
public static let liveValue: AttachmentS3Client = AttachmentS3Client.live
}
extension DependencyValues {
public var attachmentS3Client: AttachmentS3Client {
get { self[AttachmentS3ClientKey.self] }
set { self[AttachmentS3ClientKey.self] = newValue }
}
}
// this is how i can converted image to data
//
// Image+Compress.swift
// AddaMeIOS
//
// Created by Saroar Khandoker on 19.11.2020.
//
import AVFoundation
import SwiftUI
#if os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
import Cocoa
typealias UIImage = NSImage
extension NSImage {
var cgImage: CGImage? {
var proposedRect = CGRect(origin: .zero, size: size)
return cgImage(
forProposedRect: &proposedRect,
context: nil,
hints: nil)
}
convenience init?(named name: String) {
self.init(named: Name(name))
}
}
#endif
extension UIImage {
public enum JPEGQuality: CGFloat {
case lowest = 0
case low = 0.25
case medium = 0.5
case high = 0.75
case highest = 1
}
private var isHeicSupported: Bool {
// swiftlint:disable force_cast
(CGImageDestinationCopyTypeIdentifiers() as! [String]).contains("public.heic")
}
public func compressImage(_ compressionQuality: JPEGQuality? = .medium) -> (Data?, String) {
if isHeicSupported {
do {
let data = try heicData(compressionQuality: compressionQuality!)
return (data, "heic")
} catch {
print("Error creating HEIC data: \(error.localizedDescription)")
}
} else {
#if os(iOS)
guard let data = jpegData(compressionQuality: compressionQuality!.rawValue) else {
return (nil, "")
}
return (data, "jpeg")
#elseif os(OSX)
fatalError("Value of type 'NSImage' has no member 'jpegData'")
#endif
}
return (nil, "")
}
}
extension UIImage {
public enum HEICError: Error {
case heicNotSupported
case cgImageMissing
case couldNotFinalize
}
public func heicData(compressionQuality: JPEGQuality) throws -> Data {
let data = NSMutableData()
guard
let imageDestination =
CGImageDestinationCreateWithData(
data, AVFileType.heic as CFString, 1, nil
)
else {
throw HEICError.heicNotSupported
}
guard let cgImage = self.cgImage else {
throw HEICError.cgImageMissing
}
let options: NSDictionary = [
kCGImageDestinationLossyCompressionQuality: compressionQuality.rawValue
]
CGImageDestinationAddImage(imageDestination, cgImage, options)
guard CGImageDestinationFinalize(imageDestination) else {
throw HEICError.couldNotFinalize
}
return data as Data
}
}
extension UIImage {
public func heicData2(compressionQuality: JPEGQuality) -> Data? {
let destinationData = NSMutableData()
guard
let cgImage = self.cgImage,
let destination = CGImageDestinationCreateWithData(destinationData, AVFileType.heic as CFString, 1, nil)
else { return nil }
let options = [kCGImageDestinationLossyCompressionQuality: compressionQuality]
CGImageDestinationAddImage(destination, cgImage, options as CFDictionary)
CGImageDestinationFinalize(destination)
return destinationData as Data
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment