// // KMResourceDownloadManager.swift // PDF Master // // Created by lizhe on 2023/8/29. // import Cocoa import Foundation import ZipArchive // 请确保已导入 SSZipArchive 或其他合适的解压库 //#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 = KMLightMemberConfig().kResourceServerURL let kResourcePath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.path enum KMResourceDownloadState { case none case unzipFailed case moveFailed case success case retry } 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: { [unowned 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() -> Bool { let filePath: String = kResourcePath! + "/DocumentAI.bundle" let fileManager = FileManager.default debugPrint(filePath) debugPrint(FileManager.default.temporaryDirectory.appendingPathComponent("XMLResources")) let exists = fileManager.fileExists(atPath: filePath as String) if exists { self.checkDocumentAIVersion() } else { self.removeXml() } return exists } func cancelDownload() { downloadTask?.cancel() downloadTask = nil progressBlock = nil downloadResultBlock = nil } //结果处理 func dealDownloadResult(isSuccess: Bool, state: KMResourceDownloadState) { DispatchQueue.main.async { self.downloadResultBlock?(isSuccess, state) self.cancelDownload() } } func checkDocumentAIVersion() { self.downloadXML { [unowned self] content in let urlString = self.dealXML(content: content) if urlString.count != 0 { let filePath: String = kResourcePath! + "/DocumentAI.bundle" try?FileManager.default.removeItem(atPath: filePath) self.removeXml() } } } } extension KMResourceDownloadManager: XMLParserDelegate { func downloadXML(completion: @escaping (_ content: String) -> Void) { // 将 URL 字符串转换为 URL 对象 if let xmlURL = URL(string: xmlURLString) { // 创建一个 URL 请求 let request = URLRequest(url: xmlURL) // 创建一个 URLSession let session = URLSession.shared // 创建一个数据任务来下载 XML 数据 let task = session.dataTask(with: request) { (data, response, error) in if let error = error { // 处理下载错误 print("Error: \(error)") } else if let data = data { // 成功下载 XML 数据 if let xmlString = String(data: data, encoding: .utf8) { // 将 XML 数据打印出来(你可以进一步处理它,比如解析它) print("XML Data: \(xmlString)") completion(xmlString) } } } // 启动任务 task.resume() } else { // URL 字符串无效,进行错误处理 print("Invalid URL") completion("") } } func removeXml() { let fileManager = FileManager.default let folderURL = fileManager.temporaryDirectory.appendingPathComponent("XMLResources") let xmlURL = folderURL.appendingPathComponent("DocumentAI.xml") try?FileManager.default.removeItem(at: xmlURL) } func dealXML(content: String) -> String { // 1. 定义 XML 内容,包括版本号 // let xmlContent = """ // // 1.2.0 // 1.4.0 // http://test-pdf-pro.kdan.cn:3021/downloads/DocumentAI.bundle.zip // // """ let xmlContent = content // 2. 创建目标文件夹(如果不存在的话) let fileManager = FileManager.default let folderURL = fileManager.temporaryDirectory.appendingPathComponent("XMLResources") try? fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) // 3. 将 XML 内容写入文件 let xmlURL = folderURL.appendingPathComponent("TempDocumentAI.xml") try? xmlContent.write(to: xmlURL, atomically: true, encoding: .utf8) // 4. 解析 XML 并比较版本号 // 创建 XMLParser 实例 var localVersion = "0.0" var appVersion = "1.0.0" if let xmlData = try? Data(contentsOf: self.fetchXMLURL()) { let xmlParser = XMLParser(data: xmlData) let xmlDelegate = XMLDelegate() xmlParser.delegate = xmlDelegate if xmlParser.parse() { localVersion = xmlDelegate.version ?? "1.0.0" } } // 从捆绑包的 Info.plist 文件中获取版本号 if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String { appVersion = version print("应用程序版本号:\(appVersion)") } else { print("无法获取应用程序版本号。") } if let xmlData = try? Data(contentsOf: xmlURL) { let xmlParser = XMLParser(data: xmlData) let xmlDelegate = XMLDelegate() xmlParser.delegate = xmlDelegate if xmlParser.parse() { // Parsing was successful if let xmlVersion = xmlDelegate.version { if xmlVersion > localVersion { if let minVersion = xmlDelegate.minVersion { if appVersion >= minVersion { // 下载资源的逻辑 let resourceURL = xmlDelegate.resourceURL print("Download resource from \(resourceURL)") return resourceURL ?? "" } else { print("No need to download resources. Already up to date.") } } } else { print("No need to download resources. Already up to date.") } } } else { // Parsing failed } } else { // Error handling for loading XML data } return "" } func fetchXMLURL() -> URL { // 2. 创建目标文件夹(如果不存在的话) let fileManager = FileManager.default let folderURL = fileManager.temporaryDirectory.appendingPathComponent("XMLResources") try? fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) // 3. 将 XML 内容写入文件 let xmlURL = folderURL.appendingPathComponent("DocumentAI.xml") return xmlURL } func writeXML(content: String) { let xmlURL = self.fetchXMLURL() try? content.write(to: xmlURL, atomically: true, encoding: .utf8) } func xmlResourceDownloadSuccess() { let fileManager = FileManager.default let folderURL = fileManager.temporaryDirectory.appendingPathComponent("XMLResources") let xmlURL = folderURL.appendingPathComponent("DocumentAI.xml") let tempXmlURL = folderURL.appendingPathComponent("TempDocumentAI.xml") try?fileManager.removeItem(at: xmlURL) do { // 尝试更改文件名称 try fileManager.moveItem(atPath: tempXmlURL.path, toPath: xmlURL.path) print("文件重命名成功:\(xmlURL.path)") } catch { // 处理重命名失败的错误 print("文件重命名失败:\(error)") } } } 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) } } func loadFramework(destinationPath: String) { let fileManager = FileManager.default let bundlePath = destinationPath + "/DocumentAI.bundle" if fileManager.fileExists(atPath: bundlePath) { if let bundle = Bundle(path: bundlePath) { // 使用 framework 中的代码和资源 // 例如:bundle.load() print("Framework loaded successfully!") } else { print("Error loading bundle.") dealDownloadResult(isSuccess: false, state: .none) } } else { dealDownloadResult(isSuccess: false, state: .none) } } } 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) { guard let destinationPath = kResourcePath else { return } 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) self.xmlResourceDownloadSuccess() } catch { print("Failed to move framework: \(error)") dealDownloadResult(isSuccess: false, state: .moveFailed) } } } // 5. 定义一个 XML 解析器的代理类 class XMLDelegate: NSObject, XMLParserDelegate { var currentElement: String = "" var version: String? var minVersion: String? var resourceURL: String? 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) { if currentElement == "version" { if version == nil { version = string } } else if currentElement == "resourceURL" { if resourceURL == nil { resourceURL = string } } else if currentElement == "minVersion" { if minVersion == nil { minVersion = string } } } }