// // AutoTest.swift // KdanAuto // // Created by 朱东勇 on 2022/11/25. // import Foundation import AppKit var cacheObjects = NSMutableDictionary() class AutoTest : NSObject, AutoTestProtocal { var reportString : NSMutableAttributedString? = nil public var _status : AutoTestStatus = .Normal var _fileType : String = "RTF" var _type : String = "Others" var _extention : String = "rtf" class func autoTestFor(_ fileType:NSString ,type:NSString) -> AutoTest? { let key = String(fileType) + "." + String(type) if cacheObjects.value(forKey: key) != nil { let object = cacheObjects.value(forKey: key) return object as? AutoTest } // if let cacheObject as AutoTest { // return cacheObject // } let fileTypes = testTypeInfo[fileType] as! NSArray let clsname = "KdanAuto"//Bundle.main.infoDictionary! ["CFBundleExecutable"] for item in fileTypes { let cItem = item as! NSDictionary let cType = cItem["Type"] as! NSString let cExtention = cItem["Extention"] as! NSString if (cType.isEqual(to: type)) { let className = String((clsname )+"."+(cItem["Class"] as! String)) as! String let cl = NSClassFromString(className) as! AutoTest.Type let object = cl.shared() object?._fileType = fileType as! String object?._type = String(cType) object?._extention = String(cExtention) cacheObjects.setValue(object, forKey: key) return object } } let object = AutoTest.shared() object?._fileType = fileType as String object?._type = String(cType) object?._extention = "" cacheObjects.setValue(object, forKey: key) return object } class func shared() -> AutoTest? { return AutoTest() } func fileType() -> String { return _fileType } func type() -> String { return _type } func name() -> String { return "未指定类型对照测试" } func keys() -> NSArray { return ["快照对比"] } func selectedKeys() -> NSArray { let userDefaults = UserDefaults.standard let key = self.fileType() + "." + self.type() + ".selectedKeys" if userDefaults.value(forKey: key) != nil { return userDefaults.value(forKey: key) as! NSArray } self.setSelectedKeys(self.keys()) return self.keys() } func setSelectedKeys(_ keys: NSArray) { let userDefaults = UserDefaults.standard let key = self.fileType() + "." + self.type() + ".selectedKeys" userDefaults.setValue(keys, forKey: key) userDefaults.synchronize() } func status() -> AutoTestStatus { return _status } func setStatus(_ status:AutoTestStatus) { _status = status } // Auto Test func autoTest() { clearCacheFiles() if hasOriginFile() { let needCompare = self.selectedKeys().contains("快照对比") if !needCompare { _status = .Finished return } _status = .Process reportString = NSMutableAttributedString.init(string: "\n【\(String(self.fileType())) - \(self.name())】快照比对开始!\n", attributes:[.foregroundColor : NSColor.blue]) let files = DataModel.shared.originFilesFor(_fileType, type: _type) as [String] let checkDirectory = self.checkFileDirectory() let originDirectory = self.originFileDirectory() let resultDirectory = self.resultFileDirectory() for fileName in files { let fName = NSString(string: fileName).deletingPathExtension let originPath = NSString(string: originDirectory).appendingPathComponent(fName+".pdf") let resultPath = NSString(string: resultDirectory).appendingPathComponent(fName+"."+_extention) let checkPath = NSString(string: checkDirectory).appendingPathComponent(fName+"."+_extention) reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】开始转换文件 \"\(fName)\"\n", attributes:[.foregroundColor : NSColor.blue])) // ... // 执行转换过程 let convertSemaphore = DispatchSemaphore(value: 0) var success = false let convertQueue = DispatchQueue.global() convertQueue.async { success = FileConverter.shared().converter(originPath, inDesPath: resultPath) convertSemaphore.signal() } convertSemaphore.wait() var isDirectory = ObjCBool(false) if FileManager.default.fileExists(atPath: resultPath, isDirectory:&isDirectory) && success { // compare screenshoot between result file with check file if needCompare { let items = NSMutableArray() if (isDirectory.boolValue) { let searchItems = try! FileManager.default.contentsOfDirectory(atPath: resultPath) for item in NSArray(array: searchItems) { let ext = NSString(string: item as! String).pathExtension.lowercased() if NSArray(array: [_extention]).contains(ext) { let fileName = NSString(string: fName+"."+_extention+"/\(item as! String)").deletingPathExtension items.add(fileName) } } }else { items.add(fName) } for item in items { let subFileName = item as! String let subResultPath = NSString(string: resultDirectory).appendingPathComponent(subFileName+"."+_extention) reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subFileName+".\(_extention)")\"快照生成中\n", attributes:[.foregroundColor : NSColor.black])) let rComparePath = NSString(string: resultDirectory).appendingPathComponent(subFileName+"_\(DataModel.shared.latestReportID()!).jpg") let cComparePath = NSString(string: checkDirectory).appendingPathComponent(subFileName+".jpg") let processThumbSemaphore = DispatchSemaphore(value: 0) var processSuccess = false let thumbnailQueue = DispatchQueue.global() thumbnailQueue.async { processSuccess = ProcessThumbnal.process(subResultPath, desPath: rComparePath, outputSize: CGSize.init(width: 2048, height: 2048)) if ( processSuccess && FileManager.default.fileExists(atPath: rComparePath)) { let degree = self.compareJPEG(rComparePath, checkPath: cComparePath) if degree == -1 { self.reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subResultPath)\"快照对比失败,生成快照失败或无比对文件\n", attributes:[.foregroundColor : NSColor.red])) }else { self.reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subResultPath)\"快照对比完成,图像相似度 \(degree)%\n", attributes:[.foregroundColor : NSColor.red])) } }else { self.reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subResultPath)\"快照生成失败\n", attributes:[.foregroundColor : NSColor.red])) } processThumbSemaphore.signal() } processThumbSemaphore.wait() } } }else { reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(fName)\"转档失败!\n", attributes:[.foregroundColor : NSColor.red])) } } _status = .Finished }else { _status = .Normal } } func testReport() -> NSAttributedString? { return reportString } func compareJPEG(_ resultPath:String, checkPath:String) -> Double { if !FileManager.default.fileExists(atPath: resultPath) || !FileManager.default.fileExists(atPath: checkPath) { return -1 } let rImage = NSImage.init(contentsOfFile: resultPath) ?? nil let cImage = NSImage.init(contentsOfFile: checkPath) ?? nil if nil == rImage || nil == cImage { return -1 } let resultImage = rImage as! NSImage let checkImage = cImage as! NSImage let resultImageRep = NSBitmapImageRep.init(cgImage: resultImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) let checkImageRep = NSBitmapImageRep.init(cgImage: checkImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) let rWidth = resultImageRep.pixelsWide let rHeight = resultImageRep.pixelsHigh let rBitPerPixel = resultImageRep.bitsPerPixel / 8 let rBytePerRow = resultImageRep.bytesPerRow let cWidth = checkImageRep.pixelsWide let cHeight = checkImageRep.pixelsHigh let cBitPerPixel = checkImageRep.bitsPerPixel / 8 let cBytePerRow = checkImageRep.bytesPerRow let maxWidth = min(rWidth, cWidth) - 1 let maxHeight = min(rHeight, cHeight) - 1 var equalCount = 0 as Double for w in 0...maxWidth { let x = Int(w) for h in 0...maxHeight { let y = Int(h) let cColor = checkImageRep.colorAt(x: x, y: y) as! NSColor let rColor = resultImageRep.colorAt(x: x, y: y) as! NSColor if (cColor.isEqual(to: rColor)) { equalCount = equalCount + 1 } } } return Double(equalCount/(Double(cWidth) * Double(cHeight)) * 100.0) } /// Path func originFileDirectory() -> String { return DataModel.shared.directoryPath().appendingFormat("/\(self.fileType())/\(self.type())/\(kOriginPathComponent)") } func resultFileDirectory() -> String { return DataModel.shared.directoryPath().appendingFormat("/\(self.fileType())/\(self.type())/\(kResultPathComponent)") } func checkFileDirectory() -> String { return DataModel.shared.directoryPath().appendingFormat("/\(self.fileType())/\(self.type())/\(kCheckPathComponent)") } // check File Exist func hasOriginFile() -> Bool { if nil != self.originFileDirectory() && FileManager.default.fileExists(atPath: self.originFileDirectory()) { let files = try? FileManager.default.subpathsOfDirectory(atPath: self.originFileDirectory()) return (files ?? []).count > 0 } return false } func hasResultFile() -> Bool { if nil != self.resultFileDirectory() && FileManager.default.fileExists(atPath: self.resultFileDirectory()) { let files = try? FileManager.default.subpathsOfDirectory(atPath: self.resultFileDirectory()) return (files ?? []).count > 0 } return false } /// func clearCacheFiles() { let resultDirectory = self.resultFileDirectory() let searchItems = try! FileManager.default.contentsOfDirectory(atPath: resultDirectory) for item in NSArray(array: searchItems) { let path = NSString(string: resultDirectory).appendingPathComponent(item as! String) try! FileManager.default.removeItem(atPath: path) } } } extension AutoTest { func stringToImage(_ string:String) ->NSImage? { let length = Int(string.lengthOfBytes(using: .utf8)/2) var bytes = malloc(length)! for i in 0..<length { let index = i let subString = String(NSString(string: string).substring(with: NSMakeRange(Int(index * 2), 2))) bytes.storeBytes(of: UInt8(hexForString(subString)), as: UInt8.self) } var data = Data.init(bytes: bytes, count: length) let image = NSImage.init(data: data) return image } func hexForString(_ string:String) -> UInt8 { let chars = string.utf8CString if (string.lengthOfBytes(using: .utf8) >= 2) { return UInt8(intvalueForChar(chars[0]) * 16 + intvalueForChar(chars[1])) } return UInt8(0) } func intvalueForChar(_ char:CChar) -> UInt8 { let iValue = char // 0 ~ 9s if iValue >= 48 && iValue <= 57 { return UInt8(iValue - 48) }else if iValue >= 65 && iValue <= 70 { return UInt8(iValue - 65 + 10) }else if iValue >= 97 && iValue <= 102 { return UInt8(iValue - 97 + 10) } return UInt8(0) } }