app-store-emigrant 0.0.1

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.
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: []