itunes_store_transporter 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes +21 -1
- data/README.rdoc +6 -5
- data/lib/itunes/store/transporter.rb +1 -0
- data/lib/itunes/store/transporter/command.rb +4 -2
- data/lib/itunes/store/transporter/command/providers.rb +2 -1
- data/lib/itunes/store/transporter/command/schema.rb +3 -2
- data/lib/itunes/store/transporter/command/status.rb +18 -25
- data/lib/itunes/store/transporter/command/status_all.rb +21 -0
- data/lib/itunes/store/transporter/command/upload.rb +6 -0
- data/lib/itunes/store/transporter/command/verify.rb +1 -0
- data/lib/itunes/store/transporter/errors.rb +9 -8
- data/lib/itunes/store/transporter/itms_transporter.rb +9 -2
- data/lib/itunes/store/transporter/output_parser.rb +1 -0
- data/lib/itunes/store/transporter/shell.rb +17 -3
- data/lib/itunes/store/transporter/version.rb +1 -1
- data/lib/itunes/store/transporter/xml/status.rb +145 -0
- data/spec/command_spec.rb +156 -62
- data/spec/fixtures/status.yml +55 -23
- data/spec/output_parser_spec.rb +25 -9
- data/spec/shell_spec.rb +17 -12
- data/spec/spec_helper.rb +12 -1
- data/spec/transporter_spec.rb +19 -0
- data/spec/xml_status_spec.rb +60 -0
- data/spec/xml_status_spec.rb~ +22 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1706c7254e4158ee42930c52f11f48bd22ddb409
|
4
|
+
data.tar.gz: fb2b7760daa79915b5628f3631b622a4d978b375
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fb763cde86787bb12b99fdf3e1cebf2e7213d0458cccef43f9c17edbf8903ca32244829d403672ce813f5027c93882b8635a6288f5fb8bdf89645152c934a37
|
7
|
+
data.tar.gz: b3b59d68150bb088732c2c9a6f0822b59e843cdebd7e13a7439c54070baa855bb0e3da3e88fe4a43e39f8215afef7204b8b44253eedaca821132be88b414dd47
|
data/Changes
CHANGED
@@ -1,7 +1,27 @@
|
|
1
|
+
v0.2.0 2017-02-07
|
2
|
+
--------------------
|
3
|
+
Changes:
|
4
|
+
* Drop support for Ruby 1.8.7
|
5
|
+
* Status command returns an Array of Hashes
|
6
|
+
* Upload command returns true on success, not the command's output
|
7
|
+
|
8
|
+
Enhancements:
|
9
|
+
* Support for statusAll mode
|
10
|
+
* Status command XML parser
|
11
|
+
|
12
|
+
Bug Fixes:
|
13
|
+
* Status command only returned a single component status
|
14
|
+
|
15
|
+
This release was brought to you by press9 media solutions berlin - http://press9.de.
|
16
|
+
Thanks press9!
|
17
|
+
|
1
18
|
v0.1.3 2015-03-31
|
2
19
|
--------------------
|
3
20
|
Enhancements:
|
4
|
-
* Add the :batch option to support batch operations
|
21
|
+
* Add the :batch option to support batch operations
|
22
|
+
|
23
|
+
This release was brought to you by press9 media solutions berlin - http://press9.de.
|
24
|
+
Thanks press9!
|
5
25
|
|
6
26
|
v0.1.2 2015-03-07
|
7
27
|
--------------------
|
data/README.rdoc
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
= iTunes::Store::Transporter
|
2
2
|
|
3
3
|
{<img src="https://secure.travis-ci.org/sshaw/itunes_store_transporter.svg"/>}[http://travis-ci.org/sshaw/itunes_store_transporter]
|
4
|
+
{<img src="https://ci.appveyor.com/api/projects/status/k6w6ob5f7s9j8pv8?svg=true"/>}[https://ci.appveyor.com/project/sshaw/itunes-store-transporter]
|
4
5
|
{<img src="https://codeclimate.com/github/sshaw/itunes_store_transporter.svg" />}[https://codeclimate.com/github/sshaw/itunes_store_transporter]
|
5
6
|
|
6
7
|
Upload and manage your assets in the iTunes Store using the iTunes Store's Transporter (+iTMSTransporter+).
|
@@ -9,8 +10,8 @@ Upload and manage your assets in the iTunes Store using the iTunes Store's Trans
|
|
9
10
|
|
10
11
|
require "itunes/store/transporter"
|
11
12
|
|
12
|
-
itms = iTunes::Store::Transporter.new(:username => "
|
13
|
-
:shortname => "
|
13
|
+
itms = iTunes::Store::Transporter.new(:username => "SomeUser",
|
14
|
+
:shortname => "shrt",
|
14
15
|
:password => "w3c@llYoU!")
|
15
16
|
|
16
17
|
itms.upload("/path/to/yourpackage.itmsp")
|
@@ -165,14 +166,14 @@ As you can see, command options are turned into template variables.
|
|
165
166
|
|
166
167
|
=== More Info
|
167
168
|
|
168
|
-
* Docs: http://
|
169
|
+
* Docs: http://www.rubydoc.info/gems/itunes_store_transporter
|
169
170
|
* Bugs: http://github.com/sshaw/itunes_store_transporter/issues
|
170
171
|
* Source Code: http://github.com/sshaw/itunes_store_transporter
|
171
|
-
*
|
172
|
+
* Transporter GUI: http://github.com/sshaw/itunes_store_transporter_web
|
172
173
|
|
173
174
|
=== Author
|
174
175
|
|
175
|
-
Skye Shaw [
|
176
|
+
Skye Shaw [skye.shaw AT gmail.com]
|
176
177
|
|
177
178
|
=== License
|
178
179
|
|
@@ -3,6 +3,7 @@ require "itunes/store/transporter/command/lookup"
|
|
3
3
|
require "itunes/store/transporter/command/providers"
|
4
4
|
require "itunes/store/transporter/command/schema"
|
5
5
|
require "itunes/store/transporter/command/status"
|
6
|
+
require "itunes/store/transporter/command/status_all"
|
6
7
|
require "itunes/store/transporter/command/upload"
|
7
8
|
require "itunes/store/transporter/command/verify"
|
8
9
|
require "itunes/store/transporter/command/version"
|
@@ -35,6 +35,7 @@ module ITunes
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
# TODO: problem as some errors exit 0, e.g., account locked
|
38
39
|
if exitcode == 0
|
39
40
|
handle_success(stdout_lines, stderr_lines, options)
|
40
41
|
else
|
@@ -57,13 +58,14 @@ module ITunes
|
|
57
58
|
end
|
58
59
|
|
59
60
|
# TODO: conf[:warnings]
|
61
|
+
# TODO: Need to check for errors here too, 0 doesn't always mean success, e.g., account locked
|
60
62
|
def handle_success(stdout_lines, stderr_lines, options)
|
61
|
-
stdout_lines.join
|
63
|
+
stdout_lines.join("\n")
|
62
64
|
end
|
63
65
|
|
64
66
|
def handle_error(stdout_lines, stderr_lines, options, exitcode)
|
65
67
|
parser = OutputParser.new(stderr_lines)
|
66
|
-
errors = parser.errors.any? ? parser.errors : [ TransporterMessage.new(stderr_lines.join) ]
|
68
|
+
errors = parser.errors.any? ? parser.errors : [ TransporterMessage.new(stderr_lines.join("\n")) ]
|
67
69
|
raise ExecutionError.new(errors, exitcode)
|
68
70
|
end
|
69
71
|
|
@@ -4,7 +4,7 @@ module ITunes
|
|
4
4
|
module Store
|
5
5
|
module Transporter
|
6
6
|
module Command
|
7
|
-
|
7
|
+
|
8
8
|
##
|
9
9
|
# Download a RelaxNG schema file for a particular metadata specification.
|
10
10
|
#
|
@@ -16,8 +16,9 @@ module ITunes
|
|
16
16
|
options.on :type, "-schemaType", /\A(transitional|strict)\z/i, :required => true
|
17
17
|
options.on :version, "-schema", /\w+/i, :required => true
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
protected
|
21
|
+
|
21
22
|
def mode
|
22
23
|
"generateSchema"
|
23
24
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "itunes/store/transporter/command"
|
2
|
+
require "itunes/store/transporter/xml/status"
|
2
3
|
|
3
4
|
module ITunes
|
4
5
|
module Store
|
@@ -6,40 +7,32 @@ module ITunes
|
|
6
7
|
module Command # :nodoc:
|
7
8
|
|
8
9
|
##
|
9
|
-
# Retrieve the status of
|
10
|
+
# Retrieve the most recent status of previously uploaded packages
|
10
11
|
#
|
11
12
|
class Status < Mode
|
12
13
|
def initialize(*config)
|
13
14
|
super
|
14
|
-
options.on
|
15
|
+
options.on :vendor_id, "-vendor_ids", /\w/, :multiple => true
|
16
|
+
options.on :apple_id, "-apple_ids", /\w/, :multiple => true
|
17
|
+
options.on :format, "-outputFormat", %w[xml]
|
15
18
|
end
|
16
19
|
|
17
20
|
protected
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
if line =~ /\A--+/
|
23
|
-
entry = {}
|
24
|
-
while line = stdout_lines.shift
|
25
|
-
break unless line =~ /\A\s*\w/
|
26
|
-
key, value = parse_line(line)
|
27
|
-
entry[key] = value
|
28
|
-
end
|
29
|
-
(status[:status] ||= []) << entry
|
30
|
-
else
|
31
|
-
key, value = parse_line(line)
|
32
|
-
status[key] = value
|
33
|
-
end
|
34
|
-
end
|
35
|
-
status
|
21
|
+
|
22
|
+
def create_transporter_options(optz)
|
23
|
+
optz[:format] = "xml"
|
24
|
+
super
|
36
25
|
end
|
37
26
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
27
|
+
def handle_success(stdout_lines, stderr_lines, options)
|
28
|
+
# Pre-XML behavior. Not sure if it should be kept.
|
29
|
+
return [] if stdout_lines.empty?
|
30
|
+
|
31
|
+
begin
|
32
|
+
XML::Status.new.parse(stdout_lines.join(""))
|
33
|
+
rescue ParseError => e
|
34
|
+
raise TransporterError, e.message
|
35
|
+
end
|
43
36
|
end
|
44
37
|
end
|
45
38
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "itunes/store/transporter/command/status"
|
2
|
+
|
3
|
+
module ITunes
|
4
|
+
module Store
|
5
|
+
module Transporter
|
6
|
+
module Command # :nodoc:
|
7
|
+
|
8
|
+
##
|
9
|
+
# Retrieve the full status history of previously uploaded packages
|
10
|
+
#
|
11
|
+
class StatusAll < Status
|
12
|
+
protected
|
13
|
+
|
14
|
+
def mode
|
15
|
+
"statusAll"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -20,6 +20,12 @@ module ITunes
|
|
20
20
|
options.on :streams, "-numStreams", Integer # Only valid if TRANSPORT is Signiant
|
21
21
|
options.on :log_history, "-loghistory", Optout::Dir.exists
|
22
22
|
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def handle_success(stdout_lines, stderr_lines, options)
|
27
|
+
true
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
@@ -1,33 +1,34 @@
|
|
1
1
|
|
2
2
|
module ITunes
|
3
|
-
module Store
|
3
|
+
module Store
|
4
4
|
module Transporter
|
5
5
|
|
6
6
|
class TransporterError < StandardError; end
|
7
7
|
class OptionError < TransporterError; end
|
8
|
-
|
8
|
+
class ParseError < TransporterError; end
|
9
|
+
|
9
10
|
class ExecutionError < TransporterError
|
10
11
|
attr :errors
|
11
|
-
attr :exitstatus
|
12
|
-
|
12
|
+
attr :exitstatus
|
13
|
+
|
13
14
|
def initialize(errors, exitstatus = nil)
|
14
15
|
@errors = [ errors ].flatten
|
15
16
|
@exitstatus = exitstatus
|
16
17
|
super @errors.map { |e| e.to_s }.join ", "
|
17
|
-
end
|
18
|
+
end
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
class TransporterMessage
|
21
22
|
attr :code
|
22
23
|
attr :message
|
23
|
-
|
24
|
+
|
24
25
|
def initialize(message, code = nil)
|
25
26
|
@message = message
|
26
27
|
@code = code
|
27
28
|
end
|
28
29
|
|
29
30
|
# 1000...2000?
|
30
|
-
|
31
|
+
|
31
32
|
def bad_data?
|
32
33
|
(3000...4000).include?(code)
|
33
34
|
end
|
@@ -102,7 +102,7 @@ module ITunes
|
|
102
102
|
##
|
103
103
|
# :method: status
|
104
104
|
# :call-seq:
|
105
|
-
# status(options
|
105
|
+
# status(options)
|
106
106
|
#
|
107
107
|
# Retrieve the status of a previously uploaded package.
|
108
108
|
#
|
@@ -200,11 +200,18 @@ module ITunes
|
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
203
|
-
%w|lookup providers schema
|
203
|
+
%w|lookup providers schema version|.each do |command|
|
204
204
|
define_method(command) { |*options| run_command(command, options.shift) }
|
205
205
|
end
|
206
206
|
|
207
|
+
def status(options)
|
208
|
+
options = create_options(options)
|
209
|
+
command = options.delete(:all) ? Command::StatusAll : Command::Status
|
210
|
+
command.new(@config, @defaults).run(options)
|
211
|
+
end
|
212
|
+
|
207
213
|
private
|
214
|
+
|
208
215
|
def run_command(name, options)
|
209
216
|
Command.const_get(name.capitalize).new(@config, @defaults).run(create_options(options))
|
210
217
|
end
|
@@ -11,8 +11,11 @@ module ITunes
|
|
11
11
|
EXE_NAME = "iTMSTransporter"
|
12
12
|
WINDOWS_EXE = "#{EXE_NAME}.CMD"
|
13
13
|
DEFAULT_UNIX_PATH = "/usr/local/itms/bin/#{EXE_NAME}"
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
OSX_APPLICATION_LOADER_PATHS = [
|
16
|
+
"/Applications/Application Loader.app/Contents/MacOS/itms/bin/#{EXE_NAME}",
|
17
|
+
"/Developer/Applications/Utilities/Application Loader.app/Contents/MacOS/itms/bin/#{EXE_NAME}"
|
18
|
+
]
|
16
19
|
|
17
20
|
class << self
|
18
21
|
def windows?
|
@@ -33,7 +36,17 @@ module ITunes
|
|
33
36
|
root = ENV["PROGRAMFILES(x86)"] || ENV["PROGRAMFILES"] # Need C:\ in case?
|
34
37
|
File.join(root, "itms", WINDOWS_EXE)
|
35
38
|
when osx?
|
36
|
-
|
39
|
+
paths = OSX_APPLICATION_LOADER_PATHS.dup
|
40
|
+
root = `xcode-select --print-path`.chomp rescue ""
|
41
|
+
|
42
|
+
if !root.empty?
|
43
|
+
["/Applications/Application Loader.app/Contents/MacOS/itms/bin/#{EXE_NAME}",
|
44
|
+
"/Applications/Application Loader.app/Contents/itms/bin/#{EXE_NAME}"].each do |path|
|
45
|
+
paths << File.join(root, "..", path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
paths.find { |path| File.exist?(path) } || DEFAULT_UNIX_PATH
|
37
50
|
else
|
38
51
|
DEFAULT_UNIX_PATH
|
39
52
|
end
|
@@ -76,6 +89,7 @@ module ITunes
|
|
76
89
|
end
|
77
90
|
|
78
91
|
private
|
92
|
+
|
79
93
|
def poll(stdout, stderr)
|
80
94
|
read = [ stdout, stderr ]
|
81
95
|
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "rexml/document"
|
2
|
+
require "itunes/store/transporter/errors"
|
3
|
+
|
4
|
+
module ITunes
|
5
|
+
module Store
|
6
|
+
module Transporter
|
7
|
+
module XML
|
8
|
+
|
9
|
+
##
|
10
|
+
# XML parser for the status and statusAll commands XML output
|
11
|
+
#
|
12
|
+
class Status
|
13
|
+
NA = "N/A".freeze
|
14
|
+
|
15
|
+
##
|
16
|
+
#
|
17
|
+
# Parse status or statusAll XML
|
18
|
+
#
|
19
|
+
# === Arguments
|
20
|
+
#
|
21
|
+
# [xml (String|IO)] The XML
|
22
|
+
#
|
23
|
+
# === Errors
|
24
|
+
#
|
25
|
+
# ParseError, ExecutionError
|
26
|
+
#
|
27
|
+
# An ExecutionError is raised if the XML contains iTMSTransporter error messages.
|
28
|
+
#
|
29
|
+
# === Returns
|
30
|
+
#
|
31
|
+
# A Hash representation of the XML output.
|
32
|
+
# Hash keys and values are slightly different (better, I hope) than the
|
33
|
+
# elements and attributes returned by Apple. See the documentation for
|
34
|
+
# ITunes::Store::Transporter::ITMSTransporter#status
|
35
|
+
#
|
36
|
+
def parse(xml)
|
37
|
+
doc = _parse(xml)
|
38
|
+
status = []
|
39
|
+
|
40
|
+
# No elements means there's just text nodes with an error message
|
41
|
+
raise self, doc.root.get_text.to_s unless doc.root.has_elements?
|
42
|
+
|
43
|
+
doc.root.each_element do |e|
|
44
|
+
next unless e.node_type == :element
|
45
|
+
status << upload_status(e)
|
46
|
+
end
|
47
|
+
|
48
|
+
status
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def _parse(xml)
|
54
|
+
begin
|
55
|
+
doc = REXML::Document.new(xml)
|
56
|
+
rescue REXML::ParseException => e
|
57
|
+
raise ParseError, sprintf("%s, caused by line %s: %s",
|
58
|
+
"XML is not well-formed", e.line, e.source.buffer[0..32])
|
59
|
+
# For the other tricks REXML has up its sleeve :)
|
60
|
+
rescue => e
|
61
|
+
raise ParseError, "XML parsing failed: #{e}"
|
62
|
+
end
|
63
|
+
|
64
|
+
raise ParseError, "Invalid XML document: '#{xml[0..32]}'" unless doc.root
|
65
|
+
doc
|
66
|
+
end
|
67
|
+
|
68
|
+
def upload_status(e)
|
69
|
+
{
|
70
|
+
:apple_id => e.attributes["apple_identifier"],
|
71
|
+
:vendor_id => e.attributes["vendor_identifier"],
|
72
|
+
:content_status => content_status(e),
|
73
|
+
:info => upload_info(e)
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def upload_info(e)
|
78
|
+
e.get_elements("upload_status_info").map do |info|
|
79
|
+
info.attributes.each_with_object({}) do |(name, value), hash|
|
80
|
+
hash[name.to_sym] = value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def content_status(e)
|
86
|
+
e = e.get_elements("content_status_info").first
|
87
|
+
return unless e
|
88
|
+
|
89
|
+
{
|
90
|
+
:status => e.attributes["content_status"],
|
91
|
+
:review_status => e.attributes["content_review_status"],
|
92
|
+
:itunes_connect_status => e.attributes["itunes_connect_status"],
|
93
|
+
:store_status => store_status(e),
|
94
|
+
:video_components => video_components(e)
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def store_status(e)
|
99
|
+
e = e.get_elements("store_status").first
|
100
|
+
return unless e
|
101
|
+
|
102
|
+
e.attributes.each_with_object({}) do |(name, value), status|
|
103
|
+
status[name.to_sym] = territory_list(value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def video_components(video)
|
108
|
+
video.get_elements("video_components/video_component").map do |e|
|
109
|
+
hash = { :name => e.attributes["component_name"] }
|
110
|
+
|
111
|
+
[:locale, :status, :delivered].each do |name|
|
112
|
+
value = e.attributes["component_#{name}"]
|
113
|
+
value = nil if value == NA
|
114
|
+
hash[name] = value
|
115
|
+
end
|
116
|
+
|
117
|
+
hash
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def territory_list(value)
|
122
|
+
value && value != NA ? value.split(/\s*,\s*/) : []
|
123
|
+
end
|
124
|
+
|
125
|
+
def exception(text)
|
126
|
+
# Some overlap here with OutputParser, may want to create ErrorParser
|
127
|
+
text.sub!(/^\s*Error Summary\s*/, "")
|
128
|
+
text.strip!
|
129
|
+
|
130
|
+
errors = text.split(/\n\s*/).map do |line|
|
131
|
+
message, code = line, nil
|
132
|
+
if message =~ /(.+)\((-?\d+)\)\Z/
|
133
|
+
message, code = $1, $2.to_i
|
134
|
+
end
|
135
|
+
|
136
|
+
TransporterMessage.new(message.strip, code)
|
137
|
+
end
|
138
|
+
|
139
|
+
ExecutionError.new(errors)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|