app-store-emigrant 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -0
- data/README.md +15 -7
- data/app-store-emigrant.gemspec +5 -4
- data/bin/ase +1 -1
- data/lib/app-store-emigrant/app.rb +46 -34
- data/lib/app-store-emigrant/cache.rb +18 -11
- data/lib/app-store-emigrant/cli.rb +69 -24
- data/lib/app-store-emigrant/library.rb +38 -28
- data/lib/app-store-emigrant/version.rb +2 -2
- data/specs/app.rb +11 -11
- data/specs/library.rb +12 -10
- metadata +47 -17
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -14,14 +14,16 @@ For more information on the issues faced when moving countries with regards to A
|
|
14
14
|
|
15
15
|
**TL;DR**: For reasons unknown to mankind, Apple has made it rather complicated to update applications bought in different stores, particularly after emigrating. iTunes mentions updates are available, but refuses to actually update these apps when asked to. These applications are also missing from the Purchases-tab.
|
16
16
|
|
17
|
-
App Store Emigrant tries to soothe these pains - albeit only partially - by scanning your iTunes library folder for mobile applications and querying iTunes for the latest versions available. One can then manually go into the store and update each application, one by one.
|
17
|
+
App Store Emigrant tries to soothe these pains - albeit only partially - by scanning your iTunes library folder for mobile applications and querying iTunes for the latest versions available. One can then manually go into the store and update each application, one by one.
|
18
18
|
|
19
|
-
![App Store Emigrant](http://office.moonsphere.net/app-store-emigrant.png)
|
19
|
+
![App Store Emigrant](http://office.moonsphere.net/app-store-emigrant.png?v2)
|
20
20
|
|
21
21
|
Installation
|
22
22
|
------------
|
23
23
|
|
24
|
-
App Store Emigrant is available from RubyGems and can be installed through the command-line.
|
24
|
+
App Store Emigrant is available from RubyGems and can be installed through the command-line.
|
25
|
+
|
26
|
+
Fire up your favourite terminal and run:
|
25
27
|
|
26
28
|
gem install app-store-emigrant
|
27
29
|
|
@@ -33,10 +35,16 @@ Installing on **OSX** and using the **default system Ruby**? Run:
|
|
33
35
|
Usage
|
34
36
|
-----
|
35
37
|
|
36
|
-
Once installed, a new not-so-fantastically named binary called ```ase``` will be added to your path.
|
38
|
+
Once installed, a new not-so-fantastically named binary called ```ase``` will be added to your path.
|
39
|
+
|
40
|
+
Invoke its ```scan``` task to start the verification process:
|
41
|
+
|
42
|
+
ase scan
|
43
|
+
|
44
|
+
If problems arise finding your default library, specify where your mobile applications lurk:
|
37
45
|
|
38
|
-
ase
|
46
|
+
ase scan ~/m00sic
|
39
47
|
|
40
|
-
|
48
|
+
To clear the cache of application metadata, run the scan task with the ```-c``` or ```--clear-cache``` flag.
|
41
49
|
|
42
|
-
ase
|
50
|
+
ase scan --clear-cache
|
data/app-store-emigrant.gemspec
CHANGED
@@ -12,20 +12,21 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.homepage = 'https://github.com/timkurvers/app-store-emigrant'
|
13
13
|
s.summary = 'App Store Emigrant will manually attempt to verify whether any of your local mobile applications are out of date'
|
14
14
|
s.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'
|
15
|
-
|
15
|
+
|
16
16
|
s.rubyforge_project = 'app-store-emigrant'
|
17
17
|
|
18
18
|
s.files = `git ls-files`.split("\n")
|
19
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
21
|
s.require_paths = ['lib']
|
22
|
-
|
22
|
+
|
23
23
|
s.add_dependency 'rubyzip', '~> 0.9'
|
24
24
|
s.add_dependency 'rainbow', '~> 1.1'
|
25
25
|
s.add_dependency 'CFPropertyList', '~> 2.0'
|
26
|
-
|
26
|
+
s.add_dependency 'thor', '~> 0.16.0'
|
27
|
+
|
27
28
|
s.add_development_dependency 'rake'
|
28
|
-
|
29
|
+
|
29
30
|
if RUBY_VERSION < '1.9.2'
|
30
31
|
s.add_dependency 'json'
|
31
32
|
s.add_development_dependency 'minitest'
|
data/bin/ase
CHANGED
@@ -8,89 +8,101 @@ require 'zip/zipfilesystem'
|
|
8
8
|
module AppStore; end
|
9
9
|
|
10
10
|
module AppStore::Emigrant
|
11
|
-
|
11
|
+
|
12
12
|
# Represents a single iTunes mobile application
|
13
13
|
class App
|
14
|
-
|
14
|
+
|
15
15
|
# List of valid extensions for an application
|
16
16
|
VALID_EXTENSIONS = [
|
17
17
|
'.ipa'
|
18
18
|
]
|
19
|
-
|
19
|
+
|
20
20
|
# Regular expression to match a version number in filenames
|
21
21
|
FILENAME_VERSION_REGEX = Regexp.new('([0-9.]+)(?:' + VALID_EXTENSIONS.join('|').gsub!('.', '\.') + ')')
|
22
|
-
|
22
|
+
|
23
23
|
attr_reader :path
|
24
24
|
attr_writer :clouddata
|
25
|
-
|
25
|
+
|
26
26
|
# Initializes application from given path
|
27
27
|
def initialize path
|
28
28
|
@path = Pathname.new path
|
29
29
|
@path = @path.expand_path unless @path.absolute?
|
30
|
-
|
30
|
+
|
31
31
|
# Ensure application exists
|
32
32
|
unless @path.file?
|
33
33
|
raise DoesNotExist, "Given path is not a valid application: #{@path}"
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# Ensure application has valid extension
|
37
37
|
unless VALID_EXTENSIONS.include? @path.extname
|
38
38
|
raise Invalid, "Given application does not have a valid extension: #{@path}"
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
@metadata = nil
|
42
42
|
@clouddata = nil
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
# Filename of this application (including extension)
|
46
46
|
def filename
|
47
47
|
unless @filename
|
48
48
|
@filename = @path.basename.to_s
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
@filename
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
# Unique application id
|
55
55
|
def id
|
56
56
|
metadata['itemId'] rescue nil
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
# Name of this application
|
60
60
|
def name
|
61
61
|
metadata['itemName'] rescue nil
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
# Whether this application is valid (validates metadata, id and name)
|
65
65
|
def valid?
|
66
66
|
metadata && id && name rescue false
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
# Version of this application
|
70
70
|
def version
|
71
71
|
unless @version
|
72
|
-
|
72
|
+
|
73
73
|
# Extract version information (if available)
|
74
74
|
@version = metadata['bundleShortVersionString'] || metadata['bundleVersion'] rescue nil
|
75
|
-
|
75
|
+
|
76
76
|
# Otherwise, use the filename
|
77
77
|
unless @version
|
78
78
|
@version = filename[FILENAME_VERSION_REGEX, 1]
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
@version
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
|
+
# Property list name
|
87
|
+
def plist
|
88
|
+
Pathname.new(filename).basename('.ipa').to_s + '.plist'
|
89
|
+
end
|
90
|
+
|
91
|
+
# Whether this application's metadata is cached
|
92
|
+
def cached?
|
93
|
+
Cache.has? plist
|
94
|
+
end
|
95
|
+
|
96
|
+
# Forcefully caches this application's metadata
|
97
|
+
def cache!
|
98
|
+
metadata
|
99
|
+
end
|
100
|
+
|
86
101
|
# Lazily loads local metadata for this application from its iTunesMetadata.plist
|
87
102
|
def metadata
|
88
103
|
unless @metadata
|
89
|
-
|
90
|
-
|
91
|
-
plist = Pathname.new(filename).basename('.ipa').to_s + '.plist'
|
92
|
-
|
93
|
-
unless Cache.has?(plist)
|
104
|
+
|
105
|
+
unless cached?
|
94
106
|
begin
|
95
107
|
Zip::ZipFile.open(@path) do |zip|
|
96
108
|
zip.extract('iTunesMetadata.plist', Cache.path_to(plist))
|
@@ -99,13 +111,13 @@ module AppStore::Emigrant
|
|
99
111
|
raise Invalid, e.message
|
100
112
|
end
|
101
113
|
end
|
102
|
-
|
114
|
+
|
103
115
|
@metadata = CFPropertyList.native_types(CFPropertyList::List.new(:file => Cache.path_to(plist)).value)
|
104
116
|
end
|
105
|
-
|
117
|
+
|
106
118
|
@metadata
|
107
119
|
end
|
108
|
-
|
120
|
+
|
109
121
|
# Lazily queries Apple's iTunes Store API for latest cloud data
|
110
122
|
# Note: Clouddata may be nil if the application was removed from the store
|
111
123
|
def clouddata
|
@@ -113,26 +125,26 @@ module AppStore::Emigrant
|
|
113
125
|
response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + id.to_s)
|
114
126
|
@clouddata = JSON.parse(response)['results'].first
|
115
127
|
end
|
116
|
-
|
128
|
+
|
117
129
|
@clouddata
|
118
130
|
end
|
119
|
-
|
131
|
+
|
120
132
|
# Version available in the cloud
|
121
133
|
def cloudversion
|
122
134
|
clouddata && clouddata['version']
|
123
135
|
end
|
124
|
-
|
136
|
+
|
125
137
|
# Whether this application is outdated
|
126
138
|
def outdated?
|
127
139
|
return cloudversion && version != cloudversion
|
128
140
|
end
|
129
|
-
|
141
|
+
|
130
142
|
# Raised when an application does not exist
|
131
143
|
class DoesNotExist < StandardError; end
|
132
|
-
|
144
|
+
|
133
145
|
# Raised when an application is invalid (e.g non-valid extension)
|
134
146
|
class Invalid < StandardError; end
|
135
|
-
|
147
|
+
|
136
148
|
end
|
137
|
-
|
149
|
+
|
138
150
|
end
|
@@ -3,33 +3,40 @@ require 'pathname'
|
|
3
3
|
module AppStore; end
|
4
4
|
|
5
5
|
module AppStore::Emigrant
|
6
|
-
|
6
|
+
|
7
7
|
# Cache mechanism
|
8
8
|
class Cache
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
LOCATION = begin
|
11
11
|
path = Pathname.new('~/.ase-cache').expand_path
|
12
12
|
unless path.directory?
|
13
13
|
path.mkpath
|
14
14
|
end
|
15
15
|
path
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# Whether cache has this item
|
19
19
|
def self.has? item
|
20
|
-
|
20
|
+
LOCATION.join(item).file?
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
# Path to given item (whether existent or not)
|
24
24
|
def self.path_to item
|
25
|
-
|
25
|
+
LOCATION.join(item).to_s
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
# Forcefully clears the cache
|
29
29
|
def self.clear!
|
30
|
-
|
30
|
+
LOCATION.children.each do |item|
|
31
|
+
item.delete if item.file?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Number of items in the cache
|
36
|
+
def self.count
|
37
|
+
LOCATION.children.select { |item| item.file? }.length
|
31
38
|
end
|
32
|
-
|
39
|
+
|
33
40
|
end
|
34
|
-
|
41
|
+
|
35
42
|
end
|
@@ -1,44 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'rainbow'
|
4
|
+
require 'thor'
|
2
5
|
|
3
6
|
module AppStore; end
|
4
7
|
|
5
8
|
module AppStore::Emigrant
|
6
|
-
|
9
|
+
|
7
10
|
# Represents the command line interface
|
8
|
-
class CLI
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
class CLI < Thor
|
12
|
+
|
13
|
+
desc 'scan [LIBRARY PATH]', 'Scans an iTunes library and reports which of its mobile applications are out of date'
|
14
|
+
method_option :clear_cache, :type => :boolean, :default => false, :aliases => '-c', :desc => 'Clears application cache prior to scanning'
|
15
|
+
def scan path = nil
|
16
|
+
|
17
|
+
# Ensure all output is immediately flushed
|
18
|
+
$stdout.sync = true
|
19
|
+
|
16
20
|
# Use the default library on this system when no path is specified
|
17
21
|
library = path ? Library.new(path) : Library.default
|
18
|
-
|
19
|
-
#
|
22
|
+
|
23
|
+
# Clear cache if requested
|
24
|
+
if options[:clear_cache]
|
25
|
+
Cache.clear!
|
26
|
+
end
|
27
|
+
|
28
|
+
# Verify cache integrity
|
29
|
+
puts 'Verifying cache integrity..'
|
30
|
+
|
31
|
+
# Forcefully cache metadata for each application that is not yet cached
|
32
|
+
library.apps.each do |app|
|
33
|
+
|
34
|
+
unless app.cached?
|
35
|
+
begin
|
36
|
+
app.cache!
|
37
|
+
puts " + #{app.name} v#{app.version}"
|
38
|
+
rescue App::Invalid => e
|
39
|
+
puts " ! Cannot cache #{app.filename}: #{e.message}".foreground(:red).bright
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# Print cache statistics
|
46
|
+
puts "Done. Currently caching #{Cache.count} applications on your system."
|
47
|
+
|
48
|
+
puts
|
49
|
+
|
50
|
+
# Since all apps are cached, load cloud data in bulk
|
20
51
|
library.clouddata!
|
21
|
-
|
52
|
+
|
53
|
+
# Print library statistics
|
54
|
+
puts "Your library contains #{library.valid_apps.length} valid applications, of which #{library.outdated_apps.length} are outdated:"
|
55
|
+
|
22
56
|
# Loop through all applications
|
23
57
|
library.apps.each do |app|
|
24
|
-
|
58
|
+
|
25
59
|
if app.valid?
|
26
|
-
|
27
|
-
#
|
28
|
-
|
60
|
+
|
61
|
+
# Generate color-coded version
|
62
|
+
version = "v#{app.version}".foreground(app.outdated? ? :red : :green)
|
63
|
+
if app.outdated?
|
64
|
+
version = version.bright
|
65
|
+
end
|
66
|
+
|
67
|
+
# Print application name, version and whether it's outdated
|
68
|
+
print " · #{app.name} #{version}"
|
29
69
|
if app.outdated?
|
30
|
-
print "
|
70
|
+
print " · v#{app.cloudversion} available".foreground(:white)
|
31
71
|
end
|
72
|
+
|
73
|
+
puts
|
32
74
|
else
|
33
|
-
|
75
|
+
puts " ! #{app.filename} is invalid (metadata, id or name missing)".foreground(:red).bright
|
34
76
|
end
|
35
|
-
|
36
|
-
puts
|
37
|
-
|
77
|
+
|
38
78
|
end
|
39
|
-
|
79
|
+
|
40
80
|
end
|
41
|
-
|
81
|
+
|
82
|
+
desc 'version', 'Prints version information'
|
83
|
+
def version
|
84
|
+
puts "App Store Emigrant v#{AppStore::Emigrant::VERSION}"
|
85
|
+
end
|
86
|
+
|
42
87
|
end
|
43
|
-
|
88
|
+
|
44
89
|
end
|
@@ -5,34 +5,44 @@ require 'pathname'
|
|
5
5
|
module AppStore; end
|
6
6
|
|
7
7
|
module AppStore::Emigrant
|
8
|
-
|
8
|
+
|
9
9
|
# Represents a single iTunes mobile applications library
|
10
10
|
class Library
|
11
|
-
|
11
|
+
|
12
12
|
attr_reader :path
|
13
|
-
|
13
|
+
|
14
14
|
# Initializes library from given path
|
15
15
|
def initialize path
|
16
16
|
@path = Pathname.new path
|
17
17
|
@path = @path.expand_path unless @path.absolute?
|
18
|
-
|
18
|
+
|
19
19
|
# Ensure library is a valid directory
|
20
20
|
unless @path.directory?
|
21
21
|
raise DoesNotExist, "Given path is not a valid mobile applications library: #{@path}"
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
@apps = nil
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
# Retrieves a list of applications
|
28
28
|
def apps
|
29
29
|
unless @apps
|
30
30
|
load!
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
@apps
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
|
+
# Retrieves a list of valid applications
|
37
|
+
def valid_apps
|
38
|
+
apps.select { |app| app.valid? }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Retrieves a list of outdated applications
|
42
|
+
def outdated_apps
|
43
|
+
apps.select { |app| app.valid? && app.outdated? }
|
44
|
+
end
|
45
|
+
|
36
46
|
# Forcefully loads applications from disk
|
37
47
|
def load!
|
38
48
|
@apps = []
|
@@ -45,15 +55,15 @@ module AppStore::Emigrant
|
|
45
55
|
end
|
46
56
|
self
|
47
57
|
end
|
48
|
-
|
58
|
+
|
49
59
|
# Populates applications' cloud data in bulk
|
50
60
|
def clouddata!
|
51
|
-
|
61
|
+
|
52
62
|
# Collect all application ids, skipping any invalid ones
|
53
63
|
ids = apps.collect do |app|
|
54
64
|
app.id
|
55
65
|
end.compact
|
56
|
-
|
66
|
+
|
57
67
|
# Queries Apple's iTunes Store API for latest cloud data using undocumented bulk method
|
58
68
|
response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + ids.join(','))
|
59
69
|
results = JSON.parse(response)['results']
|
@@ -63,66 +73,66 @@ module AppStore::Emigrant
|
|
63
73
|
end
|
64
74
|
end
|
65
75
|
end
|
66
|
-
|
76
|
+
|
67
77
|
# Searches for application with given id
|
68
78
|
def get id
|
69
79
|
apps.select do |app|
|
70
80
|
app.id == id
|
71
81
|
end.first
|
72
82
|
end
|
73
|
-
|
83
|
+
|
74
84
|
# Searches for applications containing given snippet in filename
|
75
85
|
def find snippet
|
76
86
|
apps.select do |app|
|
77
87
|
app.filename.include? snippet
|
78
88
|
end
|
79
89
|
end
|
80
|
-
|
90
|
+
|
81
91
|
# Returns the default library (if any) for this system
|
82
92
|
# See Apple's support documents as to where libraries can be found
|
83
93
|
# - http://support.apple.com/kb/ht1391
|
84
94
|
# - http://support.apple.com/kb/ht3847
|
85
95
|
def self.default
|
86
|
-
|
96
|
+
|
87
97
|
# Use the homedir provided through the environment
|
88
98
|
homedir = ENV['HOME']
|
89
|
-
|
99
|
+
|
90
100
|
# List all locations
|
91
101
|
locations = [
|
92
|
-
|
102
|
+
|
93
103
|
# Mac OSX and Windows Vista
|
94
104
|
"#{homedir}/Music/iTunes/iTunes Media/Mobile Applications",
|
95
|
-
|
105
|
+
|
96
106
|
# Windows 7
|
97
107
|
"#{homedir}/My Music/iTunes/iTunes Media/Mobile Applications",
|
98
|
-
|
108
|
+
|
99
109
|
# Windows XP
|
100
110
|
"#{homedir}/My Documents/My Music/iTunes/iTunes Media/Mobile Applications",
|
101
|
-
|
111
|
+
|
102
112
|
# Mac OSX and Windows Vista (prior to iTunes 9)
|
103
113
|
"#{homedir}/Music/iTunes/Mobile Applications",
|
104
|
-
|
114
|
+
|
105
115
|
# Windows 7 (prior to iTunes 9)
|
106
116
|
"#{homedir}/My Music/iTunes/Mobile Applications",
|
107
|
-
|
117
|
+
|
108
118
|
# Windows XP (prior to iTunes 9)
|
109
119
|
"#{homedir}/My Documents/My Music/iTunes/Mobile Applications",
|
110
|
-
|
120
|
+
|
111
121
|
]
|
112
|
-
|
122
|
+
|
113
123
|
# Raise exception if no default library could be found
|
114
124
|
path = Dir.glob(locations).first
|
115
125
|
unless path
|
116
126
|
raise DoesNotExist, 'Could not locate default iTunes mobile applications library'
|
117
127
|
end
|
118
|
-
|
128
|
+
|
119
129
|
# Return an instance of this default library
|
120
130
|
Library.new path
|
121
131
|
end
|
122
|
-
|
132
|
+
|
123
133
|
# Raised when a library does not exist
|
124
134
|
class DoesNotExist < StandardError; end
|
125
|
-
|
135
|
+
|
126
136
|
end
|
127
|
-
|
137
|
+
|
128
138
|
end
|
data/specs/app.rb
CHANGED
@@ -6,56 +6,56 @@ require 'minitest/autorun'
|
|
6
6
|
include AppStore::Emigrant
|
7
7
|
|
8
8
|
describe App do
|
9
|
-
|
9
|
+
|
10
10
|
LIBRARY = ROOT + '/fixtures/dummy-library'
|
11
|
-
|
11
|
+
|
12
12
|
before do
|
13
13
|
@dummy = App.new(LIBRARY + '/Dummy.ipa')
|
14
14
|
@gta = App.new(LIBRARY + '/GTA.ipa')
|
15
15
|
@soosiz = App.new(LIBRARY + '/Soosiz.ipa')
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
it 'must be a valid file on disk' do
|
19
19
|
lambda do
|
20
20
|
App.new(LIBRARY + '/Non-Existent.ipa')
|
21
21
|
end.must_raise App::DoesNotExist
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
it 'must have a valid extension' do
|
25
25
|
lambda do
|
26
26
|
App.new(LIBRARY + '/Non-Existent.ipa')
|
27
27
|
end.must_raise App::DoesNotExist
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
it 'can determine its own filename' do
|
31
31
|
@gta.filename.must_equal 'GTA.ipa'
|
32
32
|
@soosiz.filename.must_equal 'Soosiz.ipa'
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
it 'will report invalid structures' do
|
36
36
|
@dummy.valid?.must_equal false
|
37
37
|
@dummy.id.must_equal nil
|
38
38
|
@dummy.version.must_equal nil
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
it 'can extract its name' do
|
42
42
|
@gta.name.must_equal 'Grand Theft Auto: Chinatown Wars'
|
43
43
|
@soosiz.name.must_equal 'Soosiz'
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
it 'can query local metadata' do
|
47
47
|
@gta.version.must_equal '0.9'
|
48
48
|
@soosiz.version.must_equal '1.1'
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
it 'can load cloud data' do
|
52
52
|
@gta.cloudversion.must_equal '1.1.0'
|
53
53
|
@soosiz.cloudversion.must_equal '1.3'
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
it 'can determine whether it is outdated' do
|
57
57
|
@gta.outdated?.must_equal true
|
58
58
|
@soosiz.outdated?.must_equal true
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
end
|
data/specs/library.rb
CHANGED
@@ -6,46 +6,48 @@ require 'minitest/autorun'
|
|
6
6
|
include AppStore::Emigrant
|
7
7
|
|
8
8
|
describe Library do
|
9
|
-
|
9
|
+
|
10
10
|
before do
|
11
11
|
@library = Library.new(ROOT + '/fixtures/dummy-library')
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
it 'must be located in a valid directory' do
|
15
15
|
@library.must_be_instance_of Library
|
16
|
-
|
16
|
+
|
17
17
|
lambda do
|
18
18
|
Library.new(ROOT + '/non-existent-library')
|
19
19
|
end.must_raise Library::DoesNotExist
|
20
20
|
end
|
21
|
-
|
21
|
+
|
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
25
|
@library.apps.length.must_equal 3
|
26
|
+
@library.valid_apps.must_be_instance_of Array
|
27
|
+
@library.valid_apps.length.must_equal 2
|
26
28
|
end
|
27
|
-
|
29
|
+
|
28
30
|
it 'can find applications by (partial) filename' do
|
29
31
|
@library.find('Dummy').length.must_equal 1
|
30
32
|
@library.find('GTA').length.must_equal 1
|
31
33
|
@library.find('Soosiz').length.must_equal 1
|
32
34
|
end
|
33
|
-
|
35
|
+
|
34
36
|
it 'can get applications by id' do
|
35
37
|
@library.get(344186162).filename.must_equal 'GTA.ipa'
|
36
38
|
@library.get(331891505).filename.must_equal 'Soosiz.ipa'
|
37
39
|
end
|
38
|
-
|
40
|
+
|
39
41
|
it 'can load cloud data in bulk' do
|
40
42
|
gta = @library.find('GTA').first
|
41
43
|
soosiz = @library.find('Soosiz').first
|
42
44
|
gta.instance_variable_get('@clouddata').must_be_nil
|
43
45
|
soosiz.instance_variable_get('@clouddata').must_be_nil
|
44
|
-
|
46
|
+
|
45
47
|
@library.clouddata!
|
46
|
-
|
48
|
+
|
47
49
|
gta.instance_variable_get('@clouddata').must_be_instance_of Hash
|
48
50
|
soosiz.instance_variable_get('@clouddata').must_be_instance_of Hash
|
49
51
|
end
|
50
|
-
|
52
|
+
|
51
53
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: app-store-emigrant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-12-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubyzip
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0.9'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.9'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: rainbow
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ~>
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: '1.1'
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.1'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: CFPropertyList
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ~>
|
@@ -43,10 +53,31 @@ dependencies:
|
|
43
53
|
version: '2.0'
|
44
54
|
type: :runtime
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: thor
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.16.0
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.16.0
|
47
78
|
- !ruby/object:Gem::Dependency
|
48
79
|
name: rake
|
49
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
50
81
|
none: false
|
51
82
|
requirements:
|
52
83
|
- - ! '>='
|
@@ -54,7 +85,12 @@ dependencies:
|
|
54
85
|
version: '0'
|
55
86
|
type: :development
|
56
87
|
prerelease: false
|
57
|
-
version_requirements:
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
58
94
|
description: App Store Emigrant will manually attempt to verify whether any of your
|
59
95
|
local mobile applications are out of date, which iTunes - unfortunately - will refuse
|
60
96
|
once you have moved countries
|
@@ -101,21 +137,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
137
|
- - ! '>='
|
102
138
|
- !ruby/object:Gem::Version
|
103
139
|
version: '0'
|
104
|
-
segments:
|
105
|
-
- 0
|
106
|
-
hash: -972531457948704449
|
107
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
141
|
none: false
|
109
142
|
requirements:
|
110
143
|
- - ! '>='
|
111
144
|
- !ruby/object:Gem::Version
|
112
145
|
version: '0'
|
113
|
-
segments:
|
114
|
-
- 0
|
115
|
-
hash: -972531457948704449
|
116
146
|
requirements: []
|
117
147
|
rubyforge_project: app-store-emigrant
|
118
|
-
rubygems_version: 1.8.
|
148
|
+
rubygems_version: 1.8.24
|
119
149
|
signing_key:
|
120
150
|
specification_version: 3
|
121
151
|
summary: App Store Emigrant will manually attempt to verify whether any of your local
|