Browse Source

Fix scanner, premium access, and import edge cases.

Prevent stale premium cache bypass, grayscale band corruption, partial scan files, duplicate bookmarks, stacked overlays, and gate scan file import separately.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 3 weeks ago
parent
commit
0c4c45dfb6

+ 16 - 4
smart_printer/ImportedFilesStore.swift

@@ -31,9 +31,18 @@ enum ImportedFilesStore {
         guard AppSettings.saveRecentFiles else { return urls.count }
         guard AppSettings.saveRecentFiles else { return urls.count }
 
 
         var bookmarks = defaults.array(forKey: bookmarksKey) as? [Data] ?? []
         var bookmarks = defaults.array(forKey: bookmarksKey) as? [Data] ?? []
+        var existingPaths = Set(
+            bookmarks.compactMap { data -> String? in
+                guard case .valid(let url) = resolveBookmarkData(data) else { return nil }
+                return normalizedPath(for: url)
+            }
+        )
         var added = 0
         var added = 0
 
 
         for url in urls {
         for url in urls {
+            let path = normalizedPath(for: url)
+            guard !existingPaths.contains(path) else { continue }
+
             let accessed = url.startAccessingSecurityScopedResource()
             let accessed = url.startAccessingSecurityScopedResource()
             defer {
             defer {
                 if accessed { url.stopAccessingSecurityScopedResource() }
                 if accessed { url.stopAccessingSecurityScopedResource() }
@@ -45,16 +54,19 @@ enum ImportedFilesStore {
                 relativeTo: nil
                 relativeTo: nil
             ) else { continue }
             ) else { continue }
 
 
-            if !bookmarks.contains(bookmark) {
-                bookmarks.append(bookmark)
-                added += 1
-            }
+            bookmarks.append(bookmark)
+            existingPaths.insert(path)
+            added += 1
         }
         }
 
 
         defaults.set(bookmarks, forKey: bookmarksKey)
         defaults.set(bookmarks, forKey: bookmarksKey)
         return added
         return added
     }
     }
 
 
+    private static func normalizedPath(for url: URL) -> String {
+        url.standardizedFileURL.path
+    }
+
     private enum BookmarkResolution {
     private enum BookmarkResolution {
         case valid(URL)
         case valid(URL)
         case stale
         case stale

+ 1 - 1
smart_printer/PaywallView.swift

@@ -456,7 +456,7 @@ enum PremiumAccess {
     static func canAccess(_ feature: AppFeature) -> Bool {
     static func canAccess(_ feature: AppFeature) -> Bool {
         let store = StoreManager.shared
         let store = StoreManager.shared
         if store.isResolvingEntitlements {
         if store.isResolvingEntitlements {
-            return store.isPremium || FreeTierManager.canAccess(feature, isPremium: false)
+            return FreeTierManager.canAccess(feature, isPremium: false)
         }
         }
         return FreeTierManager.canAccess(feature, isPremium: store.isPremium)
         return FreeTierManager.canAccess(feature, isPremium: store.isPremium)
     }
     }

+ 1 - 0
smart_printer/ScanFileView.swift

@@ -46,6 +46,7 @@ enum ScanFileService {
         case .scanner:
         case .scanner:
             startScannerScan(from: window)
             startScannerScan(from: window)
         case .importFile:
         case .importFile:
+            guard PremiumAccess.require(feature: .scanImport, from: window) else { return }
             importFile(from: window)
             importFile(from: window)
         }
         }
     }
     }

+ 24 - 11
smart_printer/ScannerService.swift

@@ -586,9 +586,6 @@ extension ScannerService: ICDeviceDelegate, ICScannerDeviceDelegate {
     nonisolated func scannerDevice(_ scanner: ICScannerDevice, didScanTo url: URL) {
     nonisolated func scannerDevice(_ scanner: ICScannerDevice, didScanTo url: URL) {
         Task { @MainActor in
         Task { @MainActor in
             scannedImageURL = url
             scannedImageURL = url
-            if let image = imageFromScanURL(url) {
-                finishScan(with: .success(image))
-            }
         }
         }
     }
     }
 
 
@@ -649,14 +646,31 @@ private extension ScanResolution {
 }
 }
 
 
 private extension ScannerService {
 private extension ScannerService {
+    struct BandImageFormat {
+        let colorSpace: CGColorSpace
+        let bitmapInfo: CGBitmapInfo
+    }
+
+    func bandImageFormat(for bandData: ICScannerBandData) -> BandImageFormat {
+        if Int(bandData.bitsPerPixel) <= 8 {
+            return BandImageFormat(
+                colorSpace: CGColorSpaceCreateDeviceGray(),
+                bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
+            )
+        }
+        return BandImageFormat(
+            colorSpace: CGColorSpaceCreateDeviceRGB(),
+            bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipLast.rawValue)
+        )
+    }
+
     func imageFromBandData(_ bandData: ICScannerBandData) -> NSImage? {
     func imageFromBandData(_ bandData: ICScannerBandData) -> NSImage? {
         guard let data = bandData.dataBuffer,
         guard let data = bandData.dataBuffer,
               !data.isEmpty,
               !data.isEmpty,
               bandData.fullImageWidth > 0,
               bandData.fullImageWidth > 0,
               bandData.dataNumRows > 0 else { return nil }
               bandData.dataNumRows > 0 else { return nil }
 
 
-        let colorSpace = CGColorSpaceCreateDeviceRGB()
-        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipLast.rawValue)
+        let format = bandImageFormat(for: bandData)
         guard let provider = CGDataProvider(data: data as CFData),
         guard let provider = CGDataProvider(data: data as CFData),
               let cgImage = CGImage(
               let cgImage = CGImage(
                   width: Int(bandData.fullImageWidth),
                   width: Int(bandData.fullImageWidth),
@@ -664,8 +678,8 @@ private extension ScannerService {
                   bitsPerComponent: Int(bandData.bitsPerComponent),
                   bitsPerComponent: Int(bandData.bitsPerComponent),
                   bitsPerPixel: Int(bandData.bitsPerPixel),
                   bitsPerPixel: Int(bandData.bitsPerPixel),
                   bytesPerRow: Int(bandData.bytesPerRow),
                   bytesPerRow: Int(bandData.bytesPerRow),
-                  space: colorSpace,
-                  bitmapInfo: bitmapInfo,
+                  space: format.colorSpace,
+                  bitmapInfo: format.bitmapInfo,
                   provider: provider,
                   provider: provider,
                   decode: nil,
                   decode: nil,
                   shouldInterpolate: true,
                   shouldInterpolate: true,
@@ -700,8 +714,7 @@ private extension ScannerService {
             fullData.replaceSubrange(startOffset..<(startOffset + copyLength), with: bandData.prefix(copyLength))
             fullData.replaceSubrange(startOffset..<(startOffset + copyLength), with: bandData.prefix(copyLength))
         }
         }
 
 
-        let colorSpace = CGColorSpaceCreateDeviceRGB()
-        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipLast.rawValue)
+        let format = bandImageFormat(for: first)
         var mutableData = fullData
         var mutableData = fullData
         return mutableData.withUnsafeMutableBytes { buffer in
         return mutableData.withUnsafeMutableBytes { buffer in
             guard let baseAddress = buffer.baseAddress,
             guard let baseAddress = buffer.baseAddress,
@@ -711,8 +724,8 @@ private extension ScannerService {
                       height: height,
                       height: height,
                       bitsPerComponent: Int(first.bitsPerComponent),
                       bitsPerComponent: Int(first.bitsPerComponent),
                       bytesPerRow: bytesPerRow,
                       bytesPerRow: bytesPerRow,
-                      space: colorSpace,
-                      bitmapInfo: bitmapInfo.rawValue
+                      space: format.colorSpace,
+                      bitmapInfo: format.bitmapInfo.rawValue
                   ),
                   ),
                   let cgImage = context.makeImage() else {
                   let cgImage = context.makeImage() else {
                 return nil
                 return nil

+ 6 - 0
smart_printer/ViewController.swift

@@ -298,6 +298,9 @@ class ViewController: NSViewController {
         guard guardPremiumFeature(.photoPreview) else { return }
         guard guardPremiumFeature(.photoPreview) else { return }
         photoPreviewOverlay?.dismiss(animated: false)
         photoPreviewOverlay?.dismiss(animated: false)
         filePreviewOverlay?.dismiss(animated: false)
         filePreviewOverlay?.dismiss(animated: false)
+        printTextOverlay?.dismiss(animated: false)
+        printContactsOverlay?.dismiss(animated: false)
+        drawPrintOverlay?.dismiss(animated: false)
         ocrFileOverlay?.dismiss(animated: false)
         ocrFileOverlay?.dismiss(animated: false)
         scanFileOverlay?.dismiss(animated: false)
         scanFileOverlay?.dismiss(animated: false)
         dismissScannerOverlay()
         dismissScannerOverlay()
@@ -317,6 +320,9 @@ class ViewController: NSViewController {
         guard guardPremiumFeature(.filePreview) else { return }
         guard guardPremiumFeature(.filePreview) else { return }
         photoPreviewOverlay?.dismiss(animated: false)
         photoPreviewOverlay?.dismiss(animated: false)
         filePreviewOverlay?.dismiss(animated: false)
         filePreviewOverlay?.dismiss(animated: false)
+        printTextOverlay?.dismiss(animated: false)
+        printContactsOverlay?.dismiss(animated: false)
+        drawPrintOverlay?.dismiss(animated: false)
         ocrFileOverlay?.dismiss(animated: false)
         ocrFileOverlay?.dismiss(animated: false)
         scanFileOverlay?.dismiss(animated: false)
         scanFileOverlay?.dismiss(animated: false)
         dismissScannerOverlay()
         dismissScannerOverlay()