app-info 2.0.0.beta3 → 2.0.0.beta4
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/CHANGELOG.md +8 -7
- data/README.md +7 -7
- data/lib/app_info.rb +19 -9
- data/lib/app_info/apk.rb +184 -0
- data/lib/app_info/core_ext/object/try.rb +2 -0
- data/lib/app_info/dsym.rb +149 -0
- data/lib/app_info/ipa.rb +230 -0
- data/lib/app_info/ipa/info_plist.rb +133 -0
- data/lib/app_info/ipa/mobile_provision.rb +89 -0
- data/lib/app_info/util.rb +26 -0
- data/lib/app_info/version.rb +1 -1
- metadata +7 -7
- data/lib/app_info/parser.rb +0 -40
- data/lib/app_info/parser/apk.rb +0 -183
- data/lib/app_info/parser/dsym.rb +0 -126
- data/lib/app_info/parser/ipa.rb +0 -229
- data/lib/app_info/parser/ipa/info_plist.rb +0 -135
- data/lib/app_info/parser/ipa/mobile_provision.rb +0 -91
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d706f0c25f15d80690f57b9315fd3e2e1a08f4288d591a300d5d0d5a8a134f4
|
4
|
+
data.tar.gz: 4259e76abf048e403f43ef99caa543b6f89ec4922f19188625b075d11786efa4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d67414e33f673066bfe14b277388a29eae8049b946c94a1e2a344e6ed5e992a7d05004140c7c58460074583e5811b3b064a1b8e37f6bb36c2ce8f6f04f02641
|
7
|
+
data.tar.gz: 0a052dfd02187fcd03930359b1ecfcf840e75d881eb8687f84d607c047e36b26d0c169f55992f4649ba09b86f56790f33679e3df8625acfb8713db284625533f
|
data/CHANGELOG.md
CHANGED
@@ -9,16 +9,17 @@ 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
|
-
### Added
|
13
|
-
|
14
|
-
- Added .dSYM.zip format support
|
15
|
-
- Added parse mobileprovision in Linux
|
16
|
-
- Added `AppInfo.file_type` to detect file type
|
17
|
-
|
18
12
|
### Changed
|
19
13
|
|
14
|
+
- Remove `Parser` module to reduce namespace. #[13](https://github.com/icyleaf/app-info/issues/13)
|
20
15
|
- Use parse Macho-O header and contents to detect file type instead of file extension name.
|
21
|
-
- Dropped Ruby 2.2 and below versions support
|
16
|
+
- Dropped Ruby 2.2 and below versions support.
|
17
|
+
|
18
|
+
### Added
|
19
|
+
|
20
|
+
- Added .dSYM.zip format support. #[8](https://github.com/icyleaf/app-info/issues/8)
|
21
|
+
- Added parse mobileprovision in Linux. #[10](https://github.com/icyleaf/app_info/pull/10)
|
22
|
+
- Added `AppInfo.file_type` to detect file type.
|
22
23
|
|
23
24
|
## [1.1.2] (2019-09-19)
|
24
25
|
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# app_info
|
2
2
|
|
3
|
+
[](.travis.yml)
|
3
4
|
[](https://travis-ci.org/icyleaf/app_info)
|
4
5
|
[](https://rubygems.org/gems/app_info)
|
5
6
|
[](LICENSE)
|
@@ -45,16 +46,15 @@ require 'app-info'
|
|
45
46
|
parser = AppInfo.parse('iphone.ipa')
|
46
47
|
parser = AppInfo.parse('ipad.ipa')
|
47
48
|
parser = AppInfo.parse('android.apk')
|
48
|
-
parser = AppInfo.parse('
|
49
|
-
parser = AppInfo.parse('provisioning_profile/uuid.mobileprovision')
|
49
|
+
parser = AppInfo.parse('u-u-i-d.mobileprovision')
|
50
50
|
parser = AppInfo.parse('App.dSYm.zip')
|
51
51
|
|
52
52
|
# If detect file type failed, you can parse in other way
|
53
|
-
parser = AppInfo::
|
54
|
-
parser = AppInfo::
|
55
|
-
parser = AppInfo::
|
56
|
-
parser = AppInfo::
|
57
|
-
parser = AppInfo::
|
53
|
+
parser = AppInfo::IPA.new('iphone.ipa')
|
54
|
+
parser = AppInfo::IPA.new('android.apk')
|
55
|
+
parser = AppInfo::InfoPlist.new('App/Info.plist')
|
56
|
+
parser = AppInfo::MobileProvision.new('provisioning_profile/uuid.mobileprovision')
|
57
|
+
parser = AppInfo::DSYM.new('App.dSYm.zip')
|
58
58
|
```
|
59
59
|
|
60
60
|
### iOS
|
data/lib/app_info.rb
CHANGED
@@ -1,23 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
require 'app_info/version'
|
4
|
-
require 'app_info/
|
5
|
-
require '
|
3
|
+
require 'app_info/ipa'
|
4
|
+
require 'app_info/ipa/info_plist'
|
5
|
+
require 'app_info/ipa/mobile_provision'
|
6
|
+
require 'app_info/apk'
|
7
|
+
require 'app_info/dsym'
|
6
8
|
|
7
9
|
# AppInfo Module
|
8
10
|
module AppInfo
|
9
|
-
class
|
10
|
-
class
|
11
|
+
class Error < StandardError; end
|
12
|
+
class NotFoundError < Error; end
|
13
|
+
class UnkownFileTypeError < Error; end
|
14
|
+
|
15
|
+
# App Platform
|
16
|
+
module Platform
|
17
|
+
IOS = 'iOS'
|
18
|
+
ANDROID = 'Android'
|
19
|
+
DSYM = 'dSYM'
|
20
|
+
end
|
11
21
|
|
12
22
|
# Get a new parser for automatic
|
13
23
|
def self.parse(file)
|
14
24
|
raise NotFoundError, file unless File.exist?(file)
|
15
25
|
|
16
26
|
case file_type(file)
|
17
|
-
when :ipa then
|
18
|
-
when :apk then
|
19
|
-
when :mobileprovision then
|
20
|
-
when :dsym then
|
27
|
+
when :ipa then IPA.new(file)
|
28
|
+
when :apk then APK.new(file)
|
29
|
+
when :mobileprovision then MobileProvision.new(file)
|
30
|
+
when :dsym then DSYM.new(file)
|
21
31
|
else
|
22
32
|
raise UnkownFileTypeError, "Sorry, AppInfo can not detect file type: #{file}"
|
23
33
|
end
|
data/lib/app_info/apk.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ruby_apk'
|
4
|
+
require 'image_size'
|
5
|
+
require 'app_info/util'
|
6
|
+
|
7
|
+
module AppInfo
|
8
|
+
# Parse APK file
|
9
|
+
class APK
|
10
|
+
attr_reader :file, :apk
|
11
|
+
|
12
|
+
# APK Devices
|
13
|
+
module Device
|
14
|
+
PHONE = 'Phone'
|
15
|
+
TABLET = 'Tablet'
|
16
|
+
WATCH = 'Watch'
|
17
|
+
TV = 'Television'
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(file)
|
21
|
+
@file = file
|
22
|
+
|
23
|
+
Zip.warn_invalid_date = false # fix invaild date format warnings
|
24
|
+
@apk = ::Android::Apk.new(file)
|
25
|
+
end
|
26
|
+
|
27
|
+
def size(humanable = false)
|
28
|
+
AppInfo::Util.file_size(@file, humanable)
|
29
|
+
end
|
30
|
+
|
31
|
+
def os
|
32
|
+
AppInfo::Platform::ANDROID
|
33
|
+
end
|
34
|
+
alias file_type os
|
35
|
+
|
36
|
+
def build_version
|
37
|
+
manifest.version_code.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def release_version
|
41
|
+
manifest.version_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def name
|
45
|
+
resource.find('@string/app_name')
|
46
|
+
end
|
47
|
+
|
48
|
+
def bundle_id
|
49
|
+
manifest.package_name
|
50
|
+
end
|
51
|
+
alias identifier bundle_id
|
52
|
+
alias package_name bundle_id
|
53
|
+
|
54
|
+
def device_type
|
55
|
+
if wear?
|
56
|
+
Device::WATCH
|
57
|
+
elsif tv?
|
58
|
+
Device::TV
|
59
|
+
else
|
60
|
+
Device::PHONE
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: find a way to detect
|
65
|
+
# def tablet?
|
66
|
+
# resource
|
67
|
+
# end
|
68
|
+
|
69
|
+
def wear?
|
70
|
+
use_features.include?('android.hardware.type.watch')
|
71
|
+
end
|
72
|
+
|
73
|
+
def tv?
|
74
|
+
use_features.include?('android.software.leanback')
|
75
|
+
end
|
76
|
+
|
77
|
+
def min_sdk_version
|
78
|
+
manifest.min_sdk_ver
|
79
|
+
end
|
80
|
+
|
81
|
+
def target_sdk_version
|
82
|
+
manifest.doc
|
83
|
+
.elements['/manifest/uses-sdk']
|
84
|
+
.attributes['targetSdkVersion']
|
85
|
+
.to_i
|
86
|
+
end
|
87
|
+
|
88
|
+
def use_permissions
|
89
|
+
manifest.use_permissions
|
90
|
+
end
|
91
|
+
|
92
|
+
def use_features
|
93
|
+
manifest_values('/manifest/uses-feature')
|
94
|
+
end
|
95
|
+
|
96
|
+
def signs
|
97
|
+
@apk.signs.each_with_object([]) do |(path, sign), obj|
|
98
|
+
obj << Sign.new(path, sign)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def certificates
|
103
|
+
@apk.certificates.each_with_object([]) do |(path, certificate), obj|
|
104
|
+
obj << Certificate.new(path, certificate)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def activities
|
109
|
+
components.select { |c| c.type == 'activity' }
|
110
|
+
end
|
111
|
+
|
112
|
+
def services
|
113
|
+
components.select { |c| c.type == 'service' }
|
114
|
+
end
|
115
|
+
|
116
|
+
def components
|
117
|
+
manifest.components
|
118
|
+
end
|
119
|
+
|
120
|
+
def manifest
|
121
|
+
@apk.manifest
|
122
|
+
end
|
123
|
+
|
124
|
+
def resource
|
125
|
+
@apk.resource
|
126
|
+
end
|
127
|
+
|
128
|
+
def dex
|
129
|
+
@apk.dex
|
130
|
+
end
|
131
|
+
|
132
|
+
def icons
|
133
|
+
unless @icons
|
134
|
+
tmp_path = File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
135
|
+
|
136
|
+
@icons = @apk.icon.each_with_object([]) do |(path, data), obj|
|
137
|
+
icon_name = File.basename(path)
|
138
|
+
icon_path = File.join(tmp_path, File.path(path))
|
139
|
+
icon_file = File.join(icon_path, icon_name)
|
140
|
+
FileUtils.mkdir_p icon_path
|
141
|
+
File.open(icon_file, 'w') do |f|
|
142
|
+
f.write data
|
143
|
+
end
|
144
|
+
|
145
|
+
obj << {
|
146
|
+
name: icon_name,
|
147
|
+
file: icon_file,
|
148
|
+
dimensions: ImageSize.path(icon_file).size
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
@icons
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def manifest_values(path, key = 'name')
|
159
|
+
values = []
|
160
|
+
manifest.doc.each_element(path) do |elem|
|
161
|
+
values << elem.attributes[key]
|
162
|
+
end
|
163
|
+
values.uniq
|
164
|
+
end
|
165
|
+
|
166
|
+
# Android Certificate
|
167
|
+
class Certificate
|
168
|
+
attr_reader :path, :certificate
|
169
|
+
def initialize(path, certificate)
|
170
|
+
@path = path
|
171
|
+
@certificate = certificate
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Android Sign
|
176
|
+
class Sign
|
177
|
+
attr_reader :path, :sign
|
178
|
+
def initialize(path, sign)
|
179
|
+
@path = path
|
180
|
+
@sign = sign
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true`
|
2
|
+
|
3
|
+
require 'zip'
|
4
|
+
require 'macho'
|
5
|
+
require 'app_info/core_ext/object/try'
|
6
|
+
|
7
|
+
module AppInfo
|
8
|
+
# DSYM parser
|
9
|
+
class DSYM
|
10
|
+
attr_reader :file
|
11
|
+
|
12
|
+
def initialize(file)
|
13
|
+
@file = file
|
14
|
+
end
|
15
|
+
|
16
|
+
def file_type
|
17
|
+
AppInfo::Platform::DSYM
|
18
|
+
end
|
19
|
+
|
20
|
+
def object
|
21
|
+
@object ||= File.basename(app_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def macho_type
|
25
|
+
@macho_type ||= ::MachO.open(app_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def machos
|
29
|
+
@machos ||= case macho_type
|
30
|
+
when ::MachO::MachOFile
|
31
|
+
[MachO.new(macho_type, File.size(app_path))]
|
32
|
+
else
|
33
|
+
size = macho_type.fat_archs.each_with_object([]) do |arch, obj|
|
34
|
+
obj << arch.size
|
35
|
+
end
|
36
|
+
|
37
|
+
machos = []
|
38
|
+
macho_type.machos.each_with_index do |file, i|
|
39
|
+
machos << MachO.new(file, size[i])
|
40
|
+
end
|
41
|
+
machos
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def release_version
|
46
|
+
info.try(:[], 'CFBundleShortVersionString')
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_version
|
50
|
+
info.try(:[], 'CFBundleVersion')
|
51
|
+
end
|
52
|
+
|
53
|
+
def identifier
|
54
|
+
info.try(:[], 'CFBundleIdentifier').sub('com.apple.xcode.dsym.', '')
|
55
|
+
end
|
56
|
+
alias bundle_id identifier
|
57
|
+
|
58
|
+
def info
|
59
|
+
return nil unless File.exist?(info_path)
|
60
|
+
|
61
|
+
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: info_path).value)
|
62
|
+
end
|
63
|
+
|
64
|
+
def info_path
|
65
|
+
@info_path ||= File.join(contents, 'Contents', 'Info.plist')
|
66
|
+
end
|
67
|
+
|
68
|
+
def app_path
|
69
|
+
unless @app_path
|
70
|
+
path = File.join(contents, 'Contents', 'Resources', 'DWARF')
|
71
|
+
name = Dir.entries(path).last
|
72
|
+
@app_path = File.join(path, name)
|
73
|
+
end
|
74
|
+
|
75
|
+
@app_path
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def contents
|
81
|
+
unless @contents
|
82
|
+
if File.directory?(@file)
|
83
|
+
@contents = @file
|
84
|
+
else
|
85
|
+
@contents = "#{Dir.mktmpdir}/AppInfo-dsym-#{SecureRandom.hex}"
|
86
|
+
dsym_dir = nil
|
87
|
+
Zip::File.open(@file) do |zip_file|
|
88
|
+
zip_file.each do |f|
|
89
|
+
dsym_dir ||= f.name
|
90
|
+
|
91
|
+
f_path = File.join(@contents, f.name)
|
92
|
+
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
@contents = File.join(@contents, dsym_dir)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@contents
|
101
|
+
end
|
102
|
+
|
103
|
+
# DSYM Mach-O
|
104
|
+
class MachO
|
105
|
+
def initialize(file, size = 0)
|
106
|
+
@file = file
|
107
|
+
@size = size
|
108
|
+
end
|
109
|
+
|
110
|
+
def cpu_name
|
111
|
+
@file.cpusubtype
|
112
|
+
end
|
113
|
+
|
114
|
+
def cpu_type
|
115
|
+
@file.cputype
|
116
|
+
end
|
117
|
+
|
118
|
+
def type
|
119
|
+
@file.filetype
|
120
|
+
end
|
121
|
+
|
122
|
+
def size(humanable = false)
|
123
|
+
return Util.size_to_humanable(@size) if humanable
|
124
|
+
|
125
|
+
@size
|
126
|
+
end
|
127
|
+
|
128
|
+
def uuid
|
129
|
+
@file[:LC_UUID][0].uuid_string
|
130
|
+
end
|
131
|
+
alias debug_id uuid
|
132
|
+
|
133
|
+
def header
|
134
|
+
@header ||= @file.header
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_h
|
138
|
+
{
|
139
|
+
uuid: uuid,
|
140
|
+
type: type,
|
141
|
+
cpu_name: cpu_name,
|
142
|
+
cpu_type: cpu_type,
|
143
|
+
size: size,
|
144
|
+
humanable_size: size(true)
|
145
|
+
}
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|