// // KMMainDocument.swift // PDF Master // // Created by wanjun on 2022/12/6. // import Cocoa typealias KMMainDocumentCloudUploadHanddler = (@escaping(Bool, String)->()) -> () @objcMembers class KMMainDocument: CTTabContents { var mainViewController: KMMainViewController? var homeWindowController: KMHomeWindowController? var homeViewController: KMHomeViewController? var isNewCreated: Bool = false var closedByUserGestureFlag: Bool = false // 标记 closedByUserGesture 这个状态需要延后存储(如果需要) var cloud: Bool = false var cloudUploadHanddler: KMMainDocumentCloudUploadHanddler? var isUnlockFromKeychain: Bool = false override func makeWindowControllers() { // Returns the storyboard that contains your document window. let mainWindow = NSApp.mainWindow var currentWindowController: KMBrowserWindowController? if mainWindow != nil { let windowController = mainWindow!.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } if (currentWindowController == nil) && (self.fileURL != nil) { let browser = KMBrowser.init() as KMBrowser browser.addHomeTabContents() browser.windowController = KMBrowserWindowController.init(browser: browser) currentWindowController = browser.windowController as? KMBrowserWindowController } mainViewController = KMMainViewController.init() mainViewController?.myDocument = self if ((self.fileURL?.path) != nil) { if !self.fileURL!.path.isPDFValid() { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "") alert.runModal() return } let pdfDocument = CPDFDocument.init(url: URL(fileURLWithPath: self.fileURL!.path)) mainViewController?.document = pdfDocument } self.view = mainViewController?.view if currentWindowController != nil { if currentWindowController?.browser != nil { // currentWindowController?.browser.add(self, at: Int32()-1, inForeground: true) // self.addWindowController(currentWindowController!) // mainViewController.browserWindowController = currentWindowController // MARK TODO: 流程调整“开启PDF后,替换Home标签” let activeBrowser = (currentWindowController?.browser.activeTabContents())! as CTTabContents let activeIndex = Int((currentWindowController?.browser.activeTabIndex())!) currentWindowController?.browser.add(self, at: Int32()-1, inForeground: true) self.addWindowController(currentWindowController!) mainViewController?.browserWindowController = currentWindowController let ishome = activeBrowser.isHome as Bool let isfirstTab = (activeIndex == 0) if ishome && !isfirstTab { let contents = activeBrowser.browser.tabContents(at: Int32(activeIndex)) as CTTabContents activeBrowser.browser.closeTab(Int32(activeIndex)) } } } } override func showWindows() { super.showWindows() self.setDataFromTmpData() } override func windowControllerDidLoadNib(_ aController: NSWindowController) { super.windowControllerDidLoadNib(aController) self.setDataFromTmpData() } override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType) async throws { do { try await super.save(to: url, ofType: typeName, for: saveOperation) } catch let outError { Swift.print(outError) } } override func write(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, originalContentsURL absoluteOriginalContentsURL: URL?) throws { var success = true if !self.isHome { if mainViewController != nil { if mainViewController?.document != nil { if mainViewController!.document!.isEncrypted { success = mainViewController!.document!.write(to: url) } else { if (mainViewController!.needSave) { // success = KMPasswordInputWindow.saveDocument(mainViewController.document!) success = mainViewController!.document!.write(to: url) } else { success = mainViewController!.document!.write(to: url) } } mainViewController!.needSave = false } } } else { success = false } if success && isNewCreated && NSDocument.SaveOperationType.saveAsOperation == saveOperation { isNewCreated = false } } override func canClose(withDelegate delegate: Any, shouldClose shouldCloseSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) { let isPrompt = KMPreferenceManager.shared.closeFileIsPrompt() if (isPrompt) { super.canClose(withDelegate: delegate, shouldClose: shouldCloseSelector, contextInfo: contextInfo) return } if (self.isNewCreated) { self.save(nil) } else if (self.isDocumentEdited) { self.save(nil) } else if (mainViewController != nil) { if self.mainViewController!.isPDFDocumentEdited || self.mainViewController!.needSave { self.save(nil) } } super.canClose(withDelegate: delegate, shouldClose: shouldCloseSelector, contextInfo: contextInfo) } override func read(from absoluteURL: URL, ofType typeName: String) throws { do { try super.read(from: absoluteURL, ofType: typeName) updateChangeCount(.changeCleared) } catch let outError { Swift.print(outError) } } override func read(from data: Data, ofType typeName: String) throws { // Insert code here to read your document from the given data of the specified type, throwing an error in case of failure. // Alternatively, you could remove this method and override read(from:ofType:) instead. If you do, you should also override isEntireFileLoaded to return false if the contents are lazily loaded. let pdfDocument = CPDFDocument.init(data: data) if pdfDocument == nil { throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) } } // MARK: Autosaving override func close() { if self.isActive { if browser != nil { var activeIndex = 0 let dex = browser.index(of: self) if dex == browser.tabCount() - 1 { activeIndex = Int(browser.tabCount()-2) } else { activeIndex = Int(dex + 1) } let activeContents = browser.tabContents(at: Int32(activeIndex)) activeContents?.addWindowController(browser.windowController) } } super.close() } // MARK: init override init() { super.init() // Add your subclass-specific initialization here. NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFListViewAnnotationsAttributeHasChangeNotification"), object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFViewDocumentChangedNotification"), object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFViewPageChangedNotification"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFListViewDidAddAnnotationNotification"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(pdfChangedNotification(_:)), name: NSNotification.Name.init(rawValue: "CPDFListViewDidRemoveAnnotationNotification"), object: nil) } override init?(baseTabContents baseContents: CTTabContents?) { super.init(baseTabContents: baseContents) if isHome { homeViewController = KMHomeViewController.init() homeViewController?.myDocument = self self.view = homeViewController?.view } } // MARK: Handling User Actions override var title: String? { get { if isHome { return NSLocalizedString("Home", comment: "") } else { return fileURL?.lastPathComponent } } set { super.title = newValue } } // MARK: Private Methods func pdfChangedNotification(_ notification: Notification) -> Void { if !isHome { let mainViewController = mainViewController // let document = notification.object as? CPDFDocument var document: CPDFDocument! if notification.object is CPDFAnnotation { let annotation = notification.object as? CPDFAnnotation document = annotation?.page.document } else if notification.object is CPDFListView { let pdflistView = notification.object as? CPDFListView document = pdflistView?.document } if mainViewController != nil { // if document == mainViewController!.document { if document == mainViewController!.document { // if notification.userInfo != nil { // updateChangeCount(.changeCleared) // } else { // updateChangeCount(.changeDone) // } updateChangeCount(.changeDone) } } } } override func updateChangeCount(_ change: NSDocument.ChangeType) { let mainWindow = NSApp.mainWindow var currentWindowController: KMBrowserWindowController? if mainWindow != nil { let windowController = mainWindow!.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } } else { for window in NSApp.windows { let windowController = window.windowController if windowController is KMBrowserWindowController { currentWindowController = (windowController as! KMBrowserWindowController) break } } } if currentWindowController != nil { if currentWindowController?.browser != nil { let activeBrowser = (currentWindowController?.browser.activeTabContents())! as CTTabContents let activeIndex = Int((currentWindowController?.browser.activeTabIndex())!) if self == activeBrowser { super.updateChangeCount(change) return } } } super.updateChangeCount(.changeCleared) } func uploadToCloud(_ callback: (@escaping(Bool, String)->())) { guard let handdler = self.cloudUploadHanddler else { return } handdler(callback) } func isPDFDocument() -> Bool { return true } func setDataFromTmpData() { if self.mainViewController == nil { return } let document = self.mainViewController!.document if (document == nil) { return } // self.tryToUnlockDocument(document!) if (document!.permissionsStatus != .owner) { var password: NSString? = nil let fileId = self.fileId(for: document!) if (fileId.isEmpty) { return } self.getPassword(&password, fileId: fileId) if (password != nil) { self.isUnlockFromKeychain = true // document.unlock(withPassword: password! as String) self.mainViewController!.password = password as String? } } } func tryToUnlockDocument(_ document: CPDFDocument) { if (document.permissionsStatus != .owner) { var password: NSString? = nil let fileId = self.fileId(for: document) if (fileId.isEmpty) { return } self.getPassword(&password, fileId: fileId) if (password != nil) { self.isUnlockFromKeychain = true document.unlock(withPassword: password! as String) } } } } extension NSDocument { @objc class func isDamage(url: URL) -> Bool { // 文件路径是否存在 if (FileManager.default.fileExists(atPath: url.path) == false) { return true } /// PDF 格式文件 if (url.pathExtension.lowercased() == "pdf") { let document = PDFDocument(url: url) if (document == nil) { return true } if (document!.isLocked) { // 加锁文件不在这里判断 return false } if (document!.pageCount <= 0) { return true } return false } // 支持的图片格式 let imageExts = ["jpg","cur","bmp","jpeg","gif","png","tiff","tif","ico","icns","tga","psd","eps","hdr","jp2","jpc","pict","sgi","heic"] let isImage = imageExts.contains(url.pathExtension.lowercased()) if (isImage == false) { // 其他格式目前返回没损坏,后续再补充(如果有需求) return false } // 图片格式 let image = NSImage(contentsOf: url) let data = image?.tiffRepresentation if (data == nil) { return true } let imageRep = NSBitmapImageRep(data: data!) imageRep!.size = image!.size var imageData: NSData? if (url.pathExtension.lowercased() == "png") { imageData = imageRep?.representation(using: .png, properties: [:]) as NSData? } else { imageData = imageRep?.representation(using: .jpeg, properties: [:]) as NSData? } if (imageData == nil) { return true } let path = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!) if (FileManager.default.fileExists(atPath: path!) == false) { try?FileManager.default.createDirectory(atPath: path!, withIntermediateDirectories: false) } var tagString: String = "" let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyMMddHHmmss" tagString.append(dateFormatter.string(from: Date())) tagString = tagString.appendingFormat("%04d", arc4random()%10000) let filePath = path?.appending("/\(tagString).png") if (imageData!.write(toFile: filePath!, atomically: true) == false) { return true } // 删除临时图片 try?FileManager.default.removeItem(atPath: filePath!) return false } @objc class func isDamage(url: URL, needAlertIfDamage need: Bool) -> Bool { let result = self.isDamage(url: url) if (result == false) { return false } if (need == false) { return true } let alert = NSAlert() alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "") alert.runModal() return true } } // MARK: - // MARK: 保存密码 extension NSDocument { func savePasswordInKeychain(_ password: String, _ document: CPDFDocument) { if (document.isLocked || password.isEmpty) { return } let fileId = self.fileId(for: document) if (fileId.isEmpty) { return } // let status: SKPasswordStatus = let label = "PDF Master: \(self.displayName!)" SKKeychain.setPassword(password, item: nil, forService: self.passwordServiceName(), account: fileId, label: label, comment: self.fileURL?.path) } func getPassword(_ password: AutoreleasingUnsafeMutablePointer, fileId: String) { let status = SKKeychain.getPassword(password, item: nil, forService: self.passwordServiceName(), account: fileId) // if (status == .found) { // } } fileprivate func fileId(for document: CPDFDocument) -> String { return "\(document.documentURL.path.hash)" } private func passwordServiceName() -> String { return "PDF Master password" } }