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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 603cbf0b2fe54965a1fcd25315fc2434f9987188
4
- data.tar.gz: 4c197138304f80f540f508c8a8f09910532ceef6
3
+ metadata.gz: 1706c7254e4158ee42930c52f11f48bd22ddb409
4
+ data.tar.gz: fb2b7760daa79915b5628f3631b622a4d978b375
5
5
  SHA512:
6
- metadata.gz: 57b4de9c4cc1571829c9151ec91e8b0773d094299454252ff40d348faed32100d65e242a1dcd322c014347d4167e5c9a3f93a6373e3ba7f7e8578d1ebda769af
7
- data.tar.gz: 812887659a510890d27f5e4a497b96463dca4ffe371e281e9be9b50a613e0bff3d2cd4a2df53eae8b82bd395ec4990f5240a6639b22b986efe58d975fb829809
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 (thanks to press9 media solutions berlin - press9.de)
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
  --------------------
@@ -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 => "CouchCaster",
13
- :shortname => "bigtimer",
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://ruby-doc.org/gems/docs/i/itunes_store_transporter-0.1.1/README_rdoc.html
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
- * Web Based GUI: http://github.com/sshaw/itunes_store_transporter_web
172
+ * Transporter GUI: http://github.com/sshaw/itunes_store_transporter_web
172
173
 
173
174
  === Author
174
175
 
175
- Skye Shaw [sshaw AT gmail.com]
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
 
@@ -10,10 +10,11 @@ module ITunes
10
10
  #
11
11
  class Providers < Mode
12
12
  protected
13
+
13
14
  def mode
14
15
  "provider"
15
16
  end
16
-
17
+
17
18
  def handle_success(stdout_lines, stderr_lines, options)
18
19
  providers = []
19
20
  stdout_lines.each do |line|
@@ -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 a previously uploaded package
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 *VENDOR_ID
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
- def handle_success(stdout_lines, stderr_lines, options)
19
- status = {}
20
- while line = stdout_lines.shift
21
- next unless line =~ /\S+/
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 parse_line(line)
39
- key, value = line.split(/:\s+/, 2).map(&:strip)
40
- key.gsub!(/\s+/, "_")
41
- key.downcase!
42
- [key.to_sym, value]
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
@@ -18,6 +18,7 @@ module ITunes
18
18
  end
19
19
 
20
20
  protected
21
+
21
22
  def create_transporter_options(optz)
22
23
  # Include the option if false
23
24
  optz[:verify_assets] = !optz[:verify_assets] if optz.include?(:verify_assets)
@@ -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 status version|.each do |command|
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
@@ -37,6 +37,7 @@ module ITunes
37
37
  end
38
38
 
39
39
  private
40
+
40
41
  def parse_output(output)
41
42
  output.each do |line|
42
43
  if line =~ ERROR_LINE
@@ -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
- DEFAULT_OSX_PATHS = ["/Developer/Applications/Utilities/Application Loader.app/Contents/MacOS/itms/bin/#{EXE_NAME}",
15
- "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/bin/#{EXE_NAME}"]
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
- DEFAULT_OSX_PATHS.find { |path| File.exist?(path) } || DEFAULT_UNIX_PATH
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
 
@@ -1,7 +1,7 @@
1
1
  module ITunes
2
2
  module Store
3
3
  module Transporter
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0"
5
5
  end
6
6
  end
7
7
  end
@@ -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