app-store-emigrant 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # For the actual gemspec see app-store-emigrant.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+
2
+ Licensed under the MIT license
3
+
4
+ Copyright (c) 2012 Tim Kurvers <tim@moonsphere.net>
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+
2
+ App Store Emigrant
3
+ ==================
4
+
5
+ Once this gem is even remotely stable, this README will be updated.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.test_files = FileList['specs/*.rb']
6
+ t.verbose = true
7
+ t.libs << 'specs'
8
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ $:.push File.expand_path('../lib', __FILE__)
4
+
5
+ require 'app-store-emigrant/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'app-store-emigrant'
9
+ s.version = AppStore::Emigrant::VERSION
10
+ s.authors = ['Tim Kurvers']
11
+ s.email = ['tim@moonsphere.net']
12
+ s.homepage = 'http://moonsphere.net'
13
+ s.summary = 'App Store Emigrant will manually attempt to verify whether any of your local mobile applications are out of date'
14
+ s.description = 'Moved countries and now iTunes refuses to notify you properly with updates? Rest assured, using App Store Emigrant you know exactly which apps are outdated'
15
+
16
+ s.rubyforge_project = 'app-store-emigrant'
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ['lib']
22
+
23
+ s.add_dependency 'rubyzip', '~> 0.9.5'
24
+ s.add_dependency 'rainbow', '~> 1.1.3'
25
+ s.add_dependency 'CFPropertyList', '~> 2.0.17'
26
+
27
+ s.add_development_dependency 'rake'
28
+
29
+ end
data/bin/ase ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'app-store-emigrant'
4
+
5
+ AppStore::Emigrant::CLI.new ARGV
@@ -0,0 +1,121 @@
1
+ require 'cfpropertylist'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'pathname'
5
+ require 'zip/zip'
6
+ require 'zip/zipfilesystem'
7
+
8
+ module AppStore; end
9
+
10
+ module AppStore::Emigrant
11
+
12
+ # Represents a single iTunes mobile application
13
+ class App
14
+
15
+ # List of valid extensions for an application
16
+ VALID_EXTENSIONS = [
17
+ '.ipa'
18
+ ]
19
+
20
+ # Regular expression to match a version number in filenames
21
+ FILENAME_VERSION_REGEX = Regexp.new('(?<version>[0-9.]+)(?:' + VALID_EXTENSIONS.join('|') + ')')
22
+
23
+ attr_reader :path
24
+
25
+ # Initializes application from given path
26
+ def initialize path
27
+ @path = Pathname.new path
28
+ @path = @path.expand_path unless @path.absolute?
29
+
30
+ # Ensure application exists
31
+ unless @path.file?
32
+ raise DoesNotExist, "Given path is not a valid application: #{@path}"
33
+ end
34
+
35
+ # Ensure application has valid extension
36
+ unless VALID_EXTENSIONS.include? @path.extname
37
+ raise Invalid, "Given application does not have a valid extension: #{@path}"
38
+ end
39
+
40
+ @metadata = nil
41
+ @clouddata = nil
42
+ end
43
+
44
+ # Filename of this application (including extension)
45
+ def filename
46
+ unless @filename
47
+ @filename = @path.basename().to_s
48
+ end
49
+
50
+ @filename
51
+ end
52
+
53
+ # Name of this application (from its metadata)
54
+ def name
55
+ metadata['itemName']
56
+ end
57
+
58
+ # Version of this application
59
+ def version
60
+ unless @version
61
+
62
+ # Extract version information (if available)
63
+ @version = metadata['bundleShortVersionString'] || metadata['bundleVersion']
64
+
65
+ # Otherwise, use the filename
66
+ unless @version
67
+ @version = filename[FILENAME_VERSION_REGEX, :version]
68
+ end
69
+
70
+ end
71
+
72
+ @version
73
+ end
74
+
75
+ # Lazily loads local metadata for this application from its iTunesMetadata.plist
76
+ def metadata
77
+ 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)
83
+ end
84
+ rescue Zip::ZipError => e
85
+ raise Invalid, e.message
86
+ end
87
+ end
88
+
89
+ @metadata
90
+ end
91
+
92
+ # Lazily queries Apple's iTunes Store API for latest cloud data
93
+ # Note: Clouddata may be nil if the application was removed from the store
94
+ def clouddata
95
+ unless @clouddata
96
+ response = Net::HTTP.get('itunes.apple.com', '/lookup?id=' + metadata['itemId'].to_s)
97
+ @clouddata = JSON.parse(response)['results'].first
98
+ end
99
+
100
+ @clouddata
101
+ end
102
+
103
+ # Version available in the cloud
104
+ def cloudversion
105
+ clouddata && clouddata['version']
106
+ end
107
+
108
+ # Whether this application is outdated
109
+ def outdated?
110
+ return cloudversion && version != cloudversion
111
+ end
112
+
113
+ # Raised when an application does not exist
114
+ class DoesNotExist < StandardError; end
115
+
116
+ # Raised when an application is invalid (e.g non-valid extension)
117
+ class Invalid < StandardError; end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,36 @@
1
+ require 'rainbow'
2
+
3
+ module AppStore; end
4
+
5
+ module AppStore::Emigrant
6
+
7
+ # Represents the command line interface
8
+ class CLI
9
+
10
+ # Initializes CLI instance with given arguments
11
+ def initialize args
12
+
13
+ # Extract the path (if any)
14
+ path, = args
15
+
16
+ # Use the default library on this system when no path is specified
17
+ library = path ? Library.new(path) : Library.default
18
+
19
+ # Loop through all applications
20
+ library.apps.each do |app|
21
+
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)"
26
+ end
27
+ puts
28
+
29
+ sleep(1)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,89 @@
1
+ require 'pathname'
2
+
3
+ module AppStore; end
4
+
5
+ module AppStore::Emigrant
6
+
7
+ # Represents a single iTunes mobile applications library
8
+ class Library
9
+
10
+ attr_reader :path
11
+
12
+ # Initializes library from given path
13
+ def initialize path
14
+ @path = Pathname.new path
15
+ @path = @path.expand_path unless @path.absolute?
16
+
17
+ # Ensure library is a valid directory
18
+ unless @path.directory?
19
+ raise DoesNotExist, "Given path is not a valid library directory: #{@path}"
20
+ end
21
+
22
+ @apps = nil
23
+ end
24
+
25
+ # Retrieves a list of applications
26
+ def apps
27
+ unless @apps
28
+ load!
29
+ end
30
+
31
+ @apps
32
+ end
33
+
34
+ # Forcefully loads applications from disk
35
+ def load!
36
+ @apps = []
37
+ @path.each_child do |item|
38
+ if item.file?
39
+ begin
40
+ @apps << App.new(item)
41
+ rescue App::Invalid; end
42
+ end
43
+ end
44
+ self
45
+ end
46
+
47
+ # Searches for applications containing given snippet in filename
48
+ def find snippet
49
+ apps.select do |app|
50
+ app.filename.include? snippet
51
+ end
52
+ end
53
+
54
+ # Returns the default library (if any) for this system
55
+ # See Apple's support document (http://support.apple.com/kb/ht1391) as to where libraries can be found
56
+ def self.default
57
+
58
+ # Use the homedir provided through the environment
59
+ homedir = ENV['HOME']
60
+
61
+ # List all locations
62
+ locations = [
63
+
64
+ # Mac OSX and Windows Vista
65
+ "#{homedir}/Music/iTunes/iTunes Media/Mobile Applications/",
66
+
67
+ # Windows 7
68
+ "#{homedir}/My Music/iTunes/iTunes Media/Mobile Applications/",
69
+
70
+ # Windows XP
71
+ "#{homedir}/My Documents/My Music/iTunes/iTunes Media/Mobile Applications/"
72
+ ]
73
+
74
+ # Raise exception if no default library could be found
75
+ path = Dir.glob(locations).first
76
+ unless path
77
+ raise DoesNotExist, 'Could not locate default iTunes library'
78
+ end
79
+
80
+ # Return an instance of this default library
81
+ Library.new path
82
+ end
83
+
84
+ # Raised when a library does not exist
85
+ class DoesNotExist < StandardError; end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module AppStore; end
3
+
4
+ module AppStore::Emigrant
5
+
6
+ VERSION = '0.0.1'
7
+
8
+ end
@@ -0,0 +1,11 @@
1
+
2
+ module AppStore; end
3
+
4
+ module AppStore::Emigrant
5
+
6
+ autoload :App, 'app-store-emigrant/app'
7
+ autoload :CLI, 'app-store-emigrant/cli'
8
+ autoload :Library, 'app-store-emigrant/library'
9
+ autoload :VERSION, 'app-store-emigrant/version'
10
+
11
+ end
data/specs/app.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'app-store-emigrant'
2
+ require 'helpers'
3
+ require 'minitest/spec'
4
+ require 'minitest/autorun'
5
+
6
+ include AppStore::Emigrant
7
+
8
+ describe App do
9
+
10
+ LIBRARY = ROOT + '/fixtures/dummy-library'
11
+
12
+ before do
13
+ @dummy = App.new(LIBRARY + '/Dummy.ipa')
14
+ @outdated = App.new(LIBRARY + '/Outdated.ipa')
15
+ end
16
+
17
+ it 'must be a valid file on disk' do
18
+ lambda do
19
+ App.new(LIBRARY + '/Non-Existent.ipa')
20
+ end.must_raise App::DoesNotExist
21
+ end
22
+
23
+ it 'must have a valid extension' do
24
+ lambda do
25
+ App.new(LIBRARY + '/Non-Existent.ipa')
26
+ end.must_raise App::DoesNotExist
27
+ end
28
+
29
+ it 'can determine its own filename' do
30
+ @dummy.filename.must_equal 'Dummy.ipa'
31
+ end
32
+
33
+ it 'can handle invalid structures' do
34
+ lambda do
35
+ @dummy.version
36
+ end.must_raise App::Invalid
37
+ end
38
+
39
+ it 'can query local metadata' do
40
+ @outdated.version.must_equal '0.9'
41
+ end
42
+
43
+ it 'can query clouddata' do
44
+ @outdated.cloudversion.must_equal '1.1.0'
45
+ end
46
+
47
+ it 'can determine whether it is outdated' do
48
+ @outdated.outdated?.must_equal true
49
+ end
50
+
51
+ end
File without changes
File without changes
@@ -0,0 +1,10 @@
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>0.9</string>
7
+ <key>itemId</key>
8
+ <integer>344186162</integer>
9
+ </dict>
10
+ </plist>
data/specs/helpers.rb ADDED
@@ -0,0 +1,3 @@
1
+
2
+ # Helper constant holding path to tests-folder
3
+ ROOT = File.dirname __FILE__
data/specs/library.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'app-store-emigrant'
2
+ require 'helpers'
3
+ require 'minitest/spec'
4
+ require 'minitest/autorun'
5
+
6
+ include AppStore::Emigrant
7
+
8
+ describe Library do
9
+
10
+ before do
11
+ @library = Library.new(ROOT + '/fixtures/dummy-library')
12
+ end
13
+
14
+ it 'must be located in a valid directory' do
15
+ @library.must_be_instance_of Library
16
+
17
+ lambda do
18
+ Library.new(ROOT + '/non-existent-library')
19
+ end.must_raise Library::DoesNotExist
20
+ end
21
+
22
+ it 'will gracefully and lazily load applications' do
23
+ @library.instance_variable_get('@apps').must_be_nil
24
+ @library.apps.must_be_instance_of Array
25
+ @library.apps.length.must_equal 2
26
+ end
27
+
28
+ it 'can find applications' do
29
+ @library.find('Dummy').length.must_equal 1
30
+ end
31
+
32
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: app-store-emigrant
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tim Kurvers
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rubyzip
16
+ requirement: &70227983767300 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70227983767300
25
+ - !ruby/object:Gem::Dependency
26
+ name: rainbow
27
+ requirement: &70227984172040 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.3
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70227984172040
36
+ - !ruby/object:Gem::Dependency
37
+ name: CFPropertyList
38
+ requirement: &70227984171580 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.0.17
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70227984171580
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &70227984171200 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70227984171200
58
+ description: Moved countries and now iTunes refuses to notify you properly with updates?
59
+ Rest assured, using App Store Emigrant you know exactly which apps are outdated
60
+ email:
61
+ - tim@moonsphere.net
62
+ executables:
63
+ - ase
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - .gitignore
68
+ - Gemfile
69
+ - LICENSE
70
+ - README.md
71
+ - Rakefile
72
+ - app-store-emigrant.gemspec
73
+ - bin/ase
74
+ - lib/app-store-emigrant.rb
75
+ - lib/app-store-emigrant/app.rb
76
+ - lib/app-store-emigrant/cli.rb
77
+ - lib/app-store-emigrant/library.rb
78
+ - lib/app-store-emigrant/version.rb
79
+ - specs/app.rb
80
+ - specs/fixtures/dummy-library/Dummy.ipa
81
+ - specs/fixtures/dummy-library/Invalid.extension
82
+ - specs/fixtures/dummy-library/Outdated.ipa
83
+ - specs/fixtures/dummy-library/iTunesMetadata.plist
84
+ - specs/helpers.rb
85
+ - specs/library.rb
86
+ homepage: http://moonsphere.net
87
+ licenses: []
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project: app-store-emigrant
106
+ rubygems_version: 1.8.15
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: App Store Emigrant will manually attempt to verify whether any of your local
110
+ mobile applications are out of date
111
+ test_files: []