123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- //
- // 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<NSString?>, 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"
- }
- }
|