File Coverage

blib/lib/Net/FTP/AutoReconnect.pm
Criterion Covered Total %
statement 9 224 4.0
branch 0 64 0.0
condition 0 3 0.0
subroutine 3 76 3.9
pod 4 45 8.8
total 16 412 3.8


line stmt bran cond sub pod time code
1             package Net::FTP::AutoReconnect;
2             our $VERSION = '0.3';
3              
4 1     1   24511 use warnings;
  1         2  
  1         61  
5 1     1   6 use strict;
  1         1  
  1         38  
6              
7 1     1   1167 use Net::FTP;
  1         62414  
  1         2505  
8              
9             =head1 NAME
10              
11             Net::FTP::AutoReconnect - FTP client class with automatic reconnect on failure
12              
13             =head1 SYNOPSIS
14              
15             C is a wrapper module around C.
16             For many commands, if anything goes wrong on the first try, it tries
17             to disconnect and reconnect to the server, restore the state to the
18             same as it was when the command was executed, then execute it again.
19             The state includes login credentials, authorize credentials, transfer
20             mode (ASCII or binary), current working directory, and any restart,
21             passive, or port commands sent.
22              
23             =head1 DESCRIPTION
24              
25             The goal of this method is to hide some implementation details of FTP
26             server systems from the programmer. In particular, many FTP systems
27             will automatically disconnect a user after a relatively short idle
28             time or after a transfer is aborted. In this case,
29             C will simply reconnect, send the commands
30             necessary to return your session to its previous state, then resend
31             the command. If that fails, it will return the error.
32              
33             It makes no effort to determine what sorts of errors are likely to
34             succeed when they're retried. Partly that's because it's hard to
35             know; if you're retreiving a file from an FTP site with several
36             mirrors and the file is not found, for example, maybe on the next try
37             you'll connect to a different server and find it. But mostly it's
38             from laziness; if you have some good ideas about how to determine when
39             to retry and when not to bother, by all means send patches.
40              
41             This module contains an instance of C, which it passes most
42             method calls along to.
43              
44             These methods also record their state: C, C,
45             C, C, C, C, C,
46             C,C, C, C. Directory changing commands
47             execute a C afterwards and store their new working directory.
48              
49             These methods are automatically retried: C, C, C,
50             C, C, C, C, C, C, C,
51             C, C, C, C, C, C, C, C,
52             C, C, C, C, C, C, C,
53             C.
54              
55             These methods are tried just once: C, C, C,
56             C, C, C, C, C,
57             C, C, C. From C: C,
58             C, C, C. C doesn't actually send any
59             FTP commands (they're sent along with the command they apply to),
60             which is why it's not restarted.
61              
62             Any other commands are unimplemented (or possibly misdocumented); if I
63             missed one you'd like, please send a patch.
64              
65             =head2 CONSTRUCTOR
66              
67             =head3 new
68              
69             All parameters are passed along verbatim to C, as well as
70             stored in case we have to reconnect.
71              
72             =cut
73             ;
74              
75             sub new {
76 0     0 1   my $self = {};
77 0           my $class = shift;
78 0           bless $self,$class;
79              
80             # Adapted from the Net::FTP constructor, version 2.77
81 0 0         if (@_ % 2)
82             {
83 0           $self->{_peer} = shift;
84 0           $self->{_args} = { @_ };
85             }
86             else
87             {
88 0           $self->{_args} = { @_ };
89 0           $self->{_peer} = delete $self->{_args}{Host};
90             }
91 0           $self->{_connect_count} = 0;
92              
93 0           $self->reconnect( 0 );
94              
95 0           $self;
96             }
97              
98             =head2 METHODS
99              
100             Most of the methods are those of L. One additional
101             method is available:
102              
103             =head3 reconnect()
104              
105             Abandon the current FTP connection and create a new one, restoring all
106             the state we can.
107              
108             =cut
109             ;
110              
111             sub reconnect
112             {
113 0     0 1   my $self = shift;
114            
115 0           my $is_reconnect = shift;
116 0 0         my $connection_type = ($is_reconnect) ? "Reconnecting" : "Connecting";
117              
118 0 0 0       warn join(' ',ref($self),$connection_type." to FTP server $self->{_peer}\n")
119             if ($ENV{DEBUG} || $self->{_args}{Debug});
120              
121 0           ++$self->{_connect_count};
122              
123 0 0         $self->{ftp} = Net::FTP->new($self->{_peer}, %{$self->{_args}})
  0            
124             or die "Couldn't create new FTP object: $@\n";
125              
126 0 0         if ($self->{login})
127             {
128 0           $self->{ftp}->login(@{$self->{login}});
  0            
129             }
130 0 0         if ($self->{authorize})
131             {
132 0           $self->{ftp}->authorize(@{$self->{authorize}});
  0            
133             }
134 0 0         if ($self->{mode})
135             {
136 0 0         if ($self->{mode} eq 'ascii')
137             {
138 0           $self->{ftp}->ascii();
139             }
140             else
141             {
142 0           $self->{ftp}->binary();
143             }
144             }
145 0 0         if ($self->{cwd})
146             {
147 0           $self->{ftp}->cwd($self->{cwd});
148             }
149 0 0         if ($self->{hash})
150             {
151 0           $self->{ftp}->hash(@{$self->{hash}});
  0            
152             }
153 0 0         if ($self->{restart})
154             {
155 0           $self->{ftp}->restart(@{$self->{restart}});
  0            
156             }
157 0 0         if ($self->{alloc})
158             {
159 0           $self->{ftp}->restart(@{$self->{alloc}});
  0            
160             }
161 0 0         if ($self->{pasv})
162             {
163 0           $self->{ftp}->pasv(@{$self->{pasv}});
  0            
164             }
165 0 0         if ($self->{port})
166             {
167 0           $self->{ftp}->port(@{$self->{port}});
  0            
168             }
169             }
170              
171             sub _auto_reconnect
172             {
173 0     0     my $self = shift;
174 0           my($code)=@_;
175              
176 0           my $ret = $code->();
177 0 0         if (!defined($ret))
178             {
179 0           $self->reconnect( 1 );
180 0           $ret = $code->();
181             }
182 0           $ret;
183             }
184              
185             sub _after_pcmd
186             {
187 0     0     my $self = shift;
188 0           my($r) = @_;
189 0 0         if ($r)
190             {
191             # succeeded
192 0           delete $self->{port};
193 0           delete $self->{pasv};
194 0           delete $self->{restart};
195 0           delete $self->{alloc};
196             }
197 0           $r;
198             }
199              
200             =head3 disconnect()
201              
202             Disconnect the current FTP connection abruptly. Mostly useful for
203             testing.
204              
205             =cut
206             ;
207              
208             sub disconnect
209             {
210 0     0 1   my $self = shift;
211 0           return POSIX::close(fileno($self->{ftp}));
212             }
213              
214             =head3 connect_count()
215              
216             Return the number of times we have connected or reconnected to this
217             server. Mostly useful for testing.
218              
219             =cut
220             ;
221              
222             sub connect_count
223             {
224 0     0 1   my $self = shift;
225 0           return $self->{_connect_count};
226             }
227              
228             sub login
229             {
230 0     0 0   my $self = shift;
231              
232 0           $self->{login} = \@_;
233 0           $self->{ftp}->login(@_);
234             }
235              
236             sub authorize
237             {
238 0     0 0   my $self = shift;
239 0           $self->{authorize} = \@_;
240 0           $self->{ftp}->authorize(@_);
241             }
242              
243             sub site
244             {
245 0     0 0   my $self = shift;
246 0           $self->{ftp}->site(@_);
247             }
248              
249             sub ascii
250             {
251 0     0 0   my $self = shift;
252 0           $self->{mode} = 'ascii';
253 0 0   0     $self->_auto_reconnect(sub { $self->{ftp}->ascii() || undef });
  0            
254             }
255              
256             sub binary
257             {
258 0     0 0   my $self = shift;
259 0           $self->{mode} = 'binary';
260 0 0   0     $self->_auto_reconnect(sub { $self->{ftp}->binary() || undef });
  0            
261             }
262              
263             sub rename
264             {
265 0     0 0   my $self = shift;
266 0           my @a = @_;
267 0 0   0     $self->_auto_reconnect(sub { $self->{ftp}->rename(@a) || undef });
  0            
268             }
269              
270             sub delete
271             {
272 0     0 0   my $self = shift;
273 0           my @a = @_;
274 0 0   0     $self->_auto_reconnect(sub { $self->{ftp}->delete(@a) || undef });
  0            
275             }
276              
277             sub cwd
278             {
279 0     0 0   my $self = shift;
280 0           my @a = @_;
281 0 0   0     my $ret = $self->_auto_reconnect(sub { $self->{ftp}->cwd(@a) || undef });
  0            
282 0 0         if ($ret)
283             {
284 0 0         $self->{cwd} = $self->{ftp}->pwd()
285             or die "Couldn't get directory after cwd\n";
286             }
287 0           $ret;
288             }
289              
290             sub cdup
291             {
292 0     0 0   my $self = shift;
293 0           my @a = @_;
294 0 0   0     my $ret = $self->_auto_reconnect(sub { $self->{ftp}->cdup(@a) || undef});
  0            
295 0 0         if ($ret)
296             {
297 0 0         $self->{cwd} = $self->{ftp}->pwd()
298             or die "Couldn't get directory after cdup\n";
299             }
300 0           $ret;
301             }
302              
303             sub pwd
304             {
305 0     0 0   my $self = shift;
306 0           my @a = @_;
307 0     0     $self->_auto_reconnect(sub { $self->{ftp}->pwd(@a)});
  0            
308             }
309              
310             sub rmdir
311             {
312 0     0 0   my $self = shift;
313 0           my @a = @_;
314 0 0   0     $self->_auto_reconnect(sub { $self->{ftp}->rmdir(@a) || undef});
  0            
315             }
316              
317             sub mkdir
318             {
319 0     0 0   my $self = shift;
320 0           my @a = @_;
321 0     0     $self->_auto_reconnect(sub { $self->{ftp}->mkdir(@a) });
  0            
322             }
323              
324             sub ls
325             {
326 0     0 0   my $self = shift;
327 0           my @a = @_;
328 0     0     my $ret = $self->_auto_reconnect(sub { $self->{ftp}->ls(@a) });
  0            
329 0 0         return $ret ? (wantarray ? @$ret : $ret) : undef;
    0          
330             }
331              
332             sub dir
333             {
334 0     0 0   my $self = shift;
335 0           my @a = @_;
336 0     0     my $ret = $self->_auto_reconnect(sub { $self->{ftp}->dir(@a) });
  0            
337 0 0         return $ret ? (wantarray ? @$ret : $ret) : undef;
    0          
338             }
339              
340             sub restart
341             {
342 0     0 0   my $self = shift;
343 0           my @a = @_;
344 0           $self->{restart} = \@a;
345 0           $self->{ftp}->restart(@_);
346             }
347              
348             sub retr
349             {
350 0     0 0   my $self = shift;
351 0           my @a = @_;
352 0 0   0     $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->retr(@a) || undef }));
  0            
353             }
354              
355             sub get
356             {
357 0     0 0   my $self = shift;
358 0           my @a = @_;
359 0     0     $self->_auto_reconnect(sub { $self->{ftp}->get(@a) });
  0            
360             }
361              
362             sub mdtm
363             {
364 0     0 0   my $self = shift;
365 0           my @a = @_;
366 0     0     $self->_auto_reconnect(sub { $self->{ftp}->mdtm(@a) });
  0            
367             }
368              
369             sub size
370             {
371 0     0 0   my $self = shift;
372 0           my @a = @_;
373 0     0     $self->_auto_reconnect(sub { $self->{ftp}->size(@a) });
  0            
374             }
375              
376             sub abort
377             {
378 0     0 0   my $self = shift;
379 0           $self->{ftp}->abort();
380             }
381              
382             sub quit
383             {
384 0     0 0   my $self = shift;
385 0           $self->{ftp}->quit();
386             }
387              
388             sub hash
389             {
390 0     0 0   my $self = shift;
391 0           my @a = @_;
392 0           $self->{hash} = \@a;
393 0           $self->{ftp}->hash(@_);
394             }
395              
396             sub alloc
397             {
398 0     0 0   my $self = shift;
399 0           my @a = @_;
400 0           $self->{alloc} = \@a;
401 0     0     $self->_auto_reconnect(sub { $self->{ftp}->alloc(@a) });
  0            
402             }
403              
404             sub put
405             {
406 0     0 0   my $self = shift;
407 0           my @a = @_;
408 0     0     $self->_auto_reconnect(sub { $self->{ftp}->put(@a) });
  0            
409             }
410              
411             sub put_unique
412             {
413 0     0 0   my $self = shift;
414 0           my @a = @_;
415 0     0     $self->_auto_reconnect(sub { $self->{ftp}->put_unique(@a) });
  0            
416             }
417              
418             sub append
419             {
420 0     0 0   my $self = shift;
421 0           my @a = @_;
422 0     0     $self->_auto_reconnect(sub { $self->{ftp}->append(@a) });
  0            
423             }
424              
425             sub unique_name
426             {
427 0     0 0   my $self = shift;
428 0           $self->{ftp}->unique_name(@_);
429             }
430              
431             sub supported
432             {
433 0     0 0   my $self = shift;
434 0           my @a = @_;
435 0     0     $self->_auto_reconnect(sub { $self->{ftp}->supported(@a) });
  0            
436             }
437              
438             sub port
439             {
440 0     0 0   my $self = shift;
441 0           my @a = @_;
442 0           $self->{port} = \@a;
443 0     0     $self->_auto_reconnect(sub { $self->{ftp}->port(@a) });
  0            
444             }
445              
446             sub pasv
447             {
448 0     0 0   my $self = shift;
449 0           my @a = @_;
450 0           $self->{pasv} = \@a;
451 0     0     $self->_auto_reconnect(sub { $self->{ftp}->pasv(@a) });
  0            
452             }
453              
454             sub nlst
455             {
456 0     0 0   my $self = shift;
457 0           my @a = @_;
458 0     0     $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->nlst(@a) }));
  0            
459             }
460              
461             sub stou
462             {
463 0     0 0   my $self = shift;
464 0           my @a = @_;
465 0     0     $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->stou(@a) }));
  0            
466             }
467              
468             sub appe
469             {
470 0     0 0   my $self = shift;
471 0           my @a = @_;
472 0     0     $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->appe(@a) }));
  0            
473             }
474              
475             sub list
476             {
477 0     0 0   my $self = shift;
478 0           my @a = @_;
479 0     0     $self->_after_pcmd($self->_auto_reconnect(sub { $self->{ftp}->list(@a) }));
  0            
480             }
481              
482             sub pasv_xfer
483             {
484 0     0 0   my $self = shift;
485 0           $self->{ftp}->pasv_xfer(@_);
486             }
487              
488             sub pasv_xfer_unique
489             {
490 0     0 0   my $self = shift;
491 0           $self->{ftp}->pasv_xfer_unique(@_);
492             }
493              
494             sub pasv_wait
495             {
496 0     0 0   my $self = shift;
497 0           $self->{ftp}->pasv_wait(@_);
498             }
499              
500             sub message
501             {
502 0     0 0   my $self = shift;
503 0           $self->{ftp}->message(@_);
504             }
505              
506             sub code
507             {
508 0     0 0   my $self = shift;
509 0           $self->{ftp}->code(@_);
510             }
511              
512             sub ok
513             {
514 0     0 0   my $self = shift;
515 0           $self->{ftp}->ok(@_);
516             }
517              
518             sub status
519             {
520 0     0 0   my $self = shift;
521 0           $self->{ftp}->status(@_);
522             }
523              
524             =head1 AUTHOR
525              
526             Scott Gifford
527              
528             =head1 BUGS
529              
530             We should really be smarter about when to retry.
531              
532             We shouldn't be hardwired to use C, but any FTP-compatible
533             class; that would allow all modules similar to this one to be chained
534             together.
535              
536             Much of this is only lightly tested; it's hard to find an FTP server
537             unreliable enough to test all aspects of it. It's mostly been tested
538             with a server that dicsonnects after an aborted transfer, and the
539             module seems to work OK.
540              
541             =head1 SEE ALSO
542              
543             L.
544              
545             =head1 COPYRIGHT
546              
547             Copyright (c) 2006 Scott Gifford. All rights reserved. This program
548             is free software; you can redistribute it and/or modify it under the
549             same terms as Perl itself.
550              
551             =cut
552              
553             1;