app-store-emigrant 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,5 +1,15 @@
1
1
  CHANGELOG
2
2
 
3
+ v0.0.5
4
+ - Restructured fixtures to improve test coverage
5
+ - Finding applications by id
6
+ - Metadata caching
7
+ - Implemented loading cloud data in bulk
8
+ - Gracefully handle invalid applications in CLI
9
+
10
+ v0.0.4
11
+ - Removed autoloading mechanism as it breaks on stock Ruby for OSX
12
+
3
13
  v0.0.3
4
14
  - Support library organization structures prior to iTunes 9
5
15
  - Improved exception messages
data/Rakefile CHANGED
@@ -6,3 +6,5 @@ Rake::TestTask.new do |t|
6
6
  t.verbose = true
7
7
  t.libs << 'specs'
8
8
  end
9
+
10
+ task :default => :test
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'app-store-emigrant/app'
3
+ require 'app-store-emigrant/cache'
3
4
  require 'app-store-emigrant/cli'
4
5
  require 'app-store-emigrant/library'
5
6
  require 'app-store-emigrant/version'
@@ -21,6 +21,7 @@ module AppStore::Emigrant
21
21
  FILENAME_VERSION_REGEX = Regexp.new('([0-9.]+)(?:' + VALID_EXTENSIONS.join('|').gsub!('.', '\.') + ')')
22
22
 
23
23
  attr_reader :path
24
+ attr_writer :clouddata
24
25
 
25
26
  # Initializes application from given path
26
27
  def initialize path
@@ -44,15 +45,25 @@ module AppStore::Emigrant
44
45
  # Filename of this application (including extension)
45
46
  def filename
46
47
  unless @filename
47
- @filename = @path.basename().to_s
48
+ @filename = @path.basename.to_s
48
49
  end
49
50
 
50
51
  @filename
51
52
  end
52
53
 
53
- # Name of this application (from its metadata)
54
+ # Unique application id
55
+ def id
56
+ metadata['itemId'] rescue nil
57
+ end
58
+
59
+ # Name of this application
54
60
  def name
55
- metadata['itemName']
61
+ metadata['itemName'] rescue nil
62
+ end
63
+
64
+ # Whether this application is valid (validates metadata, id and name)
65
+ def valid?
66
+ metadata && id && name rescue false
56
67
  end
57
68
 
58
69
  # Version of this application
@@ -60,7 +71,7 @@ module AppStore::Emigrant
60
71
  unless @version
61
72
 
62
73
  # Extract version information (if available)
63
- @version = metadata['bundleShortVersionString'] || metadata['bundleVersion']
74
+ @version = metadata['bundleShortVersionString'] || metadata['bundleVersion'] rescue nil
64
75
 
65
76
  # Otherwise, use the filename
66
77
  unless @version
@@ -75,15 +86,21 @@ module AppStore::Emigrant
75
86
  # Lazily loads local metadata for this application from its iTunesMetadata.plist
76
87
  def metadata
77
88
  unless @metadata
78
- begin
79
- Zip::ZipFile.open(@path) do |zip|
80
- data = zip.file.read('iTunesMetadata.plist')
81
- plist = CFPropertyList::List.new(:data => data)
82
- @metadata = CFPropertyList.native_types(plist.value)
89
+
90
+ # Determine plist name
91
+ plist = Pathname.new(filename).basename('.ipa').to_s + '.plist'
92
+
93
+ unless Cache.has?(plist)
94
+ begin
95
+ Zip::ZipFile.open(@path) do |zip|
96
+ zip.extract('iTunesMetadata.plist', Cache.path_to(plist))
97
+ end
98
+ rescue Zip::ZipError => e
99
+ raise Invalid, e.message
83
100
  end
84
- rescue Zip::ZipError => e
85
- raise Invalid, e.message
86
101
  end
102
+
103
+ @metadata = CFPropertyList.native_types(CFPropertyList::List.new(:file => Cache.path_to(plist)).value)
87
104
  end
88
105
 
89
106
  @metadata
@@ -93,7 +110,7 @@ module AppStore::Emigrant
93
110
  # Note: Clouddata may be nil if the application was removed from the store
94
111
  def clouddata
95
112
  unless @clouddata
96
- response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + metadata['itemId'].to_s)
113
+ response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + id.to_s)
97
114
  @clouddata = JSON.parse(response)['results'].first
98
115
  end
99
116
 
@@ -0,0 +1,35 @@
1
+ require 'pathname'
2
+
3
+ module AppStore; end
4
+
5
+ module AppStore::Emigrant
6
+
7
+ # Cache mechanism
8
+ class Cache
9
+
10
+ PATH = begin
11
+ path = Pathname.new('~/.ase-cache').expand_path
12
+ unless path.directory?
13
+ path.mkpath
14
+ end
15
+ path
16
+ end
17
+
18
+ # Whether cache has this item
19
+ def self.has? item
20
+ PATH.join(item).file?
21
+ end
22
+
23
+ # Path to given item (whether existent or not)
24
+ def self.path_to item
25
+ PATH.join(item).to_s
26
+ end
27
+
28
+ # Forcefully clears the cache
29
+ def self.clear!
30
+ # TODO: Implement clearing cache
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -16,17 +16,25 @@ module AppStore::Emigrant
16
16
  # Use the default library on this system when no path is specified
17
17
  library = path ? Library.new(path) : Library.default
18
18
 
19
+ # Load cloud data in bulk
20
+ library.clouddata!
21
+
19
22
  # Loop through all applications
20
23
  library.apps.each do |app|
21
24
 
22
- # And print their name, version and whether it's outdated
23
- print app.name + ' @ ' + "v#{app.version}".foreground(app.outdated? ? :red : :green)
24
- if app.outdated?
25
- print " (v#{app.cloudversion} available in the cloud)"
25
+ if app.valid?
26
+
27
+ # Print their name, version and whether it's outdated
28
+ print app.name + ' @ ' + "v#{app.version}".foreground(app.outdated? ? :red : :green)
29
+ if app.outdated?
30
+ print " (v#{app.cloudversion} available in the cloud)"
31
+ end
32
+ else
33
+ print app.filename + ' is invalid (metadata, id or name missing)'
26
34
  end
35
+
27
36
  puts
28
37
 
29
- sleep(0.75)
30
38
  end
31
39
 
32
40
  end
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+ require 'net/http'
1
3
  require 'pathname'
2
4
 
3
5
  module AppStore; end
@@ -44,6 +46,31 @@ module AppStore::Emigrant
44
46
  self
45
47
  end
46
48
 
49
+ # Populates applications' cloud data in bulk
50
+ def clouddata!
51
+
52
+ # Collect all application ids, skipping any invalid ones
53
+ ids = apps.collect do |app|
54
+ app.id
55
+ end.compact
56
+
57
+ # Queries Apple's iTunes Store API for latest cloud data using undocumented bulk method
58
+ response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + ids.join(','))
59
+ results = JSON.parse(response)['results']
60
+ results.each do |result|
61
+ if app = get(result['trackId'] || -1)
62
+ app.clouddata = result
63
+ end
64
+ end
65
+ end
66
+
67
+ # Searches for application with given id
68
+ def get id
69
+ apps.select do |app|
70
+ app.id == id
71
+ end.first
72
+ end
73
+
47
74
  # Searches for applications containing given snippet in filename
48
75
  def find snippet
49
76
  apps.select do |app|
@@ -3,6 +3,6 @@ module AppStore; end
3
3
 
4
4
  module AppStore::Emigrant
5
5
 
6
- VERSION = '0.0.4'
6
+ VERSION = '0.0.5'
7
7
 
8
8
  end
data/specs/app.rb CHANGED
@@ -11,7 +11,8 @@ describe App do
11
11
 
12
12
  before do
13
13
  @dummy = App.new(LIBRARY + '/Dummy.ipa')
14
- @outdated = App.new(LIBRARY + '/Outdated.ipa')
14
+ @gta = App.new(LIBRARY + '/GTA.ipa')
15
+ @soosiz = App.new(LIBRARY + '/Soosiz.ipa')
15
16
  end
16
17
 
17
18
  it 'must be a valid file on disk' do
@@ -27,25 +28,34 @@ describe App do
27
28
  end
28
29
 
29
30
  it 'can determine its own filename' do
30
- @dummy.filename.must_equal 'Dummy.ipa'
31
+ @gta.filename.must_equal 'GTA.ipa'
32
+ @soosiz.filename.must_equal 'Soosiz.ipa'
31
33
  end
32
34
 
33
- it 'can handle invalid structures' do
34
- lambda do
35
- @dummy.version
36
- end.must_raise App::Invalid
35
+ it 'will report invalid structures' do
36
+ @dummy.valid?.must_equal false
37
+ @dummy.id.must_equal nil
38
+ @dummy.version.must_equal nil
39
+ end
40
+
41
+ it 'can extract its name' do
42
+ @gta.name.must_equal 'Grand Theft Auto: Chinatown Wars'
43
+ @soosiz.name.must_equal 'Soosiz'
37
44
  end
38
45
 
39
46
  it 'can query local metadata' do
40
- @outdated.version.must_equal '0.9'
47
+ @gta.version.must_equal '0.9'
48
+ @soosiz.version.must_equal '1.1'
41
49
  end
42
50
 
43
- it 'can query clouddata' do
44
- @outdated.cloudversion.must_equal '1.1.0'
51
+ it 'can load cloud data' do
52
+ @gta.cloudversion.must_equal '1.1.0'
53
+ @soosiz.cloudversion.must_equal '1.3'
45
54
  end
46
55
 
47
56
  it 'can determine whether it is outdated' do
48
- @outdated.outdated?.must_equal true
57
+ @gta.outdated?.must_equal true
58
+ @soosiz.outdated?.must_equal true
49
59
  end
50
60
 
51
61
  end
@@ -6,5 +6,7 @@
6
6
  <string>0.9</string>
7
7
  <key>itemId</key>
8
8
  <integer>344186162</integer>
9
+ <key>itemName</key>
10
+ <string>Grand Theft Auto: Chinatown Wars</string>
9
11
  </dict>
10
12
  </plist>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>bundleVersion</key>
6
+ <string>1.1</string>
7
+ <key>itemId</key>
8
+ <integer>331891505</integer>
9
+ <key>itemName</key>
10
+ <string>Soosiz</string>
11
+ </dict>
12
+ </plist>
data/specs/helpers.rb CHANGED
@@ -1,3 +1,6 @@
1
1
 
2
2
  # Helper constant holding path to tests-folder
3
3
  ROOT = File.dirname __FILE__
4
+
5
+ # Clear the cache before running tests
6
+ AppStore::Emigrant::Cache.clear!
data/specs/library.rb CHANGED
@@ -22,11 +22,30 @@ describe Library do
22
22
  it 'will gracefully and lazily load applications' do
23
23
  @library.instance_variable_get('@apps').must_be_nil
24
24
  @library.apps.must_be_instance_of Array
25
- @library.apps.length.must_equal 2
25
+ @library.apps.length.must_equal 3
26
26
  end
27
27
 
28
- it 'can find applications' do
28
+ it 'can find applications by (partial) filename' do
29
29
  @library.find('Dummy').length.must_equal 1
30
+ @library.find('GTA').length.must_equal 1
31
+ @library.find('Soosiz').length.must_equal 1
32
+ end
33
+
34
+ it 'can get applications by id' do
35
+ @library.get(344186162).filename.must_equal 'GTA.ipa'
36
+ @library.get(331891505).filename.must_equal 'Soosiz.ipa'
37
+ end
38
+
39
+ it 'can load cloud data in bulk' do
40
+ gta = @library.find('GTA').first
41
+ soosiz = @library.find('Soosiz').first
42
+ gta.instance_variable_get('@clouddata').must_be_nil
43
+ soosiz.instance_variable_get('@clouddata').must_be_nil
44
+
45
+ @library.clouddata!
46
+
47
+ gta.instance_variable_get('@clouddata').must_be_instance_of Hash
48
+ soosiz.instance_variable_get('@clouddata').must_be_instance_of Hash
30
49
  end
31
50
 
32
51
  end
metadata CHANGED
@@ -1,110 +1,70 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: app-store-emigrant
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 0
8
- - 4
9
- version: 0.0.4
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Tim Kurvers
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2012-04-19 00:00:00 +02:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-05-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: rubyzip
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- requirements:
16
+ requirement: &70231670236280 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
25
19
  - - ~>
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 0
29
- - 9
30
- - 5
20
+ - !ruby/object:Gem::Version
31
21
  version: 0.9.5
32
22
  type: :runtime
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
35
- name: rainbow
36
23
  prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
38
- requirements:
24
+ version_requirements: *70231670236280
25
+ - !ruby/object:Gem::Dependency
26
+ name: rainbow
27
+ requirement: &70231670235480 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
39
30
  - - ~>
40
- - !ruby/object:Gem::Version
41
- segments:
42
- - 1
43
- - 1
44
- - 3
31
+ - !ruby/object:Gem::Version
45
32
  version: 1.1.3
46
33
  type: :runtime
47
- version_requirements: *id002
48
- - !ruby/object:Gem::Dependency
49
- name: CFPropertyList
50
34
  prerelease: false
51
- requirement: &id003 !ruby/object:Gem::Requirement
52
- requirements:
35
+ version_requirements: *70231670235480
36
+ - !ruby/object:Gem::Dependency
37
+ name: CFPropertyList
38
+ requirement: &70231670234800 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
53
41
  - - ~>
54
- - !ruby/object:Gem::Version
55
- segments:
56
- - 2
57
- - 0
58
- - 17
42
+ - !ruby/object:Gem::Version
59
43
  version: 2.0.17
60
44
  type: :runtime
61
- version_requirements: *id003
62
- - !ruby/object:Gem::Dependency
63
- name: rake
64
45
  prerelease: false
65
- requirement: &id004 !ruby/object:Gem::Requirement
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- segments:
70
- - 0
71
- version: "0"
46
+ version_requirements: *70231670234800
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &70231670234020 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
72
55
  type: :development
73
- version_requirements: *id004
74
- - !ruby/object:Gem::Dependency
75
- name: json
76
56
  prerelease: false
77
- requirement: &id005 !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- segments:
82
- - 0
83
- version: "0"
84
- type: :runtime
85
- version_requirements: *id005
86
- - !ruby/object:Gem::Dependency
87
- name: minitest
88
- prerelease: false
89
- requirement: &id006 !ruby/object:Gem::Requirement
90
- requirements:
91
- - - ">="
92
- - !ruby/object:Gem::Version
93
- segments:
94
- - 0
95
- version: "0"
96
- type: :development
97
- version_requirements: *id006
98
- description: App Store Emigrant will manually attempt to verify whether any of your local mobile applications are out of date, which iTunes - unfortunately - will refuse once you have moved countries
99
- email:
57
+ version_requirements: *70231670234020
58
+ description: App Store Emigrant will manually attempt to verify whether any of your
59
+ local mobile applications are out of date, which iTunes - unfortunately - will refuse
60
+ once you have moved countries
61
+ email:
100
62
  - tim@moonsphere.net
101
- executables:
63
+ executables:
102
64
  - ase
103
65
  extensions: []
104
-
105
66
  extra_rdoc_files: []
106
-
107
- files:
67
+ files:
108
68
  - .gitignore
109
69
  - .travis.yml
110
70
  - CHANGELOG
@@ -116,45 +76,48 @@ files:
116
76
  - bin/ase
117
77
  - lib/app-store-emigrant.rb
118
78
  - lib/app-store-emigrant/app.rb
79
+ - lib/app-store-emigrant/cache.rb
119
80
  - lib/app-store-emigrant/cli.rb
120
81
  - lib/app-store-emigrant/library.rb
121
82
  - lib/app-store-emigrant/version.rb
122
83
  - specs/app.rb
123
84
  - specs/fixtures/dummy-library/Dummy.ipa
85
+ - specs/fixtures/dummy-library/GTA-iTunesMetadata.plist
86
+ - specs/fixtures/dummy-library/GTA.ipa
124
87
  - specs/fixtures/dummy-library/Invalid.extension
125
- - specs/fixtures/dummy-library/Outdated.ipa
126
- - specs/fixtures/dummy-library/iTunesMetadata.plist
88
+ - specs/fixtures/dummy-library/Soosiz-iTunesMetadata.plist
89
+ - specs/fixtures/dummy-library/Soosiz.ipa
127
90
  - specs/helpers.rb
128
91
  - specs/library.rb
129
- has_rdoc: true
130
92
  homepage: https://github.com/timkurvers/app-store-emigrant
131
93
  licenses: []
132
-
133
94
  post_install_message:
134
95
  rdoc_options: []
135
-
136
- require_paths:
96
+ require_paths:
137
97
  - lib
138
- required_ruby_version: !ruby/object:Gem::Requirement
139
- requirements:
140
- - - ">="
141
- - !ruby/object:Gem::Version
142
- segments:
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ segments:
143
105
  - 0
144
- version: "0"
145
- required_rubygems_version: !ruby/object:Gem::Requirement
146
- requirements:
147
- - - ">="
148
- - !ruby/object:Gem::Version
149
- segments:
106
+ hash: -4548139282794052788
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ segments:
150
114
  - 0
151
- version: "0"
115
+ hash: -4548139282794052788
152
116
  requirements: []
153
-
154
117
  rubyforge_project: app-store-emigrant
155
- rubygems_version: 1.3.6
118
+ rubygems_version: 1.8.17
156
119
  signing_key:
157
120
  specification_version: 3
158
- summary: App Store Emigrant will manually attempt to verify whether any of your local mobile applications are out of date
121
+ summary: App Store Emigrant will manually attempt to verify whether any of your local
122
+ mobile applications are out of date
159
123
  test_files: []
160
-