itunes_store_transporter 0.1.3 → 0.2.0
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.
- 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
|