antenna-ota 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b807db1b06113f1407044abb45251cb713266f43
4
- data.tar.gz: 21078d78892d63282e1664707906e8dfea720bb8
3
+ metadata.gz: 5268c6276e2dd2cdebbd16c1a1df41dc49bab722
4
+ data.tar.gz: 953bc51904fcb09accd8aa90df9691fb7ba09113
5
5
  SHA512:
6
- metadata.gz: eb3a2ecb9baddd85d495d299dcb519927028a726fb9b009b712f2e3dd6daa94afcc45362e44c6da89c839002eb285ca094eecdae3baf8865c75721b12729f9b0
7
- data.tar.gz: a0a59e1048541991ca3e61a6f568e8a039f809c3ba13bca025758811c98192e591f1205aac3ecaa96959970a53d7e7af97ee1fbc6711f0d8be1e3c2f1a7ec814
6
+ metadata.gz: b26482d957a336ef3c6ecc5b659d0d6f07947ceccd1738687608aa94027f6aee35791f4b4c4821fc06aafe92bbbbbcc906f0fed8581d2e620583e51be42569ed
7
+ data.tar.gz: 322384d8829dcd4dd5dc5743be1a2d2b52b030250bdda63b59d48eb1ca42fa0bbe4a00a5e6beadb41a12328dc4e4f16d7acff59f1c2da813f1f95340c0c8605c
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1
6
+ - 2.2
7
+ notifications:
8
+ recipients:
9
+ - ci@funkreich.de
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/soulchild/antenna.svg?branch=master)](https://travis-ci.org/soulchild/antenna)
2
+
1
3
  # Antenna
2
4
 
3
5
  Antenna aims to take the pain out of creating and distributing all the necessary files for Enterprise iOS over-the-air distribution. It generates the mandatory XML manifest, app icons and an HTML file, automatically extracting all the needed information from the specified `.ipa` file, and uploads everything via a distribution method of your choice (currently only S3 is supported, but you're encouraged to create other storage backends). The result is a (signed S3) URL, which you may then send to your clients, so they can easily install your app from Mobile Safari with just one tap.
@@ -30,9 +32,11 @@ $ antenna
30
32
  -t, --trace Display backtrace when an error occurs
31
33
  ```
32
34
 
33
- ## Example
35
+ ## Examples
36
+
37
+ ### 1. Private, signed URL
34
38
 
35
- Create a new S3 bucket called `antenna-ota` on Amazon's `eu-central-1` S3 cluster and upload OverTheAir.ipa:
39
+ Create a new S3 bucket called `antenna-ota` on Amazon's `eu-central-1` S3 cluster and upload OverTheAir.ipa, resulting in a signed URL for distribution:
36
40
 
37
41
  ```bash
38
42
  $ antenna s3 -a <YOUR-S3-ACCESS-KEY> -s <YOUR-S3-SECRET-KEY> --file OverTheAir.ipa --region eu-central-1 --create --bucket antenna-ota
@@ -43,7 +47,20 @@ Distributing OverTheAir.html ...
43
47
  https://antenna-ota.s3.eu-central-1.amazonaws.com/OverTheAir.html?<...signing-parameters...>
44
48
  ```
45
49
 
46
- The resulting URL leads to an installation page like the following and can be distributed to your users for installation. The meta-data and app-icon is automatically extracted from the given .ipa file.
50
+ ### 2. Public, unsigned URL
51
+
52
+ Upload OverTheAir.ipa to Amazon's `eu-central-1` S3 cluster, resulting in a publically available, unsigned URL for distribution:
53
+
54
+ ```bash
55
+ $ antenna s3 -a <YOUR-S3-ACCESS-KEY> -s <YOUR-S3-SECRET-KEY> --file OverTheAir.ipa --public --acl public-read --region eu-central-1 --bucket antenna-ota
56
+ Distributing OverTheAir.ipa ...
57
+ Distributing OverTheAir.png ...
58
+ Distributing OverTheAir.plist ...
59
+ Distributing OverTheAir.html ...
60
+ https://antenna-ota.s3.eu-central-1.amazonaws.com/OverTheAir.html
61
+ ```
62
+
63
+ The resulting URLs show an installation page like the following and can be distributed to your users for installation. The meta-data and app-icon is automatically extracted from the given .ipa file:
47
64
 
48
65
  ![Installation site](https://raw.githubusercontent.com/soulchild/antenna/master/assets/example-installation.png)
49
66
 
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -18,12 +18,13 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.10"
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
22
  spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec"
23
24
 
24
25
  spec.add_dependency "aws-sdk", '~> 2.0', '>= 2.0.0'
25
26
  spec.add_dependency "commander", "~> 4.3"
26
- spec.add_dependency "highline", '~> 1.7', '>= 1.7.2'
27
+ spec.add_dependency "highline", '~> 1.7', '>= 1.7.0'
27
28
  spec.add_dependency "CFPropertyList", '~> 2.3', '>= 2.3.0'
28
29
  spec.add_dependency "rubyzip", '~> 1.0', '>= 1.0.0'
29
30
  end
@@ -1 +1 @@
1
- require "antenna/version"
1
+ require "antenna/version"
@@ -17,7 +17,8 @@ command :s3 do |c|
17
17
  c.option '-e', '--endpoint ENDPOINT', "S3 endpoint (optional, e.g. https://mys3.example.com)"
18
18
  c.option '-x', '--expires EXPIRES', "Expiration of URLs in seconds (optional, e.g. 86400 = one day)"
19
19
  c.option '-i', '--base BASE', "Base filename (optional, defaults to IPA filename without .ipa extension)"
20
- c.option '--acl ACL', "Permissions for uploaded files. Must be one of: public_read, private, public_read_write, authenticated_read (optional, defaults to private)"
20
+ c.option '-p', '--public', "Use public instead of signed URLs (you'll probably want '--acl public-read' as well)"
21
+ c.option '--acl ACL', "Permissions for uploaded files. Must be one of: private, public-read, public-read-write, authenticated-read, bucket-owner-read, bucket-owner-full-control (optional, defaults to private)"
21
22
 
22
23
  c.action do |args, options|
23
24
  determine_file! unless @file = options.file
@@ -40,7 +41,14 @@ command :s3 do |c|
40
41
 
41
42
  s3 = Antenna::Distributor::S3.new(@access_key_id, @secret_access_key, @region, @endpoint)
42
43
  distributor = Antenna::Distributor.new(s3)
43
- puts distributor.distribute @file, { :bucket => @bucket, :create => !!options.create, :expire => options.expires, :acl => @acl, :base => options.base }
44
+ puts distributor.distribute @file, {
45
+ :bucket => @bucket,
46
+ :create => !!options.create,
47
+ :public => !!options.public,
48
+ :expire => options.expires,
49
+ :acl => options.acl,
50
+ :base => options.base
51
+ }
44
52
  end
45
53
 
46
54
  private
@@ -60,7 +60,7 @@ module Antenna
60
60
  end
61
61
 
62
62
  def process_app_icon(ipa)
63
- ipa.bundle_icon_files["57x57@2x"] || ipa.bundle_icon_files["60x60@2x"] || ipa.bundle_icon_files["60x60@3x"]
63
+ ipa.bundle_icon(57, 2) || ipa.bundle_icon(57, 1) || ipa.bundle_icon(60, 2) || ipa.bundle_icon(60, 1)
64
64
  end
65
65
 
66
66
  def build_manifest(ipa, ipa_url, app_icon_url)
@@ -7,6 +7,7 @@ module Antenna
7
7
  :access_key_id => access_key_id,
8
8
  :secret_access_key => secret_access_key,
9
9
  :region => region || "us-east-1",
10
+ :force_path_style => true
10
11
  }
11
12
  options[:endpoint] = endpoint if endpoint
12
13
  @s3 = Aws::S3::Resource.new(options)
@@ -34,9 +35,14 @@ module Antenna
34
35
  :key => filename,
35
36
  :content_type => content_type,
36
37
  :body => data,
38
+ :acl => @options[:acl]
37
39
  })
38
40
 
39
- URI.parse(object.presigned_url(:get, { :expires_in => @options[:expire] }))
41
+ if @options[:public]
42
+ URI.parse(object.public_url)
43
+ else
44
+ URI.parse(object.presigned_url(:get, { :expires_in => @options[:expire] }))
45
+ end
40
46
  end
41
47
  end
42
48
  end
@@ -1,13 +1,32 @@
1
- require "CFPropertyList"
1
+ require "cfpropertylist"
2
2
 
3
3
  module Antenna
4
4
  class InfoPlist
5
- attr_accessor :bundle_display_name, :bundle_short_version, :bundle_identifier, :bundle_version, :bundle_icon_filenames, :bundle_minimum_os_version
5
+ attr_accessor :bundle_display_name, :bundle_short_version, :bundle_identifier, :bundle_version, :bundle_icons, :bundle_minimum_os_version
6
+
7
+ class << self
8
+ # Class method to instantiate object with data from file.
9
+ def from_file(filename)
10
+ self.new(File.read(filename))
11
+ end
12
+
13
+ # Heuristically determines available icon sizes and resolutions from icon filenames
14
+ # and normalizes them into a hash. This hopefully works for most cases out there.
15
+ # Returns a hash of hashes like the following:
16
+ # { icon_width => { icon_resolution => icon_filename } }
17
+ def determine_icons(iconfiles)
18
+ icons = Hash.new { |h, k| h[k] = { } }
19
+ iconfiles.each { |file|
20
+ (width, height, resolution) = file.to_s.scan(/(\d+)?x?(\d+)?@?(\d+)?x?(\.png)?$/).flatten
21
+ next unless width
22
+ icons[width.to_i][(resolution || 1).to_i] = File.basename(file, ".*")
23
+ }
24
+ icons
25
+ end
26
+ end
6
27
 
7
28
  def initialize(data)
8
- infoplist = CFPropertyList::List.new(
9
- :data => data,
10
- )
29
+ infoplist = CFPropertyList::List.new(:data => data)
11
30
  infoplist_data = CFPropertyList.native_types(infoplist.value)
12
31
 
13
32
  @bundle_display_name = infoplist_data["CFBundleDisplayName"] || infoplist_data["CFBundleName"]
@@ -15,12 +34,15 @@ module Antenna
15
34
  @bundle_short_version = infoplist_data["CFBundleShortVersionString"]
16
35
  @bundle_version = infoplist_data["CFBundleVersion"]
17
36
  @bundle_minimum_os_version = infoplist_data["MinimumOSVersion"]
37
+ @bundle_icons = {}
18
38
 
19
- icons = infoplist_data["CFBundleIcons"]
20
- if icons
21
- primary_icon = icons["CFBundlePrimaryIcon"]
22
- if primary_icon
23
- @bundle_icon_filenames = primary_icon["CFBundleIconFiles"]
39
+ if icons = infoplist_data["CFBundleIconFiles"]
40
+ @bundle_icons = self.class.determine_icons(icons)
41
+ else
42
+ if icons = infoplist_data["CFBundleIcons"]
43
+ if primary_icon = icons["CFBundlePrimaryIcon"]
44
+ @bundle_icons = self.class.determine_icons(primary_icon["CFBundleIconFiles"])
45
+ end
24
46
  end
25
47
  end
26
48
  end
@@ -11,38 +11,34 @@ module Antenna
11
11
  @bundle_icon_files = {}
12
12
 
13
13
  Zip::File.open(filename) do |zipfile|
14
- zipfile.dir.entries("Payload").each do |entry|
15
- # Find app name
16
- if entry =~ /.app$/
17
- app_entry = zipfile.find_entry("Payload/#{entry}")
18
- if app_entry
19
- @app_name = entry
14
+ # Determine app name
15
+ @app_name = zipfile.dir.entries('Payload').
16
+ select { |file| file =~ /.app$/ }.
17
+ first
18
+ raise "Unable to determine app name from #{filename}" unless @app_name
20
19
 
21
- # Find and parse Info.plist
22
- infoplist_entry = zipfile.find_entry("Payload/#{@app_name}/Info.plist")
23
- if infoplist_entry
24
- infoplist_data = infoplist_entry.get_input_stream.read
25
- @info_plist = Antenna::InfoPlist.new(infoplist_data)
26
- break
27
- end
28
- end
29
- end
30
- end
31
-
32
- say "Info.plist not found in #{filename}" and abort unless @info_plist
20
+ # Find and read Info.plist
21
+ infoplist_entry = zipfile.get_entry("Payload/#{@app_name}/Info.plist")
22
+ infoplist_data = infoplist_entry.get_input_stream.read
23
+ @info_plist = Antenna::InfoPlist.new(infoplist_data)
24
+ raise "Unable to find Info.plist in #{filename}" unless @info_plist
25
+ end
26
+ end
33
27
 
34
- # Extract main icon files
35
- @info_plist.bundle_icon_filenames.each do |icon|
36
- icon_glob = "Payload/#{@app_name}/#{icon}*.png"
37
- zipfile.glob(icon_glob).each do |entry|
38
- (width, height, resolution) = entry.to_s.scan(/(\d+)x(\d+)@(\d+)x\.png$/).flatten
39
- if width and height and resolution
40
- key = "#{width}x#{height}@#{resolution}x"
41
- @bundle_icon_files[key] = entry.get_input_stream.read
28
+ # Returns icon image data for given pixel size and resolution (defaults to 1).
29
+ def bundle_icon(size, resolution=1)
30
+ icon_data = nil
31
+ if resolutions = @info_plist.bundle_icons[size]
32
+ if filename = resolutions[resolution]
33
+ icon_glob = "Payload/#{@app_name}/#{filename}*.png"
34
+ Zip::File.open(@filename) do |zipfile|
35
+ zipfile.glob(icon_glob).each do |icon|
36
+ icon_data = icon.get_input_stream.read
42
37
  end
43
38
  end
44
39
  end
45
40
  end
41
+ icon_data
46
42
  end
47
43
 
48
44
  def input_stream
@@ -57,4 +57,4 @@ EOF
57
57
  ERB.new(template).result(binding)
58
58
  end
59
59
  end
60
- end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module Antenna
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: antenna-ota
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobi Kremer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-29 00:00:00.000000000 Z
11
+ date: 2015-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
19
+ version: '1.7'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.10'
26
+ version: '1.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: aws-sdk
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -81,7 +95,7 @@ dependencies:
81
95
  version: '1.7'
82
96
  - - ">="
83
97
  - !ruby/object:Gem::Version
84
- version: 1.7.2
98
+ version: 1.7.0
85
99
  type: :runtime
86
100
  prerelease: false
87
101
  version_requirements: !ruby/object:Gem::Requirement
@@ -91,7 +105,7 @@ dependencies:
91
105
  version: '1.7'
92
106
  - - ">="
93
107
  - !ruby/object:Gem::Version
94
- version: 1.7.2
108
+ version: 1.7.0
95
109
  - !ruby/object:Gem::Dependency
96
110
  name: CFPropertyList
97
111
  requirement: !ruby/object:Gem::Requirement
@@ -144,6 +158,8 @@ extensions: []
144
158
  extra_rdoc_files: []
145
159
  files:
146
160
  - ".gitignore"
161
+ - ".rspec"
162
+ - ".travis.yml"
147
163
  - CHANGELOG.md
148
164
  - Gemfile
149
165
  - LICENSE.txt