ftputils 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,12 @@
1
+ = Change Log
2
+
3
+ == 0.2.1
4
+
5
+ * Add timeouts to FTPUtils and FTPConnection methods. Defaults to 5 minutes,
6
+ but can be set with FTPUtils.timeout_period=(seconds).
7
+
8
+ == 0.2.0
9
+
10
+ * Major internal refactoring, pulling FTPConnection out of FTPURI
11
+ * Add FTPFile, which implements a subset of the File API for FTP URIs
12
+ * Add FTPUtils.ls
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
data/ftputils.gemspec ADDED
@@ -0,0 +1,67 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ftputils}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Bruz Marzolf"]
12
+ s.date = %q{2011-06-20}
13
+ s.description = %q{Implements a subset of the methods in FileUtils for FTP URIs, falling back on FileUtils when a path doesn't look like FTP}
14
+ s.email = %q{bmarzolf@systemsbiology.org}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "CHANGELOG.rdoc",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "ftputils.gemspec",
27
+ "lib/ftputils.rb",
28
+ "lib/ftputils/ext/class.rb",
29
+ "lib/ftputils/ftpconnection.rb",
30
+ "lib/ftputils/ftpfile.rb",
31
+ "lib/ftputils/ftpuri.rb",
32
+ "spec/ftpconnection_spec.rb",
33
+ "spec/ftpfile_spec.rb",
34
+ "spec/ftpuri_spec.rb",
35
+ "spec/ftputils_spec.rb",
36
+ "spec/spec.opts",
37
+ "spec/spec_helper.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/bmarzolf/ftputils}
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.6}
42
+ s.summary = %q{Like FileUtils for FTP}
43
+ s.test_files = [
44
+ "spec/ftpconnection_spec.rb",
45
+ "spec/ftpfile_spec.rb",
46
+ "spec/ftpuri_spec.rb",
47
+ "spec/ftputils_spec.rb",
48
+ "spec/spec_helper.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<ftpfxp>, [">= 0.0.4"])
57
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
58
+ else
59
+ s.add_dependency(%q<ftpfxp>, [">= 0.0.4"])
60
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<ftpfxp>, [">= 0.0.4"])
64
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
65
+ end
66
+ end
67
+
data/lib/ftputils.rb CHANGED
@@ -7,61 +7,79 @@ end
7
7
 
8
8
  require 'fileutils'
9
9
  require 'open-uri'
10
+ require 'timeout'
10
11
 
11
12
  class FTPUtils
12
13
 
14
+ @@timeout_period = 300
15
+
16
+ def self.timeout_period
17
+ # default to a 5 minute timeout
18
+ @@timeout_period || 300
19
+ end
20
+
21
+ def self.timeout_period=(seconds)
22
+ @@timeout_period = seconds
23
+ end
24
+
13
25
  def self.cp(src, dest, options = {})
14
- # handle all combinations of copying to/from FTP and local files
15
- case [ftp_url?(src), ftp_url?(dest)]
16
- when [true, true]
17
- raise "src should be a filename, not a directory" if FTPFile.directory?(src)
26
+ timeout(timeout_period) do
27
+ # handle all combinations of copying to/from FTP and local files
28
+ case [ftp_url?(src), ftp_url?(dest)]
29
+ when [true, true]
30
+ raise "src should be a filename, not a directory" if FTPFile.directory?(src)
18
31
 
19
- dest_path = FTPFile.dirname(dest) + "/" + ( FTPFile.basename(dest) || FTPFile.basename(src) )
20
- FTPConnection.connect(src).fxpto(FTPConnection.connect(dest), dest_path, FTPFile.relative_path(src))
21
- when [true, false]
22
- raise "src should be a filename, not a directory" if FTPFile.directory?(src)
32
+ dest_path = FTPFile.dirname(dest) + "/" + ( FTPFile.basename(dest) || FTPFile.basename(src) )
33
+ FTPConnection.connect(src).fxpto(FTPConnection.connect(dest), dest_path, FTPFile.relative_path(src))
34
+ when [true, false]
35
+ raise "src should be a filename, not a directory" if FTPFile.directory?(src)
23
36
 
24
- filename = FTPFile.basename(src)
37
+ filename = FTPFile.basename(src)
25
38
 
26
- if File.directory? dest
27
- dest += "/#{filename}"
28
- end
39
+ if File.directory? dest
40
+ dest += "/#{filename}"
41
+ end
29
42
 
30
- connection = FTPConnection.connect(src)
31
- connection.chdir FTPFile.dirname(src)
32
- connection.getbinaryfile filename, dest, 1024
33
- when [false, true]
34
- raise "src should be a filename, not a directory" if File.directory? src
43
+ connection = FTPConnection.connect(src)
44
+ connection.chdir FTPFile.dirname(src)
45
+ connection.getbinaryfile filename, dest, 1024
46
+ when [false, true]
47
+ raise "src should be a filename, not a directory" if File.directory? src
35
48
 
36
- dest_path = FTPFile.relative_path(dest)
49
+ dest_path = FTPFile.relative_path(dest)
37
50
 
38
- if FTPFile.directory?(dest)
39
- dest_path += "/#{File.basename(src)}"
40
- end
51
+ if FTPFile.directory?(dest)
52
+ dest_path += "/#{File.basename(src)}"
53
+ end
41
54
 
42
- connection = FTPConnection.connect(dest)
43
- connection.chdir FTPFile.dirname(dest)
55
+ connection = FTPConnection.connect(dest)
56
+ connection.chdir FTPFile.dirname(dest)
44
57
 
45
- begin
46
58
  connection.putbinaryfile src, dest_path, 1024
47
- rescue Net::FTPPermError
48
- raise "Unable to copy #{src} to #{dest_path}, possibly due to FTP server permissions"
59
+ when [false, false]
60
+ FileUtils.cp src, dest, options
49
61
  end
50
- when [false, false]
51
- FileUtils.cp src, dest, options
52
62
  end
63
+ rescue Net::FTPPermError
64
+ raise "Unable to copy #{src} to #{dest}, possibly due to FTP server permissions"
65
+ rescue Timeout::Error
66
+ raise "Copying #{src} to #{dest} timed out after #{timeout_period} seconds"
53
67
  end
54
68
 
55
69
  def self.rm(path)
56
- if ftp_url?(path)
57
- raise "Can't use FTPUtils.rm on directories. Instead use FTPUtils.rm_r" if FTPFile.directory?(path)
58
-
59
- connection = FTPConnection.connect(path)
60
- connection.chdir FTPFile.dirname(path)
61
- connection.delete FTPFile.basename(path)
62
- else
63
- FileUtils.rm path
70
+ timeout(timeout_period) do
71
+ if ftp_url?(path)
72
+ raise "Can't use FTPUtils.rm on directories. Instead use FTPUtils.rm_r" if FTPFile.directory?(path)
73
+
74
+ connection = FTPConnection.connect(path)
75
+ connection.chdir FTPFile.dirname(path)
76
+ connection.delete FTPFile.basename(path)
77
+ else
78
+ FileUtils.rm path
79
+ end
64
80
  end
81
+ rescue Timeout::Error
82
+ raise "Removing #{path} timed out after #{timeout_period} seconds"
65
83
  end
66
84
 
67
85
  def self.mv(src, dest, options = {})
@@ -70,78 +88,94 @@ class FTPUtils
70
88
  end
71
89
 
72
90
  def self.rm_r(path)
73
- if ftp_url?(path)
74
- if FTPFile.directory?(path)
75
- connection = FTPConnection.connect(path)
76
- connection.chdir FTPFile.relative_path(path)
77
-
78
- files = connection.nlst
79
- files.each {|file| rm_r "#{path}/#{file}"}
80
-
81
- connection.rmdir FTPFile.relative_path(path)
91
+ timeout(timeout_period) do
92
+ if ftp_url?(path)
93
+ if FTPFile.directory?(path)
94
+ connection = FTPConnection.connect(path)
95
+ connection.chdir FTPFile.relative_path(path)
96
+
97
+ files = connection.nlst
98
+ files.each {|file| rm_r "#{path}/#{file}"}
99
+
100
+ connection.rmdir FTPFile.relative_path(path)
101
+ else
102
+ rm(path)
103
+ end
82
104
  else
83
- rm(path)
105
+ FileUtils.rm_r path
84
106
  end
85
- else
86
- FileUtils.rm_r path
87
107
  end
108
+ rescue Timeout::Error
109
+ raise "Removing #{path} timed out after #{timeout_period} seconds"
88
110
  end
89
111
 
90
112
  def self.mkdir_p(path)
91
- if ftp_url?(path)
92
- connection = FTPConnection.connect(path)
113
+ timeout(timeout_period) do
114
+ if ftp_url?(path)
115
+ connection = FTPConnection.connect(path)
93
116
 
94
- subdirs = FTPFile.relative_path(path).split(/\//)
95
- subdirs.each do |subdir|
96
- next if subdir == ""
117
+ subdirs = FTPFile.relative_path(path).split(/\//)
118
+ subdirs.each do |subdir|
119
+ next if subdir == ""
97
120
 
98
- connection.mkdir subdir
99
- connection.chdir subdir
121
+ connection.mkdir subdir
122
+ connection.chdir subdir
123
+ end
124
+ else
125
+ FileUtils.mkdir_p path
100
126
  end
101
- else
102
- FileUtils.mkdir_p path
103
127
  end
128
+ rescue Timeout::Error
129
+ raise "Creating #{path} timed out after #{timeout_period} seconds"
104
130
  end
105
131
 
106
132
  def self.cp_r(src, dest, options = {})
107
- # handle all combinations of copying to/from FTP and local files
108
- if ftp_url?(src)
109
- if FTPFile.directory?(src)
110
- mkdir_p dest
111
-
112
- connection = FTPConnection.connect(src)
113
- files = connection.nlst
114
- files.each {|file| cp_r "#{src}/#{file}", "#{dest}/#{file}", options}
115
- else
116
- cp(src, dest, options)
117
- end
118
- elsif ftp_url?(dest)
119
- if FTPFile.directory?(dest)
120
- mkdir_p dest
121
-
122
- files = Dir.entries(src)
123
- files.each {|file| cp_r "#{src}/#{file}", "#{dest}/#{file}", options}
133
+ timeout(timeout_period) do
134
+ # handle all combinations of copying to/from FTP and local files
135
+ if ftp_url?(src)
136
+ if FTPFile.directory?(src)
137
+ mkdir_p dest
138
+
139
+ connection = FTPConnection.connect(src)
140
+ files = connection.nlst
141
+ files.each {|file| cp_r "#{src}/#{file}", "#{dest}/#{file}", options}
142
+ else
143
+ cp(src, dest, options)
144
+ end
145
+ elsif ftp_url?(dest)
146
+ if FTPFile.directory?(dest)
147
+ mkdir_p dest
148
+
149
+ files = Dir.entries(src)
150
+ files.each {|file| cp_r "#{src}/#{file}", "#{dest}/#{file}", options}
151
+ else
152
+ cp(src, dest, options)
153
+ end
124
154
  else
125
- cp(src, dest, options)
155
+ FileUtils.cp_r src, dest
126
156
  end
127
- else
128
- FileUtils.cp_r src, dest
129
157
  end
158
+ rescue Timeout::Error
159
+ raise "Copying #{src} to #{dest} timed out after #{timeout_period} seconds"
130
160
  end
131
161
 
132
162
  def self.ls(path)
133
- if ftp_url?(path)
134
- if FTPFile.directory?(path)
135
- connection = FTPConnection.connect(path)
136
- connection.chdir FTPFile.relative_path(path)
137
-
138
- return connection.nlst
163
+ timeout(timeout_period) do
164
+ if ftp_url?(path)
165
+ if FTPFile.directory?(path)
166
+ connection = FTPConnection.connect(path)
167
+ connection.chdir FTPFile.relative_path(path)
168
+
169
+ return connection.nlst
170
+ else
171
+ nil
172
+ end
139
173
  else
140
- nil
174
+ Dir.entries path
141
175
  end
142
- else
143
- Dir.entries path
144
176
  end
177
+ rescue Timeout::Error
178
+ raise "Listing #{path} timed out after #{timeout_period} seconds"
145
179
  end
146
180
 
147
181
  private
@@ -1,29 +1,35 @@
1
+ require 'timeout'
2
+
1
3
  class FTPUtils
2
4
  class FTPConnection
3
5
  cattr_accessor :connections
4
6
 
5
7
  def self.connect(uri)
6
- if uri.match(/^ftp:\/\/(.*?):(.*?)@(.*?)(\/.*)*$/)
7
- username = $1
8
- password = $2
9
- host = $3
10
-
11
- connection = self.establish_connection(host, username, password)
12
-
13
- # need to reset to the top directory since connections are cached and
14
- # could have been left elsewhere. this also provides a way to see if the
15
- # connection has expired and needs to be reloaded
16
- begin
17
- connection.chdir "/"
18
- rescue Net::FTPTempError
19
- connection = self.establish_connection(host, username, password, true)
20
- retry
8
+ timeout(FTPUtils.timeout_period) do
9
+ if uri.match(/^ftp:\/\/(.*?):(.*?)@(.*?)(\/.*)*$/)
10
+ username = $1
11
+ password = $2
12
+ host = $3
13
+
14
+ connection = self.establish_connection(host, username, password)
15
+
16
+ # need to reset to the top directory since connections are cached and
17
+ # could have been left elsewhere. this also provides a way to see if the
18
+ # connection has expired and needs to be reloaded
19
+ begin
20
+ connection.chdir "/"
21
+ rescue Net::FTPTempError
22
+ connection = self.establish_connection(host, username, password, true)
23
+ retry
24
+ end
25
+
26
+ return connection
27
+ else
28
+ raise "Invalid FTP URL provided: #{uri}"
21
29
  end
22
-
23
- return connection
24
- else
25
- raise "Invalid FTP URL provided: #{uri}"
26
30
  end
31
+ rescue Timeout::Error
32
+ raise "Connecting to #{uri} timed out after #{FTPUtils.timeout_period} seconds"
27
33
  end
28
34
 
29
35
  def self.clear_connection_cache
@@ -52,5 +52,17 @@ describe FTPUtils::FTPConnection do
52
52
  FTPUtils::FTPConnection.connect("ftp://admin:test@myhost/path/to/file.txt")
53
53
  end.should raise_error(Net::FTPPermError)
54
54
  end
55
+
56
+ it "should raise an error if connecting takes too long" do
57
+ FTPUtils.timeout_period = 0.1
58
+
59
+ Net::FTPFXP.should_receive(:new) do
60
+ sleep 1
61
+ end
62
+
63
+ lambda do
64
+ FTPUtils::FTPConnection.connect("ftp://admin:test@myhost/path/to/file.txt")
65
+ end.should raise_error("Connecting to ftp://admin:test@myhost/path/to/file.txt timed out after 0.1 seconds")
66
+ end
55
67
  end
56
68
  end
@@ -76,6 +76,33 @@ describe "FTPUtils" do
76
76
  FTPUtils.cp "ftp://admin:test@host1/subdir1", "ftp://admin:test@host2/subdir2"
77
77
  end.should raise_error("src should be a filename, not a directory")
78
78
  end
79
+
80
+ it "should time out a transfer that takes too long" do
81
+ FTPUtils.timeout_period = 0.1
82
+
83
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/file1.txt").
84
+ and_return(false)
85
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host2/file2.txt").
86
+ and_return("/")
87
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host2/file2.txt").
88
+ and_return("file2.txt")
89
+
90
+ mock_src_connection = mock(FTPUtils::FTPConnection)
91
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/file1.txt").
92
+ and_return(mock_src_connection)
93
+ mock_dest_connection = mock(FTPUtils::FTPConnection)
94
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host2/file2.txt").
95
+ and_return(mock_dest_connection)
96
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/file1.txt").
97
+ and_return("/file1.txt")
98
+ mock_src_connection.should_receive(:fxpto).with(mock_dest_connection, "//file2.txt", "/file1.txt") do
99
+ sleep 1
100
+ end
101
+
102
+ lambda do
103
+ FTPUtils.cp "ftp://admin:test@host1/file1.txt", "ftp://admin:test@host2/file2.txt"
104
+ end.should raise_error("Copying ftp://admin:test@host1/file1.txt to ftp://admin:test@host2/file2.txt timed out after 0.1 seconds")
105
+ end
79
106
  end
80
107
 
81
108
  describe "from FTP to the local filesystem" do
@@ -119,6 +146,32 @@ describe "FTPUtils" do
119
146
  FTPUtils.cp "ftp://admin:test@host1/subdir1/file1.txt", "/home/me"
120
147
  end
121
148
 
149
+ it "should time out a transfer that takes too long" do
150
+ FTPUtils.timeout_period = 0.1
151
+
152
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file1.txt").
153
+ and_return(false)
154
+
155
+ FTPUtils::FTPFile.should_receive(:basename).with("ftp://admin:test@host1/subdir1/file1.txt").
156
+ and_return("file1.txt")
157
+
158
+ File.should_receive(:directory?).with("/home/me/file2.txt").and_return(false)
159
+
160
+ mock_src_connection = mock(FTPUtils::FTPConnection)
161
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/file1.txt").
162
+ and_return(mock_src_connection)
163
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host1/subdir1/file1.txt").
164
+ and_return("/subdir1")
165
+ mock_src_connection.should_receive(:chdir).with("/subdir1")
166
+ mock_src_connection.should_receive(:getbinaryfile).with("file1.txt", "/home/me/file2.txt", 1024) do
167
+ sleep 1
168
+ end
169
+
170
+ lambda do
171
+ FTPUtils.cp "ftp://admin:test@host1/subdir1/file1.txt", "/home/me/file2.txt"
172
+ end.should raise_error("Copying ftp://admin:test@host1/subdir1/file1.txt to /home/me/file2.txt timed out after 0.1 seconds")
173
+ end
174
+
122
175
  it "should raise an error if the source URI is a directory" do
123
176
  FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
124
177
  and_return(true)
@@ -179,7 +232,29 @@ describe "FTPUtils" do
179
232
 
180
233
  lambda do
181
234
  FTPUtils.cp "/home/me/file1.txt", "ftp://admin:test@host2/file2.txt"
182
- end.should raise_error("Unable to copy /home/me/file1.txt to /file2.txt, possibly due to FTP server permissions")
235
+ end.should raise_error("Unable to copy /home/me/file1.txt to ftp://admin:test@host2/file2.txt, possibly due to FTP server permissions")
236
+ end
237
+
238
+ it "should time out a transfer that takes too long" do
239
+ FTPUtils.timeout_period = 0.1
240
+
241
+ File.should_receive(:directory?).with("/home/me/file1.txt").and_return(false)
242
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host2/file2.txt").
243
+ and_return("/file2.txt")
244
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host2/file2.txt").
245
+ and_return(false)
246
+ mock_dest_connection = mock(FTPUtils::FTPConnection)
247
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host2/file2.txt").
248
+ and_return(mock_dest_connection)
249
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host2/file2.txt").and_return("/")
250
+ mock_dest_connection.should_receive(:chdir).with("/")
251
+ mock_dest_connection.should_receive(:putbinaryfile).with("/home/me/file1.txt", "/file2.txt", 1024) do
252
+ sleep 1
253
+ end
254
+
255
+ lambda do
256
+ FTPUtils.cp "/home/me/file1.txt", "ftp://admin:test@host2/file2.txt"
257
+ end.should raise_error("Copying /home/me/file1.txt to ftp://admin:test@host2/file2.txt timed out after 0.1 seconds")
183
258
  end
184
259
 
185
260
  it "should raise an error if the source file is a directory" do
@@ -228,6 +303,25 @@ describe "FTPUtils" do
228
303
 
229
304
  FTPUtils.rm "file.txt"
230
305
  end
306
+
307
+ it "should time out a removal that takes too long" do
308
+ FTPUtils.timeout_period = 0.1
309
+
310
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file.txt").
311
+ and_return(false)
312
+ mock_connection = mock(FTPUtils::FTPConnection)
313
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/file.txt").
314
+ and_return(mock_connection)
315
+ FTPUtils::FTPFile.should_receive(:dirname).with("ftp://admin:test@host1/subdir1/file.txt").and_return("/subdir1")
316
+ mock_connection.should_receive(:chdir).with("/subdir1")
317
+ mock_connection.should_receive(:delete).with("file.txt") do
318
+ sleep 1
319
+ end
320
+
321
+ lambda do
322
+ FTPUtils.rm "ftp://admin:test@host1/subdir1/file.txt"
323
+ end.should raise_error("Removing ftp://admin:test@host1/subdir1/file.txt timed out after 0.1 seconds")
324
+ end
231
325
  end
232
326
 
233
327
  describe "removing recursively" do
@@ -275,6 +369,43 @@ describe "FTPUtils" do
275
369
 
276
370
  FTPUtils.rm_r "dir"
277
371
  end
372
+
373
+ it "should time out a removal that takes too long" do
374
+ FTPUtils.timeout_period = 0.1
375
+
376
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1").
377
+ and_return(true)
378
+ mock_connection = mock(FTPUtils::FTPConnection)
379
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1").
380
+ and_return(mock_connection)
381
+ mock_connection.should_receive(:chdir).with("/subdir1")
382
+ mock_connection.should_receive(:nlst).and_return( ["subdir2", "file.txt"] )
383
+
384
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/subdir2").
385
+ and_return(true)
386
+ mock_subdir_connection = mock(FTPUtils::FTPConnection)
387
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/subdir2").
388
+ and_return(mock_subdir_connection)
389
+ mock_subdir_connection.should_receive(:chdir).with("/subdir1/subdir2")
390
+ mock_subdir_connection.should_receive(:nlst).and_return( [] )
391
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1/subdir2").twice.
392
+ and_return("/subdir1/subdir2")
393
+ mock_subdir_connection.should_receive(:rmdir).with("/subdir1/subdir2")
394
+
395
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1/file.txt").
396
+ and_return(false)
397
+ FTPUtils.should_receive(:rm).with("ftp://admin:test@host1/subdir1/file.txt")
398
+
399
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1").twice.
400
+ and_return("/subdir1")
401
+ mock_connection.should_receive(:rmdir).with("/subdir1") do
402
+ sleep 1
403
+ end
404
+
405
+ lambda do
406
+ FTPUtils.rm_r "ftp://admin:test@host1/subdir1"
407
+ end.should raise_error("Removing ftp://admin:test@host1/subdir1 timed out after 0.1 seconds")
408
+ end
278
409
  end
279
410
 
280
411
  describe "create a directory and all its parents" do
@@ -299,6 +430,25 @@ describe "FTPUtils" do
299
430
 
300
431
  FTPUtils.mkdir_p("subdir1/subdir2")
301
432
  end
433
+
434
+ it "should time out a creation that takes too long" do
435
+ FTPUtils.timeout_period = 0.1
436
+
437
+ mock_connection = mock(FTPUtils::FTPConnection)
438
+ FTPUtils::FTPConnection.should_receive(:connect).with("ftp://admin:test@host1/subdir1/subdir2").
439
+ and_return(mock_connection)
440
+
441
+ FTPUtils::FTPFile.should_receive(:relative_path).with("ftp://admin:test@host1/subdir1/subdir2").
442
+ and_return("/subdir1/subdir2")
443
+
444
+ mock_connection.should_receive(:mkdir).with("subdir1") do
445
+ sleep 1
446
+ end
447
+
448
+ lambda do
449
+ FTPUtils.mkdir_p "ftp://admin:test@host1/subdir1/subdir2"
450
+ end.should raise_error("Creating ftp://admin:test@host1/subdir1/subdir2 timed out after 0.1 seconds")
451
+ end
302
452
  end
303
453
 
304
454
  describe "copying recursively" do
@@ -375,6 +525,18 @@ describe "FTPUtils" do
375
525
 
376
526
  FTPUtils.cp_r "/home/me/subdir1", "/home/me/subdir2"
377
527
  end
528
+
529
+ it "should time out a copy that takes too long" do
530
+ FTPUtils.timeout_period = 0.1
531
+
532
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1") do
533
+ sleep 1
534
+ end
535
+
536
+ lambda do
537
+ FTPUtils.cp_r "ftp://admin:test@host1/subdir1", "ftp://admin:test@host2/subdir2"
538
+ end.should raise_error("Copying ftp://admin:test@host1/subdir1 to ftp://admin:test@host2/subdir2 timed out after 0.1 seconds")
539
+ end
378
540
  end
379
541
 
380
542
  describe "listing the entries in a directory" do
@@ -402,5 +564,17 @@ describe "FTPUtils" do
402
564
 
403
565
  FTPUtils.ls("/home/me/subdir1")
404
566
  end
567
+
568
+ it "should time out a directory listing that takes too long" do
569
+ FTPUtils.timeout_period = 0.1
570
+
571
+ FTPUtils::FTPFile.should_receive(:directory?).with("ftp://admin:test@host1/subdir1") do
572
+ sleep 1
573
+ end
574
+
575
+ lambda do
576
+ FTPUtils.ls("ftp://admin:test@host1/subdir1")
577
+ end.should raise_error("Listing ftp://admin:test@host1/subdir1 timed out after 0.1 seconds")
578
+ end
405
579
  end
406
580
  end
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,3 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
3
  require 'ftputils'
4
- require 'spec'
5
- require 'spec/autorun'
6
-
7
- Spec::Runner.configure do |config|
8
-
9
- end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 0
9
- version: 0.2.0
8
+ - 1
9
+ version: 0.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Bruz Marzolf
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-06-04 00:00:00 -07:00
17
+ date: 2011-06-20 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -56,11 +56,12 @@ extra_rdoc_files:
56
56
  - README.rdoc
57
57
  files:
58
58
  - .document
59
- - .gitignore
59
+ - CHANGELOG.rdoc
60
60
  - LICENSE
61
61
  - README.rdoc
62
62
  - Rakefile
63
63
  - VERSION
64
+ - ftputils.gemspec
64
65
  - lib/ftputils.rb
65
66
  - lib/ftputils/ext/class.rb
66
67
  - lib/ftputils/ftpconnection.rb
@@ -77,8 +78,8 @@ homepage: http://github.com/bmarzolf/ftputils
77
78
  licenses: []
78
79
 
79
80
  post_install_message:
80
- rdoc_options:
81
- - --charset=UTF-8
81
+ rdoc_options: []
82
+
82
83
  require_paths:
83
84
  - lib
84
85
  required_ruby_version: !ruby/object:Gem::Requirement
data/.gitignore DELETED
@@ -1,21 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC