app-info 2.6.4 → 2.7.0.beta5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +19 -2
- data/CHANGELOG.md +30 -1
- data/README.md +9 -6
- data/app_info.gemspec +2 -1
- data/lib/app_info/aab.rb +218 -0
- data/lib/app_info/apk.rb +3 -2
- data/lib/app_info/core_ext/string/inflector.rb +35 -0
- data/lib/app_info/core_ext.rb +4 -0
- data/lib/app_info/dsym.rb +7 -3
- data/lib/app_info/error.rb +13 -0
- data/lib/app_info/helper.rb +130 -0
- data/lib/app_info/info_plist.rb +6 -6
- data/lib/app_info/ipa.rb +6 -4
- data/lib/app_info/macos.rb +6 -4
- data/lib/app_info/mobile_provision.rb +2 -2
- data/lib/app_info/png_uncrush.rb +1 -1
- data/lib/app_info/proguard.rb +4 -2
- data/lib/app_info/protobuf/manifest.rb +147 -0
- data/lib/app_info/protobuf/models/Configuration.proto +206 -0
- data/lib/app_info/protobuf/models/Configuration_pb.rb +139 -0
- data/lib/app_info/protobuf/models/README.md +19 -0
- data/lib/app_info/protobuf/models/Resources.proto +588 -0
- data/lib/app_info/protobuf/models/Resources_pb.rb +344 -0
- data/lib/app_info/protobuf/resources.rb +229 -0
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +69 -56
- metadata +33 -8
- data/lib/app_info/util.rb +0 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87af7ee3238f0c866c3d4ad7110bba74ae1950e28f1619049ec52470253bf45a
|
4
|
+
data.tar.gz: 8eeaaa20c0f5e4036e2c2138f51f42554d0e5b73dc340c9a71ea9ce344d0c55a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c61cf34da0123d9cd6e0df63f3f342d61858c3dfc9dfd8e57cfce7a18af9bae5fbc85b8a20e79a9e8b54a630b240105ce036b0f03c49fd96df8945fa472ae148
|
7
|
+
data.tar.gz: ae7f4064095ad2c1518095876532c7c6364a785209fefb9e8fbf1fa7c3fe02fe3027df20572db55b5fb72d8cdadd70e59738ec0458a3fd391c264b168f744ff5
|
data/.rubocop.yml
CHANGED
@@ -20,10 +20,12 @@ AllCops:
|
|
20
20
|
Exclude:
|
21
21
|
- 'bin/*'
|
22
22
|
- 'spec/**/*'
|
23
|
-
- vendor/bundle/**/*
|
23
|
+
- 'vendor/bundle/**/*'
|
24
24
|
- 'Rakefile'
|
25
25
|
- 'app_info.gemspec'
|
26
26
|
- 'lib/app-info.rb'
|
27
|
+
- 'lib/app_info/protobuf/models/*_pb.rb'
|
28
|
+
- 'main.rb'
|
27
29
|
|
28
30
|
Metrics/AbcSize:
|
29
31
|
Max: 100
|
@@ -56,4 +58,19 @@ Lint/AssignmentInCondition:
|
|
56
58
|
Enabled: false
|
57
59
|
|
58
60
|
Style/Documentation:
|
59
|
-
Enabled: false
|
61
|
+
Enabled: false
|
62
|
+
|
63
|
+
Style/PerlBackrefs:
|
64
|
+
Exclude:
|
65
|
+
- 'lib/app_info/core_ext/string/inflector.rb'
|
66
|
+
|
67
|
+
Style/DocumentDynamicEvalDefinition:
|
68
|
+
Enabled: false
|
69
|
+
|
70
|
+
Metrics/BlockNesting:
|
71
|
+
Exclude:
|
72
|
+
- 'lib/app_info/dsym.rb'
|
73
|
+
|
74
|
+
|
75
|
+
Style/SlicingWithRange:
|
76
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -9,6 +9,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
9
9
|
|
10
10
|
> List all changes before release a new version.
|
11
11
|
|
12
|
+
## [2.7.0.beta5] (2021-10-14)
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
|
16
|
+
- Renamed methods of inflector (Conflicts with similar external methods, such like ActiveSupport Core Extensions)
|
17
|
+
- Keep same behavior methods between apk and aab
|
18
|
+
|
19
|
+
## [2.7.0.beta2] (2021-09-29)
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
|
23
|
+
- Fix allocator undefined data class [#38](https://github.com/icyleaf/app_info/pull/38)
|
24
|
+
|
25
|
+
## [2.7.0.beta1] (2021-09-27)
|
26
|
+
|
27
|
+
### Added
|
28
|
+
|
29
|
+
- Android App Bundle a.k.a `aab` file parts support [#36](https://github.com/icyleaf/app_info/pull/36)
|
30
|
+
|
31
|
+
## [2.6.5] (2021-09-17)
|
32
|
+
|
33
|
+
### Added
|
34
|
+
|
35
|
+
- Add ability to retrieve manifest metadata (depend on playtestcloud/ruby_apk forked one)
|
36
|
+
|
12
37
|
## [2.6.4] (2021-09-10)
|
13
38
|
|
14
39
|
### Fixed
|
@@ -179,7 +204,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
179
204
|
|
180
205
|
- Updated dependency of CFPropertly list be a range between 2.3.4. (thanks @[cschroed](https://github.com/cschroed))
|
181
206
|
|
182
|
-
[Unreleased]: https://github.com/icyleaf/app-info/compare/v2.
|
207
|
+
[Unreleased]: https://github.com/icyleaf/app-info/compare/v2.7.0.beta5..HEAD
|
208
|
+
[2.7.0.beta5]: https://github.com/icyleaf/app-info/compare/v2.7.0.beta2...v2.7.0.beta5
|
209
|
+
[2.7.0.beta2]: https://github.com/icyleaf/app-info/compare/v2.7.0.beta1...v2.7.0.beta2
|
210
|
+
[2.7.0.beta1]: https://github.com/icyleaf/app-info/compare/v2.6.5...v2.7.0.beta1
|
211
|
+
[2.6.5]: https://github.com/icyleaf/app-info/compare/v2.6.4...v2.6.5
|
183
212
|
[2.6.4]: https://github.com/icyleaf/app-info/compare/v2.6.3...v2.6.4
|
184
213
|
[2.6.3]: https://github.com/icyleaf/app-info/compare/v2.6.1...v2.6.3
|
185
214
|
[2.6.1]: https://github.com/icyleaf/app-info/compare/v2.6.0...v2.6.1
|
data/README.md
CHANGED
@@ -5,16 +5,18 @@
|
|
5
5
|
[](https://rubygems.org/gems/app_info)
|
6
6
|
[](LICENSE)
|
7
7
|
|
8
|
-
Teardown tool for mobile(ipa
|
8
|
+
Teardown tool for mobile app (ipa, apk and aab file), macOS app and dSYM.zip file, analysis metedata like version, name, icon etc.
|
9
9
|
|
10
10
|
## Support
|
11
11
|
|
12
|
-
- Android
|
12
|
+
- Android file
|
13
|
+
- `.apk`
|
14
|
+
- `.aab` (Androld App Bundle)
|
13
15
|
- iOS ipa file
|
14
|
-
- Info.plist file
|
15
|
-
-
|
16
|
-
- macOS App
|
17
|
-
-
|
16
|
+
- `Info.plist` file
|
17
|
+
- `.mobileprovision`/`.provisionprofile` file
|
18
|
+
- Zipped macOS App file
|
19
|
+
- Zipped dSYMs file
|
18
20
|
|
19
21
|
## Installation
|
20
22
|
|
@@ -54,6 +56,7 @@ parser = AppInfo.parse('App.dSYm.zip')
|
|
54
56
|
# If detect file type failed, you can parse in other way
|
55
57
|
parser = AppInfo::IPA.new('iphone.ipa')
|
56
58
|
parser = AppInfo::APK.new('android.apk')
|
59
|
+
parser = AppInfo::AAB.new('android.aab')
|
57
60
|
parser = AppInfo::InfoPlist.new('Info.plist')
|
58
61
|
parser = AppInfo::MobileProvision.new('uuid.mobileprovision')
|
59
62
|
parser = AppInfo::Macos.new('App.dSYm.zip')
|
data/app_info.gemspec
CHANGED
@@ -23,10 +23,11 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_dependency 'CFPropertyList', '< 3.1.0', '>= 2.3.4'
|
24
24
|
spec.add_dependency 'image_size', '>= 1.5', '< 2.2'
|
25
25
|
spec.add_dependency 'ruby-macho', '< 3', '>= 1.4'
|
26
|
-
spec.add_dependency '
|
26
|
+
spec.add_dependency 'android_parser', '~> 2.4.1'
|
27
27
|
spec.add_dependency 'rubyzip', '>= 1.2', '< 3.0'
|
28
28
|
spec.add_dependency 'uuidtools', '>= 2.1.5', '< 2.3.0'
|
29
29
|
spec.add_dependency 'icns', '~> 0.2.0'
|
30
|
+
spec.add_dependency 'google-protobuf', '~> 3.18.0'
|
30
31
|
|
31
32
|
spec.add_development_dependency 'bundler', '>= 1.12'
|
32
33
|
spec.add_development_dependency 'rake', '>= 10.0'
|
data/lib/app_info/aab.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_info/protobuf/manifest'
|
4
|
+
require 'image_size'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
module AppInfo
|
8
|
+
# Parse APK file
|
9
|
+
class AAB
|
10
|
+
include Helper::HumanFileSize
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_reader :file
|
14
|
+
|
15
|
+
# APK Devices
|
16
|
+
module Device
|
17
|
+
PHONE = 'Phone'
|
18
|
+
TABLET = 'Tablet'
|
19
|
+
WATCH = 'Watch'
|
20
|
+
TV = 'Television'
|
21
|
+
end
|
22
|
+
|
23
|
+
BASE_PATH = 'base'
|
24
|
+
BASE_MANIFEST = "#{BASE_PATH}/manifest/AndroidManifest.xml"
|
25
|
+
BASE_RESOURCES = "#{BASE_PATH}/resources.pb"
|
26
|
+
|
27
|
+
def initialize(file)
|
28
|
+
@file = file
|
29
|
+
end
|
30
|
+
|
31
|
+
def size(human_size: false)
|
32
|
+
file_to_human_size(@file, human_size: human_size)
|
33
|
+
end
|
34
|
+
|
35
|
+
def os
|
36
|
+
Platform::ANDROID
|
37
|
+
end
|
38
|
+
alias file_type os
|
39
|
+
|
40
|
+
def_delegators :manifest, :version_name
|
41
|
+
|
42
|
+
alias release_version version_name
|
43
|
+
|
44
|
+
def package_name
|
45
|
+
manifest.package
|
46
|
+
end
|
47
|
+
alias identifier package_name
|
48
|
+
alias bundle_id package_name
|
49
|
+
|
50
|
+
def version_code
|
51
|
+
manifest.version_code.to_s
|
52
|
+
end
|
53
|
+
alias build_version version_code
|
54
|
+
|
55
|
+
def name
|
56
|
+
manifest.label
|
57
|
+
end
|
58
|
+
|
59
|
+
def device_type
|
60
|
+
if wear?
|
61
|
+
Device::WATCH
|
62
|
+
elsif tv?
|
63
|
+
Device::TV
|
64
|
+
else
|
65
|
+
Device::PHONE
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# TODO: find a way to detect
|
70
|
+
# Found answer but not works: https://stackoverflow.com/questions/9279111/determine-if-the-device-is-a-smartphone-or-tablet
|
71
|
+
# def tablet?
|
72
|
+
# resource.first_package
|
73
|
+
# .entries('bool')
|
74
|
+
# .select{|e| e.name == 'isTablet' }
|
75
|
+
# .size >= 1
|
76
|
+
# end
|
77
|
+
|
78
|
+
def wear?
|
79
|
+
use_features.include?('android.hardware.type.watch')
|
80
|
+
end
|
81
|
+
|
82
|
+
def tv?
|
83
|
+
use_features.include?('android.software.leanback')
|
84
|
+
end
|
85
|
+
|
86
|
+
def min_sdk_version
|
87
|
+
manifest.uses_sdk.min_sdk_version
|
88
|
+
end
|
89
|
+
alias min_os_version min_sdk_version
|
90
|
+
|
91
|
+
def target_sdk_version
|
92
|
+
manifest.uses_sdk.target_sdk_version
|
93
|
+
end
|
94
|
+
|
95
|
+
def use_features
|
96
|
+
@use_features ||= manifest&.uses_feature&.map(&:name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def use_permissions
|
100
|
+
@use_permissions ||= manifest&.uses_permission&.map(&:name)
|
101
|
+
end
|
102
|
+
|
103
|
+
def activities
|
104
|
+
@activities ||= manifest.activities
|
105
|
+
end
|
106
|
+
|
107
|
+
def services
|
108
|
+
@services ||= manifest.services
|
109
|
+
end
|
110
|
+
|
111
|
+
def components
|
112
|
+
@components ||= manifest.components.transform_values
|
113
|
+
end
|
114
|
+
|
115
|
+
def signs
|
116
|
+
return @signs if @signs
|
117
|
+
|
118
|
+
@signs = []
|
119
|
+
each_file do |path, data|
|
120
|
+
# find META-INF/xxx.{RSA|DSA}
|
121
|
+
next unless path =~ %r{^META-INF/} && data.unpack('CC') == [0x30, 0x82]
|
122
|
+
|
123
|
+
@signs << APK::Sign.new(path, OpenSSL::PKCS7.new(data))
|
124
|
+
end
|
125
|
+
|
126
|
+
@signs
|
127
|
+
end
|
128
|
+
|
129
|
+
def certificates
|
130
|
+
@certificates ||= signs.each_with_object([]) do |sign, obj|
|
131
|
+
obj << APK::Certificate.new(sign.path, sign.sign.certificates[0])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def each_file
|
136
|
+
zip.each do |entry|
|
137
|
+
next unless entry.file?
|
138
|
+
|
139
|
+
yield entry.name, @zip.read(entry)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def read_file(name, base_path: BASE_PATH)
|
144
|
+
content = @zip.read(entry(name, base_path: base_path))
|
145
|
+
return parse_binary_xml(content) if xml_file?(name)
|
146
|
+
|
147
|
+
content
|
148
|
+
end
|
149
|
+
|
150
|
+
def entry(name, base_path: BASE_PATH)
|
151
|
+
entry = @zip.find_entry(File.join(base_path, name))
|
152
|
+
raise NotFoundError, "'#{name}'" if entry.nil?
|
153
|
+
|
154
|
+
entry
|
155
|
+
end
|
156
|
+
|
157
|
+
def manifest
|
158
|
+
io = zip.read(zip.find_entry(BASE_MANIFEST))
|
159
|
+
@manifest ||= Protobuf::Manifest.parse(io, resource)
|
160
|
+
end
|
161
|
+
|
162
|
+
def resource
|
163
|
+
io = zip.read(zip.find_entry(BASE_RESOURCES))
|
164
|
+
@resource ||= Protobuf::Resources.parse(io)
|
165
|
+
end
|
166
|
+
|
167
|
+
def zip
|
168
|
+
@zip ||= Zip::File.open(@file)
|
169
|
+
end
|
170
|
+
|
171
|
+
def icons
|
172
|
+
@icons ||= manifest.icons.each_with_object([]) do |res, obj|
|
173
|
+
path = res.value
|
174
|
+
filename = File.basename(path)
|
175
|
+
filepath = File.join(contents, File.dirname(path))
|
176
|
+
file = File.join(filepath, filename)
|
177
|
+
FileUtils.mkdir_p filepath
|
178
|
+
|
179
|
+
binary_data = read_file(path)
|
180
|
+
File.write(file, binary_data, encoding: Encoding::BINARY)
|
181
|
+
|
182
|
+
obj << {
|
183
|
+
name: filename,
|
184
|
+
file: file,
|
185
|
+
dimensions: ImageSize.path(file).size
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def clear!
|
191
|
+
return unless @contents
|
192
|
+
|
193
|
+
FileUtils.rm_rf(@contents)
|
194
|
+
|
195
|
+
@aab = nil
|
196
|
+
@contents = nil
|
197
|
+
@icons = nil
|
198
|
+
@app_path = nil
|
199
|
+
@info = nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def contents
|
203
|
+
@contents ||= File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def xml_file?(file)
|
209
|
+
File.extname(file) == '.xml'
|
210
|
+
end
|
211
|
+
|
212
|
+
# TODO: how to convert xml content after decode protoubufed content
|
213
|
+
def parse_binary_xml(io)
|
214
|
+
io
|
215
|
+
# Aapt::Pb::XmlNode.decode(io)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/lib/app_info/apk.rb
CHANGED
@@ -7,6 +7,7 @@ require 'forwardable'
|
|
7
7
|
module AppInfo
|
8
8
|
# Parse APK file
|
9
9
|
class APK
|
10
|
+
include Helper::HumanFileSize
|
10
11
|
extend Forwardable
|
11
12
|
|
12
13
|
attr_reader :file
|
@@ -24,11 +25,11 @@ module AppInfo
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def size(human_size: false)
|
27
|
-
|
28
|
+
file_to_human_size(@file, human_size: human_size)
|
28
29
|
end
|
29
30
|
|
30
31
|
def os
|
31
|
-
|
32
|
+
Platform::ANDROID
|
32
33
|
end
|
33
34
|
alias file_type os
|
34
35
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
module Inflector
|
5
|
+
def ai_snakecase
|
6
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
7
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
8
|
+
.tr('-', '_')
|
9
|
+
.gsub(/\s/, '_')
|
10
|
+
.gsub(/__+/, '_')
|
11
|
+
.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def ai_camelcase(first_letter: :upper, separators: ['-', '_', '\s'])
|
15
|
+
str = dup
|
16
|
+
|
17
|
+
separators.each do |s|
|
18
|
+
str = str.gsub(/(?:#{s}+)([a-z])/) { $1.upcase }
|
19
|
+
end
|
20
|
+
|
21
|
+
case first_letter
|
22
|
+
when :upper, true
|
23
|
+
str = str.gsub(/(\A|\s)([a-z])/) { $1 + $2.upcase }
|
24
|
+
when :lower, false
|
25
|
+
str = str.gsub(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
|
26
|
+
end
|
27
|
+
|
28
|
+
str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class String
|
34
|
+
include AppInfo::Inflector
|
35
|
+
end
|
data/lib/app_info/dsym.rb
CHANGED
@@ -5,6 +5,8 @@ require 'macho'
|
|
5
5
|
module AppInfo
|
6
6
|
# DSYM parser
|
7
7
|
class DSYM
|
8
|
+
include Helper::Archive
|
9
|
+
|
8
10
|
attr_reader :file
|
9
11
|
|
10
12
|
def initialize(file)
|
@@ -12,7 +14,7 @@ module AppInfo
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def file_type
|
15
|
-
|
17
|
+
Platform::DSYM
|
16
18
|
end
|
17
19
|
|
18
20
|
def object
|
@@ -91,7 +93,7 @@ module AppInfo
|
|
91
93
|
@contents = @file
|
92
94
|
else
|
93
95
|
dsym_dir = nil
|
94
|
-
@contents =
|
96
|
+
@contents = unarchive(@file, path: 'dsym') do |path, zip_file|
|
95
97
|
zip_file.each do |f|
|
96
98
|
unless dsym_dir
|
97
99
|
dsym_dir = f.name
|
@@ -114,6 +116,8 @@ module AppInfo
|
|
114
116
|
|
115
117
|
# DSYM Mach-O
|
116
118
|
class MachO
|
119
|
+
include Helper::HumanFileSize
|
120
|
+
|
117
121
|
def initialize(file, size = 0)
|
118
122
|
@file = file
|
119
123
|
@size = size
|
@@ -132,7 +136,7 @@ module AppInfo
|
|
132
136
|
end
|
133
137
|
|
134
138
|
def size(human_size: false)
|
135
|
-
return
|
139
|
+
return number_to_human_size(@size) if human_size
|
136
140
|
|
137
141
|
@size
|
138
142
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
# App Platform
|
5
|
+
module Platform
|
6
|
+
MACOS = 'macOS'
|
7
|
+
IOS = 'iOS'
|
8
|
+
ANDROID = 'Android'
|
9
|
+
DSYM = 'dSYM'
|
10
|
+
PROGUARD = 'Proguard'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Device Type
|
14
|
+
module Device
|
15
|
+
MACOS = 'macOS'
|
16
|
+
IPHONE = 'iPhone'
|
17
|
+
IPAD = 'iPad'
|
18
|
+
UNIVERSAL = 'Universal'
|
19
|
+
end
|
20
|
+
|
21
|
+
module AndroidDevice
|
22
|
+
PHONE = 'Phone'
|
23
|
+
TABLET = 'Tablet'
|
24
|
+
WATCH = 'Watch'
|
25
|
+
TV = 'Television'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Icon Key
|
29
|
+
ICON_KEYS = {
|
30
|
+
Device::IPHONE => ['CFBundleIcons'],
|
31
|
+
Device::IPAD => ['CFBundleIcons~ipad'],
|
32
|
+
Device::UNIVERSAL => ['CFBundleIcons', 'CFBundleIcons~ipad'],
|
33
|
+
Device::MACOS => %w[CFBundleIconFile CFBundleIconName]
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
module Helper
|
37
|
+
module HumanFileSize
|
38
|
+
def file_to_human_size(file, human_size:)
|
39
|
+
number = File.size(file)
|
40
|
+
human_size ? number_to_human_size(number) : number
|
41
|
+
end
|
42
|
+
|
43
|
+
FILE_SIZE_UNITS = %w[B KB MB GB TB].freeze
|
44
|
+
|
45
|
+
def number_to_human_size(number)
|
46
|
+
if number.to_i < 1024
|
47
|
+
exponent = 0
|
48
|
+
else
|
49
|
+
max_exp = FILE_SIZE_UNITS.size - 1
|
50
|
+
exponent = (Math.log(number) / Math.log(1024)).to_i
|
51
|
+
exponent = max_exp if exponent > max_exp
|
52
|
+
number = format('%<number>.2f', number: (number / (1024**exponent.to_f)))
|
53
|
+
end
|
54
|
+
|
55
|
+
"#{number} #{FILE_SIZE_UNITS[exponent]}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module Archive
|
60
|
+
require 'zip'
|
61
|
+
require 'fileutils'
|
62
|
+
require 'securerandom'
|
63
|
+
|
64
|
+
# Unarchive zip file
|
65
|
+
#
|
66
|
+
# source: https://github.com/soffes/lagunitas/blob/master/lib/lagunitas/ipa.rb
|
67
|
+
def unarchive(file, path: nil)
|
68
|
+
path = path ? "#{path}-" : ''
|
69
|
+
root_path = "#{Dir.mktmpdir}/AppInfo-#{path}#{SecureRandom.hex}"
|
70
|
+
Zip::File.open(file) do |zip_file|
|
71
|
+
if block_given?
|
72
|
+
yield root_path, zip_file
|
73
|
+
else
|
74
|
+
zip_file.each do |f|
|
75
|
+
f_path = File.join(root_path, f.name)
|
76
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
77
|
+
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
root_path
|
83
|
+
end
|
84
|
+
|
85
|
+
def tempdir(file, prefix:)
|
86
|
+
dest_path ||= File.join(File.dirname(file), prefix)
|
87
|
+
dest_file = File.join(dest_path, File.basename(file))
|
88
|
+
|
89
|
+
Dir.mkdir(dest_path, 0_700) unless Dir.exist?(dest_path)
|
90
|
+
|
91
|
+
dest_file
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module Defines
|
96
|
+
def create_class(klass_name, parent_class, namespace:)
|
97
|
+
klass = Class.new(parent_class) do
|
98
|
+
yield if block_given?
|
99
|
+
end
|
100
|
+
|
101
|
+
name = namespace.to_s.empty? ? klass_name : "#{namespace}::#{klass_name}"
|
102
|
+
if Object.const_get(namespace).const_defined?(klass_name)
|
103
|
+
Object.const_get(namespace).const_get(klass_name)
|
104
|
+
elsif Object.const_defined?(name)
|
105
|
+
Object.const_get(name)
|
106
|
+
else
|
107
|
+
Object.const_get(namespace).const_set(klass_name, klass)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def define_instance_method(key, value)
|
112
|
+
instance_variable_set("@#{key}", value)
|
113
|
+
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
114
|
+
def #{key}
|
115
|
+
@#{key}
|
116
|
+
end
|
117
|
+
RUBY
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module ReferenceParser
|
122
|
+
def reference_segments(value)
|
123
|
+
new_value = value.is_a?(Aapt::Pb::Reference) ? value.name : value
|
124
|
+
return new_value.split('/', 2) if new_value.include?('/')
|
125
|
+
|
126
|
+
[nil, new_value]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/lib/app_info/info_plist.rb
CHANGED
@@ -67,13 +67,13 @@ module AppInfo
|
|
67
67
|
def device_type
|
68
68
|
device_family = info.try(:[], 'UIDeviceFamily')
|
69
69
|
if device_family == [1]
|
70
|
-
|
70
|
+
Device::IPHONE
|
71
71
|
elsif device_family == [2]
|
72
|
-
|
72
|
+
Device::IPAD
|
73
73
|
elsif device_family == [1, 2]
|
74
|
-
|
74
|
+
Device::UNIVERSAL
|
75
75
|
elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTPlatformName').nil?
|
76
|
-
|
76
|
+
Device::MACOS
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
@@ -112,13 +112,13 @@ module AppInfo
|
|
112
112
|
def_delegators :info, :to_h
|
113
113
|
|
114
114
|
def method_missing(method_name, *args, &block)
|
115
|
-
info.try(:[],
|
115
|
+
info.try(:[], method_name.to_s.ai_camelcase) ||
|
116
116
|
info.send(method_name) ||
|
117
117
|
super
|
118
118
|
end
|
119
119
|
|
120
120
|
def respond_to_missing?(method_name, *args)
|
121
|
-
info.key?(
|
121
|
+
info.key?(method_name.to_s.ai_camelcase) ||
|
122
122
|
info.respond_to?(method_name) ||
|
123
123
|
super
|
124
124
|
end
|
data/lib/app_info/ipa.rb
CHANGED
@@ -8,6 +8,8 @@ require 'cfpropertylist'
|
|
8
8
|
module AppInfo
|
9
9
|
# IPA parser
|
10
10
|
class IPA
|
11
|
+
include Helper::HumanFileSize
|
12
|
+
include Helper::Archive
|
11
13
|
extend Forwardable
|
12
14
|
|
13
15
|
attr_reader :file
|
@@ -28,11 +30,11 @@ module AppInfo
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def size(human_size: false)
|
31
|
-
|
33
|
+
file_to_human_size(@file, human_size: human_size)
|
32
34
|
end
|
33
35
|
|
34
36
|
def os
|
35
|
-
|
37
|
+
Platform::IOS
|
36
38
|
end
|
37
39
|
alias file_type os
|
38
40
|
|
@@ -195,7 +197,7 @@ module AppInfo
|
|
195
197
|
end
|
196
198
|
|
197
199
|
def contents
|
198
|
-
@contents ||=
|
200
|
+
@contents ||= unarchive(@file, path: 'ios')
|
199
201
|
end
|
200
202
|
|
201
203
|
private
|
@@ -213,7 +215,7 @@ module AppInfo
|
|
213
215
|
|
214
216
|
# Uncrush png to normal png file (iOS)
|
215
217
|
def uncrush_png(src_file)
|
216
|
-
dest_file =
|
218
|
+
dest_file = tempdir(src_file, prefix: 'uncrushed')
|
217
219
|
PngUncrush.decompress(src_file, dest_file)
|
218
220
|
File.exist?(dest_file) ? dest_file : nil
|
219
221
|
end
|