File Coverage

blib/lib/Net/FTP/RetrHandle.pm
Criterion Covered Total %
statement 170 246 69.1
branch 71 130 54.6
condition 24 47 51.0
subroutine 27 45 60.0
pod 6 21 28.5
total 298 489 60.9


line stmt bran cond sub pod time code
1             package Net::FTP::RetrHandle;
2             our $VERSION = '0.2';
3              
4 3     3   2096415 use warnings;
  3         8  
  3         129  
5 3     3   17 use strict;
  3         6  
  3         138  
6              
7 3     3   17 use constant DEFAULT_MAX_SKIPSIZE => 1024 * 1024 * 2;
  3         11  
  3         228  
8 3     3   16 use constant DEFAULT_BLOCKSIZE => 10240; # Net::FTP's default
  3         6  
  3         143  
9              
10 3     3   18 use base 'IO::Seekable';
  3         6  
  3         4451  
11             # We don't use base 'IO::Handle'; it currently confuses Archive::Zip.
12              
13 3     3   22628 use Carp;
  3         8  
  3         1337  
14 3     3   22 use Scalar::Util;
  3         6  
  3         288  
15              
16              
17             =head1 NAME
18              
19             Net::FTP::RetrHandle - Tied or IO::Handle-compatible interface to a file retrieved by FTP
20              
21             =head1 SYNOPSIS
22              
23             Provides a file reading interface for reading all or parts of files
24             located on a remote FTP server, including emulation of C and
25             support for downloading only the parts of the file requested.
26              
27             =head1 DESCRIPTION
28              
29             Support for skipping the beginning of the file is implemented with the
30             FTP C command, which starts a retrieval at any point in the
31             file. Support for skipping the end of the file is implemented with
32             the FTP C command, which stops the transfer. With these two
33             commands and some careful tracking of the current file position, we're
34             able to reliably emulate a C pair, and get only the parts
35             of the file that are actually read.
36              
37             This was originally designed for use with
38             L; it's reliable enough that the table of
39             contents and individual files can be extracted from a remote ZIP
40             archive without downloading the whole thing. See L below.
41              
42             An interface compatible with L is provided,
43             along with a C-based interface.
44              
45             Remember that an FTP server can only do one thing at a time, so make
46             sure to C your connection before asking the FTP server to do
47             nything else.
48              
49             =head1 CONSTRUCTOR
50              
51             =head2 new ( $ftp, $filename, options... )
52              
53             Creates a new L-compatible object to fetch all
54             or parts of C<$filename> using the FTP connection C<$ftp>.
55              
56             Available options:
57              
58             =over 4
59              
60             =item MaxSkipSize => $size
61              
62             If we need to move forward in a file or close the connection,
63             sometimes it's faster to just read the bytes we don't need than to
64             abort the connection and restart. This setting tells how many
65             unnecessary bytes we're willing to read rather than abort. An
66             appropriate setting depends on the speed of transferring files and the
67             speed of reconnecting to the server.
68              
69             =item BlockSize => $size
70              
71             When doing buffered reads, how many bytes to read at once. The
72             default is the same as the default for L, so it's
73             generally best to leave it alone.
74              
75             =item AlreadyBinary => $bool
76              
77             If set to a true value, we assume the server is already in binary
78             mode, and don't try to set it.
79              
80             =back
81              
82             =cut
83 3     3   18 use constant USAGE => "Usage: Net::FTP::RetrHandle\->new(ftp => \$ftp_obj, filename => \$filename)\n";
  3         8  
  3         10281  
84             sub new
85             {
86 3     3 1 257087 my $class = shift;
87 3 50       19 my $ftp = shift
88             or croak USAGE;
89 3 50       14 my $filename = shift
90             or croak USAGE;
91 3         30 my $self = { MaxSkipSize => DEFAULT_MAX_SKIPSIZE,
92             BlockSize => DEFAULT_BLOCKSIZE,
93             @_,
94             ftp => $ftp, filename => $filename,
95             pos => 0, nextpos => 0};
96 3 100       22 $self->{size} = $self->{ftp}->size($self->{filename})
97             or return undef;
98 1 50       15913 $self->{ftp}->binary()
99             unless ($self->{AlreadyBinary});
100              
101 1         7895 bless $self,$class;
102             }
103              
104             =head1 METHODS
105              
106             Most of the methods implemented behave exactly like those from
107             L.
108              
109             These methods are implemented: C, C, C, C,
110             C, C, C, C, C, C,
111             C, C, C, C, C, C.
112              
113             =cut ;
114              
115 0     0 0 0 sub opened { 1; }
116              
117             sub seek
118             {
119 12     12 1 22398 my $self = shift;
120 12   100     64 my $pos = shift || 0;
121 12   100     63 my $whence = shift || 0;
122 12 50       45 warn " SEEK: self=$self, pos=$pos, whence=$whence\n"
123             if ($ENV{DEBUG});
124 12         56 my $curpos = $self->tell();
125 12         33 my $newpos = _newpos($self->tell(),$self->{size},$pos,$whence);
126 12         27 my $ret;
127 12 100 66     167 if ($newpos == $curpos)
    50 66        
128             {
129 1         7 return $curpos;
130             }
131             elsif (defined($self->{_buf}) and ($newpos > $curpos) and ($newpos < ($curpos + length($self->{_buf}))))
132             {
133             # Just seeking within the buffer (or not at all)
134 0         0 substr($self->{_buf},0,$newpos - $curpos,'');
135 0         0 $ret = $newpos;
136             }
137             else
138             {
139 11         40 $ret = $self->sysseek($newpos,0);
140 11         24 $self->{_buf} = '';
141             }
142 11         59 return $ret;
143             }
144              
145             sub _newpos
146             {
147            
148 28     28   69 my($curpos,$size,$pos,$whence)=@_;
149 28 100       121 if ($whence == 0) # seek_set
    50          
    50          
150             {
151 14         317 return $pos;
152             }
153             elsif ($whence == 1) # seek_cur
154             {
155 0         0 return $curpos + $pos;
156             }
157             elsif ($whence == 2) # seek_end
158             {
159 14         42 return $size + $pos;
160             }
161             else
162             {
163 0         0 die "Invalid value $whence for whence!";
164             }
165             }
166              
167             sub sysseek
168             {
169 16     16 1 10468 my $self = shift;
170 16   100     197 my $pos = shift || 0;
171 16   100     62 my $whence = shift || 0;
172 16 50       50 warn "SYSSEEK: self=$self, pos=$pos, whence=$whence\n"
173             if ($ENV{DEBUG});
174 16         166 my $newpos = _newpos($self->{nextpos},$self->{size},$pos,$whence);
175              
176 16         43 $self->{eof}=undef;
177 16         64 return $self->{nextpos}=$newpos;
178             }
179              
180             sub tell
181             {
182 34     34 1 53 my $self = shift;
183 34 50       283 return $self->{nextpos} - (defined($self->{_buf}) ? length($self->{_buf}) : 0);
184             }
185              
186             # WARNING: ASCII mode probably breaks seek.
187             sub binmode
188             {
189 0     0 0 0 my $self = shift;
190 0   0     0 my $mode = shift || ':raw';
191 0 0 0     0 return if (defined($self->{curmode}) && ($self->{curmode} eq $mode));
192 0 0 0     0 if (defined($mode) and $mode eq ':crlf')
193             {
194 0         0 $self->_finish_connection();
195 0 0       0 $self->{ftp}->ascii()
196             or return $self->seterr();
197             }
198             else
199             {
200 0         0 $self->_finish_connection();
201 0 0       0 $self->{ftp}->binary()
202             or return $self->seterr();
203             }
204 0         0 $self->{curmode} = $mode;
205             }
206              
207             sub _min
208             {
209 93 100   93   303 return $_[0] < $_[1] ? $_[0] : $_[1];
210             }
211              
212             sub _max
213             {
214 12 50   12   65 return $_[0] > $_[1] ? $_[0] : $_[1];
215             }
216              
217             sub read
218             {
219 95     95 0 9820028 my $self = shift;
220             # return $self->sysread(@_);
221            
222 95         207 my(undef,$len,$offset)=@_;
223 95   100     364 $offset ||= 0;
224 95 50       360 warn "READ(buf,$len,$offset)\n"
225             if ($ENV{DEBUG});
226            
227 95 100 66     1464 if (!defined($self->{_buf}) || length($self->{_buf}) <= 0)
    50          
228             {
229 12 100       51 $self->sysread($self->{_buf},_max($len,$self->{BlockSize}))
230             or return 0;
231             }
232             elsif (length($self->{_buf}) < $len)
233             {
234 0         0 $self->sysread($self->{_buf},_max($len-length($self->{_buf}),$self->{BlockSize}),length($self->{_buf}));
235             }
236 93         671 my $ret = _min($len,length($self->{_buf}));
237 93 100       382 if (!defined($_[0])) { $_[0] = '' }
  23         39  
238 93         414 substr($_[0],$offset) = substr($self->{_buf},0,$len,'');
239 93         212 $self->{read_count}++;
240              
241 93         252 return $ret;
242             }
243              
244             sub sysread
245             {
246 20     20 0 3013 my $self = shift;
247 20 50       108 if ($self->{eof})
248             {
249 0         0 return 0;
250             }
251            
252 20         45 my(undef,$len,$offset) = @_;
253 20   50     99 $offset ||= 0;
254              
255 20 50       75 warn "SYSREAD(buf,$len,$offset)\n"
256             if ($ENV{DEBUG});
257 20 100       140 if ($self->{nextpos} >= $self->{size})
258             {
259 3         8 $self->{eof} = 1;
260 3         9 $self->{pos} = $self->{nextpos};
261 3         20 return 0;
262             }
263              
264 17 100       55 if ($self->{pos} != $self->{nextpos})
265             {
266             # They seeked.
267 16 50       55 if ($self->{ftp_running})
268             {
269 16 50       44 warn "Seek detected, nextpos=$self->{nextpos}, pos=$self->{pos}, MaxSkipSize=$self->{MaxSkipSize}\n"
270             if ($ENV{DEBUG});
271 16 100 66     88 if ($self->{nextpos} > $self->{pos} and ($self->{nextpos} - $self->{pos}) < $self->{MaxSkipSize})
272             {
273 5         13 my $br = $self->{nextpos}-$self->{pos};
274 5 50       15 warn "Reading $br bytes to skip ahead\n"
275             if ($ENV{DEBUG});
276 5         7 my $junkbuff;
277 5         15 while ($br > 0)
278             {
279 9 50       29 warn "Trying to read $br more bytes\n"
280             if ($ENV{DEBUG});
281 9         52 my $b = $self->{ftp_data}->read($junkbuff,$br);
282 9 50 33     26459 if ($b == 0)
    50          
283             {
284 0         0 $self->_at_eof();
285 0         0 return 0;
286             }
287             elsif (!defined($b) || $b < 0)
288             {
289 0         0 return $self->seterr();
290             }
291             else
292             {
293 9         35 $br -= $b;
294             }
295             }
296 5         20 $self->{pos}=$self->{nextpos};
297             }
298             else
299             {
300 11 50       42 warn "Aborting connection to move to new position\n"
301             if ($ENV{DEBUG});
302 11         38 $self->_finish_connection();
303             }
304             }
305             }
306              
307 17 100       84 if (!$self->{ftp_running})
308             {
309 12         89 $self->{ftp}->restart($self->{nextpos});
310 12 50       182 $self->{ftp_data} = $self->{ftp}->retr($self->{filename})
311             or return $self->seterr();
312 12         8965298 $self->{ftp_running} = 1;
313 12         82 $self->{pos}=$self->{nextpos};
314             }
315              
316 17         28 my $tmpbuf;
317 17         105 my $rb = $self->{ftp_data}->read($tmpbuf,$len);
318 17 50 33     4102 if ($rb == 0)
    50          
319             {
320 0         0 $self->_at_eof();
321 0         0 return 0;
322             }
323             elsif (!defined($rb) || $rb < 0)
324             {
325 0         0 return $self->seterr();
326             }
327              
328 17 100       80 if (!defined($_[0])) { $_[0] = '' }
  1         4  
329 17         96 substr($_[0],$offset) = $tmpbuf;
330 17         46 $self->{pos} += $rb;
331 17         41 $self->{nextpos} += $rb;
332              
333 17         34 $self->{sysread_count}++;
334 17         130 $rb;
335             }
336              
337             sub _at_eof
338             {
339 0     0   0 my $self = shift;
340 0         0 $self->{eof}=1;
341 0         0 $self->_finish_connection();
342             # $self->{ftp_data}->_close();
343 0         0 $self->{ftp_running} = $self->{ftp_data} = undef;
344             }
345            
346             sub _finish_connection
347             {
348 12     12   24 my $self = shift;
349 12 50       30 warn "_finish_connection\n"
350             if ($ENV{DEBUG});
351 12 50       293 return unless ($self->{ftp_running});
352            
353 12 50       52 if ($self->{size} - $self->{pos} < $self->{MaxSkipSize})
354             {
355 12 50       30 warn "Skipping " . ($self->{size}-$self->{pos}) . " bytes\n"
356             if ($ENV{DEBUG});
357 12         19 my $junkbuff;
358             my $br;
359 12         82 while(($br = $self->{ftp_data}->read($junkbuff,8192)))
360             {
361             # Read until EOF or error
362             }
363 12 50       13854 defined($br)
364             or $self->seterr();
365             }
366 12 50       42 warn "Shutting down existing FTP DATA session...\n"
367             if ($ENV{DEBUG});
368              
369 12         19 my $closeret;
370             {
371 12         18 eval {
  12         22  
372 12         432 $closeret = $self->{ftp_data}->close();
373             };
374             # Work around a timeout bug in Net::FTP
375 12 50 33     94793 if ($@ && $@ =~ /^Timeout /)
376             {
377 0 0       0 warn "Timeout closing connection, retrying...\n"
378             if ($ENV{DEBUG});
379 0         0 select(undef,undef,undef,1);
380 0         0 redo;
381             }
382             }
383              
384 12         72 $self->{ftp_running} = $self->{ftp_data} = undef;
385 12 50       379 return $closeret ? 1 : $self->seterr();
386             }
387              
388             sub write
389             {
390 0     0 0 0 die "Only reading currently supported";
391             }
392              
393             sub close
394             {
395 1     1 0 857 my $self = shift;
396 1 50       10 return $self->{ftp_data} ? $self->_finish_connection()
397             : 1;
398             }
399              
400             sub eof
401             {
402 1     1 0 584 my $self = shift;
403 1 50       7 if ($self->{eof})
404             {
405 1         7 return 1;
406             }
407              
408 0         0 my $c = $self->getc;
409 0 0       0 if (!defined($c))
410             {
411 0         0 return 1;
412             }
413 0         0 $self->ungetc(ord($c));
414 0         0 return undef;
415             }
416              
417             sub getc
418             {
419 21     21 0 22048 my $self = shift;
420 21         27 my $c;
421 21         70 my $rb = $self->read($c,1);
422 21 100       59 if ($rb < 1)
423             {
424 1         5 return undef;
425             }
426 20         112 return $c;
427             }
428              
429             sub ungetc
430             {
431 10     10 0 82 my $self = shift;
432             # Note that $c is the ordinal value of a character, not the
433             # character itself (for some reason)
434 10         82 my($c)=@_;
435 10         52 $self->{_buf} = chr($c) . $self->{_buf};
436             }
437              
438             sub getline
439             {
440 231     231 0 20683 my $self = shift;
441 231 50 33     1749 if (!defined($/))
    50          
442 0         0 {
443 0         0 my $buf;
444 0         0 while($self->read($buf,$self->{BlockSize},length($buf)) > 0)
445             {
446             # Keep going
447             }
448 0         0 return $buf;
449             }
450             elsif (ref($/) && looks_like_number ${$/} )
451             {
452 0         0 my $buf;
453 0 0       0 $self->read($buf,${$/})
  0         0  
454             or return undef;
455 0         0 return $buf;
456             }
457              
458 231         367 my $rs;
459 231 50       565 if ($/ eq '')
460             {
461 0         0 $rs = "\n\n";
462             }
463             else
464             {
465 231         380 $rs = $/;
466             }
467 231         243 my $eol;
468 231 100       550 if (!defined($self->{_buf})) { $self->{_buf} = '' }
  1         5  
469 3     3   5350 while (($eol=index($self->{_buf},$rs)) < $[)
  3         1577  
  3         3799  
  231         1017  
470             {
471 4 100       16 if ($self->{eof})
472             {
473             # return what's left
474 1 50       9 if (length($self->{_buf}) == 0)
475             {
476 1         6 return undef;
477             }
478             else
479             {
480 0         0 return substr($self->{_buf},0,length($self->{_buf}),'');
481             }
482             }
483             else
484             {
485 3         25 $self->sysread($self->{_buf},$self->{BlockSize},length($self->{_buf}));
486             }
487             }
488             # OK, we should have a match.
489 230         1330 my $tmpbuf = substr($self->{_buf},0,$eol+length($rs),'');
490 230   33     801 while ($/ eq '' and substr($self->{_buf},0,1) eq "\n")
491             {
492 0         0 substr($self->{_buf},0,1)='';
493             }
494 230         817 return $tmpbuf;
495             }
496              
497             sub getlines
498             {
499 1     1 0 250 my $self = shift;
500 1         3 my @lines;
501             my $line;
502 1         6 while (defined($line = $self->getline()))
503             {
504 220         826 push(@lines,$line);
505             }
506 1         91 @lines;
507             }
508              
509             sub error
510             {
511 0     0 0 0 return undef;
512             }
513              
514             sub seterr
515             {
516 0     0 0 0 my $self = shift;
517 0         0 $self->{_error} = 1;
518 0         0 return undef;
519             }
520              
521             sub clearerr
522             {
523 0     0 0 0 my $self = shift;
524 0         0 $self->{_error} = undef;
525 0         0 return 0;
526             }
527              
528             sub getpos
529             {
530 0     0 1 0 my $self = shift;
531 0         0 return $self->tell();
532             }
533              
534             sub setpos
535             {
536 0     0 1 0 my $self = shift;
537 0         0 return $self->seek(@_);
538             }
539              
540             sub DESTROY
541             {
542 1     1   7337 my $self = shift;
543 1 50       13 if (UNIVERSAL::isa($self,'GLOB'))
544             {
545 0 0       0 $self = tied *$self
546             or die "$self not tied?...";
547             }
548 1 50       5 if ($self->{ftp_data})
549             {
550 0         0 $self->_finish_connection();
551             }
552 1 50       17 warn "sysread called ".$self->{sysread_count}." times.\n"
553             if ($ENV{DEBUG});
554             }
555              
556             =head1 TIED INTERFACE
557              
558             Instead of a L-compatible interface, you can
559             use a C-based interface to use the standard Perl I/O operators.
560             You can use it like this:
561              
562             use Net::FTP::RetrHandle;
563             # Create FTP object in $ftp
564             # Store filename in $filename
565             tie *FH, 'Net::FTP::RetrHandle', $ftp, $filename
566             or die "Error in tie!\n";
567              
568             =cut
569             ;
570             sub TIEHANDLE
571             {
572 1     1   1529522 my $class = shift;
573 1         10 my $obj = $class->new(@_);
574 1         207380 $obj;
575             }
576              
577             sub READ
578             {
579 0     0     my $self = shift;
580 0           $self->read(@_);
581             }
582              
583             sub READLINE
584             {
585 0     0     my $self = shift;
586 0 0         return wantarray ? $self->getlines(@_)
587             : $self->getline(@_);
588             }
589              
590             sub GETC
591             {
592 0     0     my $self = shift;
593 0           return $self->getc(@_);
594             }
595              
596             sub SEEK
597             {
598 0     0     my $self = shift;
599 0           return $self->seek(@_);
600             }
601              
602             sub SYSSEEK
603             {
604 0     0 0   my $self = shift;
605 0           return $self->sysseek(@_);
606             }
607              
608             sub TELL
609             {
610 0     0     my $self = shift;
611 0           return $self->tell();
612             }
613              
614             sub CLOSE
615             {
616 0     0     my $self = shift;
617 0           return $self->close(@_);
618             }
619              
620             sub EOF
621             {
622 0     0     my $self = shift;
623 0           return $self->eof(@_);
624              
625             }
626             sub UNTIE
627             {
628 0     0     tied($_[0])->close(@_);
629             }
630              
631             =head1 EXAMPLE
632              
633             Here's an example of listing a Zip file without downloading the whole
634             thing:
635              
636             #!/usr/bin/perl
637            
638             use warnings;
639             use strict;
640            
641             use Net::FTP;
642             use Net::FTP::AutoReconnect;
643             use Net::FTP::RetrHandle;
644             use Archive::Zip;
645            
646             my $ftp = Net::FTP::AutoReconnect->new("ftp.info-zip.com", Debug => $ENV{DEBUG})
647             or die "connect error\n";
648             $ftp->login('anonymous','example@example.com')
649             or die "login error\n";
650             $ftp->cwd('/pub/infozip/UNIX/LINUX')
651             or die "cwd error\n";
652             my $fh = Net::FTP::RetrHandle->new($ftp,'unz551x-glibc.zip')
653             or die "Couldn't get handle to remote file\n";
654             my $zip = Archive::Zip->new($fh)
655             or die "Couldn't create Zip object\n";
656             foreach my $fn ($zip->memberNames())
657             {
658             print "unz551-glibc.zip: $fn\n";
659             }
660              
661              
662             =head1 AUTHOR
663              
664             Scott Gifford
665              
666             =head1 BUGS
667              
668             The distinction between tied filehandles and C-compatible
669             filehandles should be blurrier. It seems like other file handle
670             objects you can freely mix method calls and traditional Perl
671             operations, but I can't figure out how to do it.
672              
673             Many FTP servers don't like frequent connection aborts. If that's the
674             case, try L, which will hide much of that
675             from you.
676              
677             If the filehandle is tied and created with C, C
678             doesn't work with older versions of Perl. No idea why.
679              
680             =head1 SEE ALSO
681              
682             L, L, L.
683              
684             =head1 COPYRIGHT
685              
686             Copyright (c) 2006 Scott Gifford. All rights reserved. This program
687             is free software; you can redistribute it and/or modify it under the
688             same terms as Perl itself.
689              
690             =cut
691              
692             1;