app-info 3.0.0.beta1 → 3.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +4 -4
- data/lib/app_info/aab.rb +57 -114
- data/lib/app_info/android/signature.rb +5 -5
- data/lib/app_info/android/signatures/base.rb +41 -37
- data/lib/app_info/android/signatures/info.rb +135 -129
- data/lib/app_info/android/signatures/v1.rb +56 -52
- data/lib/app_info/android/signatures/v2.rb +114 -110
- data/lib/app_info/android/signatures/v3.rb +124 -120
- data/lib/app_info/android/signatures/v4.rb +13 -9
- data/lib/app_info/android.rb +181 -0
- data/lib/app_info/apk.rb +68 -100
- data/lib/app_info/apple.rb +192 -0
- data/lib/app_info/certificate.rb +14 -19
- data/lib/app_info/const.rb +48 -13
- data/lib/app_info/dsym.rb +6 -3
- data/lib/app_info/error.rb +1 -7
- data/lib/app_info/file.rb +30 -4
- data/lib/app_info/helper/file_size.rb +1 -1
- data/lib/app_info/info_plist.rb +54 -25
- data/lib/app_info/ipa.rb +39 -118
- data/lib/app_info/macos.rb +38 -94
- data/lib/app_info/mobile_provision.rb +49 -20
- data/lib/app_info/pe.rb +103 -7
- data/lib/app_info/png_uncrush.rb +19 -0
- data/lib/app_info/proguard.rb +21 -2
- data/lib/app_info/protobuf/manifest.rb +5 -1
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +4 -3
- metadata +4 -2
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'macho'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'cfpropertylist'
|
7
|
+
|
8
|
+
module AppInfo
|
9
|
+
# Apple base parser for ipa and macos file
|
10
|
+
class Apple < File
|
11
|
+
extend Forwardable
|
12
|
+
include Helper::HumanFileSize
|
13
|
+
include Helper::Archive
|
14
|
+
|
15
|
+
# Export types
|
16
|
+
module ExportType
|
17
|
+
# debug configuration
|
18
|
+
DEBUG = :debug
|
19
|
+
# adhoc configuration (iOS only)
|
20
|
+
ADHOC = :adhoc
|
21
|
+
# enterprise configuration (iOS only)
|
22
|
+
ENTERPRISE = :enterprise
|
23
|
+
# release configuration
|
24
|
+
RELEASE = :release
|
25
|
+
# release configuration but download from App Store
|
26
|
+
APPSTORE = :appstore
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Symbol] {Manufacturer}
|
30
|
+
def manufacturer
|
31
|
+
Manufacturer::APPLE
|
32
|
+
end
|
33
|
+
|
34
|
+
# return file size
|
35
|
+
# @example Read file size in integer
|
36
|
+
# aab.size # => 3618865
|
37
|
+
#
|
38
|
+
# @example Read file size in human readabale
|
39
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
40
|
+
#
|
41
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
42
|
+
# @return [Integer, String]
|
43
|
+
def size(human_size: false)
|
44
|
+
file_to_human_size(@file, human_size: human_size)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!method device
|
48
|
+
# @see InfoPlist#device
|
49
|
+
# @!method platform
|
50
|
+
# @see InfoPlist#platform
|
51
|
+
# @!method iphone?
|
52
|
+
# @see InfoPlist#iphone?
|
53
|
+
# @!method ipad?
|
54
|
+
# @see InfoPlist#ipad?
|
55
|
+
# @!method universal?
|
56
|
+
# @see InfoPlist#universal?
|
57
|
+
# @!method build_version
|
58
|
+
# @see InfoPlist#build_version
|
59
|
+
# @!method name
|
60
|
+
# @see InfoPlist#name
|
61
|
+
# @!method release_version
|
62
|
+
# @see InfoPlist#release_version
|
63
|
+
# @!method identifier
|
64
|
+
# @see InfoPlist#identifier
|
65
|
+
# @!method bundle_id
|
66
|
+
# @see InfoPlist#bundle_id
|
67
|
+
# @!method display_name
|
68
|
+
# @see InfoPlist#display_name
|
69
|
+
# @!method bundle_name
|
70
|
+
# @see InfoPlist#bundle_name
|
71
|
+
# @!method min_sdk_version
|
72
|
+
# @see InfoPlist#min_sdk_version
|
73
|
+
# @!method min_os_version
|
74
|
+
# @see InfoPlist#min_os_version
|
75
|
+
def_delegators :info, :device, :platform, :iphone?, :ipad?, :universal?, :macos?,
|
76
|
+
:build_version, :name, :release_version, :identifier, :bundle_id,
|
77
|
+
:display_name, :bundle_name, :min_sdk_version, :min_os_version
|
78
|
+
|
79
|
+
# @!method devices
|
80
|
+
# @see MobileProvision#devices
|
81
|
+
# @!method team_name
|
82
|
+
# @see MobileProvision#team_name
|
83
|
+
# @!method team_identifier
|
84
|
+
# @see MobileProvision#team_identifier
|
85
|
+
# @!method profile_name
|
86
|
+
# @see MobileProvision#profile_name
|
87
|
+
# @!method expired_date
|
88
|
+
# @see MobileProvision#expired_date
|
89
|
+
def_delegators :mobileprovision, :devices, :team_name, :team_identifier,
|
90
|
+
:profile_name, :expired_date
|
91
|
+
|
92
|
+
# @return [String, nil]
|
93
|
+
def distribution_name
|
94
|
+
"#{profile_name} - #{team_name}" if profile_name && team_name
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [String]
|
98
|
+
def release_type
|
99
|
+
if stored?
|
100
|
+
ExportType::APPSTORE
|
101
|
+
else
|
102
|
+
build_type
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# return iOS build configuration, not correct in macOS app.
|
107
|
+
# @return [String]
|
108
|
+
def build_type
|
109
|
+
if mobileprovision?
|
110
|
+
return ExportType::RELEASE if macos?
|
111
|
+
|
112
|
+
devices ? ExportType::ADHOC : ExportType::ENTERPRISE
|
113
|
+
else
|
114
|
+
ExportType::DEBUG
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Array<MachO>, nil]
|
119
|
+
def archs
|
120
|
+
return unless ::File.exist?(binary_path)
|
121
|
+
|
122
|
+
file = MachO.open(binary_path)
|
123
|
+
case file
|
124
|
+
when MachO::MachOFile
|
125
|
+
[file.cpusubtype]
|
126
|
+
else
|
127
|
+
file.machos.each_with_object([]) do |arch, obj|
|
128
|
+
obj << arch.cpusubtype
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
alias architectures archs
|
133
|
+
|
134
|
+
# @abstract Subclass and override {#icons} to implement.
|
135
|
+
def icons(_uncrush: true)
|
136
|
+
not_implemented_error!(__method__)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @abstract Subclass and override {#stored?} to implement.
|
140
|
+
def stored?
|
141
|
+
not_implemented_error!(__method__)
|
142
|
+
end
|
143
|
+
|
144
|
+
# force remove developer certificate data from {#mobileprovision} method
|
145
|
+
# @return [nil]
|
146
|
+
def hide_developer_certificates
|
147
|
+
mobileprovision.delete('DeveloperCertificates') if mobileprovision?
|
148
|
+
end
|
149
|
+
|
150
|
+
# @return [MobileProvision]
|
151
|
+
def mobileprovision
|
152
|
+
return unless mobileprovision?
|
153
|
+
|
154
|
+
@mobileprovision ||= MobileProvision.new(mobileprovision_path)
|
155
|
+
end
|
156
|
+
|
157
|
+
# @return [Boolean]
|
158
|
+
def mobileprovision?
|
159
|
+
::File.exist?(mobileprovision_path)
|
160
|
+
end
|
161
|
+
|
162
|
+
# @abstract Subclass and override {#mobileprovision_path} to implement.
|
163
|
+
def mobileprovision_path
|
164
|
+
not_implemented_error!(__method__)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @return [InfoPlist]
|
168
|
+
def info
|
169
|
+
@info ||= InfoPlist.new(info_path)
|
170
|
+
end
|
171
|
+
|
172
|
+
# @abstract Subclass and override {#info_path} to implement.
|
173
|
+
def info_path
|
174
|
+
not_implemented_error!(__method__)
|
175
|
+
end
|
176
|
+
|
177
|
+
# @abstract Subclass and override {#app_path} to implement.
|
178
|
+
def app_path
|
179
|
+
not_implemented_error!(__method__)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @abstract Subclass and override {#clear!} to implement.
|
183
|
+
def clear!
|
184
|
+
not_implemented_error!(__method__)
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [String] contents path of contents
|
188
|
+
def contents
|
189
|
+
@contents ||= unarchive(@file, prefix: format.to_s)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
data/lib/app_info/certificate.rb
CHANGED
@@ -6,6 +6,7 @@ module AppInfo
|
|
6
6
|
class Certificate
|
7
7
|
# Parse Raw data into X509 cerificate wrapper
|
8
8
|
# @param [String] certificate raw data
|
9
|
+
# @return [AppInfo::Certificate]
|
9
10
|
def self.parse(data)
|
10
11
|
cert = OpenSSL::X509::Certificate.new(data)
|
11
12
|
new(cert)
|
@@ -54,18 +55,22 @@ module AppInfo
|
|
54
55
|
convert_cert_name(raw.subject, format: format)
|
55
56
|
end
|
56
57
|
|
58
|
+
# @return [Time]
|
57
59
|
def created_at
|
58
60
|
raw.not_before
|
59
61
|
end
|
60
62
|
|
63
|
+
# @return [Time]
|
61
64
|
def expired_at
|
62
65
|
raw.not_after
|
63
66
|
end
|
64
67
|
|
68
|
+
# @return [Boolean]
|
65
69
|
def expired?
|
66
70
|
expired_at < Time.now.utc
|
67
71
|
end
|
68
72
|
|
73
|
+
# @return [String] format always be :x509.
|
69
74
|
def format
|
70
75
|
:x509
|
71
76
|
end
|
@@ -113,36 +118,25 @@ module AppInfo
|
|
113
118
|
end
|
114
119
|
end
|
115
120
|
|
116
|
-
# return
|
117
|
-
|
121
|
+
# return length of public key
|
122
|
+
# @return [Integer]
|
123
|
+
# @raise NotImplementedError
|
124
|
+
def length
|
118
125
|
case public_key
|
119
126
|
when OpenSSL::PKey::RSA
|
120
127
|
public_key.n.num_bits
|
121
128
|
when OpenSSL::PKey::DSA, OpenSSL::PKey::DH
|
122
129
|
public_key.p.num_bits
|
123
130
|
when OpenSSL::PKey::EC
|
124
|
-
raise NotImplementedError, "key
|
131
|
+
raise NotImplementedError, "key length for #{public_key.inspect} not implemented"
|
125
132
|
end
|
126
133
|
end
|
134
|
+
alias size length
|
127
135
|
|
128
136
|
# return fingerprint of certificate
|
137
|
+
# @return [String]
|
129
138
|
def fingerprint(name = :sha256, transform: :lower, delimiter: nil)
|
130
139
|
digest = OpenSSL::Digest.new(name.to_s.upcase)
|
131
|
-
# digest = case name.to_sym
|
132
|
-
# when :sha1
|
133
|
-
# OpenSSL::Digest::SHA1.new
|
134
|
-
# when :sha224
|
135
|
-
# OpenSSL::Digest::SHA224.new
|
136
|
-
# when :sha384
|
137
|
-
# OpenSSL::Digest::SHA384.new
|
138
|
-
# when :sha512
|
139
|
-
# OpenSSL::Digest::SHA512.new
|
140
|
-
# when :md5
|
141
|
-
# OpenSSL::Digest::MD5.new
|
142
|
-
# else
|
143
|
-
# OpenSSL::Digest::SHA256.new
|
144
|
-
# end
|
145
|
-
|
146
140
|
digest.update(raw.to_der)
|
147
141
|
fingerprint = digest.to_s
|
148
142
|
fingerprint = fingerprint.upcase if transform.to_sym == :upper
|
@@ -152,6 +146,7 @@ module AppInfo
|
|
152
146
|
end
|
153
147
|
|
154
148
|
# Orginal OpenSSL X509 certificate
|
149
|
+
# @return [OpenSSL::X509::Certificate]
|
155
150
|
def raw
|
156
151
|
@cert
|
157
152
|
end
|
@@ -175,7 +170,7 @@ module AppInfo
|
|
175
170
|
end
|
176
171
|
|
177
172
|
def respond_to_missing?(method, *args)
|
178
|
-
@cert.
|
173
|
+
@cert.respond_to?(method.to_sym) || super
|
179
174
|
end
|
180
175
|
end
|
181
176
|
end
|
data/lib/app_info/const.rb
CHANGED
@@ -3,39 +3,74 @@
|
|
3
3
|
module AppInfo
|
4
4
|
# Full Format
|
5
5
|
module Format
|
6
|
-
#
|
7
|
-
|
6
|
+
# Apple
|
7
|
+
|
8
8
|
INFOPLIST = :infoplist
|
9
9
|
MOBILEPROVISION = :mobileprovision
|
10
10
|
DSYM = :dsym
|
11
11
|
|
12
|
+
# macOS
|
13
|
+
|
14
|
+
MACOS = :macos
|
15
|
+
|
16
|
+
# iOS
|
17
|
+
|
18
|
+
IPA = :ipa
|
19
|
+
|
12
20
|
# Android
|
21
|
+
|
13
22
|
APK = :apk
|
14
23
|
AAB = :aab
|
15
24
|
PROGUARD = :proguard
|
16
25
|
|
17
|
-
# macOS
|
18
|
-
MACOS = :macos
|
19
|
-
|
20
26
|
# Windows
|
27
|
+
|
21
28
|
PE = :pe
|
22
29
|
|
23
30
|
UNKNOWN = :unknown
|
24
31
|
end
|
25
32
|
|
33
|
+
# Manufacturer
|
34
|
+
module Manufacturer
|
35
|
+
APPLE = :apple
|
36
|
+
GOOGLE = :google
|
37
|
+
MICROSOFT = :microsoft
|
38
|
+
end
|
39
|
+
|
26
40
|
# Platform
|
27
41
|
module Platform
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
42
|
+
MACOS = :macos
|
43
|
+
IOS = :ios
|
44
|
+
ANDROID = :android
|
45
|
+
WINDOWS = :windows
|
32
46
|
end
|
33
47
|
|
34
48
|
# Apple Device Type
|
35
49
|
module Device
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
50
|
+
# macOS
|
51
|
+
MACOS = :macos
|
52
|
+
|
53
|
+
# Apple iPhone
|
54
|
+
IPHONE = :iphone
|
55
|
+
# Apple iPad
|
56
|
+
IPAD = :ipad
|
57
|
+
# Apple Watch
|
58
|
+
IWATCH = :iwatch # not implemented yet
|
59
|
+
# Apple Universal (iPhone and iPad)
|
60
|
+
UNIVERSAL = :universal
|
61
|
+
|
62
|
+
# Android Phone
|
63
|
+
PHONE = :phone
|
64
|
+
# Android Tablet (not implemented yet)
|
65
|
+
TABLET = :tablet
|
66
|
+
# Android Watch
|
67
|
+
WATCH = :watch
|
68
|
+
# Android TV
|
69
|
+
TELEVISION = :television
|
70
|
+
# Android Car Automotive
|
71
|
+
AUTOMOTIVE = :automotive
|
72
|
+
|
73
|
+
# Windows
|
74
|
+
WINDOWS = :windows
|
40
75
|
end
|
41
76
|
end
|
data/lib/app_info/dsym.rb
CHANGED
@@ -7,16 +7,18 @@ module AppInfo
|
|
7
7
|
class DSYM < File
|
8
8
|
include Helper::Archive
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
# @return [Symbol] {Manufacturer}
|
11
|
+
def manufacturer
|
12
|
+
Manufacturer::APPLE
|
12
13
|
end
|
13
14
|
|
14
15
|
def each_file(&block)
|
15
16
|
files.each { |file| block.call(file) }
|
16
17
|
end
|
17
18
|
|
19
|
+
# @return [Array<DebugInfo>] dsym_files files by alphabetical order
|
18
20
|
def files
|
19
|
-
@files ||= Dir.children(contents).each_with_object([]) do |file, obj|
|
21
|
+
@files ||= Dir.children(contents).sort.each_with_object([]) do |file, obj|
|
20
22
|
obj << DebugInfo.new(::File.join(contents, file))
|
21
23
|
end
|
22
24
|
end
|
@@ -30,6 +32,7 @@ module AppInfo
|
|
30
32
|
@files = nil
|
31
33
|
end
|
32
34
|
|
35
|
+
# @return [String] contents path of dsym
|
33
36
|
def contents
|
34
37
|
@contents ||= lambda {
|
35
38
|
return @file if ::File.directory?(@file)
|
data/lib/app_info/error.rb
CHANGED
@@ -9,13 +9,7 @@ module AppInfo
|
|
9
9
|
|
10
10
|
class NotFoundError < Error; end
|
11
11
|
|
12
|
-
class NotFoundWinBinraryError < NotFoundError; end
|
13
|
-
|
14
12
|
class ProtobufParseError < Error; end
|
15
13
|
|
16
|
-
class
|
17
|
-
|
18
|
-
# @deprecated Correct to the new {UnknownFileTypeError} class because typo.
|
19
|
-
# It will remove since 2.7.0.
|
20
|
-
class UnkownFileTypeError < Error; end
|
14
|
+
class UnknownFormatError < Error; end
|
21
15
|
end
|
data/lib/app_info/file.rb
CHANGED
@@ -10,14 +10,40 @@ module AppInfo
|
|
10
10
|
@logger = logger
|
11
11
|
end
|
12
12
|
|
13
|
-
# @
|
14
|
-
def
|
15
|
-
|
13
|
+
# @return [Symbol] {Format}
|
14
|
+
def format
|
15
|
+
@format ||= lambda {
|
16
|
+
if instance_of?(AppInfo::File) || instance_of?(AppInfo::Apple) ||
|
17
|
+
instance_of?(AppInfo::Android)
|
18
|
+
not_implemented_error!(__method__)
|
19
|
+
end
|
20
|
+
|
21
|
+
self.class.name.split('::')[-1].downcase.to_sym
|
22
|
+
}.call
|
23
|
+
end
|
24
|
+
|
25
|
+
# @abstract Subclass and override {#platform} to implement.
|
26
|
+
def platform
|
27
|
+
not_implemented_error!(__method__)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @abstract Subclass and override {#manufacturer} to implement.
|
31
|
+
def manufacturer
|
32
|
+
not_implemented_error!(__method__)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @abstract Subclass and override {#device} to implement.
|
36
|
+
def device
|
37
|
+
not_implemented_error!(__method__)
|
16
38
|
end
|
17
39
|
|
18
40
|
# @abstract Subclass and override {#size} to implement
|
19
41
|
def size(human_size: false)
|
20
|
-
|
42
|
+
not_implemented_error!(__method__)
|
43
|
+
end
|
44
|
+
|
45
|
+
def not_implemented_error!(method)
|
46
|
+
raise NotImplementedError, ".#{method} method implantation required in #{self.class}"
|
21
47
|
end
|
22
48
|
end
|
23
49
|
end
|
@@ -16,7 +16,7 @@ module AppInfo::Helper
|
|
16
16
|
max_exp = FILE_SIZE_UNITS.size - 1
|
17
17
|
exponent = (Math.log(number) / Math.log(1024)).to_i
|
18
18
|
exponent = max_exp if exponent > max_exp
|
19
|
-
number = format('%<number>.2f', number: (number / (1024**exponent.to_f)))
|
19
|
+
number = Kernel.format('%<number>.2f', number: (number / (1024**exponent.to_f)))
|
20
20
|
end
|
21
21
|
|
22
22
|
"#{number} #{FILE_SIZE_UNITS[exponent]}"
|
data/lib/app_info/info_plist.rb
CHANGED
@@ -17,94 +17,120 @@ module AppInfo
|
|
17
17
|
Device::MACOS => %w[CFBundleIconFile CFBundleIconName]
|
18
18
|
}.freeze
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
# @return [Symbol] {Manufacturer}
|
21
|
+
def manufacturer
|
22
|
+
Manufacturer::APPLE
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Symbol] {Platform}
|
26
|
+
def platform
|
27
|
+
case device
|
28
|
+
when Device::MACOS
|
29
|
+
Platform::MACOS
|
30
|
+
when Device::IPHONE, Device::IPAD, Device::UNIVERSAL
|
31
|
+
Platform::IOS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Symbol] {Device}
|
36
|
+
def device
|
37
|
+
if device_family == [1]
|
38
|
+
Device::IPHONE
|
39
|
+
elsif device_family == [2]
|
40
|
+
Device::IPAD
|
41
|
+
elsif device_family == [1, 2]
|
42
|
+
Device::UNIVERSAL
|
43
|
+
elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTManufacturerName').nil?
|
44
|
+
Device::MACOS
|
45
|
+
else
|
46
|
+
raise NotImplementedError, "Unkonwn device: #{device_family}"
|
47
|
+
end
|
22
48
|
end
|
23
49
|
|
50
|
+
# @return [String, nil]
|
24
51
|
def version
|
25
52
|
release_version || build_version
|
26
53
|
end
|
27
54
|
|
55
|
+
# @return [String, nil]
|
28
56
|
def build_version
|
29
57
|
info.try(:[], 'CFBundleVersion')
|
30
58
|
end
|
31
59
|
|
60
|
+
# @return [String, nil]
|
32
61
|
def release_version
|
33
62
|
info.try(:[], 'CFBundleShortVersionString')
|
34
63
|
end
|
35
64
|
|
65
|
+
# @return [String, nil]
|
36
66
|
def identifier
|
37
67
|
info.try(:[], 'CFBundleIdentifier')
|
38
68
|
end
|
39
69
|
alias bundle_id identifier
|
40
70
|
|
71
|
+
# @return [String, nil]
|
41
72
|
def name
|
42
73
|
display_name || bundle_name
|
43
74
|
end
|
44
75
|
|
76
|
+
# @return [String, nil]
|
45
77
|
def display_name
|
46
78
|
info.try(:[], 'CFBundleDisplayName')
|
47
79
|
end
|
48
80
|
|
81
|
+
# @return [String, nil]
|
49
82
|
def bundle_name
|
50
83
|
info.try(:[], 'CFBundleName')
|
51
84
|
end
|
52
85
|
|
86
|
+
# @return [String, nil]
|
53
87
|
def min_os_version
|
54
88
|
min_sdk_version || min_system_version
|
55
89
|
end
|
56
90
|
|
57
|
-
#
|
58
91
|
# Extract the Minimum OS Version from the Info.plist (iOS Only)
|
59
|
-
#
|
92
|
+
# @return [String, nil]
|
60
93
|
def min_sdk_version
|
61
94
|
info.try(:[], 'MinimumOSVersion')
|
62
95
|
end
|
63
96
|
|
64
|
-
#
|
65
97
|
# Extract the Minimum OS Version from the Info.plist (macOS Only)
|
66
|
-
#
|
98
|
+
# @return [String, nil]
|
67
99
|
def min_system_version
|
68
100
|
info.try(:[], 'LSMinimumSystemVersion')
|
69
101
|
end
|
70
102
|
|
103
|
+
# @return [Array<String>]
|
71
104
|
def icons
|
72
|
-
@icons ||= ICON_KEYS[
|
73
|
-
end
|
74
|
-
|
75
|
-
def device_type
|
76
|
-
device_family = info.try(:[], 'UIDeviceFamily')
|
77
|
-
if device_family == [1]
|
78
|
-
Device::IPHONE
|
79
|
-
elsif device_family == [2]
|
80
|
-
Device::IPAD
|
81
|
-
elsif device_family == [1, 2]
|
82
|
-
Device::UNIVERSAL
|
83
|
-
elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTPlatformName').nil?
|
84
|
-
Device::MACOS
|
85
|
-
end
|
105
|
+
@icons ||= ICON_KEYS[device]
|
86
106
|
end
|
87
107
|
|
108
|
+
# @return [Boolean]
|
88
109
|
def iphone?
|
89
|
-
|
110
|
+
device == Device::IPHONE
|
90
111
|
end
|
91
112
|
|
113
|
+
# @return [Boolean]
|
92
114
|
def ipad?
|
93
|
-
|
115
|
+
device == Device::IPAD
|
94
116
|
end
|
95
117
|
|
118
|
+
# @return [Boolean]
|
96
119
|
def universal?
|
97
|
-
|
120
|
+
device == Device::UNIVERSAL
|
98
121
|
end
|
99
122
|
|
123
|
+
# @return [Boolean]
|
100
124
|
def macos?
|
101
|
-
|
125
|
+
device == Device::MACOS
|
102
126
|
end
|
103
127
|
|
128
|
+
# @return [Array<String>]
|
104
129
|
def device_family
|
105
130
|
info.try(:[], 'UIDeviceFamily') || []
|
106
131
|
end
|
107
132
|
|
133
|
+
# @return [String]
|
108
134
|
def release_type
|
109
135
|
if stored?
|
110
136
|
'Store'
|
@@ -113,10 +139,13 @@ module AppInfo
|
|
113
139
|
end
|
114
140
|
end
|
115
141
|
|
142
|
+
# @return [String, nil]
|
116
143
|
def [](key)
|
117
144
|
info.try(:[], key.to_s)
|
118
145
|
end
|
119
146
|
|
147
|
+
# @!method to_h
|
148
|
+
# @see CFPropertyList#to_h
|
120
149
|
def_delegators :info, :to_h
|
121
150
|
|
122
151
|
def method_missing(method_name, *args, &block)
|
@@ -140,7 +169,7 @@ module AppInfo
|
|
140
169
|
end
|
141
170
|
|
142
171
|
def app_path
|
143
|
-
@app_path ||= case
|
172
|
+
@app_path ||= case device
|
144
173
|
when Device::MACOS
|
145
174
|
::File.dirname(@file)
|
146
175
|
else
|