File Coverage

lib/App/MtAws.pm
Criterion Covered Total %
statement 86 216 39.8
branch 10 80 12.5
condition 3 11 27.2
subroutine 26 35 74.2
pod 0 7 0.0
total 125 349 35.8


line stmt bran cond sub pod time code
1             # mt-aws-glacier - Amazon Glacier sync client
2             # Copyright (C) 2012-2014 Victor Efimov
3             # http://mt-aws.com (also http://vs-dev.com) vs@vs-dev.com
4             # License: GPLv3
5             #
6             # This file is part of "mt-aws-glacier"
7             #
8             # mt-aws-glacier is free software: you can redistribute it and/or modify
9             # it under the terms of the GNU General Public License as published by
10             # the Free Software Foundation, either version 3 of the License, or
11             # (at your option) any later version.
12             #
13             # mt-aws-glacier is distributed in the hope that it will be useful,
14             # but WITHOUT ANY WARRANTY; without even the implied warranty of
15             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16             # GNU General Public License for more details.
17             #
18             # You should have received a copy of the GNU General Public License
19             # along with this program. If not, see <http://www.gnu.org/licenses/>.
20              
21              
22             =head1 NAME
23              
24             mt-aws-glacier - Perl Multithreaded Multipart sync to Amazon Glacier
25              
26             =head1 SYNOPSIS
27              
28             More info in README.md or L<https://github.com/vsespb/mt-aws-glacier> or L<http://mt-aws.com/>
29              
30             =cut
31              
32              
33             package App::MtAws;
34              
35 5     5   195362 use strict;
  5         8  
  5         137  
36 5     5   22 use warnings;
  5         7  
  5         124  
37 5     5   25 use utf8;
  5         7  
  5         27  
38 5     5   176 use 5.008008; # minumum perl version is 5.8.8
  5         15  
39              
40             our $VERSION = '1.114_2';
41             our $VERSION_MATURITY = "";
42              
43 5     5   19 use constant ONE_MB => 1024*1024;
  5         7  
  5         323  
44              
45 5     5   2054 use App::MtAws::ParentWorker;
  5         14  
  5         181  
46 5     5   2330 use App::MtAws::ChildWorker;
  5         15  
  5         158  
47              
48 5     5   2578 use App::MtAws::QueueJob::CreateVault;
  5         10  
  5         124  
49 5     5   2075 use App::MtAws::QueueJob::DeleteVault;
  5         10  
  5         115  
50 5     5   2107 use App::MtAws::QueueJob::RetrieveInventory;
  5         8  
  5         114  
51 5     5   1984 use App::MtAws::QueueJob::FetchAndDownload;
  5         13  
  5         136  
52 5     5   2130 use App::MtAws::QueueJob::Upload;
  5         10  
  5         133  
53              
54 5     5   26 use File::Find ;
  5         6  
  5         254  
55 5     5   23 use File::Spec;
  5         6  
  5         103  
56 5     5   2498 use App::MtAws::Journal;
  5         10  
  5         159  
57 5     5   31 use App::MtAws::ConfigDefinition;
  5         8  
  5         632  
58 5     5   2268 use App::MtAws::ForkEngine qw/with_forks fork_engine/;
  5         13  
  5         385  
59 5     5   28 use Carp;
  5         5  
  5         207  
60 5     5   20 use IO::Handle;
  5         5  
  5         122  
61              
62 5     5   20 use App::MtAws::Utils;
  5         7  
  5         484  
63 5     5   23 use App::MtAws::Exceptions;
  5         6  
  5         242  
64 5     5   3359 use PerlIO::encoding;
  5         2451  
  5         11210  
65              
66             sub check_module_versions
67             {
68 5     5 0 1141 for (keys %INC) {
69 920 100       1405 if (my ($mod) = /^App\/MtAws\/(.*)\.pmc?$/) {
70 186         210 $mod =~ s!/!::!g;
71 186         175 my $module = "App::MtAws::$mod";
72 186         952 my $got = $module->VERSION;
73 186 100       338 $got = 'undef' unless defined $got;
74 186 100       320 die "FATAL: wrong version of $module, expected $VERSION, found $got" unless $got eq $VERSION;
75             }
76             };
77             }
78              
79             sub print_system_modules_version
80             {
81 1     1 0 1193 for my $module (sort keys %INC) {
82 205 100 100     798 if ($module !~ /^App\/MtAws/ && $module =~ /\.pmc?/) {
83 154         129 my $name = $module;
84 154         250 $name =~ s[/][::]g;
85 154         259 $name =~ s[\.pmc?$][];
86 154         5008 my $ver = eval qq{\$${name}::VERSION};
87 154 100       330 $ver = 'undef' unless defined $ver;
88 154         714 print "$name\t$ver\t$INC{$module}\n";
89             }
90             }
91             }
92              
93             sub load_all_dynamic_modules
94             {
95 1     1 0 604 require App::MtAws::Command::Sync;
96 1         634 require App::MtAws::Command::Retrieve;
97 1         426 require App::MtAws::Command::CheckLocalHash;
98 1         385 require App::MtAws::Command::DownloadInventory;
99             }
100              
101             sub check_all_dynamic_modules
102             {
103             # we load here all dynamically loaded modules, to check that installation is correct.
104 1     1 0 758 load_all_dynamic_modules();
105 1         4 check_module_versions;
106             }
107              
108             sub main
109             {
110 0     0 0   $|=1;
111 0           STDERR->autoflush(1);
112 0           print "MT-AWS-Glacier, Copyright 2012-2014 Victor Efimov http://mt-aws.com/ Version $VERSION$VERSION_MATURITY\n\n";
113              
114 0 0         warn "**NOT RECOMMENDED FOR PRODUCTION USE UNDER CYGWIN**\n\n" if ($^O eq 'cygwin');
115 0 0         die "**DEVELOPMENT VERSION, NOT FOR PRODUCTION USE. EXITING**\n\n" if ($VERSION =~ /_/);
116 0 0 0       warn "**NOT TESTED UNDER PERLIO=stdio**\n\n" if (defined $ENV{PERLIO} && $ENV{PERLIO} =~ /stdio/);
117 0 0         die "Will *not* work under Win32\n" if ($^O eq 'MSWin32');
118              
119 0           check_module_versions();
120 0 0         unless (defined eval {process(); 1;}) {
  0            
  0            
121 0           dump_error(q{});
122 0           exit(1);
123             }
124 0           print "OK DONE\n";
125 0           exit(0);
126             }
127              
128             sub process
129             {
130 0     0 0   my ($P) = @_;
131 0           my ($src, $vault, $journal);
132 0           my $maxchildren = 4;
133 0           my $config = {};
134 0           my $config_filename;
135              
136              
137 0           my $res = App::MtAws::ConfigDefinition::get_config()->parse_options(@ARGV);
138 0           my ($action, $options) = ($res->{command}, $res->{options});
139 0 0         if ($res->{warnings}) {
140 0           while (@{$res->{warnings}}) {
  0            
141 0           my ($warning, $warning_text) = (shift @{$res->{warnings}}, shift @{$res->{warning_texts}});
  0            
  0            
142 0           print STDERR "WARNING: $warning_text\n";
143             }
144             }
145 0 0         if ($res->{error_texts}) {
146 0           for (@{$res->{error_texts}}) {
  0            
147 0           print STDERR "ERROR: ".$_."\n";
148             }
149 0           die exception cmd_error => 'Error in command line/config'
150             }
151 0 0 0       if ($action ne 'help' && $action ne 'version') {
152 0           $PerlIO::encoding::fallback = Encode::FB_QUIET;
153 0           binmode STDERR, ":encoding($options->{'terminal-encoding'})";
154 0           binmode STDOUT, ":encoding($options->{'terminal-encoding'})";
155             }
156              
157 0           my %journal_opts = ( journal_encoding => $options->{'journal-encoding'} );
158              
159 0 0         if ($action eq 'sync') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
160 0 0         die "Not a directory $options->{dir}" unless -d binaryfilename $options->{dir};
161              
162             my $j = App::MtAws::Journal->new(%journal_opts, journal_file => $options->{journal}, root_dir => $options->{dir},
163 0           filter => $options->{filters}{parsed}, leaf_optimization => $options->{'leaf-optimization'}, follow => $options->{'follow'});
164              
165 0           require App::MtAws::Command::Sync;
166 0           check_module_versions;
167 0           App::MtAws::Command::Sync::run($options, $j);
168              
169             } elsif ($action eq 'upload-file') {
170              
171 0 0         defined(my $relfilename = $options->{relfilename})||confess;
172 0           my $partsize = delete $options->{partsize};
173              
174 0           my $j = App::MtAws::Journal->new(%journal_opts, journal_file => $options->{journal});
175              
176             with_forks 1, $options, sub {
177              
178 0     0     $j->read_journal(should_exist => 0);
179              
180             ## no Test::Tabs
181             die <<"END"
182             File with same name already exists in Journal.
183             In the current version of mtglacier you are disallowed to store multiple versions of same file.
184             Multiversion will be implemented in the future versions.
185             END
186 0 0         if (defined $j->{journal_h}->{$relfilename});
187             ## use Test::Tabs
188              
189 0 0         if ($options->{'data-type'} ne 'filename') {
190 0           binmode STDIN;
191 0           check_stdin_not_empty(); # after we fork, but before we touch Journal for write and create Amazon Glacier upload id
192             }
193              
194 0           $j->open_for_write();
195              
196             my $ft = ($options->{'data-type'} eq 'filename') ?
197             App::MtAws::QueueJob::Upload->new(
198 0 0         filename => $options->{filename}, relfilename => $relfilename,
199             partsize => ONE_MB*$partsize, delete_after_upload => 0) :
200             App::MtAws::QueueJob::Upload->new(
201             stdin => 1, relfilename => $relfilename,
202             partsize => ONE_MB*$partsize, delete_after_upload => 0);
203              
204 0           my ($R) = fork_engine->{parent_worker}->process_task($ft, $j);
205 0 0         die unless $R;
206 0           $j->close_for_write();
207             }
208 0           } elsif ($action eq 'purge-vault') {
209 0           my $j = App::MtAws::Journal->new(%journal_opts, journal_file => $options->{journal}, filter => $options->{filters}{parsed});
210              
211             with_forks !$options->{'dry-run'}, $options, sub {
212 0     0     $j->read_journal(should_exist => 1);
213              
214 0           my $archives = $j->{archive_h};
215 0 0         if (scalar keys %$archives) {
216 0 0         if ($options->{'dry-run'}) {
217 0           for (keys %$archives) {
218 0           print "Will DELETE archive $_ (filename $archives->{$_}{relfilename})\n"
219             }
220             } else {
221 0           $j->open_for_write();
222              
223 0           my @filelist = map { {archive_id => $_, relfilename =>$archives->{$_}->{relfilename} } } keys %{$archives};
  0            
  0            
224             my $ft = App::MtAws::QueueJob::Iterator->new(iterator => sub {
225 0 0         if (my $rec = shift @filelist) {
226             return App::MtAws::QueueJob::Delete->new(
227             relfilename => $rec->{relfilename}, archive_id => $rec->{archive_id},
228 0           );
229             } else {
230 0           return;
231             }
232 0           });
233 0           my ($R) = fork_engine->{parent_worker}->process_task($ft, $j);
234 0 0         die unless $R;
235              
236 0           $j->close_for_write();
237             }
238             } else {
239 0           print "Nothing to delete\n";
240             }
241             }
242 0           } elsif ($action eq 'restore') {
243 0           my $j = App::MtAws::Journal->new(%journal_opts, journal_file => $options->{journal}, root_dir => $options->{dir}, filter => $options->{filters}{parsed}, use_active_retrievals => 1);
244 0 0         confess unless $options->{'max-number-of-files'};
245              
246              
247 0           require App::MtAws::Command::Retrieve;
248 0           check_module_versions;
249 0           App::MtAws::Command::Retrieve::run($options, $j);
250             } elsif ($action eq 'restore-completed') {
251 0           my $j = App::MtAws::Journal->new(%journal_opts, journal_file => $options->{journal}, root_dir => $options->{dir}, filter => $options->{filters}{parsed});
252              
253             with_forks !$options->{'dry-run'}, $options, sub {
254 0     0     $j->read_journal(should_exist => 1);
255              
256 0           my $files = $j->{journal_h};
257             # TODO: refactor
258 0           my %filelist = map { $_->{archive_id} => $_ }
259 0           grep { !-f binaryfilename $_->{filename} }
260             map {
261 0           my $entry = $j->latest($_);
262             {
263             archive_id => $entry->{archive_id}, mtime => $entry->{mtime}, size => $entry->{size},
264 0           treehash => $entry->{treehash}, relfilename =>$_, filename=> $j->absfilename($_)
265             }
266             }
267 0           keys %{$files};
  0            
268 0 0         if (keys %filelist) {
269 0 0         if ($options->{'dry-run'}) {
270 0           for (values %filelist) {
271 0           print "Will DOWNLOAD (if available) archive $_->{archive_id} (filename $_->{relfilename})\n";
272             }
273             } else {
274 0   0       my $ft = App::MtAws::QueueJob::FetchAndDownload->new(file_downloads => $options->{file_downloads}||{}, archives => \%filelist);
275 0           my ($R) = fork_engine->{parent_worker}->process_task($ft, $j);
276 0 0         die unless $R;
277             }
278             } else {
279 0           print "Nothing to restore\n";
280             }
281             }
282 0           } elsif ($action eq 'check-local-hash') {
283 0           my $j = App::MtAws::Journal->new(%journal_opts, journal_file => $options->{journal}, root_dir => $options->{dir}, filter => $options->{filters}{parsed});
284 0           require App::MtAws::Command::CheckLocalHash;
285 0           check_module_versions;
286 0           App::MtAws::Command::CheckLocalHash::run($options, $j);
287             } elsif ($action eq 'retrieve-inventory') {
288 0           $options->{concurrency} = 1; # TODO implement this in ConfigEngine
289              
290             with_forks 1, $options, sub {
291 0     0     my $ft = App::MtAws::QueueJob::RetrieveInventory->new(format => $options->{'request-inventory-format'});
292 0           my ($R) = fork_engine->{parent_worker}->process_task($ft, undef);
293             }
294 0           } elsif ($action eq 'download-inventory') {
295 0           $options->{concurrency} = 1; # TODO implement this in ConfigEngine
296 0           my $j = App::MtAws::Journal->new(%journal_opts, journal_file => $options->{'new-journal'});
297 0           require App::MtAws::Command::DownloadInventory;
298 0           check_module_versions;
299 0           App::MtAws::Command::DownloadInventory::run($options, $j);
300             } elsif ($action eq 'create-vault') {
301 0           $options->{concurrency} = 1;
302              
303             with_forks 1, $options, sub {
304 0     0     my $ft = App::MtAws::QueueJob::CreateVault->new(name => $options->{'vault-name'});
305 0           my ($R) = fork_engine->{parent_worker}->process_task($ft, undef);
306             }
307 0           } elsif ($action eq 'delete-vault') {
308 0           $options->{concurrency} = 1;
309              
310             with_forks 1, $options, sub {
311 0     0     my $ft = App::MtAws::QueueJob::DeleteVault->new(name => $options->{'vault-name'});
312 0           my ($R) = fork_engine->{parent_worker}->process_task($ft, undef);
313             }
314 0           } elsif ($action eq 'help') {
315             ## no Test::Tabs
316 0           print <<"END";
317             Usage: mtglacier.pl COMMAND [POSITIONAL ARGUMENTS] [OPTION]...
318              
319             Common options:
320             --config - config file
321             --journal - journal file (append only)
322             --dir - source local directory
323             --vault - Glacier vault name
324             --concurrency - number of parallel workers to run
325             --max-number-of-files - max number of files to sync/restore
326             --protocol - Use http or https to connect to Glacier
327             --partsize - Glacier multipart upload part size
328             --filter --include --exclude - File filtering
329             --dry-run - Don't do anything
330             --token - to be used with STS/IAM
331             --timeout - socket timeout
332             Commands:
333             sync
334             --new --replace-modified --delete-removed - Sync modes
335             --leaf-optimization - Don't use directory hardlinks count when traverse.
336             --follow - Follow symbolic links
337             --detect treehash|mtime|mtime-or-treehash|mtime-and-treehash|always-positive|size-only
338             purge-vault
339             restore
340             restore-completed
341             --segment-size - Size for multi-segment download, in megabytes
342             check-local-hash
343             retrieve-inventory
344             --request-inventory-format - json or csv
345             download-inventory
346             --new-journal - Write inventory as new journal
347             create-vault VAULT-NAME
348             delete-vault VAULT-NAME
349             upload-file
350             --filename - File to upload
351             --set-rel-filename - Relative filename to use in Journal (if dir not specified)
352             --stdin - Upload from STDIN
353             --check-max-file-size - Specify to ensure there will be less than 10 000 parts
354             version - prints debug information about software installed
355             Config format (text file):
356             key=YOURKEY
357             secret=YOURSECRET
358             # region: eu-west-1, us-east-1 etc
359             region=us-east-1
360             # protocol=http (default) or https
361             protocol=http
362             END
363              
364             ## use Test::Tabs
365              
366             } elsif ($action eq 'version') {
367 0           check_all_dynamic_modules();
368 0           print "mt-aws-glacier version: $VERSION $VERSION_MATURITY\n";
369 0           print "Perl Version: $]\n";
370 0           print_system_modules_version();
371             } else {
372 0           die "Wrong usage";
373             }
374             }
375              
376             sub check_stdin_not_empty
377             {
378 0 0   0 0   die "Empty input from STDIN - cannot upload empty archive"
379             if eof(STDIN); # we block until first byte arrive, then we put it back in to buffer
380             }
381              
382             1;