PDF-Archiver/PDF-Archiver

View on GitHub
ArchiveCore/Sources/ArchiveSharedConstants/Extensions/NSItemProvider.swift

Summary

Maintainability
B
4 hrs
Test Coverage
//
//  NSItemProvider.swift
//  
//
//  Created by Julian Kahnert on 28.12.20.
//

import Foundation
import PDFKit
import UniformTypeIdentifiers
#if os(macOS)
import AppKit.NSImage
private typealias Image = NSImage
#else
import UIKit.UIImage
private typealias Image = UIImage
#endif

public extension NSItemProvider {
    enum NSItemProviderError: Error {
        case timeout
    }

    func saveData(at url: URL, with validUTIs: [UTType]) throws -> Bool {
        var error: Error?
        var data: Data?

        for uti in validUTIs where hasItemConformingToTypeIdentifier(uti.identifier) {
            do {
                data = try syncLoadItem(forTypeIdentifier: uti)
            } catch let inputError {
                error = inputError
            }

            guard let data = data else { continue }

            if Image(data: data) != nil {
                let fileUrl = url.appendingPathComponent(UUID().uuidString).appendingPathExtension("jpeg")
                try data.write(to: fileUrl)
                return true
            } else if PDFDocument(data: data) != nil {
                let fileUrl = url.appendingPathComponent(UUID().uuidString).appendingPathExtension("pdf")
                try data.write(to: fileUrl)
                return true
            }
        }

        if let err = error {
            throw err
        }

        return false
    }

    func syncLoadItem(forTypeIdentifier uti: UTType) throws -> Data? {
        var data: Data?
        var error: Error?
        let semaphore = DispatchSemaphore(value: 0)
        self.loadItem(forTypeIdentifier: uti.identifier, options: nil) { rawData, rawError in
            defer {
                semaphore.signal()
            }
            if let rawError = rawError {
                error = rawError
            }

            if let pathData = rawData as? Data,
               let path = String(data: pathData, encoding: .utf8),
               let url = URL(string: path),
               let inputData = Self.getDataIfValid(from: url) {
                data = inputData

            } else if let url = rawData as? URL,
                      let inputData = Self.getDataIfValid(from: url) {
                data = inputData

            } else if let inputData = Self.validate(rawData as? Data) {
                data = inputData

            } else if let image = rawData as? Image {
                #if os(macOS)
                // swiftlint:disable:next force_unwrapping
                let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!
                let bitmapRep = NSBitmapImageRep(cgImage: cgImage)
                data = bitmapRep.representation(using: .jpeg, properties: [.compressionFactor: NSNumber(value: 1)])
                #else
                data = image.jpegData(compressionQuality: 1)
                #endif
            }
        }
        let timeoutResult = semaphore.wait(timeout: .now() + .seconds(10))
        guard timeoutResult == .success else {
            throw NSItemProviderError.timeout
        }

        if let error = error {
            throw error
        }

        return data
    }

    private static func getDataIfValid(from url: URL) -> Data? {
        guard let data = try? Data(contentsOf: url) else { return nil }
        return validate(data)
    }

    private static func validate(_ data: Data?) -> Data? {
        guard let inputData = data else { return data }
        if PDFDocument(data: inputData) == nil && Image(data: inputData) == nil {
            return nil
        }
        return inputData
    }
}