apple_epf 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +90 -0
- data/Rakefile +35 -0
- data/lib/apple_epf.rb +43 -0
- data/lib/apple_epf/downloader.rb +164 -0
- data/lib/apple_epf/errors.rb +8 -0
- data/lib/apple_epf/extractor.rb +43 -0
- data/lib/apple_epf/logging.rb +32 -0
- data/lib/apple_epf/main.rb +113 -0
- data/lib/apple_epf/parser.rb +95 -0
- data/lib/apple_epf/version.rb +3 -0
- data/lib/core_ext/array.rb +30 -0
- data/lib/core_ext/module.rb +63 -0
- data/lib/tasks/apple_epf_tasks.rake +4 -0
- data/spec/lib/apple_epf/downloader_spec.rb +220 -0
- data/spec/lib/apple_epf/exctractor_spec.rb +72 -0
- data/spec/lib/apple_epf/main_spec.rb +185 -0
- data/spec/lib/apple_epf/parser_spec.rb +66 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/fixture_helper.rb +13 -0
- data/spec/support/fixtures/itunes/epf/current_full_list.html +20 -0
- data/spec/support/fixtures/itunes/epf/current_inc_list.html +19 -0
- data/spec/support/fixtures/itunes/epf/incremental/itunes20130111.tbz +0 -0
- data/spec/support/fixtures/itunes/epf/incremental/itunes20130111/application +21 -0
- data/spec/support/fixtures/itunes/epf/incremental/itunes20130111/application_with_nil +7 -0
- data/spec/support/fixtures/itunes/epf/incremental/itunes20130111/test_file.txt +0 -0
- data/spec/support/fixtures/itunes/epf/incremental/popularity20130111.tbz +0 -0
- data/spec/support/fixtures/itunes/epf/incremental/popularity20130111/popularity1 +0 -0
- data/spec/support/fixtures/itunes/epf/incremental/popularity20130111/popularity2 +0 -0
- metadata +255 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
module AppleEpf
|
2
|
+
class Parser
|
3
|
+
FIELD_SEPARATOR = 1.chr
|
4
|
+
RECORD_SEPARATOR = 2.chr + "\n"
|
5
|
+
COMMENT_CHAR = '#'
|
6
|
+
|
7
|
+
attr_accessor :filename, :header_info, :footer_info
|
8
|
+
|
9
|
+
def initialize(filename)
|
10
|
+
@filename = filename
|
11
|
+
@header_info = {}
|
12
|
+
@footer_info = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_metadata
|
16
|
+
begin
|
17
|
+
parse_file
|
18
|
+
load_header_info
|
19
|
+
load_footer_info
|
20
|
+
@header_info.merge(@footer_info)
|
21
|
+
ensure
|
22
|
+
close_file
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_rows(&block)
|
27
|
+
File.foreach( @filename, RECORD_SEPARATOR ) do |line|
|
28
|
+
unless line[0].chr == COMMENT_CHAR
|
29
|
+
line = line.chomp( RECORD_SEPARATOR )
|
30
|
+
block.call( line.split( FIELD_SEPARATOR, -1) ) if block_given?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parse_file
|
38
|
+
@file = File.new( @filename, 'r', encoding: 'UTF-8' )
|
39
|
+
end
|
40
|
+
|
41
|
+
def close_file
|
42
|
+
@file.close if @file
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_line(accept_comment = false)
|
46
|
+
valid_line = false
|
47
|
+
until valid_line
|
48
|
+
begin
|
49
|
+
line = @file.readline( RECORD_SEPARATOR )
|
50
|
+
rescue EOFError => e
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
valid_line = accept_comment ? true : !line.start_with?( COMMENT_CHAR )
|
54
|
+
end
|
55
|
+
line.sub!( COMMENT_CHAR, '' ) if accept_comment
|
56
|
+
line.chomp!( RECORD_SEPARATOR )
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_header_info
|
60
|
+
# File
|
61
|
+
file_hash = { :file => File.basename( @filename ) }
|
62
|
+
@header_info.merge! ( file_hash )
|
63
|
+
|
64
|
+
# Columns
|
65
|
+
line = read_line(true)
|
66
|
+
column_hash = { :columns => line.split( FIELD_SEPARATOR ) }
|
67
|
+
@header_info.merge! ( column_hash )
|
68
|
+
|
69
|
+
# Primary keys
|
70
|
+
line = read_line(true).sub!( 'primaryKey:', '' )
|
71
|
+
primary_hash = { :primary_keys => line.split( FIELD_SEPARATOR ) }
|
72
|
+
@header_info.merge! ( primary_hash )
|
73
|
+
|
74
|
+
# DB types
|
75
|
+
line = read_line(true).sub!( 'dbTypes:', '' )
|
76
|
+
primary_hash = { :db_types => line.split( FIELD_SEPARATOR ) }
|
77
|
+
@header_info.merge! ( primary_hash )
|
78
|
+
|
79
|
+
# Export type
|
80
|
+
line = read_line(true).sub!( 'exportMode:', '' )
|
81
|
+
primary_hash = { :export_type => line.split( FIELD_SEPARATOR ) }
|
82
|
+
@header_info.merge! ( primary_hash )
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def load_footer_info
|
87
|
+
@file.seek(-40, IO::SEEK_END)
|
88
|
+
records = @file.read.split( COMMENT_CHAR ).last.chomp!( RECORD_SEPARATOR ).sub( 'recordsWritten:', '' )
|
89
|
+
records_hash = { :records => records }
|
90
|
+
@footer_info.merge! ( records_hash )
|
91
|
+
@file.rewind
|
92
|
+
@footer_info
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# activesupport/lib/active_support/core_ext/array/extract_options.rb
|
2
|
+
class Hash
|
3
|
+
# By default, only instances of Hash itself are extractable.
|
4
|
+
# Subclasses of Hash may implement this method and return
|
5
|
+
# true to declare themselves as extractable. If a Hash
|
6
|
+
# is extractable, Array#extract_options! pops it from
|
7
|
+
# the Array when it is the last element of the Array.
|
8
|
+
def extractable_options?
|
9
|
+
instance_of?(Hash)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Array
|
14
|
+
# Extracts options from a set of arguments. Removes and returns the last
|
15
|
+
# element in the array if it's a hash, otherwise returns a blank hash.
|
16
|
+
#
|
17
|
+
# def options(*args)
|
18
|
+
# args.extract_options!
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# options(1, 2) # => {}
|
22
|
+
# options(1, 2, :a => :b) # => {:a=>:b}
|
23
|
+
def extract_options!
|
24
|
+
if last.is_a?(Hash) && last.extractable_options?
|
25
|
+
pop
|
26
|
+
else
|
27
|
+
{}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
|
2
|
+
class Module
|
3
|
+
def mattr_reader(*syms)
|
4
|
+
options = syms.extract_options!
|
5
|
+
syms.each do |sym|
|
6
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
7
|
+
@@#{sym} = nil unless defined? @@#{sym}
|
8
|
+
|
9
|
+
def self.#{sym}
|
10
|
+
@@#{sym}
|
11
|
+
end
|
12
|
+
EOS
|
13
|
+
|
14
|
+
unless options[:instance_reader] == false || options[:instance_accessor] == false
|
15
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
16
|
+
def #{sym}
|
17
|
+
@@#{sym}
|
18
|
+
end
|
19
|
+
EOS
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def mattr_writer(*syms)
|
25
|
+
options = syms.extract_options!
|
26
|
+
syms.each do |sym|
|
27
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
28
|
+
def self.#{sym}=(obj)
|
29
|
+
@@#{sym} = obj
|
30
|
+
end
|
31
|
+
EOS
|
32
|
+
|
33
|
+
unless options[:instance_writer] == false || options[:instance_accessor] == false
|
34
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
35
|
+
def #{sym}=(obj)
|
36
|
+
@@#{sym} = obj
|
37
|
+
end
|
38
|
+
EOS
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Extends the module object with module and instance accessors for class attributes,
|
44
|
+
# just like the native attr* accessors for instance attributes.
|
45
|
+
#
|
46
|
+
# module AppConfiguration
|
47
|
+
# mattr_accessor :google_api_key
|
48
|
+
# self.google_api_key = "123456789"
|
49
|
+
#
|
50
|
+
# mattr_accessor :paypal_url
|
51
|
+
# self.paypal_url = "www.sandbox.paypal.com"
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# AppConfiguration.google_api_key = "overriding the api key!"
|
55
|
+
#
|
56
|
+
# To opt out of the instance writer method, pass :instance_writer => false.
|
57
|
+
# To opt out of the instance reader method, pass :instance_reader => false.
|
58
|
+
# To opt out of both instance methods, pass :instance_accessor => false.
|
59
|
+
def mattr_accessor(*syms)
|
60
|
+
mattr_reader(*syms)
|
61
|
+
mattr_writer(*syms)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe AppleEpf::Downloader do
|
5
|
+
|
6
|
+
let(:type) { 'incremental' }
|
7
|
+
let(:filedate) { Date.parse('17-01-2013') }
|
8
|
+
let(:file) { 'popularity' }
|
9
|
+
let(:downloader) {AppleEpf::Downloader.new(type, file, filedate)}
|
10
|
+
let(:file_exists) { true }
|
11
|
+
|
12
|
+
describe "#get_filename_by_date_and_type" do
|
13
|
+
before do
|
14
|
+
downloader.stub(:file_exists?){ file_exists }
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should raise exception if path can not be determined" do
|
18
|
+
downloader.type = 'crazytype'
|
19
|
+
expect {
|
20
|
+
downloader.get_filename_by_date_and_type
|
21
|
+
}.to raise_exception
|
22
|
+
end
|
23
|
+
|
24
|
+
context "type is full" do
|
25
|
+
let(:type) { 'full' }
|
26
|
+
|
27
|
+
context "and file exists" do
|
28
|
+
let(:file_exists) { true }
|
29
|
+
|
30
|
+
it "should return valid url if file exists" do
|
31
|
+
downloader.filedate = Date.parse('17-01-2013')
|
32
|
+
downloader.get_filename_by_date_and_type.should == "20130116/popularity20130116.tbz"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "and file does not exists" do
|
37
|
+
let(:file_exists) { false }
|
38
|
+
|
39
|
+
it "should raise exception" do
|
40
|
+
downloader.filedate = Date.parse('17-01-2013')
|
41
|
+
expect {
|
42
|
+
downloader.get_filename_by_date_and_type
|
43
|
+
}.to raise_exception(AppleEpf::FileNotExist)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
context "type is incremental" do
|
50
|
+
let(:type) { 'incremental' }
|
51
|
+
|
52
|
+
context "and file exists" do
|
53
|
+
let(:file_exists) { true }
|
54
|
+
|
55
|
+
it "should return valid url if file exists" do
|
56
|
+
downloader.filedate = Date.parse('17-01-2013')
|
57
|
+
downloader.get_filename_by_date_and_type.should == "20130109/incremental/20130117/popularity20130117.tbz"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "and file does not exists" do
|
62
|
+
let(:file_exists) { false }
|
63
|
+
|
64
|
+
it "should raise exception" do
|
65
|
+
downloader.filedate = Date.parse('17-01-2013')
|
66
|
+
expect {
|
67
|
+
downloader.get_filename_by_date_and_type
|
68
|
+
}.to raise_exception(AppleEpf::FileNotExist)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context "type is file" do
|
75
|
+
pending
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#main_dir_date" do
|
81
|
+
|
82
|
+
context "full" do
|
83
|
+
let(:type) { 'full' }
|
84
|
+
it "should return the same week wednesday" do
|
85
|
+
downloader.filedate = Date.parse('17-01-2013') #thursday
|
86
|
+
downloader.send(:main_dir_date).should == "20130116"
|
87
|
+
end
|
88
|
+
# it "should return wednesday of this week if filedate is thur-sun" do
|
89
|
+
# downloader.filedate = Date.parse('17-01-2013') #thursday
|
90
|
+
# downloader.send(:main_dir_date).should == "20130116"
|
91
|
+
|
92
|
+
# downloader.filedate = Date.parse('19-01-2013') #sut
|
93
|
+
# downloader.send(:main_dir_date).should == "20130116"
|
94
|
+
# end
|
95
|
+
|
96
|
+
# it "should return wednesday of prev week if filedate is mon-wed" do
|
97
|
+
# downloader.filedate = Date.parse('21-01-2013') #monday
|
98
|
+
# downloader.send(:main_dir_date).should == "20130123"
|
99
|
+
|
100
|
+
# downloader.filedate = Date.parse('23-01-2013') #wednesday
|
101
|
+
# downloader.send(:main_dir_date).should == "20130123"
|
102
|
+
# end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "incremental" do
|
106
|
+
let(:type) { 'incremental' }
|
107
|
+
it "should return wednesday of this week if filedate is friday-sunday" do
|
108
|
+
downloader.filedate = Date.parse('18-01-2013') #friday
|
109
|
+
downloader.send(:main_dir_date).should == "20130116"
|
110
|
+
|
111
|
+
downloader.filedate = Date.parse('19-01-2013') #sut
|
112
|
+
downloader.send(:main_dir_date).should == "20130116"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should return wednesday of prev week if filedate is monday-thursday" do
|
116
|
+
downloader.filedate = Date.parse('21-01-2013') #monday
|
117
|
+
downloader.send(:main_dir_date).should == "20130116"
|
118
|
+
|
119
|
+
downloader.filedate = Date.parse('24-01-2013') #thursday
|
120
|
+
downloader.send(:main_dir_date).should == "20130116"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "download" do
|
126
|
+
let(:filedate) { Date.parse('21-01-2013') }
|
127
|
+
|
128
|
+
before do
|
129
|
+
@tmp_dir = [Dir.tmpdir, 'epm_files'].join('/')
|
130
|
+
FileUtils.mkpath @tmp_dir
|
131
|
+
|
132
|
+
AppleEpf.configure do |config|
|
133
|
+
config.apple_id = 'test'
|
134
|
+
config.apple_password = 'test'
|
135
|
+
config.extract_dir = @tmp_dir
|
136
|
+
end
|
137
|
+
|
138
|
+
downloader.stub(:download_and_compare_md5_checksum)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should properly set url for download" do
|
142
|
+
downloader.stub(:file_exists?){ file_exists }
|
143
|
+
downloader.stub(:start_download)
|
144
|
+
downloader.download
|
145
|
+
downloader.apple_filename_full.should eq("http://feeds.itunes.apple.com/feeds/epf/v3/full/20130116/incremental/20130121/popularity20130121.tbz")
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should properly set local file to store file in" do
|
149
|
+
downloader.stub(:file_exists?){ file_exists }
|
150
|
+
downloader.stub(:start_download)
|
151
|
+
downloader.download
|
152
|
+
downloader.download_to.should eq("#{@tmp_dir}/incremental/popularity20130121.tbz")
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should download and save file" do
|
156
|
+
stub_request(:get, "http://test:test@feeds.itunes.apple.com/feeds/epf/v3/full/20130123/popularity20130123.tbz").
|
157
|
+
to_return(:status => 200, :body => "Test\nWow", :headers => {})
|
158
|
+
|
159
|
+
downloader = AppleEpf::Downloader.new('full', file, filedate)
|
160
|
+
downloader.stub(:download_and_compare_md5_checksum)
|
161
|
+
downloader.stub(:file_exists?){ file_exists }
|
162
|
+
downloader.download
|
163
|
+
IO.read(downloader.download_to).should eq("Test\nWow")
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should retry 3 times to download" do
|
167
|
+
pending
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "dirpath" do
|
171
|
+
before do
|
172
|
+
downloader.stub(:file_exists?){ file_exists }
|
173
|
+
downloader.stub(:start_download)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should be able to change dir where to save files" do
|
177
|
+
tmp_dir = Dir.tmpdir
|
178
|
+
downloader.dirpath = [tmp_dir, 'whatever_path'].join('/')
|
179
|
+
downloader.download.should == "#{tmp_dir}/whatever_path/incremental/popularity20130121.tbz"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "#download_and_compare_md5_checksum" do
|
184
|
+
before do
|
185
|
+
downloader.unstub(:download_and_compare_md5_checksum)
|
186
|
+
end
|
187
|
+
it "should raise exception if md5 file does not match real md5 checksum of file" do
|
188
|
+
stub_request(:get, "http://test:test@feeds.itunes.apple.com/feeds/epf/v3/full/20130116/incremental/20130121/popularity20130121.tbz").
|
189
|
+
to_return(:status => 200, :body => "Test\nWow", :headers => {})
|
190
|
+
|
191
|
+
stub_request(:get, "http://test:test@feeds.itunes.apple.com/feeds/epf/v3/full/20130116/incremental/20130121/popularity20130121.tbz.md5").
|
192
|
+
to_return(:status => 200, :body => "tupo", :headers => {})
|
193
|
+
|
194
|
+
downloader.stub(:file_exists?){ file_exists }
|
195
|
+
|
196
|
+
expect {
|
197
|
+
downloader.download
|
198
|
+
}.to raise_exception(AppleEpf::Md5CompareError)
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should not raise exception if md5 is ok" do
|
203
|
+
stub_request(:get, "http://test:test@feeds.itunes.apple.com/feeds/epf/v3/full/20130116/incremental/20130121/popularity20130121.tbz").
|
204
|
+
to_return(:status => 200, :body => "Test\nWow", :headers => {})
|
205
|
+
|
206
|
+
stub_request(:get, "http://test:test@feeds.itunes.apple.com/feeds/epf/v3/full/20130116/incremental/20130121/popularity20130121.tbz.md5").
|
207
|
+
to_return(:status => 200, :body => "MD5 (popularity20130116.tbz) = 0371a79664856494e840af9e1e6c0152\n", :headers => {})
|
208
|
+
|
209
|
+
|
210
|
+
downloader.stub(:file_exists?){ file_exists }
|
211
|
+
|
212
|
+
expect {
|
213
|
+
downloader.download
|
214
|
+
}.not_to raise_exception(AppleEpf::Md5CompareError)
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe AppleEpf::Extractor do
|
5
|
+
let(:file_basename) { 'itunes20130111.tbz' }
|
6
|
+
let(:files_to_extract) { ['application', 'test_file.txt'] }
|
7
|
+
|
8
|
+
before do
|
9
|
+
@tmp_dir = [Dir.tmpdir, 'test_epm_files'].join('/')
|
10
|
+
FileUtils.mkpath @tmp_dir
|
11
|
+
|
12
|
+
AppleEpf.configure do |config|
|
13
|
+
config.apple_id = 'test'
|
14
|
+
config.apple_password = 'test'
|
15
|
+
config.extract_dir = @tmp_dir
|
16
|
+
end
|
17
|
+
|
18
|
+
@copy_to = "#{@tmp_dir}/#{file_basename}"
|
19
|
+
FileUtils.copy_file(apple_epf_inc_filename(file_basename), @copy_to)
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
FileUtils.remove_dir(@tmp_dir)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "initialize" do
|
27
|
+
it "should set instance variables" do
|
28
|
+
extractor = AppleEpf::Extractor.new(@copy_to, files_to_extract)
|
29
|
+
|
30
|
+
extractor.filename.should == @copy_to
|
31
|
+
extractor.dirname.should == @tmp_dir
|
32
|
+
extractor.basename.should == file_basename
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "perform" do
|
37
|
+
it "should raise error if extracting was not successful" do
|
38
|
+
files_to_extract = ['application', 'wrong_file.txt']
|
39
|
+
extractor = AppleEpf::Extractor.new(@copy_to, files_to_extract)
|
40
|
+
|
41
|
+
expect {
|
42
|
+
extractor.perform
|
43
|
+
}.to raise_exception ("Unable to extract files '#{files_to_extract.join(' ')}' from #{@copy_to}")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return list if extracted files" do
|
47
|
+
extractor = AppleEpf::Extractor.new(@copy_to, files_to_extract)
|
48
|
+
extractor.perform
|
49
|
+
extractor.file_entry.tbz_file.should == @copy_to
|
50
|
+
|
51
|
+
expected_extracted = files_to_extract.map do |f|
|
52
|
+
File.join(@tmp_dir, 'itunes20130111', f)
|
53
|
+
end
|
54
|
+
|
55
|
+
extractor.file_entry.extracted_files.should == Hash[files_to_extract.zip(expected_extracted)]
|
56
|
+
extractor.file_entry.tbz_file.should == @copy_to
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should remove file if successfully untarred" do
|
60
|
+
extractor = AppleEpf::Extractor.new(@copy_to, files_to_extract)
|
61
|
+
extractor.perform
|
62
|
+
File.exists?(extractor.filename).should be_false
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not remove file if successfully untarred and it was asked to leave file" do
|
66
|
+
extractor = AppleEpf::Extractor.new(@copy_to, files_to_extract)
|
67
|
+
extractor.keep_tbz_after_extract = true
|
68
|
+
extractor.perform
|
69
|
+
File.exists?(extractor.filename).should be_true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|