itunes_store_transporter 0.0.1 → 0.0.2
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/Changes +12 -0
- data/README.rdoc +18 -11
- data/bin/itms +72 -45
- data/lib/itunes/store/transporter.rb +0 -1
- data/lib/itunes/store/transporter/command.rb +21 -22
- data/lib/itunes/store/transporter/command/lookup.rb +4 -3
- data/lib/itunes/store/transporter/command/status.rb +23 -9
- data/lib/itunes/store/transporter/command/verify.rb +8 -3
- data/lib/itunes/store/transporter/output_parser.rb +53 -54
- data/lib/itunes/store/transporter/shell.rb +5 -1
- data/lib/itunes/store/transporter/version.rb +1 -1
- data/spec/command_spec.rb +72 -39
- data/spec/fixtures/errors_and_warnings.yml +45 -0
- data/spec/fixtures/providers.yml +5 -0
- data/spec/fixtures/status.yml +29 -0
- data/spec/fixtures/stderr.yml +20 -0
- data/spec/fixtures/stdout.yml +5 -0
- data/spec/shell_spec.rb +17 -10
- data/spec/spec_helper.rb +14 -15
- metadata +27 -12
@@ -1,3 +1,4 @@
|
|
1
|
+
require "tmpdir"
|
1
2
|
require "fileutils"
|
2
3
|
require "itunes/store/transporter/errors"
|
3
4
|
require "itunes/store/transporter/command"
|
@@ -24,7 +25,7 @@ module ITunes
|
|
24
25
|
options[:destination] = Dir.mktmpdir
|
25
26
|
super
|
26
27
|
ensure
|
27
|
-
FileUtils.rm_rf(options[:destination])
|
28
|
+
FileUtils.rm_rf(options[:destination]) if options[:destination]
|
28
29
|
end
|
29
30
|
|
30
31
|
protected
|
@@ -33,13 +34,13 @@ module ITunes
|
|
33
34
|
path = File.join(options[:destination], "#{id}.itmsp", "metadata.xml")
|
34
35
|
|
35
36
|
if !File.exists?(path)
|
36
|
-
raise
|
37
|
+
raise TransporterError, "No metadata file exists at #{path}"
|
37
38
|
end
|
38
39
|
|
39
40
|
begin
|
40
41
|
metadata = File.read(path)
|
41
42
|
rescue StandardError => e
|
42
|
-
raise
|
43
|
+
raise TransporterError, "Failed to read metadata file #{path}: #{e}"
|
43
44
|
end
|
44
45
|
|
45
46
|
metadata
|
@@ -10,24 +10,38 @@ module ITunes
|
|
10
10
|
#
|
11
11
|
class Status < Mode
|
12
12
|
def initialize(*config)
|
13
|
-
super
|
13
|
+
super
|
14
14
|
options.on *VENDOR_ID
|
15
15
|
end
|
16
16
|
|
17
17
|
protected
|
18
18
|
def handle_success(stdout_lines, stderr_lines, options)
|
19
19
|
status = {}
|
20
|
-
stdout_lines.
|
21
|
-
next unless line =~ /\
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
26
34
|
end
|
27
35
|
status
|
28
|
-
end
|
29
|
-
end
|
36
|
+
end
|
30
37
|
|
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]
|
43
|
+
end
|
44
|
+
end
|
31
45
|
end
|
32
46
|
end
|
33
47
|
end
|
@@ -20,11 +20,17 @@ module ITunes
|
|
20
20
|
end
|
21
21
|
|
22
22
|
protected
|
23
|
+
def create_transporter_options(optz)
|
24
|
+
# Include the option if false
|
25
|
+
optz[:verify_assets] = !optz[:verify_assets] if optz.include?(:verify_assets)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
23
29
|
# Verify mode returns 0 if there are no packages to verify but will emit an error message about the lack of packages
|
24
30
|
def handle_success(stdout_lines, stderr_lines, options)
|
25
|
-
parser =
|
31
|
+
parser = OutputParser.new(stderr_lines)
|
26
32
|
if parser.errors.any?
|
27
|
-
raise
|
33
|
+
raise ExecutionError.new(parser.errors, 0)
|
28
34
|
else
|
29
35
|
true
|
30
36
|
end
|
@@ -34,4 +40,3 @@ module ITunes
|
|
34
40
|
end
|
35
41
|
end
|
36
42
|
end
|
37
|
-
|
@@ -5,78 +5,77 @@ module ITunes
|
|
5
5
|
class Transporter
|
6
6
|
class OutputParser
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
# is creates an instance of ITunes::Store::Transporter::TransporterMessage
|
11
|
-
|
8
|
+
##
|
9
|
+
# This class extracts error and warning messages output by +iTMSTransporter+. For each message
|
10
|
+
# is creates an instance of ITunes::Store::Transporter::TransporterMessage
|
11
|
+
#
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
attr :errors
|
14
|
+
attr :warnings
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
ERROR_LINE = />\s+ERROR:\s+(.+)/
|
17
|
+
WARNING_LINE = />\s+WARN:\s+(.+)/
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
# Generic messages we want to ignore.
|
20
|
+
SKIP_ERRORS = [ /\boperation was not successful/i,
|
21
21
|
/\bunable to verify the package/i,
|
22
22
|
/\bwill NOT be verified/,
|
23
23
|
/^an error has occurred/i,
|
24
24
|
/^an error occurred while/i,
|
25
25
|
/^unknown operation/i,
|
26
|
-
|
26
|
+
/\bunable to authenticate the package/i ]
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
28
|
+
##
|
29
|
+
# === Arguments
|
30
|
+
#
|
31
|
+
# [output (Array)] +iTMSTransporter+ output
|
32
|
+
#
|
33
|
+
def initialize(output)
|
34
|
+
@errors = []
|
35
|
+
@warnings = []
|
36
|
+
parse_output(output) if Array === output
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
39
|
+
private
|
40
|
+
def parse_output(output)
|
41
|
+
output.each do |line|
|
42
|
+
if line =~ ERROR_LINE
|
43
|
+
error = $1
|
44
|
+
next if SKIP_ERRORS.any? { |skip| error =~ skip }
|
45
|
+
errors << create_message(error)
|
46
|
+
elsif line =~ WARNING_LINE
|
47
|
+
warnings << create_message($1)
|
48
|
+
end
|
49
|
+
end
|
50
50
|
|
51
|
-
# Unique messages only. The block form of uniq() not available on Ruby < 1.9.2
|
51
|
+
# Unique messages only. The block form of uniq() is not available on Ruby < 1.9.2
|
52
52
|
[errors, warnings].each do |e|
|
53
|
-
e.
|
54
|
-
|
55
|
-
|
56
|
-
end.values)
|
53
|
+
next if e.empty?
|
54
|
+
uniq = {}
|
55
|
+
e.replace(e.select { |m| uniq.include?(m.message) ? false : uniq[m.message] = true })
|
57
56
|
end
|
58
|
-
|
57
|
+
end
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
def create_message(line)
|
60
|
+
case line
|
61
|
+
when /^(?:ERROR|WARNING)\s+ITMS-(\d+):\s+(.+)/
|
62
|
+
code = $1
|
63
|
+
message = $2
|
64
|
+
when /(.+)\s+\((\d+)\)$/,
|
66
65
|
# Is this correct?
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
66
|
+
/(.+)\s+errorCode\s+=\s+\((\d+)\)$/
|
67
|
+
message = $1
|
68
|
+
code = $2
|
69
|
+
else
|
70
|
+
message = line
|
71
|
+
code = nil
|
72
|
+
end
|
74
73
|
|
75
|
-
|
74
|
+
message.gsub! /^"/, ""
|
76
75
|
message.gsub! /"(?: at .+)?$/, ""
|
77
76
|
|
78
|
-
|
79
|
-
|
77
|
+
TransporterMessage.new(message, code ? code.to_i : nil)
|
78
|
+
end
|
80
79
|
end
|
81
80
|
end
|
82
81
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "childprocess"
|
2
|
+
require "itunes/store/transporter/errors"
|
2
3
|
|
3
4
|
module ITunes
|
4
5
|
module Store
|
@@ -35,6 +36,8 @@ module ITunes
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def exec(argv, &block)
|
39
|
+
raise ArgumentError, "block required" unless block_given?
|
40
|
+
|
38
41
|
begin
|
39
42
|
process = ChildProcess.build(path, *argv)
|
40
43
|
|
@@ -54,7 +57,7 @@ module ITunes
|
|
54
57
|
|
55
58
|
poll(stdout[0], stderr[0], &block)
|
56
59
|
rescue ChildProcess::Error, SystemCallError => e
|
57
|
-
raise
|
60
|
+
raise TransporterError, e.message
|
58
61
|
ensure
|
59
62
|
process.wait if process.alive?
|
60
63
|
[ stdout, stderr ].flatten.each { |io| io.close if !io.closed? }
|
@@ -68,6 +71,7 @@ module ITunes
|
|
68
71
|
read = [ stdout, stderr ]
|
69
72
|
|
70
73
|
loop do
|
74
|
+
# TODO: Not working on jruby
|
71
75
|
if ready = select(read, nil, nil, 1)
|
72
76
|
ready.each do |set|
|
73
77
|
next unless set.any?
|
data/spec/command_spec.rb
CHANGED
@@ -3,12 +3,12 @@ require "stringio"
|
|
3
3
|
|
4
4
|
shared_examples_for "a transporter option" do |option, expected|
|
5
5
|
it "creates the correct command line argument" do
|
6
|
-
|
6
|
+
expect_shell_args(*expected)
|
7
7
|
subject.run(options.merge(option))
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
shared_examples_for "a vendor_id option" do
|
11
|
+
shared_examples_for "a vendor_id option" do
|
12
12
|
it_should_behave_like "a transporter option", { :vendor_id => "vID" }, "-vendor_id", "vID"
|
13
13
|
end
|
14
14
|
|
@@ -22,25 +22,25 @@ shared_examples_for "a transporter option that expects a directory" do |option,
|
|
22
22
|
lambda { subject.run(options.merge(option => "__baaaaahd_directory__")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /does not exist/)
|
23
23
|
end
|
24
24
|
end
|
25
|
-
end
|
25
|
+
end
|
26
26
|
|
27
27
|
shared_examples_for "a boolean transporter option" do |option, expected|
|
28
|
-
context "when true" do
|
28
|
+
context "when true" do
|
29
29
|
it "creates the command line argument" do
|
30
|
-
|
30
|
+
expect_shell_args(*expected)
|
31
31
|
subject.run(options.merge(option => true))
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
context "when false" do
|
35
|
+
context "when false" do
|
36
36
|
it "does not create the command line argument" do
|
37
|
-
ITunes::Store::Transporter::Shell.any_instance.
|
37
|
+
ITunes::Store::Transporter::Shell.any_instance.should_receive(:exec) { |*arg| arg.first.should_not include(*expected); 0 }
|
38
38
|
subject.run(options.merge(option => false))
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
context "when not boolean" do
|
43
|
-
it "raises an OptionError" do
|
42
|
+
context "when not boolean" do
|
43
|
+
it "raises an OptionError" do
|
44
44
|
lambda { subject.run(options.merge(option => "sshaw")) }.should raise_exception(ITunes::Store::Transporter::OptionError, /does not accept/)
|
45
45
|
end
|
46
46
|
end
|
@@ -76,7 +76,7 @@ shared_examples_for "a subclass of Command::Base" do
|
|
76
76
|
it "automatically sets NoPause to true" do
|
77
77
|
ENV["PROGRAMFILES"] = "C:\\"
|
78
78
|
shell = ITunes::Store::Transporter::Shell
|
79
|
-
|
79
|
+
expect_shell_args("-WONoPause", "true")
|
80
80
|
shell.stub(:windows? => true)
|
81
81
|
subject.run(options)
|
82
82
|
end
|
@@ -166,7 +166,7 @@ shared_examples_for "a subclass of Command::Base" do
|
|
166
166
|
e.errors.should have(2).items
|
167
167
|
|
168
168
|
# just check one
|
169
|
-
e.errors[0].should be_a(ITunes::Store::Transporter::TransporterMessage)
|
169
|
+
e.errors[0].should be_a(ITunes::Store::Transporter::TransporterMessage)
|
170
170
|
e.errors[0].code.should == 9000
|
171
171
|
e.errors[0].message.should match("Your audio of screwed up!")
|
172
172
|
e.errors[1].code.should == 4009
|
@@ -297,7 +297,7 @@ describe ITunes::Store::Transporter::Command::Upload do
|
|
297
297
|
describe ":log_history" do
|
298
298
|
it_should_behave_like "a transporter option that expects a directory", :log_history, "-loghistory"
|
299
299
|
end
|
300
|
-
|
300
|
+
|
301
301
|
describe ":success" do
|
302
302
|
it_should_behave_like "a transporter option that expects a directory", :success, "-success"
|
303
303
|
end
|
@@ -321,27 +321,27 @@ describe ITunes::Store::Transporter::Command::Lookup do
|
|
321
321
|
before(:each) do
|
322
322
|
@tmpdir = Dir.mktmpdir
|
323
323
|
Dir.stub(:mktmpdir => @tmpdir)
|
324
|
-
|
324
|
+
|
325
325
|
id = options[:vendor_id] || options[:apple_id]
|
326
326
|
@package = File.join(@tmpdir, "#{id}.itmsp")
|
327
327
|
Dir.mkdir(@package)
|
328
|
-
|
328
|
+
|
329
329
|
@metadata = "<x>Metadata</x>"
|
330
330
|
File.open(File.join(@package, "metadata.xml"), "w") { |io| io.write(@metadata) }
|
331
|
-
|
332
|
-
mock_output
|
333
331
|
end
|
334
|
-
|
332
|
+
|
335
333
|
after(:each) { FileUtils.rm_rf(@tmpdir) }
|
336
|
-
|
334
|
+
|
337
335
|
describe "#run" do
|
338
|
-
|
336
|
+
before { mock_output }
|
337
|
+
|
338
|
+
context "when successful" do
|
339
339
|
it "returns the metadata and deletes the temp directory used to output the metadata" do
|
340
340
|
subject.run(options).should == @metadata
|
341
341
|
File.exists?(@tmpdir).should be_false
|
342
342
|
end
|
343
343
|
|
344
|
-
context "when the metadata file was not created" do
|
344
|
+
context "when the metadata file was not created" do
|
345
345
|
before { FileUtils.rm_rf(@tmpdir) }
|
346
346
|
|
347
347
|
it "raises a TransporterError" do
|
@@ -354,14 +354,14 @@ describe ITunes::Store::Transporter::Command::Lookup do
|
|
354
354
|
# One of these two should be requied, but they should be mutually exclusive
|
355
355
|
describe "options" do
|
356
356
|
describe ":vendor_id" do
|
357
|
-
let(:options) { create_options({:vendor_id => "vID"}) }
|
357
|
+
let(:options) { create_options({:vendor_id => "vID"}) }
|
358
358
|
it_should_behave_like "a vendor_id option"
|
359
359
|
end
|
360
360
|
|
361
361
|
describe ":apple_id" do
|
362
|
-
let(:options) { create_options({:apple_id => "aID"}) }
|
362
|
+
let(:options) { create_options({:apple_id => "aID"}) }
|
363
363
|
it_should_behave_like "a transporter option", { :apple_id => "aID" }, "-apple_id", "aID"
|
364
|
-
end
|
364
|
+
end
|
365
365
|
end
|
366
366
|
end
|
367
367
|
|
@@ -407,24 +407,45 @@ describe ITunes::Store::Transporter::Command::Status do
|
|
407
407
|
|
408
408
|
describe "#run" do
|
409
409
|
context "when successful" do
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
410
|
+
context "with a single status" do
|
411
|
+
it "returns the status information for the package" do
|
412
|
+
mock_output(:stdout => "status.vendor_id_123123", :stderr => "stderr.info")
|
413
|
+
subject.run(options).should == {
|
414
|
+
:vendor_identifier => "123123",
|
415
|
+
:apple_identifier => "123123",
|
416
|
+
:itunesconnect_status => "Not ready for sale",
|
417
|
+
:status => [ { :upload_created => "2000-01-01 00:00:00",
|
418
|
+
:upload_state => "Uploaded",
|
419
|
+
:upload_state_id => "1",
|
420
|
+
:content_state => "Irie",
|
421
|
+
:content_state_id => "2" } ]
|
422
|
+
}
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
context "with multiple status" do
|
427
|
+
it "returns all the status information for the package" do
|
428
|
+
mock_output(:stdout => "status.vendor_id_789789", :stderr => "stderr.info")
|
429
|
+
subject.run(options).should == {
|
430
|
+
:vendor_identifier => "789789",
|
431
|
+
:status => [ { :upload_created => "2000-01-01 00:00:00",
|
432
|
+
:upload_state => "Uploaded",
|
433
|
+
:upload_state_id => "1",
|
434
|
+
:content_state => "Irie",
|
435
|
+
:content_state_id => "2" },
|
436
|
+
{ :upload_created => "2000-01-02 00:00:00",
|
437
|
+
:upload_state => "Some Failure",
|
438
|
+
:upload_state_id => "2",
|
439
|
+
:content_state => "Still Irie",
|
440
|
+
:content_state_id => "3" } ]
|
441
|
+
}
|
442
|
+
end
|
422
443
|
end
|
423
444
|
end
|
424
445
|
end
|
425
446
|
|
426
|
-
describe "options" do
|
427
|
-
describe ":vendor_id" do
|
447
|
+
describe "options" do
|
448
|
+
describe ":vendor_id" do
|
428
449
|
it_should_behave_like "a vendor_id option"
|
429
450
|
end
|
430
451
|
end
|
@@ -448,9 +469,9 @@ describe ITunes::Store::Transporter::Command::Verify do
|
|
448
469
|
end
|
449
470
|
end
|
450
471
|
|
451
|
-
# If no packages were verfied it exits with 0 but emits an error message
|
452
472
|
context "with errors" do
|
453
473
|
it "raises an ExecutionError" do
|
474
|
+
# If no packages were verfied it exits with 0 but emits an error message
|
454
475
|
mock_output(:exit => 0, :stderr => "stderr.errors");
|
455
476
|
lambda { subject.run(options) }.should raise_exception(ITunes::Store::Transporter::ExecutionError)
|
456
477
|
end
|
@@ -460,7 +481,19 @@ describe ITunes::Store::Transporter::Command::Verify do
|
|
460
481
|
|
461
482
|
describe "options" do
|
462
483
|
describe ":verify_assets" do
|
463
|
-
|
484
|
+
context "when true" do
|
485
|
+
it "does not create the command line argument" do
|
486
|
+
ITunes::Store::Transporter::Shell.any_instance.should_receive(:exec) { |*arg| arg.first.should_not include("-disableAssetVerification"); 0 }
|
487
|
+
subject.run(options.merge(:verify_assets => true))
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
context "when false" do
|
492
|
+
it "creates the command line argument" do
|
493
|
+
expect_shell_args("-disableAssetVerification")
|
494
|
+
subject.run(options.merge(:verify_assets => false))
|
495
|
+
end
|
496
|
+
end
|
464
497
|
end
|
465
498
|
end
|
466
499
|
end
|