File Coverage

blib/lib/App/MBUtiny/Storage/FTP.pm
Criterion Covered Total %
statement 43 245 17.5
branch 1 124 0.8
condition 0 58 0.0
subroutine 12 18 66.6
pod 7 7 100.0
total 63 452 13.9


line stmt bran cond sub pod time code
1             package App::MBUtiny::Storage::FTP; # $Id: FTP.pm 121 2019-07-01 19:51:50Z abalama $
2 2     2   14 use strict;
  2         4  
  2         61  
3 2     2   8 use utf8;
  2         4  
  2         10  
4              
5             =encoding utf-8
6              
7             =head1 NAME
8              
9             App::MBUtiny::Storage::FTP - App::MBUtiny::Storage subclass for FTP storage support
10              
11             =head1 VIRSION
12              
13             Version 1.00
14              
15             =head1 SYNOPSIS
16              
17            
18            
19             #FixUP on
20             URL ftp://user:password@example.com:21/path/to/backup/dir1
21             URL ftp://user:password@example.com:21/path/to/backup/dir2
22             Set Passive 1
23             Set Debug 1
24             Comment FTP storage said blah-blah-blah # Optional for collector
25            
26              
27             # . . .
28              
29            
30              
31             =head1 DESCRIPTION
32              
33             App::MBUtiny::Storage subclass for FTP storage support
34              
35             =head2 del
36              
37             Removes the specified file.
38             This is backend method of L
39              
40             =head2 get
41              
42             Gets the backup file from storage and saves it to specified path.
43             This is backend method of L
44              
45             =head2 init
46              
47             The method performs initialization of storage.
48             This is backend method of L
49              
50             =head2 list
51              
52             Gets backup file list on storage.
53             This is backend method of L
54              
55             =head2 ftp_storages
56              
57             my @list = $storage->ftp_storages;
58              
59             Returns list of FTP storage nodes
60              
61             =head2 put
62              
63             Sends backup file to storage.
64             This is backend method of L
65              
66             =head2 test
67              
68             Storage testing.
69             This is backend method of L
70              
71             =head1 HISTORY
72              
73             See C file
74              
75             =head1 TO DO
76              
77             See C file
78              
79             =head1 BUGS
80              
81             * none noted
82              
83             =head1 SEE ALSO
84              
85             L
86              
87             =head1 AUTHOR
88              
89             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
90              
91             =head1 COPYRIGHT
92              
93             Copyright (C) 1998-2019 D&D Corporation. All Rights Reserved
94              
95             =head1 LICENSE
96              
97             This program is free software; you can redistribute it and/or
98             modify it under the same terms as Perl itself.
99              
100             See C file and L
101              
102             =cut
103              
104 2     2   67 use vars qw/ $VERSION /;
  2         2  
  2         101  
105             $VERSION = '1.00';
106              
107 2     2   11 use Storable qw/dclone/;
  2         3  
  2         68  
108 2     2   8 use Net::FTP;
  2         4  
  2         81  
109 2     2   10 use URI;
  2         4  
  2         36  
110 2     2   8 use List::Util qw/uniq/;
  2         3  
  2         103  
111 2     2   12 use CTK::ConfGenUtil;
  2         2  
  2         112  
112 2     2   10 use CTK::TFVals qw/ :ALL /;
  2         5  
  2         324  
113 2     2   12 use App::MBUtiny::Util qw/ node2anode set2attr hide_password filesize /;
  2         4  
  2         110  
114              
115             use constant {
116 2         4024 STORAGE_SIGN => 'FTP',
117 2     2   12 };
  2         19  
118              
119             sub init {
120 1     1 1 12 my $self = shift;
121 1         5 $self->maybe::next::method();
122 1         3 $self->storage_status(STORAGE_SIGN, -1);
123 1         1 my $useftp = 0;
124              
125 1         4 my $ftp_nodes = dclone(node2anode(node($self->{host}, 'ftp')));
126             #print explain($ftp_nodes), "\n";
127              
128 1         3 my %ftp_storages;
129 1         3 foreach my $ftp_node (@$ftp_nodes) {
130 0   0     0 my $urls = array($ftp_node, 'url') || [];
131 0   0     0 my $attr = set2attr($ftp_node),
132             my $cmnt = value($ftp_node, 'comment') || "";
133 0         0 foreach my $url (@$urls) {
134 0         0 my $url_wop = hide_password($url, 2);
135             $ftp_storages{$url} = {
136             url => $url,
137             url_wop => $url_wop,
138             attr => $attr,
139 0 0       0 comment => join("\n", grep {$_} ($url_wop, $cmnt)),
  0         0  
140             fixup => value($ftp_node, 'fixup') ? 1 : 0,
141             };
142 0         0 $useftp++;
143             }
144             }
145 1         4 $self->{ftp_storages} = [(values(%ftp_storages))];
146              
147 1 50       4 $self->storage_status(STORAGE_SIGN, $useftp) if $useftp;
148             #print explain($self->{ftp_storages}), "\n";
149 1         3 return $self;
150             }
151             sub ftp_storages {
152 0     0 1   my $self = shift;
153 0   0       my $storages = $self->{ftp_storages} || [];
154 0           return @$storages;
155             }
156              
157             sub test {
158 0     0 1   my $self = shift;
159 0           my %params = @_; $self->maybe::next::method(%params);
  0            
160 0           my $sign = STORAGE_SIGN;
161 0 0         return -1 if $self->storage_status($sign) <= 0; # SKIP
162              
163 0           my @test = ();
164 0           foreach my $storage ($self->ftp_storages) {
165 0           my $uri = new URI($storage->{url});
166 0           my $url_wop = $storage->{url_wop};
167 0           my $attr = dclone($storage->{attr});
168 0 0         $attr->{Port} = $uri->port if $uri->port;
169              
170             # Create object
171 0 0         my $ftp = new Net::FTP($uri->host, %$attr) or do {
172 0           my $err = sprintf("Can't connect to %s: %s", $url_wop, $@);
173 0           $self->storage_status($sign, 0);
174 0           push @test, [0, $url_wop, $err];
175 0           next;
176             };
177              
178             # Login
179 0 0 0       $ftp->login($uri->user || "anonymous", $uri->password || "anonymous\@example.com") or do {
      0        
180 0           my $err = sprintf("Can't login to %s: %s", $url_wop, $ftp->message);
181 0           $self->storage_status($sign, 0);
182 0           push @test, [0, $url_wop, $err];
183 0           next;
184             };
185              
186             # Change dir (chdir + mkdir)
187 0   0       my $path = $uri->path // ""; $path =~ s/^\///;
  0            
188 0 0         if (length($path)) {
189 0 0         $ftp->cwd($path) or do {
190 0 0         my $dir = $ftp->mkdir($path, 1) or do {
191 0           my $err = sprintf("Can't create directory %s on %s: %s", $path, $url_wop, $ftp->message);
192 0           $self->storage_status($sign, 0);
193 0           push @test, [0, $url_wop, $err];
194 0           next;
195             };
196 0 0         $ftp->cwd($path) or do {
197 0           my $err = sprintf("Can't change directory %s on %s: %s", $dir, $url_wop, $ftp->message);
198 0           $self->storage_status($sign, 0);
199 0           push @test, [0, $url_wop, $err];
200 0           next;
201             };
202             };
203             }
204              
205             # Quit
206 0           $ftp->quit;
207 0           push @test, [1, $url_wop];
208             }
209              
210 0           $self->{test}->{$sign} = [@test];
211 0           return 1;
212             }
213             sub put {
214 0     0 1   my $self = shift;
215 0           my %params = @_; $self->maybe::next::method(%params);
  0            
216 0 0         return $self->storage_status(STORAGE_SIGN, -1) if $self->storage_status(STORAGE_SIGN) <= 0; # SKIP and set SKIP
217 0           my $status = 1;
218 0           my $name = $params{name}; # File name only
219 0           my $file = $params{file}; # Path to local file
220 0   0       my $src_size = $params{size} || 0;
221              
222 0           foreach my $storage ($self->ftp_storages) {
223 0           my $uri = new URI($storage->{url});
224 0           my $url_wop = $storage->{url_wop};
225 0   0       my $comment = $storage->{comment} || "";
226 0   0       my $path = $uri->path // ""; $path =~ s/^\///;
  0            
227 0           my $attr = dclone($storage->{attr});
228 0 0         $attr->{Port} = $uri->port if $uri->port;
229 0           my $ostat = 1;
230              
231             # Create object
232 0 0         my $ftp = new Net::FTP($uri->host, %$attr) or do {
233 0           $self->error(sprintf("Can't connect to %s: %s", $url_wop, $@));
234 0           $ostat = 0;
235             };
236              
237             # Login
238 0 0         if ($ostat) {
239 0 0 0       $ftp->login($uri->user || "anonymous", $uri->password || "anonymous\@example.com") or do {
      0        
240 0           $self->error(sprintf("Can't login to %s: %s", $url_wop, $ftp->message));
241 0           $ostat = 0;
242             };
243             }
244              
245             # Change dir
246 0 0 0       if ($ostat && length($path)) {
247 0 0         $ftp->cwd($path) or do {
248 0           $self->error(sprintf("Can't change directory %s on %s: %s", $path, $url_wop, $ftp->message));
249 0           $ostat = 0;
250             };
251             }
252              
253             # Put file
254 0 0         if ($ostat) {
255 0           $ftp->binary;
256 0 0         $ftp->put($file, $name) or do {
257 0           $self->error(sprintf("Can't put file %s to %s: %s", $name, $url_wop, $ftp->message));
258 0           $ostat = 0;
259             };
260             }
261              
262             # Get file size
263 0 0         if ($ostat) {
264 0   0       my $dst_size = $ftp->size($name) || 0;
265 0 0         unless ($src_size == $dst_size) {
266 0           $self->error(sprintf("An error occurred while sending data to %s. Sizes are different: SRC=%d; DST=%d", $url_wop, $src_size, $dst_size));
267 0           $ostat = 0;
268             }
269             }
270              
271             # Quit
272 0 0         $ftp->quit if $ftp;
273              
274             # Fixup!
275 0 0         $self->fixup("put", $ostat, $comment) if $storage->{fixup};
276 0 0         $status = 0 unless $ostat;
277             }
278              
279 0 0         $self->storage_status(STORAGE_SIGN, 0) unless $status;
280             }
281             sub get {
282 0     0 1   my $self = shift;
283 0           my %params = @_;
284 0 0         if ($self->storage_status(STORAGE_SIGN) <= 0) { # SKIP and set SKIP
285 0           $self->maybe::next::method(%params);
286 0           return $self->storage_status(STORAGE_SIGN, -1);
287             }
288 0           my $name = $params{name}; # archive name
289 0           my $file = $params{file}; # destination archive file path
290              
291 0           foreach my $storage ($self->ftp_storages) {
292 0           my $uri = new URI($storage->{url});
293 0           my $url_wop = $storage->{url_wop};
294 0   0       my $path = $uri->path // ""; $path =~ s/^\///;
  0            
295 0           my $attr = dclone($storage->{attr});
296 0 0         $attr->{Port} = $uri->port if $uri->port;
297              
298             # Create object
299 0 0         my $ftp = new Net::FTP($uri->host, %$attr) or do {
300 0           $self->error(sprintf("Can't connect to %s: %s", $url_wop, $@));
301 0           next;
302             };
303              
304             # Login
305 0 0 0       $ftp->login($uri->user || "anonymous", $uri->password || "anonymous\@example.com") or do {
      0        
306 0           $self->error(sprintf("Can't login to %s: %s", $url_wop, $ftp->message));
307 0 0         $ftp->quit if $ftp; # Quit
308 0           next;
309             };
310              
311             # Change dir
312 0 0         if (length($path)) {
313 0 0         $ftp->cwd($path) or do {
314 0           $self->error(sprintf("Can't change directory %s on %s: %s", $path, $url_wop, $ftp->message));
315 0 0         $ftp->quit if $ftp; # Quit
316 0           next;
317             };
318             }
319              
320             # Get file size
321 0   0       my $src_size = $ftp->size($name) || 0;
322              
323             # Get file
324 0           $ftp->binary;
325 0 0         $ftp->get($name, $file) or do {
326 0           $self->error(sprintf("Can't get file %s from %s: %s", $name, $url_wop, $ftp->message));
327 0 0         $ftp->quit if $ftp; # Quit
328 0           next;
329             };
330              
331             # Quit
332 0 0         $ftp->quit if $ftp;
333              
334             # Check size
335 0   0       my $dst_size = filesize($file) // 0;
336 0 0         unless ($src_size == $dst_size) {
337 0           $self->error(sprintf("An error occurred while fetching data from %s. Sizes are different: SRC=%d; DST=%d", $url_wop, $src_size, $dst_size));
338 0           next;
339             }
340              
341             # Validate
342 0 0         unless ($self->validate($file)) { # FAIL validation!
343 0           $self->error(sprintf("FTP storage %s failed: file %s is not valid!", $url_wop, $file));
344             next
345 0           }
346              
347             # Done!
348 0           return $self->storage_status(STORAGE_SIGN, 1);
349             }
350              
351 0           $self->storage_status(STORAGE_SIGN, 0);
352 0           $self->maybe::next::method(%params);
353             }
354             sub del {
355 0     0 1   my $self = shift;
356 0           my $name = shift;
357 0           $self->maybe::next::method($name);
358 0 0         return $self->storage_status(STORAGE_SIGN, -1) if $self->storage_status(STORAGE_SIGN) <= 0; # SKIP and set SKIP
359 0           my $status = 1;
360              
361 0           foreach my $storage ($self->ftp_storages) {
362 0           my $uri = new URI($storage->{url});
363 0           my $url_wop = $storage->{url_wop};
364 0   0       my $path = $uri->path // ""; $path =~ s/^\///;
  0            
365 0           my $attr = dclone($storage->{attr});
366 0 0         $attr->{Port} = $uri->port if $uri->port;
367 0           my $ostat = 1;
368              
369             # Create object
370 0 0         my $ftp = new Net::FTP($uri->host, %$attr) or do {
371 0           $self->error(sprintf("Can't connect to %s: %s", $url_wop, $@));
372 0           $ostat = 0;
373             };
374              
375             # Login
376 0 0         if ($ostat) {
377 0 0 0       $ftp->login($uri->user || "anonymous", $uri->password || "anonymous\@example.com") or do {
      0        
378 0           $self->error(sprintf("Can't login to %s: %s", $storage->{url_wop}, $ftp->message));
379 0           $ostat = 0;
380             };
381             }
382              
383             # Change dir
384 0 0 0       if ($ostat && length($path)) {
385 0 0         $ftp->cwd($path) or do {
386 0           $self->error(sprintf("Can't change directory %s on %s: %s", $path, $url_wop, $ftp->message));
387 0           $ostat = 0;
388             };
389             }
390              
391             # Get list
392 0           my @ls = ();
393 0 0         if ($ostat) {
394 0           @ls = $ftp->ls();
395             }
396              
397             # Delete file
398 0 0 0       if ($ostat && grep { $_ eq $name } @ls ) {
  0            
399 0 0         $ftp->delete($name) or do {
400 0           $self->error(sprintf("Can't delete file %s from %s: %s", $name, $url_wop, $ftp->message));
401 0           $ostat = 0;
402             };
403             }
404              
405             # Quit
406 0 0         $ftp->quit if $ftp;
407              
408             # Fixup!
409 0 0         $self->fixup("del", $name) if $storage->{fixup};
410 0 0         $status = 0 unless $ostat;
411             }
412 0 0         $self->storage_status(STORAGE_SIGN, 0) unless $status;
413             }
414             sub list {
415 0     0 1   my $self = shift;
416 0           my %params = @_; $self->maybe::next::method(%params);
  0            
417 0 0         return $self->storage_status(STORAGE_SIGN, -1) if $self->storage_status(STORAGE_SIGN) <= 0; # SKIP and set SKIP
418 0           my $sign = STORAGE_SIGN;
419              
420 0           my @list = ();
421 0           foreach my $storage ($self->ftp_storages) {
422 0           my $uri = new URI($storage->{url});
423 0           my $url_wop = $storage->{url_wop};
424 0   0       my $path = $uri->path // ""; $path =~ s/^\///;
  0            
425 0           my $attr = dclone($storage->{attr});
426 0 0         $attr->{Port} = $uri->port if $uri->port;
427 0           my $ostat = 1;
428              
429             # Create object
430 0 0         my $ftp = new Net::FTP($uri->host, %$attr) or do {
431 0           $self->error(sprintf("Can't connect to %s: %s", $url_wop, $@));
432 0           $ostat = 0;
433             };
434              
435             # Login
436 0 0         if ($ostat) {
437 0 0 0       $ftp->login($uri->user || "anonymous", $uri->password || "anonymous\@example.com") or do {
      0        
438 0           $self->error(sprintf("Can't login to %s: %s", $storage->{url_wop}, $ftp->message));
439 0           $ostat = 0;
440             };
441             }
442              
443             # Change dir
444 0 0 0       if ($ostat && length($path)) {
445 0 0         $ftp->cwd($path) or do {
446 0           $self->error(sprintf("Can't change directory %s on %s: %s", $path, $url_wop, $ftp->message));
447 0           $ostat = 0;
448             };
449             }
450              
451             # Get list
452 0 0         if ($ostat) {
453 0           my @ls = $ftp->ls();
454 0 0         push @list, grep { defined($_) && length($_) } @ls;
  0            
455             }
456              
457             # Quit
458 0 0         $ftp->quit if $ftp;
459             }
460 0           $self->{list}->{$sign} = [uniq(@list)];
461 0           return 1;
462             }
463              
464             1;
465              
466             __END__