ref: move code around
This commit is contained in:
@@ -6,9 +6,9 @@ private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "App
|
||||
|
||||
|
||||
struct AppIcon {
|
||||
let meta: QuickLookInfo
|
||||
let meta: MetaInfo
|
||||
|
||||
init(_ meta: QuickLookInfo) {
|
||||
init(_ meta: MetaInfo) {
|
||||
self.meta = meta
|
||||
}
|
||||
|
||||
@@ -53,6 +53,128 @@ struct AppIcon {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Plist
|
||||
|
||||
extension AppIcon {
|
||||
/// Parse app plist to find the bundle icon filename.
|
||||
/// @param appPlist If `nil`, will load plist on the fly (used for thumbnail)
|
||||
/// @return Filenames which do not necessarily exist on filesystem. This may include `@2x` and/or no file extension.
|
||||
private func iconNamesFromPlist(_ appPlist: PlistDict?) -> [String] {
|
||||
let appPlist = appPlist == nil ? meta.readPlistApp()! : appPlist!
|
||||
// Check for CFBundleIcons (since 5.0)
|
||||
if let icons = unpackNameListFromPlistDict(appPlist["CFBundleIcons"]), !icons.isEmpty {
|
||||
return icons
|
||||
}
|
||||
// iPad-only apps
|
||||
if let icons = unpackNameListFromPlistDict(appPlist["CFBundleIcons~ipad"]), !icons.isEmpty {
|
||||
return icons
|
||||
}
|
||||
// Check for CFBundleIconFiles (since 3.2)
|
||||
if let icons = appPlist["CFBundleIconFiles"] as? [String], !icons.isEmpty {
|
||||
return icons
|
||||
}
|
||||
// key found on iTunesU app
|
||||
if let icons = appPlist["Icon files"] as? [String], !icons.isEmpty {
|
||||
return icons
|
||||
}
|
||||
// Check for CFBundleIconFile (legacy, before 3.2)
|
||||
if let icon = appPlist["CFBundleIconFile"] as? String { // may be nil
|
||||
return [icon]
|
||||
}
|
||||
return [] // [self sortedByResolution:icons];
|
||||
}
|
||||
|
||||
/// Given a filename, search Bundle or Filesystem for files that match. Select the filename with the highest resolution.
|
||||
private func expandImageName(_ iconList: [String]) -> String? {
|
||||
var matches: [String] = []
|
||||
switch meta.type {
|
||||
case .IPA:
|
||||
guard let zipFile = meta.zipFile else {
|
||||
// in case unzip in memory is not available, fallback to pattern matching with dynamic suffix
|
||||
return "Payload/*.app/\(iconList.first!)*"
|
||||
}
|
||||
for iconPath in iconList {
|
||||
let zipPath = "Payload/*.app/\(iconPath)*"
|
||||
for zip in zipFile.filesMatching(zipPath) {
|
||||
if zip.sizeUncompressed > 0 {
|
||||
matches.append(zip.filepath)
|
||||
}
|
||||
}
|
||||
if matches.count > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case .Archive, .Extension:
|
||||
let basePath = meta.effectiveUrl ?? meta.url
|
||||
for iconPath in iconList {
|
||||
let fileName = iconPath.components(separatedBy: "/").last!
|
||||
let parentDir = basePath.appendingPathComponent(iconPath, isDirectory: false).deletingLastPathComponent().path
|
||||
guard let files = try? FileManager.default.contentsOfDirectory(atPath: parentDir) else {
|
||||
continue
|
||||
}
|
||||
for file in files {
|
||||
if file.hasPrefix(fileName) {
|
||||
let fullPath = parentDir + "/" + file
|
||||
if let fSize = try? FileManager.default.attributesOfItem(atPath: fullPath)[FileAttributeKey.size] as? Int {
|
||||
if fSize > 0 {
|
||||
matches.append(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches.count > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches.isEmpty ? nil : sortedByResolution(matches).first
|
||||
}
|
||||
|
||||
/// Deep select icons from plist key `CFBundleIcons` and `CFBundleIcons~ipad`
|
||||
private func unpackNameListFromPlistDict(_ bundleDict: Any?) -> [String]? {
|
||||
if let bundleDict = bundleDict as? PlistDict {
|
||||
if let primaryDict = bundleDict["CFBundlePrimaryIcon"] as? PlistDict {
|
||||
if let icons = primaryDict["CFBundleIconFiles"] as? [String] {
|
||||
return icons
|
||||
}
|
||||
if let name = primaryDict["CFBundleIconName"] as? String { // key found on a .tipa file
|
||||
return [name]
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// @return lower index means higher resolution.
|
||||
private func resolutionIndex(_ iconName: String) -> Int {
|
||||
let lower = iconName.lowercased()
|
||||
// "defaultX" = launch image
|
||||
let penalty = lower.contains("small") || lower.hasPrefix("default") ? 20 : 0
|
||||
|
||||
let resolutionOrder: [String] = [
|
||||
"@3x", "180", "167", "152", "@2x", "120",
|
||||
"144", "114", "87", "80", "76", "72", "58", "57"
|
||||
]
|
||||
for (i, res) in resolutionOrder.enumerated() {
|
||||
if iconName.contains(res) {
|
||||
return i + penalty
|
||||
}
|
||||
}
|
||||
return 50 + penalty
|
||||
}
|
||||
|
||||
/// Given a list of filenames, order them highest resolution first.
|
||||
private func sortedByResolution(_ icons: [String]) -> [String] {
|
||||
return icons.sorted { (icon1, icon2) -> Bool in
|
||||
let index1 = self.resolutionIndex(icon1)
|
||||
let index2 = self.resolutionIndex(icon2)
|
||||
return index1 < index2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Extension: NSImage
|
||||
|
||||
// AppIcon extension
|
||||
|
||||
Reference in New Issue
Block a user