File Coverage

blib/lib/Net/SFTP/Foreign/Tempdir/Extract.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Net::SFTP::Foreign::Tempdir::Extract;
2 5     5   84304 use strict;
  5         10  
  5         194  
3 5     5   20 use warnings;
  5         8  
  5         146  
4 5     5   20 use base qw{Package::New};
  5         14  
  5         2672  
5 5     5   3451 use File::Tempdir qw{};
  5         102143  
  5         116  
6 5     5   5294 use Net::SFTP::Foreign qw{};
  5         225114  
  5         158  
7 5     5   2718 use Net::SFTP::Foreign::Tempdir::Extract::File;
  0            
  0            
8              
9             our $VERSION = '0.10';
10              
11             =head1 NAME
12              
13             Net::SFTP::Foreign::Tempdir::Extract - Secure FTP client integrating Path::Class, Tempdir, and Archive Extraction
14              
15             =head1 SYNOPSIS
16              
17             use Net::SFTP::Foreign::Tempdir::Extract;
18             my $sftp=Net::SFTP::Foreign::Tempdir::Extract->new(
19             host => $host,
20             user => $user,
21             match => qr/\.zip\Z/,
22             backup => './backup', #default is not to backup
23             delete => 1, #default is not to delete
24             );
25             my $file=$sftp->next;
26              
27             =head1 DESCRIPTION
28              
29             Secure FTP client which downloads files locally to a temp directory for operations and automatically cleans up all temp files after variables are out of scope.
30              
31             This package assume SSH keys are correctly installed on local account and remote server.
32              
33             =head1 USAGE
34              
35             =head2 File Downloader
36              
37             This is a simple file downloader implementation
38              
39             use Net::SFTP::Foreign::Tempdir::Extract;
40             my $sftp=Net::SFTP::Foreign::Tempdir::Extract->new(host=>$remote_host, user=>$remote_user);
41             my $file=$sftp->download($remote_folder, $remote_filename);
42              
43             =head2 File Watcher
44              
45             This is a simple file watcher implementation
46              
47             use Net::SFTP::Foreign::Tempdir::Extract;
48             my $sftp=Net::SFTP::Foreign::Tempdir::Extract->new(host=>'remote_server', user=>'remote_account', match=>qr/\.zip\Z/, folder=>'/remote_folder');
49             my $file=$sftp->next or exit; #nothing to process so exit
50             print "$file"; #process file here
51              
52             =head2 Subclass
53              
54             This is a typical subclass implementation for a particular infrastructure
55              
56             {
57             package My::SFTP;
58             use base qw{Net::SFTP::Foreign::Tempdir::Extract};
59             sub host {'remote_server.domain.tld'};
60             sub folder {'/remote_folder'};
61             sub match {qr/\.zip\Z/};
62             sub backup {time};
63             1;
64             }
65              
66             my $sftp=My::SFTP->new;
67             while (my $file=$sftp->next) {
68             printf "File %s is a %s\n", "$file", ref($file);
69             }
70              
71             Which outputs something like this.
72              
73             File /tmp/hwY9jVeYo3/file1.zip is a Net::SFTP::Foreign::Tempdir::Extract::File
74             File /tmp/ytWaYdPXuD/file2.zip is a Net::SFTP::Foreign::Tempdir::Extract::File
75             File /tmp/JrsrkleBOy/file3.zip is a Net::SFTP::Foreign::Tempdir::Extract::File
76              
77             =head1 CONSTRUCTOR
78              
79             =head2 new
80              
81             =head1 METHODS
82              
83             =head2 download
84              
85             Downloads the named file in the folder.
86              
87             my $file=$sftp->download('remote_file.zip'); #isa Net::SFTP::Foreign::Tempdir::Extract::File
88             my $file=$sftp->download('/remote_folder', 'remote_file.zip'); # which isa Path::Class::File object with an extract method
89              
90             =cut
91              
92             sub download {
93             my $self = shift;
94             my $sftp = $self->sftp;
95             my $remote = pop or die('Error: filename required.');
96             my $folder = shift || $self->folder;
97             my $tmpdir = File::Tempdir->new or die('Error: Could not create File::Tempdir object');
98             my $local_folder = $tmpdir->name or die('Error: Temporary directory not configured.');
99             $sftp->setcwd($folder) or die(sprintf('Error: %s', $sftp->error));
100             $sftp->mget($remote, $local_folder) or die(sprintf('Error: %s', $sftp->error));
101             my $file=Net::SFTP::Foreign::Tempdir::Extract::File->new($local_folder => $remote);
102             die("Error: Could not read $file.") unless -r $file;
103             $file->{'__tmpdir'}=$tmpdir; #must keep tmpdir scope alive
104             my $backup=$self->backup;
105             if ($backup) {
106             $sftp->mkpath($backup) or die('Error: Cannot create backup directory');
107             $sftp->rename($remote, "$backup/$remote") or die("Error: Cannot rename remote file $remote to $backup/$remote");
108             } elsif ($self->delete) {
109             $sftp->remove($remote) or warn("Warning: Cannot delete remote file $remote");
110             }
111             return $file;
112             }
113              
114             =head2 next
115              
116             Downloads the next file in list and saves it locally to a temporary folder. Returns a L object or undef if there are no more files.
117              
118             my $file=$sftp->next or exit; #get file or exit
119              
120             while (my $file=$sftp->next) {
121             print "$file";
122             }
123              
124             =cut
125              
126             sub next {
127             my $self=shift;
128             my $list=$self->list;
129             if (@$list) {
130             my $file=shift @$list;
131             #print Dumper($file);
132             return $self->download($file);
133             } else {
134             return;
135             }
136             }
137              
138             =head2 list
139              
140             Returns list of filenames remaining to be processed that match the folder and regular expression
141              
142             Note: List is shifted for each call to next method
143              
144             =cut
145              
146             sub list {
147             my $self=shift;
148             $self->{'list'}=shift if @_;
149             unless (defined($self->{'list'})) {
150             #printf "%s: Listing files in folder: %s\n", DateTime->now, $self->folder;
151             $self->{'list'}=$self->sftp->ls($self->folder,
152             wanted => $self->match,
153             ordered => 1,
154             no_wanted => qr/\A\.{1,2}\Z/,
155             names_only => 1,
156             );
157             die(sprintf(qq{Error: File list did not return as expected. Verify folder "%s" exists and is readable.}, $self->folder))
158             unless (defined($self->{'list'}) and ref($self->{'list'}) eq 'ARRAY');
159             }
160             return wantarray ? @{$self->{'list'}} : $self->{'list'};
161             }
162              
163             =head1 PROPERTIES
164              
165             =head2 host
166              
167             SFTP server host name.
168              
169             $sftp->host(""); #default
170              
171             =cut
172              
173             sub host {
174             my $self=shift;
175             $self->{'host'}=shift if @_;
176             $self->{'host'}=$self->_host_default unless defined($self->{'host'});
177             return $self->{'host'};
178             }
179              
180             sub _host_default {
181             return "";
182             }
183              
184             =head2 user
185              
186             SFTP user name (defaults to current user)
187              
188             $sftp->user(undef); #default
189              
190             =cut
191              
192             sub user {
193             my $self=shift;
194             $self->{'user'}=shift if @_;
195             return $self->{'user'};
196             }
197              
198             =head2 options
199              
200             SSH options passed to the more property of L as an array reference.
201              
202             $sftp->options(['-q']); #default
203             $sftp->options([]); #no options
204             $sftp->options(['-v']); #verbose
205              
206             =cut
207              
208             sub options {
209             my $self=shift;
210             $self->{'options'}=shift if @_;
211             $self->{'options'}=['-q'] unless defined $self->{'options'};
212             die 'Error: options must be an array reference.' unless ref($self->{'options'}) eq 'ARRAY';
213             return $self->{'options'};
214             }
215              
216             =head2 folder
217              
218             Folder on remote SFTP server.
219              
220             $sftp->folder("/home/user/download");
221              
222             Note: Some SFTP servers put clients in a change rooted environment.
223              
224             =cut
225              
226             sub folder {
227             my $self=shift;
228             $self->{'folder'}=shift if @_;
229             $self->{'folder'}=$self->_folder_default unless defined $self->{'folder'};
230             return $self->{'folder'};
231             }
232              
233             sub _folder_default {
234             return '/incoming';
235             }
236              
237             =head2 match
238              
239             Regular Expression to match file names for the next iterator
240              
241             $sftp->match(qr/\Aremote_file\.zip\Z/); #exact file
242             $sftp->match(qr/\.zip\Z/); #any zip file
243             $sftp->match(undef); #reset to default - all files
244              
245             =cut
246              
247             sub match {
248             my $self=shift;
249             $self->{'match'}=shift if @_;
250             $self->{'match'}=$self->_match_default unless defined($self->{'match'});
251             return $self->{'match'};
252             }
253              
254             sub _match_default {
255             return qr//;
256             }
257              
258             =head2 backup
259              
260             Sets or returns the backup folder property.
261              
262             $sftp->backup(""); #don't backup
263             $sftp->backup("./folder"); #backup to folder
264              
265             Note: If configured, backup overrides delete option.
266              
267             =cut
268              
269             sub backup {
270             my $self=shift;
271             $self->{'backup'}=shift if @_;
272             $self->{'backup'}="" unless defined($self->{'backup'});
273             return $self->{'backup'};
274             }
275              
276             =head2 delete
277              
278             Sets or returns the delete boolean property.
279              
280             $sftp->delete(0); #don't delete
281             $sftp->delete(1); #delete after downloaded
282              
283             Note: Ineffective when backup option is configured.
284              
285             =cut
286              
287             sub delete {
288             my $self=shift;
289             $self->{'delete'}=shift if @_;
290             $self->{'delete'}=0 unless defined($self->{'delete'});
291             return $self->{'delete'};
292             }
293              
294             =head1 OBJECT ACCESSORS
295              
296             =head2 sftp
297              
298             Returns a cached connected L object
299              
300             =cut
301              
302             sub sftp {
303             my $self=shift;
304             unless (defined $self->{'sftp'}) {
305             my %params = ();
306             $params{'host'} = $self->host or die('Error: host required');
307             $params{'user'} = $self->user if $self->user; #not required
308             $params{'more'} = $self->options if @{$self->options} > 0; #not required
309             my $sftp = Net::SFTP::Foreign->new($params{'host'}, %params);
310             die(sprintf("Error connecting to %s@%s: %s", $params{'user'}, $params{'host'}, $sftp->error)) if $sftp->error;
311             $self->{'sftp'} = $sftp;
312             }
313             return $self->{'sftp'};
314             }
315              
316             =head1 BUGS
317              
318             Send email to author and log on RT.
319              
320             =head1 SUPPORT
321              
322             DavisNetworks.com supports all Perl applications including this package.
323              
324             =head2 Testing
325              
326             This packages relies on the SSH keys to be operational for the local account. To test your SSH keys from the command line type `sftp user@server`. If this command prompts the user for a password, then your SSH keys are not installed correctly. You cannot reliably test with `ssh user@server` as the remote administrator may have disabled the terminal service over SSH.
327              
328             =head1 AUTHOR
329              
330             Michael R. Davis
331             CPAN ID: MRDVT
332             Satellite Tracking of People, LLC
333             mdavis@stopllc.com
334             http://www.stopllc.com/
335              
336             =head1 COPYRIGHT
337              
338             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
339              
340             The full text of the license can be found in the LICENSE file included with this module.
341              
342             =head1 SEE ALSO
343              
344             =head2 Building Blocks
345              
346             L, L, L
347              
348             =cut
349              
350             1;