123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- //
- // KMResourceDownloadManager.swift
- // PDF Reader Pro
- //
- // Created by lizhe on 2023/8/29.
- //
- import Cocoa
- import Foundation
- import ZipArchive // 请确保已导入 SSZipArchive 或其他合适的解压库
- import ComDocumentAIKit
- import ComPDFKit_Conversion
- //#if DEBUG
- //let xmlURLString = "http://test-pdf-pro.kdan.cn:3021/downloads/DocumentAI.xml"
- //#else
- //let xmlURLString = "https://www.pdfreaderpro.com/downloads/DocumentAI.xml"
- //#endif
- //let documentAIString = "http://test-pdf-pro.kdan.cn:3021/downloads/DocumentAI.bundle.zip"
- let xmlURLString = KMServerConfig().kResourceServerURL
- let kResourcePath: String = kAppSupportOfBundleIdentifierDirectory.path
- let kLocalFilePath: String = kAppSupportOfBundleIdentifierDirectory.appendingPathComponent("DocumentAI.bundle").path
- enum KMResourceDownloadState {
- case none
- case unzipFailed
- case moveFailed
- case success
- case retry
- case cancel
- }
- struct Version: Comparable {
- let components: [Int]
-
- init?(_ version: String) {
- components = version.split(separator: ".").compactMap { Int($0) }
- guard !components.isEmpty else { return nil }
- }
-
- static func < (lhs: Version, rhs: Version) -> Bool {
- for (l, r) in zip(lhs.components, rhs.components) {
- if l != r { return l < r }
- }
- return lhs.components.count < rhs.components.count
- }
- }
- class KMResourceDownloadManager: NSObject {
- static let manager = KMResourceDownloadManager()
-
- var downloadTask: URLSessionDownloadTask?
- var progressBlock: ((Double) -> Void)?
- var downloadResultBlock: ((Bool, KMResourceDownloadState) -> Void)?
- var reachabilityAlert: NSAlert?
-
-
- func downloadFramework(progress: @escaping (Double) -> Void, result: @escaping (Bool, KMResourceDownloadState) -> Void) {
- self.progressBlock = progress
- self.downloadResultBlock = result
-
- KMRequestServer.requestServer.reachabilityStatusChange { [weak self] status in
- if status == .notReachable {
- KMPrint("无网络")
- self?.downloadTask?.cancel()
- self?.downloadTask = nil
- self?.downloadResultBlock?(false, .none)
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in
- if self?.reachabilityAlert == nil {
- self?.reachabilityAlert = NSAlert()
- self?.reachabilityAlert?.messageText = NSLocalizedString("Network Disconnected", comment: "")
- self?.reachabilityAlert?.informativeText = NSLocalizedString("Please connect to the internet and download again.", comment: "")
- self?.reachabilityAlert?.addButton(withTitle: NSLocalizedString("Retry", comment: ""))
- self?.reachabilityAlert?.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
- var window = NSWindow.currentWindow()
- if window != nil {
- self?.reachabilityAlert?.beginSheetModal(for: window) { result in
- if (result == .alertSecondButtonReturn) {
- self?.reachabilityAlert = nil
- self?.cancelDownload()
- } else if result == .alertFirstButtonReturn {
- self?.reachabilityAlert = nil
- self?.downloadResultBlock?(false, .retry)
- self?.cancelDownload()
- return
- }
- self?.reachabilityAlert = nil
- }
- } else {
- self?.reachabilityAlert = nil
- }
- } else {
- self?.reachabilityAlert = nil
- }
- })
- } else {
- KMPrint("有网络")
- }
- }
-
- if self.downloadTask == nil {
- self.downloadXML { [unowned self] content in
- let urlString = self.dealXML(content: content)
- // let urlString = "http://test-pdf-pro.kdan.cn:3021/downloads/DocumentAI.bundle.zip"
- if let url = URL(string: urlString) {
- let configuration = URLSessionConfiguration.default
- let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
- let downloadTask = session.downloadTask(with: url)
- downloadTask.resume()
- self.downloadTask = downloadTask
- } else {
- dealDownloadResult(isSuccess: false, state: .none)
- }
- }
- } else {
- dealDownloadResult(isSuccess: false, state: .none)
- }
- }
-
- func documentAIBundleExists(complete:@escaping (_ result: Bool) -> Void) {
- let filePath: String = kLocalFilePath
- let fileManager = FileManager.default
- debugPrint(filePath)
- debugPrint(FileManager.default.temporaryDirectory.appendingPathComponent("XMLResources"))
- let exists = fileManager.fileExists(atPath: filePath as String)
- //如果存在则判断版本是否符合,如果不符合则删除后重新下载
- if exists {
- self.checkDocumentAIVersion(complete: complete)
- self.loadDocumentAIBundle(bundlePath: kLocalFilePath)
- } else {
- self.removeDownloadResourcePath()
- complete(false)
- }
- }
-
- func cancelDownload() {
- downloadTask?.cancel()
- downloadTask = nil
- progressBlock = nil
- downloadResultBlock = nil
-
- self.downloadResultBlock?(false, .cancel)
- }
-
- //结果处理
- func dealDownloadResult(isSuccess: Bool, state: KMResourceDownloadState) {
- DispatchQueue.main.async {
- self.downloadResultBlock?(isSuccess, state)
- self.cancelDownload()
- }
- }
-
- func checkDocumentAIVersion(complete: @escaping (_ results: Bool) -> Void) {
- self.downloadXML { [unowned self] content in
- let urlString = self.dealXML(content: content)
- if urlString.count != 0 {
- try?FileManager.default.removeItem(atPath: kLocalFilePath)
-
- complete(false)
- } else {
- complete(true)
- }
- }
- }
-
- func loadDocumentAIBundle(bundlePath: String) {
- guard FileManager.default.fileExists(atPath: bundlePath) else {
- print("Bundle does not exist at specified path.")
- return
- }
-
- if let bundle = Bundle(path: bundlePath) {
- if let resourcePath = bundle.path(forResource: "DocumentAI", ofType: "model") {
- print("Found resource at path: \(resourcePath)")
- //绑定资源包
- CDocumentAIKit.sharedInstance().setOCRModelPath(bundlePath)
- CPDFConvertKit.setOCRModelPath(bundlePath)
- } else {
- print("Resource not found.")
- }
- } else {
- print("Failed to load bundle.")
- }
- }
- }
- //MARK: UI
- extension KMResourceDownloadManager {
- func downLoadOCRResource(window: NSWindow) {
- #if VERSION_DMG
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
- KMResourceDownloadManager.manager.documentAIBundleExists(complete: { [weak self] result in
- if result {
-
- } else {
- let alert = NSAlert()
- alert.alertStyle = .critical
- alert.messageText = KMLocalizedString("Need DownLoad OCR Resource", comment: "")
- alert.addButton(withTitle: KMLocalizedString("OK", comment: ""))
- alert.addButton(withTitle: KMLocalizedString("Cancel", comment: ""))
- alert.beginSheetModal(for: NSWindow.currentWindow()) {[weak self] response in
- if response == NSApplication.ModalResponse.alertFirstButtonReturn {
- self?.downLoad(window: window)
- }
- }
- }
- })
- }
- #endif
- }
-
- #if VERSION_DMG
- func downLoad(window: NSWindow) {
- let controller = KMOCRDownloadViewController(nibName: "KMOCRDownloadViewController", bundle: nil)
- let tempWindow = NSWindow(contentViewController: controller)
- tempWindow.maxSize = CGSizeMake(480, 208)
- tempWindow.minSize = CGSizeMake(480, 208)
- tempWindow.styleMask.remove(.resizable)
- window.beginSheet(tempWindow)
-
- controller.closeAction = { [unowned self] controller2 in
- window.endSheet(tempWindow)
- }
-
- controller.completionAction = { [unowned self] controller2 in
- window.endSheet(tempWindow)
- }
-
- controller.begin()
- }
- #endif
- }
- extension KMResourceDownloadManager: XMLParserDelegate {
- func downloadXML(completion: @escaping (_ content: String) -> Void) {
- if let xmlURL = URL(string: xmlURLString) {
- let request = URLRequest(url: xmlURL)
- let session = URLSession.shared
- let task = session.dataTask(with: request) { (data, response, error) in
- if let error = error {
- print("Error: \(error)")
- } else if let data = data {
- if let xmlString = String(data: data, encoding: .utf8) {
- print("XML Data: \(xmlString)")
- completion(xmlString)
- }
- }
- }
- task.resume()
- } else {
- print("Invalid URL")
- completion("")
- }
- }
-
- func dealXML(content: String) -> String {
- //
- // 1. 定义 XML 内容,包括版本号
- // let xmlContent = """
- // <root>
- // <resources>
- // <resource>
- // <maxVersion>1.3.0</maxVersion>
- // <minVersion>1.1.0</minVersion>
- // <resourceURL>https://www.pdfreaderpro.com/downloads/DocumentAI.bundle.1.0.0.zip</resourceURL>
- // </resource>
- // <resource>
- // <maxVersion>4.6.3</maxVersion>
- // <minVersion>1.4.0</minVersion>
- // <resourceURL>https://www.pdfreaderpro.com/downloads/DocumentAI.bundle.2.0.0.zip</resourceURL>
- // </resource>
- // </resources>
- // </root>
- // """
- let xmlContent = content
-
- let parser = ResourceParser()
- let resources = parser.parse(data: xmlContent.data(using: .utf8)!)
-
- // 从捆绑包的 Info.plist 文件中获取版本号
- var appVersion = "1.0.0"
- if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
- appVersion = version
- print("应用程序版本号:\(appVersion)")
- } else {
- print("无法获取应用程序版本号。")
- }
- if let resourceURL = shouldDownloadResource(for: appVersion, resources: resources) {
- if let local = self.getDownloadedResourcePath(for: kLocalFilePath) {
- if local != resourceURL {
- //如果本地存在两个地址不相同 则下载
- print("No suitable resource found for the current version.")
- return resourceURL
- } else {
- //如果本地存在 两个地址相同 则不下载
- print("No suitable resource found for the current version.")
- return ""
- }
- } else {
- //如果本地不存在 则下载
- print("Download resource from: \(resourceURL)")
- return resourceURL
- }
- } else {
- //如果找不到下载链接 则不下载
- print("No suitable resource found for the current version.")
- return ""
- }
- }
-
- func shouldDownloadResource(for currentVersion: String, resources: [Resource]) -> String? {
- for resource in resources {
- if isVersion(currentVersion, between: resource.minVersion, and: resource.maxVersion) {
- return resource.resourceURL
- }
- }
- return nil
- }
- func isVersion(_ version: String, between minVersion: String, and maxVersion: String) -> Bool {
- guard let current = Version(version),
- let min = Version(minVersion),
- let max = Version(maxVersion) else {
- return false
- }
- return current >= min && current <= max
- }
-
- func saveDownloadedResource(resourceURL: String, localPath: String) {
- var resources = UserDefaults.standard.dictionary(forKey: "downloadedResources") as? [String: String] ?? [:]
- resources[localPath] = resourceURL
- UserDefaults.standard.set(resources, forKey: "downloadedResources")
- }
-
- func getDownloadedResourcePath(for resourceURL: String) -> String? {
- let resources = UserDefaults.standard.dictionary(forKey: "downloadedResources") as? [String: String]
- return resources?[resourceURL]
- }
-
- func removeDownloadResourcePath() {
- UserDefaults.standard.removeObject(forKey: "downloadedResources")
- }
-
- func validateResource(for resourceURL: String) -> Bool {
- if let localPath = getDownloadedResourcePath(for: resourceURL) {
- return FileManager.default.fileExists(atPath: localPath)
- }
- return false
- }
- }
- extension KMResourceDownloadManager {
- //MARK: 解压
- func unzipFramework(at zipURL: URL, to destinationPath: String) {
- let fileManager = FileManager.default
- var success = false
- if zipURL.pathExtension == "zip" {
- success = SSZipArchive.unzipFile(atPath: zipURL.path, toDestination: destinationPath)
- } else {
- // 如果是其他类型的压缩文件,可以使用其他解压库
- // success = YourCustomUnzipLibrary.unzipFile(atPath: zipURL.path, toDestination: destinationPath)
- }
-
- if success {
- print("File unzipped successfully!")
- try? fileManager.removeItem(at: zipURL)
- } else {
- print("Failed to unzip file.")
- dealDownloadResult(isSuccess: false, state: .unzipFailed)
- }
- }
- }
- extension KMResourceDownloadManager: URLSessionDelegate, URLSessionDownloadDelegate {
- //MARK: 网络下载
- func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
- let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
- print("Download progress: \(progress)")
- progressBlock?(progress)
- }
-
- func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
- let destinationPath = kResourcePath
-
- let fileManager = FileManager.default
- let destinationURL = URL(fileURLWithPath: destinationPath).appendingPathComponent("DocumentAI.bundle.zip")
-
- do {
- try fileManager.moveItem(at: location, to: destinationURL)
- print("Framework downloaded and installed successfully!")
- unzipFramework(at: destinationURL, to: destinationPath)
- dealDownloadResult(isSuccess: true, state: .success)
- if let sourceURL = downloadTask.originalRequest?.url {
- saveDownloadedResource(resourceURL: sourceURL.absoluteString, localPath: kLocalFilePath)
- }
- } catch {
- print("Failed to move framework: \(error)")
- dealDownloadResult(isSuccess: false, state: .moveFailed)
- }
- }
- }
- struct Resource {
- let maxVersion: String
- let minVersion: String
- let resourceURL: String
- }
- // 5. 定义一个 XML 解析器的代理类
- class ResourceParser: NSObject, XMLParserDelegate {
- private var resources: [Resource] = []
- private var currentElement = ""
- var currentMaxVersion = ""
- var currentMinVersion = ""
- var currentResourceURL = ""
-
- func parse(data: Data) -> [Resource] {
- let parser = XMLParser(data: data)
- parser.delegate = self
- parser.parse()
- return resources
- }
-
- func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
- currentElement = elementName
- }
-
- func parser(_ parser: XMLParser, foundCharacters string: String) {
- switch currentElement {
- case "maxVersion":
- currentMaxVersion += string
- case "minVersion":
- currentMinVersion += string
- case "resourceURL":
- currentResourceURL += string
- default:
- break
- }
- }
-
- func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
- if elementName == "resource" {
- resources.append(
- Resource(
- maxVersion: currentMaxVersion.trimmingCharacters(in: .whitespacesAndNewlines),
- minVersion: currentMinVersion.trimmingCharacters(in: .whitespacesAndNewlines),
- resourceURL: currentResourceURL.trimmingCharacters(in: .whitespacesAndNewlines)
- )
- )
- currentMaxVersion = ""
- currentMinVersion = ""
- currentResourceURL = ""
- }
- }
- }
|