ref: move code around

This commit is contained in:
relikd
2025-10-30 12:09:00 +01:00
parent df438c8581
commit d615ae0844
18 changed files with 822 additions and 799 deletions

View File

@@ -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