From 0b41abd4ef77a762a13691cbacf527bdfd3f0393 Mon Sep 17 00:00:00 2001 From: Amir Abbas Date: Wed, 1 Mar 2017 13:28:44 +0330 Subject: [PATCH] Optimized PDF thumbnail/meta handling - Fixed ISO speed and GPS Area image meta - Fixed Dropbox `name ! BEGINSWiTH %` search query --- README.md | 2 +- Sources/DropboxFileProvider.swift | 4 +- Sources/ExtendedLocalFileProvider.swift | 9 +- Sources/FileProvider.swift | 131 +++++++++++++----------- Sources/LocalHelper.swift | 2 +- 5 files changed, 82 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index ffc6ff8..1a2a0b8 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ For LocalFileProvider if you want to deal with `Documents` folder let documentsProvider = LocalFileProvider() // Equals with: -let documentsProvider = LocalFileProvider(directory: .documentDirectory, domainMask: = .userDomainMask) +let documentsProvider = LocalFileProvider(for: .documentDirectory, in: .userDomainMask) // Equals with: let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! diff --git a/Sources/DropboxFileProvider.swift b/Sources/DropboxFileProvider.swift index f04facd..cd6a252 100644 --- a/Sources/DropboxFileProvider.swift +++ b/Sources/DropboxFileProvider.swift @@ -539,8 +539,8 @@ extension DropboxFileProvider: ExtendedFileProvider { } } if let data = data { - if DropboxFileProvider.dataIsPDF(data) { - image = DropboxFileProvider.convertToImage(pdfData: data) + if DropboxFileProvider.dataIsPDF(data), let pageImage = DropboxFileProvider.convertToImage(pdfData: data) { + image = pageImage } else if let contentType = (response as? HTTPURLResponse)?.allHeaderFields["Content-Type"] as? String, contentType.contains("text/html") { // TODO: Implement converting html returned type of get_preview to image } else { diff --git a/Sources/ExtendedLocalFileProvider.swift b/Sources/ExtendedLocalFileProvider.swift index 3382a7d..03de303 100644 --- a/Sources/ExtendedLocalFileProvider.swift +++ b/Sources/ExtendedLocalFileProvider.swift @@ -225,8 +225,7 @@ public struct LocalFileInformationGenerator { /// Thumbnail generator closure for portable document files files. static public var pdfThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in - guard let data = try? Data(contentsOf: fileURL) else { return nil } - return LocalFileProvider.convertToImage(pdfData: data) + return LocalFileProvider.convertToImage(pdfURL: fileURL) } /// Thumbnail generator closure for office document files. @@ -289,7 +288,7 @@ public struct LocalFileInformationGenerator { add(key: "Location", value: "\(latitude), \(longitude)") } add(key: "Altitude", value: tiffDict[kCGImagePropertyGPSAltitude as String] as? NSNumber) - add(key: "Area", value: tiffDict[kCGImagePropertyGPSAreaInformation as String] as? NSNumber) + add(key: "Area", value: tiffDict[kCGImagePropertyGPSAreaInformation as String]) add(key: "Color space", value: imageDict[kCGImagePropertyColorModel as String]) add(key: "Focal length", value: exifDict[kCGImagePropertyExifFocalLength as String]) @@ -300,7 +299,7 @@ public struct LocalFileInformationGenerator { let expfrac = simplify(Int64(exp.doubleValue * 10_000_000_000_000), 10_000_000_000_000) add(key: "Exposure time", value: "\(expfrac.newTop)/\(expfrac.newBottom)") } - add(key: "ISO speed", value: (exifDict[kCGImagePropertyExifISOSpeedRatings as String] as? NSArray)?.first) + add(key: "ISO speed", value: (exifDict[kCGImagePropertyExifISOSpeedRatings as String] as? [NSNumber])?.first) return (dic, keys) } @@ -429,7 +428,7 @@ public struct LocalFileInformationGenerator { return nil } - if let data = try? Data(contentsOf: fileURL), let provider = CGDataProvider(data: data as CFData), let reference = CGPDFDocument(provider), let dict = reference.info { + if let provider = CGDataProvider(url: fileURL as CFURL), let reference = CGPDFDocument(provider), let dict = reference.info { add(key: "Title", value: getKey("Title", from: dict)) add(key: "Author", value: getKey("Author", from: dict)) add(key: "Subject", value: getKey("Subject", from: dict)) diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index 9946ee2..ba56172 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -810,63 +810,77 @@ extension ExtendedFileProvider { let cfPDFData: CFData = pdfData as CFData if let provider = CGDataProvider(data: cfPDFData), let reference = CGPDFDocument(provider), let pageRef = reference.page(at: page) { - let frame = pageRef.getBoxRect(CGPDFBox.mediaBox) - var size = frame.size - let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) - - #if os(macOS) - let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not - - size.width *= CGFloat(ppp) - size.height *= CGFloat(ppp) - - let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), - bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace, - bytesPerRow: 0, bitsPerPixel: 0) - - guard let context = NSGraphicsContext(bitmapImageRep: rep!) else { - return nil - } - - NSGraphicsContext.saveGraphicsState() - NSGraphicsContext.setCurrent(context) - - let transform = pageRef.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true) - context.cgContext.concatenate(transform) - - context.cgContext.translateBy(x: 0, y: size.height) - context.cgContext.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp)) - context.cgContext.drawPDFPage(pageRef) - - let resultingImage = NSImage(size: size) - resultingImage.addRepresentation(rep!) - return resultingImage - #else - let ppp = Int(UIScreen.main.scale) // fetch device is retina or not - guard let context = UIGraphicsGetCurrentContext() else { - return nil - } - size.width *= CGFloat(ppp) - size.height *= CGFloat(ppp) - UIGraphicsBeginImageContext(size) - - context.saveGState() - let transform = pageRef.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true) - context.concatenate(transform) - - context.translateBy(x: 0, y: size.height) - context.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp)) - context.drawPDFPage(pageRef) - - context.restoreGState() - let resultingImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return resultingImage - #endif + return self.convertToImage(pdfPage: pageRef) } return nil } + internal static func convertToImage(pdfURL: URL, page: Int = 1) -> ImageClass? { + // To accelerate, supporting only local file URL + guard pdfURL.isFileURL else { return nil } + + if let provider = CGDataProvider(url: pdfURL as CFURL), let reference = CGPDFDocument(provider), let pageRef = reference.page(at: page) { + return self.convertToImage(pdfPage: pageRef) + } + return nil + } + + private static func convertToImage(pdfPage: CGPDFPage) -> ImageClass? { + let frame = pdfPage.getBoxRect(CGPDFBox.mediaBox) + var size = frame.size + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + + #if os(macOS) + let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not + + size.width *= CGFloat(ppp) + size.height *= CGFloat(ppp) + + let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), + bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace, + bytesPerRow: 0, bitsPerPixel: 0) + + guard let context = NSGraphicsContext(bitmapImageRep: rep!) else { + return nil + } + + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.setCurrent(context) + + let transform = pdfPage.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true) + context.cgContext.concatenate(transform) + + context.cgContext.translateBy(x: 0, y: size.height) + context.cgContext.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp)) + context.cgContext.drawPDFPage(pdfPage) + + let resultingImage = NSImage(size: size) + resultingImage.addRepresentation(rep!) + return resultingImage + #else + let ppp = Int(UIScreen.main.scale) // fetch device is retina or not + guard let context = UIGraphicsGetCurrentContext() else { + return nil + } + size.width *= CGFloat(ppp) + size.height *= CGFloat(ppp) + UIGraphicsBeginImageContext(size) + + context.saveGState() + let transform = pdfPage.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true) + context.concatenate(transform) + + context.translateBy(x: 0, y: size.height) + context.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp)) + context.drawPDFPage(pdfPage) + + context.restoreGState() + let resultingImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return resultingImage + #endif + } + internal static func scaleDown(image: ImageClass, toSize maxSize: CGSize) -> ImageClass { let height, width: CGFloat if image.size.width > image.size.height { @@ -1052,20 +1066,23 @@ public protocol FoundationErrorEnum { extension NSPredicate { func findValue(forKey key: String?, operator op: NSComparisonPredicate.Operator? = nil) -> Any? { - let val = findAllValues(forKey: key).lazy.filter { op == nil || $0.operator == op! } + let val = findAllValues(forKey: key).lazy.filter { (op == nil || $0.operator == op!) && !$0.not } return val.first?.value } - func findAllValues(forKey key: String?) -> [(value: Any, operator: NSComparisonPredicate.Operator)] { + func findAllValues(forKey key: String?) -> [(value: Any, operator: NSComparisonPredicate.Operator, not: Bool)] { if let cQuery = self as? NSCompoundPredicate { let find = cQuery.subpredicates.flatMap { ($0 as! NSPredicate).findAllValues(forKey: key) } + if cQuery.compoundPredicateType == .not { + return find.map { return ($0.value, $0.operator, !$0.not) } + } return find } else if let cQuery = self as? NSComparisonPredicate { if cQuery.leftExpression.expressionType == .keyPath, key == nil || cQuery.leftExpression.keyPath == key!, let const = cQuery.rightExpression.constantValue { - return [(value: const, operator: cQuery.predicateOperatorType)] + return [(value: const, operator: cQuery.predicateOperatorType, false)] } if cQuery.rightExpression.expressionType == .keyPath, key == nil || cQuery.rightExpression.keyPath == key!, let const = cQuery.leftExpression.constantValue { - return [(value: const, operator: cQuery.predicateOperatorType)] + return [(value: const, operator: cQuery.predicateOperatorType, false)] } return [] } else { diff --git a/Sources/LocalHelper.swift b/Sources/LocalHelper.swift index c358028..e7f478d 100644 --- a/Sources/LocalHelper.swift +++ b/Sources/LocalHelper.swift @@ -96,7 +96,7 @@ internal final class LocalFolderMonitor { init(url: URL, handler: @escaping ()->Void) { self.url = url descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY) - source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: qq) + source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: .write, queue: qq) // Folder monitoring is recursive and deep. Monitoring a root folder may be very costly // We have a 0.2 second delay to ensure we wont call handler 1000s times when there is // a huge file operation. This ensures app will work smoothly while this 250 milisec won't