feat: support for apk
This commit is contained in:
@@ -27,6 +27,25 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.google.android.apk</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>application/vnd.android.package-archive</string>
|
||||||
|
</array>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>apk</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF5D2EA1199B00613856 /* MetaInfo.swift */; };
|
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF5D2EA1199B00613856 /* MetaInfo.swift */; };
|
||||||
5405CF652EA1376B00613856 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF642EA1376B00613856 /* Zip.swift */; };
|
5405CF652EA1376B00613856 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF642EA1376B00613856 /* Zip.swift */; };
|
||||||
|
540B77D92ED79BBD009E030C /* MetaInfo+Apk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540B77D82ED79BB2009E030C /* MetaInfo+Apk.swift */; };
|
||||||
|
540B77DA2ED79C6B009E030C /* MetaInfo+Apk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540B77D82ED79BB2009E030C /* MetaInfo+Apk.swift */; };
|
||||||
|
540B77DC2ED79CC1009E030C /* AndroidXML in Frameworks */ = {isa = PBXBuildFile; productRef = 540B77DB2ED79CC1009E030C /* AndroidXML */; };
|
||||||
|
540B77DE2ED79CC8009E030C /* AndroidXML in Frameworks */ = {isa = PBXBuildFile; productRef = 540B77DD2ED79CC8009E030C /* AndroidXML */; };
|
||||||
5412DECE2EBC168600F9040D /* Preview+ArchiveInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */; };
|
5412DECE2EBC168600F9040D /* Preview+ArchiveInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */; };
|
||||||
5412DED02EBC283000F9040D /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECF2EBC283000F9040D /* RuntimeError.swift */; };
|
5412DED02EBC283000F9040D /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECF2EBC283000F9040D /* RuntimeError.swift */; };
|
||||||
543FE5742EB3BB5E0059F98B /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 543FE5732EB3BB5E0059F98B /* AppIcon.icns */; };
|
543FE5742EB3BB5E0059F98B /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 543FE5732EB3BB5E0059F98B /* AppIcon.icns */; };
|
||||||
@@ -126,6 +130,7 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
5405CF5D2EA1199B00613856 /* MetaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaInfo.swift; sourceTree = "<group>"; };
|
5405CF5D2EA1199B00613856 /* MetaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaInfo.swift; sourceTree = "<group>"; };
|
||||||
5405CF642EA1376B00613856 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; };
|
5405CF642EA1376B00613856 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; };
|
||||||
|
540B77D82ED79BB2009E030C /* MetaInfo+Apk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MetaInfo+Apk.swift"; sourceTree = "<group>"; };
|
||||||
5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+ArchiveInfo.swift"; sourceTree = "<group>"; };
|
5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+ArchiveInfo.swift"; sourceTree = "<group>"; };
|
||||||
5412DECF2EBC283000F9040D /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = "<group>"; };
|
5412DECF2EBC283000F9040D /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = "<group>"; };
|
||||||
54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AssetCarReader.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AssetCarReader.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -192,6 +197,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
540B77DC2ED79CC1009E030C /* AndroidXML in Frameworks */,
|
||||||
54B6FFF02EB6AA0F007397C0 /* AssetCarReader.framework in Frameworks */,
|
54B6FFF02EB6AA0F007397C0 /* AssetCarReader.framework in Frameworks */,
|
||||||
54D3A6FE2EA465B4001EF4F6 /* CoreGraphics.framework in Frameworks */,
|
54D3A6FE2EA465B4001EF4F6 /* CoreGraphics.framework in Frameworks */,
|
||||||
54442C232E378BAF008A870E /* Quartz.framework in Frameworks */,
|
54442C232E378BAF008A870E /* Quartz.framework in Frameworks */,
|
||||||
@@ -202,6 +208,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
540B77DE2ED79CC8009E030C /* AndroidXML in Frameworks */,
|
||||||
54B6FFF52EB6AA14007397C0 /* AssetCarReader.framework in Frameworks */,
|
54B6FFF52EB6AA14007397C0 /* AssetCarReader.framework in Frameworks */,
|
||||||
54581FD12EB29A0B0043A0B3 /* QuickLookThumbnailing.framework in Frameworks */,
|
54581FD12EB29A0B0043A0B3 /* QuickLookThumbnailing.framework in Frameworks */,
|
||||||
54581FD22EB29A0B0043A0B3 /* Quartz.framework in Frameworks */,
|
54581FD22EB29A0B0043A0B3 /* Quartz.framework in Frameworks */,
|
||||||
@@ -215,6 +222,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5405CF5D2EA1199B00613856 /* MetaInfo.swift */,
|
5405CF5D2EA1199B00613856 /* MetaInfo.swift */,
|
||||||
|
540B77D82ED79BB2009E030C /* MetaInfo+Apk.swift */,
|
||||||
5412DECF2EBC283000F9040D /* RuntimeError.swift */,
|
5412DECF2EBC283000F9040D /* RuntimeError.swift */,
|
||||||
54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */,
|
54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */,
|
||||||
54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */,
|
54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */,
|
||||||
@@ -406,6 +414,7 @@
|
|||||||
);
|
);
|
||||||
name = "QL Preview";
|
name = "QL Preview";
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
540B77DB2ED79CC1009E030C /* AndroidXML */,
|
||||||
);
|
);
|
||||||
productName = QLPreview;
|
productName = QLPreview;
|
||||||
productReference = 54442C202E378BAF008A870E /* QLAppBundle Preview Extension.appex */;
|
productReference = 54442C202E378BAF008A870E /* QLAppBundle Preview Extension.appex */;
|
||||||
@@ -427,6 +436,7 @@
|
|||||||
);
|
);
|
||||||
name = "QL Thumbnail";
|
name = "QL Thumbnail";
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
540B77DD2ED79CC8009E030C /* AndroidXML */,
|
||||||
);
|
);
|
||||||
productName = QLThumbnail;
|
productName = QLThumbnail;
|
||||||
productReference = 54581FCF2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex */;
|
productReference = 54581FCF2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex */;
|
||||||
@@ -462,6 +472,9 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 54442BEB2E378B71008A870E;
|
mainGroup = 54442BEB2E378B71008A870E;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
|
packageReferences = (
|
||||||
|
54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */,
|
||||||
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = 54442BF52E378B71008A870E /* Products */;
|
productRefGroup = 54442BF52E378B71008A870E /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -549,6 +562,7 @@
|
|||||||
547F52ED2EB2C822002B6D5F /* Preview+AppInfo.swift in Sources */,
|
547F52ED2EB2C822002B6D5F /* Preview+AppInfo.swift in Sources */,
|
||||||
549E3BA12EBAE7D300ADFF56 /* URL+File.swift in Sources */,
|
549E3BA12EBAE7D300ADFF56 /* URL+File.swift in Sources */,
|
||||||
547F52F42EB2CA05002B6D5F /* Preview+Entitlements.swift in Sources */,
|
547F52F42EB2CA05002B6D5F /* Preview+Entitlements.swift in Sources */,
|
||||||
|
540B77D92ED79BBD009E030C /* MetaInfo+Apk.swift in Sources */,
|
||||||
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */,
|
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */,
|
||||||
547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */,
|
547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */,
|
||||||
54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */,
|
54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */,
|
||||||
@@ -567,6 +581,7 @@
|
|||||||
54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */,
|
54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */,
|
||||||
549E3BA22EBAECD400ADFF56 /* URL+File.swift in Sources */,
|
549E3BA22EBAECD400ADFF56 /* URL+File.swift in Sources */,
|
||||||
547899752EB38F3D00F96B80 /* AppIcon.swift in Sources */,
|
547899752EB38F3D00F96B80 /* AppIcon.swift in Sources */,
|
||||||
|
540B77DA2ED79C6B009E030C /* MetaInfo+Apk.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -821,6 +836,7 @@
|
|||||||
ENABLE_USER_SELECTED_FILES = readonly;
|
ENABLE_USER_SELECTED_FILES = readonly;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||||
@@ -847,6 +863,7 @@
|
|||||||
ENABLE_USER_SELECTED_FILES = readonly;
|
ENABLE_USER_SELECTED_FILES = readonly;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||||
@@ -1014,6 +1031,30 @@
|
|||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/relikd/AndroidXML";
|
||||||
|
requirement = {
|
||||||
|
kind = exactVersion;
|
||||||
|
version = 1.3.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
540B77DB2ED79CC1009E030C /* AndroidXML */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */;
|
||||||
|
productName = AndroidXML;
|
||||||
|
};
|
||||||
|
540B77DD2ED79CC8009E030C /* AndroidXML */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 54D891112ED7313100BF23C4 /* XCRemoteSwiftPackageReference "AndroidXML" */;
|
||||||
|
productName = AndroidXML;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 54442BEC2E378B71008A870E /* Project object */;
|
rootObject = 54442BEC2E378B71008A870E /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "c869761611793a3eebb4e2f56e7aebab4faa8db4159e6116b059292c98af7094",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "androidxml",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/relikd/AndroidXML",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "fd522d612f24ee813c80b1a1b0f6bd311b2735c3",
|
||||||
|
"version" : "1.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
<string>com.apple.xcode.archive</string>
|
<string>com.apple.xcode.archive</string>
|
||||||
<string>com.opa334.trollstore.tipa</string>
|
<string>com.opa334.trollstore.tipa</string>
|
||||||
<string>dyn.ah62d4rv4ge81k4puqe</string>
|
<string>dyn.ah62d4rv4ge81k4puqe</string>
|
||||||
|
<string>com.google.android.apk</string>
|
||||||
|
<string>dyn.ah62d4rv4ge80c6dp</string>
|
||||||
|
<string>public.archive.apk</string>
|
||||||
</array>
|
</array>
|
||||||
<key>QLSupportsSearchableItems</key>
|
<key>QLSupportsSearchableItems</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
<string>com.apple.xcode.archive</string>
|
<string>com.apple.xcode.archive</string>
|
||||||
<string>com.opa334.trollstore.tipa</string>
|
<string>com.opa334.trollstore.tipa</string>
|
||||||
<string>dyn.ah62d4rv4ge81k4puqe</string>
|
<string>dyn.ah62d4rv4ge81k4puqe</string>
|
||||||
|
<string>com.google.android.apk</string>
|
||||||
|
<string>dyn.ah62d4rv4ge80c6dp</string>
|
||||||
|
<string>public.archive.apk</string>
|
||||||
</array>
|
</array>
|
||||||
<key>QLThumbnailMinimumDimension</key>
|
<key>QLThumbnailMinimumDimension</key>
|
||||||
<integer>16</integer>
|
<integer>16</integer>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ThumbnailProvider: QLThumbnailProvider {
|
|||||||
|
|
||||||
override func provideThumbnail(for request: QLFileThumbnailRequest, _ handler: @escaping (QLThumbnailReply?, Error?) -> Void) {
|
override func provideThumbnail(for request: QLFileThumbnailRequest, _ handler: @escaping (QLThumbnailReply?, Error?) -> Void) {
|
||||||
let meta = MetaInfo(request.fileURL)
|
let meta = MetaInfo(request.fileURL)
|
||||||
guard let appPlist = meta.readPlistApp() else {
|
guard let appPlist = meta.readPlistApp(iconOnly: true) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let img = AppIcon(meta).extractImage(from: appPlist).withRoundCorners()
|
let img = AppIcon(meta).extractImage(from: appPlist).withRoundCorners()
|
||||||
|
|||||||
@@ -73,6 +73,21 @@
|
|||||||
__ProvisionDeviceIds__
|
__ProvisionDeviceIds__
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="__ApkFeaturesRequiredHidden__">
|
||||||
|
<h2>Features (required)</h2>
|
||||||
|
__ApkFeaturesRequiredList__
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="__ApkFeaturesOptionalHidden__">
|
||||||
|
<h2>Features (optional)</h2>
|
||||||
|
__ApkFeaturesOptionalList__
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="__ApkPermissionsHidden__">
|
||||||
|
<h2>Permissions</h2>
|
||||||
|
__ApkPermissionsList__
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>File info</h2>
|
<h2>File info</h2>
|
||||||
__FileName__<br />
|
__FileName__<br />
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ struct AppIcon {
|
|||||||
/// Try multiple methods to extract image.
|
/// Try multiple methods to extract image.
|
||||||
/// This method will always return an image even if none is found, in which case it returns the default image.
|
/// This method will always return an image even if none is found, in which case it returns the default image.
|
||||||
func extractImage(from appPlist: PlistDict?) -> NSImage {
|
func extractImage(from appPlist: PlistDict?) -> NSImage {
|
||||||
|
if meta.type == .APK {
|
||||||
|
if let iconPath = appPlist?["appIcon"] as? String,
|
||||||
|
let data = meta.zipFile!.unzipFile(iconPath),
|
||||||
|
let img = NSImage(data: data) {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
return defaultIcon()
|
||||||
|
}
|
||||||
// no need to unwrap the plist, and most .ipa should include the Artwork anyway
|
// no need to unwrap the plist, and most .ipa should include the Artwork anyway
|
||||||
if meta.type == .IPA {
|
if meta.type == .IPA {
|
||||||
if let data = meta.zipFile!.unzipFile("iTunesArtwork") {
|
if let data = meta.zipFile!.unzipFile("iTunesArtwork") {
|
||||||
@@ -116,6 +124,9 @@ extension AppIcon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .APK:
|
||||||
|
return nil // handled in `extractImage()`
|
||||||
|
|
||||||
case .Archive, .Extension:
|
case .Archive, .Extension:
|
||||||
for iconPath in iconList {
|
for iconPath in iconList {
|
||||||
let fileName = iconPath.components(separatedBy: "/").last!
|
let fileName = iconPath.components(separatedBy: "/").last!
|
||||||
|
|||||||
255
src/MetaInfo+Apk.swift
Normal file
255
src/MetaInfo+Apk.swift
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import Foundation
|
||||||
|
import AndroidXML
|
||||||
|
import os // OSLog
|
||||||
|
|
||||||
|
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "MetaInfo+Apk")
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Full Manifest
|
||||||
|
|
||||||
|
extension MetaInfo {
|
||||||
|
/// Extract `AndroidManifest.xml` and parse its content
|
||||||
|
func readApkManifest() -> PlistDict? {
|
||||||
|
guard let data = self.readPayloadFile("AndroidManifest.xml", osxSubdir: nil) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let storage = ApkXmlManifestParser()
|
||||||
|
if let xml = try? AndroidXML.init(data: data) {
|
||||||
|
let ALLOWED_TAGS = [ // keep in sync with `ApkXmlManifestParser`
|
||||||
|
"manifest",
|
||||||
|
"application",
|
||||||
|
"uses-feature",
|
||||||
|
"uses-permission",
|
||||||
|
"uses-permission-sdk-23",
|
||||||
|
"uses-sdk",
|
||||||
|
]
|
||||||
|
let parser = xml.parseXml()
|
||||||
|
let ignore = XMLParser()
|
||||||
|
try? parser.iterElements({ startTag, attributes in
|
||||||
|
if ALLOWED_TAGS.contains(startTag) {
|
||||||
|
storage.parser(ignore, didStartElement: startTag, namespaceURI: nil, qualifiedName: nil, attributes: try attributes.asDictStr())
|
||||||
|
}
|
||||||
|
}) { endTag in
|
||||||
|
if ALLOWED_TAGS.contains(endTag) {
|
||||||
|
storage.parser(ignore, didEndElement: endTag, namespaceURI: nil, qualifiedName: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fallback to xml-string parser
|
||||||
|
let parser = XMLParser(data: data)
|
||||||
|
parser.delegate = storage
|
||||||
|
parser.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
var rv = storage.result
|
||||||
|
guard let _ = rv["packageId"] else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
os_log(.debug, log: log, "[apk] resolving resources name: %{public}@ icon: %{public}@", String(describing: rv["appName"]), String(describing: rv["appIcon"]))
|
||||||
|
let resolved = self.resolveResources([(ResourceType.Name, rv["appName"] as? String), (ResourceType.Icon, rv["appIcon"] as? String)])
|
||||||
|
os_log(.debug, log: log, "[apk] resolved %{public}@", String(describing: resolved))
|
||||||
|
rv["appName"] = resolved[0]
|
||||||
|
rv["appIcon"] = resolved[1]
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper to use same code for binary-xml and string-xml parsing
|
||||||
|
private class ApkXmlManifestParser: NSObject, XMLParserDelegate {
|
||||||
|
private var _scope: [String] = []
|
||||||
|
private var _rv: [String: String] = [:]
|
||||||
|
private var _perm: [String] = []
|
||||||
|
private var _featOpt: [String] = []
|
||||||
|
private var _featReq: [String] = []
|
||||||
|
var result: PlistDict {
|
||||||
|
(_rv as PlistDict).merging([
|
||||||
|
"permissions": _perm,
|
||||||
|
"featuresOptional": _featOpt,
|
||||||
|
"featuresRequired": _featReq,
|
||||||
|
]) { $1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attrs: [String : String] = [:]) {
|
||||||
|
// keep in sync with ALLOWED_TAGS above
|
||||||
|
switch elementName {
|
||||||
|
case "manifest":
|
||||||
|
if _scope == [] {
|
||||||
|
_rv["packageId"] = attrs["package"] // "org.bundle.id"
|
||||||
|
_rv["versionName"] = attrs["android:versionName"] // "7.62.3"
|
||||||
|
_rv["versionCode"] = attrs["android:versionCode"] // "160700"
|
||||||
|
// attrs["platformBuildVersionCode"] // "35"
|
||||||
|
// attrs["platformBuildVersionName"] // "15"
|
||||||
|
}
|
||||||
|
case "application":
|
||||||
|
if _scope == ["manifest"] {
|
||||||
|
_rv["appName"] = attrs["android:label"] // @resource-ref
|
||||||
|
_rv["appIcon"] = attrs["android:icon"] // @resource-ref
|
||||||
|
}
|
||||||
|
case "uses-permission", "uses-permission-sdk-23":
|
||||||
|
// no "permission" because that will produce duplicates with "uses-permission"
|
||||||
|
if _scope == ["manifest"], let name = attrs["android:name"] {
|
||||||
|
_perm.append(name)
|
||||||
|
}
|
||||||
|
case "uses-feature":
|
||||||
|
if _scope == ["manifest"], let name = attrs["android:name"] {
|
||||||
|
let optional = attrs["android:required"] == "false"
|
||||||
|
optional ? _featOpt.append(name) : _featReq.append(name)
|
||||||
|
}
|
||||||
|
case "uses-sdk":
|
||||||
|
if _scope == ["manifest"] {
|
||||||
|
_rv["sdkVerMin"] = attrs["android:minSdkVersion"] ?? "1" // "21"
|
||||||
|
_rv["sdkVerTarget"] = attrs["android:targetSdkVersion"] // "35"
|
||||||
|
}
|
||||||
|
default: break // ignore
|
||||||
|
}
|
||||||
|
_scope.append(elementName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
|
||||||
|
_scope.removeLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Icon only
|
||||||
|
|
||||||
|
extension MetaInfo {
|
||||||
|
/// Same as `readApkManifest()` but only extract `appIcon`.
|
||||||
|
func readApkIconOnly() -> PlistDict? {
|
||||||
|
guard let data = self.readPayloadFile("AndroidManifest.xml", osxSubdir: nil) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var icon: String? = nil
|
||||||
|
if let xml = try? AndroidXML.init(data: data) {
|
||||||
|
let parser = xml.parseXml()
|
||||||
|
try? parser.iterElements({ startTag, attributes in
|
||||||
|
if startTag == "application" {
|
||||||
|
icon = try? attributes.asDictStr()["android:icon"]
|
||||||
|
}
|
||||||
|
}) {_ in}
|
||||||
|
} else {
|
||||||
|
// fallback to xml-string parser
|
||||||
|
icon = ApkXmlIconParser().run(data)
|
||||||
|
}
|
||||||
|
if let icon = self.resolveResources([(.Icon, icon)])[0] {
|
||||||
|
return ["appIcon": icon]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shorter form of `ApkXmlManifestParser` to only exctract the icon reference (used for Thumbnail Provider)
|
||||||
|
private class ApkXmlIconParser: NSObject, XMLParserDelegate {
|
||||||
|
var result: String? = nil
|
||||||
|
|
||||||
|
func run(_ data: Data) -> String? {
|
||||||
|
let parser = XMLParser(data: data)
|
||||||
|
parser.delegate = self
|
||||||
|
parser.parse()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attrs: [String : String] = [:]) {
|
||||||
|
if elementName == "application" {
|
||||||
|
result = attrs["android:icon"]
|
||||||
|
parser.abortParsing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Resolve resource
|
||||||
|
|
||||||
|
private extension MetaInfo {
|
||||||
|
// currently there are only two types of resources, "android:icon" and "android:label"
|
||||||
|
enum ResourceType {
|
||||||
|
case Name
|
||||||
|
case Icon
|
||||||
|
}
|
||||||
|
func resolveResources(_ ids: [(ResourceType, String?)]) -> [String?] {
|
||||||
|
guard let data = self.readPayloadFile("resources.arsc", osxSubdir: nil),
|
||||||
|
let xml = try? AndroidXML.init(data: data), xml.type == .Table else {
|
||||||
|
return ids.map { _ in nil }
|
||||||
|
}
|
||||||
|
let parser = xml.parseTable()
|
||||||
|
return ids.map { typ, val in
|
||||||
|
guard let val, let ref = try? TblTableRef(val) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case .Name: return parser.getName(ref)
|
||||||
|
case .Icon: return parser.getIconDirect(ref) ?? parser.getIconIndirect(ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Tbl_Parser {
|
||||||
|
func getName(_ ref: TblTableRef) -> String? {
|
||||||
|
// why the heck are these even allowed?
|
||||||
|
// apparently there can be references onto references
|
||||||
|
var ref = ref
|
||||||
|
while let res = try? self.getResource(ref) {
|
||||||
|
guard let val = res.entries.first?.entry.value else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch val.dataType {
|
||||||
|
case .Reference: ref = val.asTableRef // and continue
|
||||||
|
case .String: return val.resolve(self.stringPool)
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `ref.entry != 0`, lookup resource with matching id
|
||||||
|
func getIconDirect(_ ref: TblTableRef) -> String? {
|
||||||
|
guard ref.entry != 0, let res = try? self.getResource(ref) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var best: ResValue? = nil
|
||||||
|
var bestScore: UInt16 = 0
|
||||||
|
for e in res.entries {
|
||||||
|
let density = e.config.screenType.density
|
||||||
|
if density == .any || density == .None {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let val = e.entry.value {
|
||||||
|
if density.rawValue > bestScore {
|
||||||
|
bestScore = density.rawValue
|
||||||
|
best = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best?.resolve(self.stringPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// if `ref.entry == 0`, iterate over all entries and search for attribute name `ic_launcher`
|
||||||
|
func getIconIndirect(_ ref: TblTableRef) -> String? {
|
||||||
|
// sadly we cannot just `getResource()` because that can point to an app banner
|
||||||
|
guard let pkg = try? self.getPackage(ref.package),
|
||||||
|
var pool = pkg.stringPool(for: .Keys),
|
||||||
|
let (_, types) = try? pkg.getType(ref.type) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var best: ResValue? = nil
|
||||||
|
var bestScore: UInt16 = 0
|
||||||
|
for typ in types {
|
||||||
|
let density = typ.config.screenType.density
|
||||||
|
if density == .any || density == .None {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try? typ.iterValues { _, entry in
|
||||||
|
let attrName = pool.getStringCached(entry.key)
|
||||||
|
guard attrName == "ic_launcher", let val = entry.value else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if density.rawValue > bestScore {
|
||||||
|
bestScore = density.rawValue
|
||||||
|
best = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best?.resolve(self.stringPool)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ enum FileType {
|
|||||||
case IPA
|
case IPA
|
||||||
case Archive
|
case Archive
|
||||||
case Extension
|
case Extension
|
||||||
|
case APK
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MetaInfo {
|
struct MetaInfo {
|
||||||
@@ -25,7 +26,7 @@ struct MetaInfo {
|
|||||||
/// Use file url and UTI type to generate an info object to pass around.
|
/// Use file url and UTI type to generate an info object to pass around.
|
||||||
init(_ url: URL) {
|
init(_ url: URL) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.UTI = try! url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier ?? "Unknown"
|
self.UTI = try! url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier ?? "Unknown"
|
||||||
|
|
||||||
var isOSX = false
|
var isOSX = false
|
||||||
var effective: URL? = nil
|
var effective: URL? = nil
|
||||||
@@ -46,6 +47,10 @@ struct MetaInfo {
|
|||||||
}
|
}
|
||||||
case "com.apple.application-and-system-extension":
|
case "com.apple.application-and-system-extension":
|
||||||
self.type = FileType.Extension
|
self.type = FileType.Extension
|
||||||
|
case "com.google.android.apk", "dyn.ah62d4rv4ge80c6dp", "public.archive.apk":
|
||||||
|
self.type = FileType.APK
|
||||||
|
zipFile = ZipFile(self.url.path)
|
||||||
|
// case "com.google.android.apkm", "dyn.ah62d4rv4ge80c6dpry":
|
||||||
default:
|
default:
|
||||||
os_log(.error, log: log, "Unsupported file type: %{public}@", self.UTI)
|
os_log(.error, log: log, "Unsupported file type: %{public}@", self.UTI)
|
||||||
fatalError()
|
fatalError()
|
||||||
@@ -58,7 +63,7 @@ struct MetaInfo {
|
|||||||
/// Evaluate path with `osxSubdir` and `filename`
|
/// Evaluate path with `osxSubdir` and `filename`
|
||||||
func effectiveUrl(_ osxSubdir: String?, _ filename: String) -> URL {
|
func effectiveUrl(_ osxSubdir: String?, _ filename: String) -> URL {
|
||||||
switch self.type {
|
switch self.type {
|
||||||
case .IPA:
|
case .IPA, .APK:
|
||||||
return effectiveUrl
|
return effectiveUrl
|
||||||
case .Archive, .Extension:
|
case .Archive, .Extension:
|
||||||
if isOSX, let osxSubdir {
|
if isOSX, let osxSubdir {
|
||||||
@@ -75,16 +80,20 @@ struct MetaInfo {
|
|||||||
switch self.type {
|
switch self.type {
|
||||||
case .IPA:
|
case .IPA:
|
||||||
return zipFile!.unzipFile("Payload/*.app/".appending(filename))
|
return zipFile!.unzipFile("Payload/*.app/".appending(filename))
|
||||||
|
case .APK:
|
||||||
|
return zipFile!.unzipFile(filename)
|
||||||
case .Archive, .Extension:
|
case .Archive, .Extension:
|
||||||
return try? Data(contentsOf: self.effectiveUrl(osxSubdir, filename))
|
return try? Data(contentsOf: self.effectiveUrl(osxSubdir, filename))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read app default `Info.plist`. (used for both, Preview and Thumbnail)
|
/// Read app default `Info.plist`. (used for both, Preview and Thumbnail)
|
||||||
func readPlistApp() -> PlistDict? {
|
func readPlistApp(iconOnly: Bool = false) -> PlistDict? {
|
||||||
switch self.type {
|
switch self.type {
|
||||||
case .IPA, .Archive, .Extension:
|
case .IPA, .Archive, .Extension:
|
||||||
return self.readPayloadFile("Info.plist", osxSubdir: nil)?.asPlistOrNil()
|
return self.readPayloadFile("Info.plist", osxSubdir: nil)?.asPlistOrNil()
|
||||||
|
case .APK:
|
||||||
|
return iconOnly ? self.readApkIconOnly() : self.readApkManifest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,4 +42,81 @@ extension PreviewGenerator {
|
|||||||
"AppMinOS": minVersion,
|
"AppMinOS": minVersion,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Process info stored in `Info.plist`
|
||||||
|
mutating func procAppInfoAndroid(_ appPlist: PlistDict) {
|
||||||
|
let featuresRequired = appPlist["featuresRequired"] as! [String]
|
||||||
|
let featuresOptional = appPlist["featuresOptional"] as! [String]
|
||||||
|
let permissions = appPlist["permissions"] as! [String]
|
||||||
|
|
||||||
|
func asList(_ list: [String]) -> String {
|
||||||
|
"<pre>\(list.joined(separator: "\n"))</pre>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveSDK(_ sdk: String?) -> String {
|
||||||
|
sdk == nil ? "" : "\(sdk!) (Android \(ANDROID_SDK_MAP[sdk!] ?? "?"))"
|
||||||
|
}
|
||||||
|
self.apply([
|
||||||
|
"AppInfoHidden": CLASS_VISIBLE,
|
||||||
|
"AppName": appPlist["appName"] as? String ?? "",
|
||||||
|
"AppVersion": appPlist["versionName"] as? String ?? "",
|
||||||
|
"AppBuildVer": appPlist["versionCode"] as? String ?? "",
|
||||||
|
"AppId": appPlist["packageId"] as? String ?? "",
|
||||||
|
|
||||||
|
"ApkFeaturesRequiredHidden": featuresRequired.isEmpty ? CLASS_HIDDEN : CLASS_VISIBLE,
|
||||||
|
"ApkFeaturesRequiredList": asList(featuresRequired),
|
||||||
|
"ApkFeaturesOptionalHidden": featuresOptional.isEmpty ? CLASS_HIDDEN : CLASS_VISIBLE,
|
||||||
|
"ApkFeaturesOptionalList": asList(featuresOptional),
|
||||||
|
"ApkPermissionsHidden": permissions.isEmpty ? CLASS_HIDDEN : CLASS_VISIBLE,
|
||||||
|
"ApkPermissionsList": asList(permissions),
|
||||||
|
|
||||||
|
"AppDeviceFamily": "Android",
|
||||||
|
"AppSDK": resolveSDK(appPlist["sdkVerTarget"] as? String),
|
||||||
|
"AppMinOS": resolveSDK(appPlist["sdkVerMin"] as? String),
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let ANDROID_SDK_MAP: [String: String] = [
|
||||||
|
"1": "1.0",
|
||||||
|
"2": "1.1",
|
||||||
|
"3": "1.5",
|
||||||
|
"4": "1.6",
|
||||||
|
"5": "2.0",
|
||||||
|
"6": "2.0.1",
|
||||||
|
"7": "2.1.x",
|
||||||
|
"8": "2.2.x",
|
||||||
|
"9": "2.3, 2.3.1, 2.3.2",
|
||||||
|
"10": "2.3.3, 2.3.4",
|
||||||
|
"11": "3.0.x",
|
||||||
|
"12": "3.1.x",
|
||||||
|
"13": "3.2",
|
||||||
|
"14": "4.0, 4.0.1, 4.0.2",
|
||||||
|
"15": "4.0.3, 4.0.4",
|
||||||
|
"16": "4.1, 4.1.1",
|
||||||
|
"17": "4.2, 4.2.2",
|
||||||
|
"18": "4.3",
|
||||||
|
"19": "4.4",
|
||||||
|
"20": "4.4W",
|
||||||
|
"21": "5.0",
|
||||||
|
"22": "5.1",
|
||||||
|
"23": "6.0",
|
||||||
|
"24": "7.0",
|
||||||
|
"25": "7.1, 7.1.1",
|
||||||
|
"26": "8.0",
|
||||||
|
"27": "8.1",
|
||||||
|
"28": "9",
|
||||||
|
"29": "10",
|
||||||
|
"30": "11",
|
||||||
|
"31": "12",
|
||||||
|
"32": "12",
|
||||||
|
"33": "13",
|
||||||
|
"34": "14",
|
||||||
|
"35": "15",
|
||||||
|
"36": "16",
|
||||||
|
// can we assume new versions will stick to this scheme?
|
||||||
|
"37": "17",
|
||||||
|
"38": "18",
|
||||||
|
"39": "19",
|
||||||
|
"40": "20",
|
||||||
|
]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ extension MetaInfo {
|
|||||||
case .Archive:
|
case .Archive:
|
||||||
// not `readPayloadFile` because plist is in root dir
|
// not `readPayloadFile` because plist is in root dir
|
||||||
return try? Data(contentsOf: self.url.appendingPathComponent("Info.plist", isDirectory: false)).asPlistOrNil()
|
return try? Data(contentsOf: self.url.appendingPathComponent("Info.plist", isDirectory: false)).asPlistOrNil()
|
||||||
case .IPA, .Extension:
|
case .IPA, .Extension, .APK:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ extension PreviewGenerator {
|
|||||||
return Entitlements(forBinary: tmpPath + "/" + bundleExecutable)
|
return Entitlements(forBinary: tmpPath + "/" + bundleExecutable)
|
||||||
case .Archive, .Extension:
|
case .Archive, .Extension:
|
||||||
return Entitlements(forBinary: meta.effectiveUrl("MacOS", bundleExecutable).path)
|
return Entitlements(forBinary: meta.effectiveUrl("MacOS", bundleExecutable).path)
|
||||||
|
case .APK:
|
||||||
|
// not applicable for Android
|
||||||
|
return Entitlements.withoutBinary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ extension MetaInfo {
|
|||||||
case .IPA:
|
case .IPA:
|
||||||
// not `readPayloadFile` because plist is in root dir
|
// not `readPayloadFile` because plist is in root dir
|
||||||
return self.zipFile!.unzipFile("iTunesMetadata.plist")?.asPlistOrNil()
|
return self.zipFile!.unzipFile("iTunesMetadata.plist")?.asPlistOrNil()
|
||||||
case .Archive, .Extension:
|
case .Archive, .Extension, .APK:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,24 +15,38 @@ struct PreviewGenerator {
|
|||||||
"EntitlementsHidden": CLASS_HIDDEN,
|
"EntitlementsHidden": CLASS_HIDDEN,
|
||||||
"EntitlementsWarningHidden": CLASS_HIDDEN,
|
"EntitlementsWarningHidden": CLASS_HIDDEN,
|
||||||
"ProvisionHidden": CLASS_HIDDEN,
|
"ProvisionHidden": CLASS_HIDDEN,
|
||||||
|
"ApkFeaturesRequiredHidden": CLASS_HIDDEN,
|
||||||
|
"ApkFeaturesOptionalHidden": CLASS_HIDDEN,
|
||||||
|
"ApkPermissionsHidden": CLASS_HIDDEN,
|
||||||
]
|
]
|
||||||
let meta: MetaInfo
|
let meta: MetaInfo
|
||||||
|
|
||||||
init(_ meta: MetaInfo) throws {
|
init(_ meta: MetaInfo) throws {
|
||||||
self.meta = meta
|
self.meta = meta
|
||||||
guard let plistApp = meta.readPlistApp() else {
|
guard let plistApp = meta.readPlistApp() else {
|
||||||
throw RuntimeError("Info.plist not found")
|
let isAndroid = meta.type == .APK
|
||||||
|
throw RuntimeError(isAndroid ? "AndroidManifest.xml not found" : "Info.plist not found")
|
||||||
}
|
}
|
||||||
let plistProvision = meta.readPlistProvision()
|
|
||||||
|
|
||||||
data["QuickLookTitle"] = stringForFileType(meta)
|
data["QuickLookTitle"] = stringForFileType(meta)
|
||||||
|
|
||||||
procAppInfo(plistApp, isOSX: meta.isOSX)
|
switch meta.type {
|
||||||
procArchiveInfo(meta.readPlistXCArchive())
|
case .IPA, .Archive, .Extension:
|
||||||
procItunesMeta(meta.readPlistItunes())
|
procAppInfoApple(plistApp, isOSX: meta.isOSX)
|
||||||
procTransportSecurity(plistApp)
|
if meta.type == .IPA {
|
||||||
procEntitlements(meta, plistApp, plistProvision)
|
procItunesMeta(meta.readPlistItunes())
|
||||||
procProvision(plistProvision, isOSX: meta.isOSX)
|
} else if meta.type == .Archive {
|
||||||
|
procArchiveInfo(meta.readPlistXCArchive())
|
||||||
|
}
|
||||||
|
procTransportSecurity(plistApp)
|
||||||
|
|
||||||
|
let plistProvision = meta.readPlistProvision()
|
||||||
|
procEntitlements(meta, plistApp, plistProvision)
|
||||||
|
procProvision(plistProvision, isOSX: meta.isOSX)
|
||||||
|
|
||||||
|
case .APK:
|
||||||
|
procAppInfoAndroid(plistApp)
|
||||||
|
}
|
||||||
procFileInfo(meta.url)
|
procFileInfo(meta.url)
|
||||||
procFooterInfo()
|
procFooterInfo()
|
||||||
// App Icon (last, because the image uses a lot of memory)
|
// App Icon (last, because the image uses a lot of memory)
|
||||||
@@ -46,7 +60,7 @@ struct PreviewGenerator {
|
|||||||
/// Title of the preview window
|
/// Title of the preview window
|
||||||
private func stringForFileType(_ meta: MetaInfo) -> String {
|
private func stringForFileType(_ meta: MetaInfo) -> String {
|
||||||
switch meta.type {
|
switch meta.type {
|
||||||
case .IPA: return "App info"
|
case .IPA, .APK: return "App info"
|
||||||
case .Archive: return "Archive info"
|
case .Archive: return "Archive info"
|
||||||
case .Extension: return "App extension info"
|
case .Extension: return "App extension info"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user