diff options
author | Aurélien Aptel <aurelien.aptel@gmail.com> | 2013-07-04 17:54:43 +0200 |
---|---|---|
committer | Jim McDonough <jmcd@samba.org> | 2013-11-05 08:42:41 -0500 |
commit | bfd6b7bf096b1c29890e0c83ac9db90118191690 (patch) | |
tree | 83354ad5e894aa134651ce83e81a3ec0ca42b457 /source3/script/tests | |
parent | 60edcc790510b4c14044dd96a00dbe7368dfdc6f (diff) | |
download | samba-bfd6b7bf096b1c29890e0c83ac9db90118191690.tar.gz samba-bfd6b7bf096b1c29890e0c83ac9db90118191690.tar.xz samba-bfd6b7bf096b1c29890e0c83ac9db90118191690.zip |
test_smbclient_tarmode.pl: refactor, cleanup and document in POD
Signed-off-by: Aurélien Aptel <aurelien.aptel@gmail.com>
Reviewed-by: David Disseldorp <ddiss@samba.org>
Reviewed-by: Jim McDonough <jmcd@samba.org>
Diffstat (limited to 'source3/script/tests')
-rwxr-xr-x | source3/script/tests/test_smbclient_tarmode.pl | 637 |
1 files changed, 459 insertions, 178 deletions
diff --git a/source3/script/tests/test_smbclient_tarmode.pl b/source3/script/tests/test_smbclient_tarmode.pl index d93fec81f4c..d0cbf72b860 100755 --- a/source3/script/tests/test_smbclient_tarmode.pl +++ b/source3/script/tests/test_smbclient_tarmode.pl @@ -2,7 +2,7 @@ =head1 NAME -test_smbclient_tarmode.pl - Test for smbclient tar backup feature +C<test_smbclient_tarmode.pl> - Test for smbclient tar backup feature =cut @@ -40,6 +40,7 @@ use Term::ANSIColor; sub d {print Dumper @_;} # DEFAULTS +# 'our' to make them available in the File package our $USER = ''; our $PW = ''; our $HOST = 'localhost'; @@ -50,18 +51,40 @@ our $LOCALPATH = '/media/data/smb-test'; our $TMP = '/tmp/smb-tmp'; our $BIN = 'smbclient'; -our $SINGLE_TEST = -1; +my $SINGLE_TEST = -1; +my $LIST_TEST = 0; -our @SMBARGS = (); +my @SMBARGS = (); our $DEBUG = 0; -our $MAN = 0; -our $HELP = 0; -our $CLEAN = 0; +my $MAN = 0; +my $HELP = 0; +my $CLEAN = 0; + +# all tests +my @TESTS = ( + ['create, normal files (no attributes)', \&test_creation_normal, 'normal'], + ['create, normal nested files (no attributes)', \&test_creation_normal, 'nested'], + ['create, incremental with -g', \&test_creation_incremental, '-g'], + ['create, incremental with tarmode', \&test_creation_incremental, 'tarmode inc'], + ['create, reset archived files with -a', \&test_creation_reset, '-a'], + ['create, reset archived files with tarmode', \&test_creation_reset, 'tarmode reset'], + ['create, files newer than a file', \&test_creation_newer], + ['create, combination of tarmode filter', \&test_creation_attr], + ['create, explicit include', \&test_creation_include], + ['create, explicit exclude', \&test_creation_exclude], + ['create, include w/ filelist (F)', \&test_creation_list], + ['create, wildcard and regex', \&test_creation_wildcard], + ['extract, normal files', \&test_extraction_normal], + ['extract, explicit include', \&test_extraction_include], + ['extract, explicit exclude', \&test_extraction_exclude], + ['extract, include w/ filelist (F)', \&test_extraction_list], + ['extract, wildcard and regex', \&test_extraction_wildcard], +); =head1 SYNOPSIS -test_smbclient_tarmode.pl [options] -- [smbclient options] + test_smbclient_tarmode.pl [options] -- [smbclient options] Options: -h, --help brief help message @@ -86,6 +109,9 @@ test_smbclient_tarmode.pl [options] -- [smbclient options] path to the smbclient binary to use Test: + --list + list tests + --test N only run test number N @@ -102,6 +128,7 @@ GetOptions('u|user=s' => \$USER, 'b|bin=s' => \$BIN, 'test=i' => \$SINGLE_TEST, + 'list' => \$LIST_TEST, 'clean' => \$CLEAN, 'debug' => \$DEBUG, @@ -110,6 +137,7 @@ GetOptions('u|user=s' => \$USER, pod2usage(0) if $HELP; pod2usage(-exitval => 0, -verbose => 2) if $MAN; +list_test(), exit 0 if $LIST_TEST; if($USER xor $PW) { die "Need both user and password when one is provided\n"; @@ -138,53 +166,49 @@ if($CLEAN) { # RUN TESTS -my @all_tests = ( - [\&test_creation_normal, 'normal'], - [\&test_creation_normal, 'nested'], - [\&test_creation_incremental, '-g'], - [\&test_creation_incremental, 'tarmode inc'], - [\&test_creation_reset, '-a'], - [\&test_creation_reset, 'tarmode reset'], - [\&test_creation_newer], - [\&test_creation_attr], - [\&test_creation_include,], - [\&test_creation_exclude,], - [\&test_creation_list,], - [\&test_creation_wildcard], - [\&test_extraction_normal], - [\&test_extraction_include], - [\&test_extraction_exclude], - [\&test_extraction_list], - [\&test_extraction_wildcard], -); - if($SINGLE_TEST == -1) { - run_test(@all_tests); + run_test(@TESTS); } -elsif(0 <= $SINGLE_TEST&&$SINGLE_TEST < @all_tests) { - run_test($all_tests[$SINGLE_TEST]); +elsif(0 <= $SINGLE_TEST && $SINGLE_TEST < @TESTS) { + run_test($TESTS[$SINGLE_TEST]); } else { die "Test number is invalid\n"; } -##### +################################# -# TEST DEFINITIONS -# each test must return the number of error +=head1 DOCUMENTATION -sub test_creation_newer { +=head2 Defining a test + +=over + +=item * Create a function C<test_yourtest> + +=item * Usa the File module, documented below + +=item * Use C<smb_tar>, C<smb_client>, C<check_tar> or C<check_remote> + +=item * Return number of error - say "TEST: creation -- backup files newer than a file"; +=item * Add function to C<@TESTS> +=back + +The function must be placed in the C<@TESTS> list along with a short +description and optional arguments. + +=cut + +sub test_creation_newer { my @files; my $dt = 3000; # create oldest file at - DT my $oldest = File->new_remote('oldest'); - $oldest->set_attr(); $oldest->set_time(time - $dt); # create limit file @@ -192,12 +216,10 @@ sub test_creation_newer { # create newA file at + DT my $newA = File->new_remote('newA'); - $newA->set_attr(); $newA->set_time(time + $dt); # create newB file at + DT my $newB = File->new_remote('newB'); - $newB->set_attr(); $newB->set_time(time + $dt); # get files newer than limit_file @@ -208,9 +230,6 @@ sub test_creation_newer { } sub test_creation_attr { - - say "TEST: creation -- combinations of tarmodes (nosystem, nohidden, etc)"; - my @attr = qw/r h s a/; my @all; my @inc; @@ -218,7 +237,6 @@ sub test_creation_attr { # one normal file my $f = File->new_remote("file-n.txt"); - $f->set_attr(); push @all, $f; # combinaisions of attributes @@ -232,19 +250,19 @@ sub test_creation_attr { } } - @inc = grep { !$_->{attr}{s} } @all; + @inc = grep { !$_->attr('s') } @all; smb_tar('tarmode nosystem', '-Tc', $TAR, $DIR); $err += check_tar($TAR, \@inc); - @inc = grep { !$_->{attr}{h} } @all; + @inc = grep { !$_->attr('h') } @all; smb_tar('tarmode nohidden', '-Tc', $TAR, $DIR); $err += check_tar($TAR, \@inc); - @inc = grep { !$_->{attr}{h} and !$_->{attr}{s} } @all; + @inc = grep { !$_->attr_any('h', 's') } @all; smb_tar('tarmode nohidden nosystem', '-Tc', $TAR, $DIR); $err += check_tar($TAR, \@inc); - @inc = grep { $_->{attr}{a} and !$_->{attr}{h} and !$_->{attr}{s} } @all; + @inc = grep { $_->attr('a') && !$_->attr_any('h', 's') } @all; smb_tar('tarmode inc nohidden nosystem', '-Tc', $TAR, $DIR); $err += check_tar($TAR, \@inc); @@ -254,8 +272,6 @@ sub test_creation_attr { sub test_creation_reset { my ($mode) = @_; - say "TEST: creation -- reset archived files w/ $mode"; - my @files; my $n = 3; for(1..$n) { @@ -273,7 +289,7 @@ sub test_creation_reset { my $err = check_tar($TAR, \@files); return $err if($err > 0); - for my $f (File->list($DIR)) { + for my $f (File::list($DIR)) { if($f->{attr}{a}) { printf " ! %s %s\n", $f->attr_str, $f->remotepath; $err++; @@ -285,14 +301,11 @@ sub test_creation_reset { sub test_creation_normal { my ($mode) = @_; - say "TEST: creation -- normal files $mode (no attributes)"; - my $prefix = ($mode =~ /nest/) ? "/foo/bar/bar/" : ''; my @files; my $n = 5; for(1..$n) { my $f = File->new_remote($prefix."file-$_"); - $f->set_attr(); push @files, $f; } smb_tar('tarmode full', '-Tc', $TAR, $DIR); @@ -302,8 +315,6 @@ sub test_creation_normal { sub test_creation_incremental { my ($mode) = @_; - say "TEST: creation -- incremental w/ $mode (backup only archived files)"; - my @files; my $n = 10; for(1..$n) { @@ -329,14 +340,10 @@ sub test_creation_incremental { sub test_extraction_normal { - - say "TEST: extraction -- backup and restore normal files"; - my @files; my $n = 5; for(1..$n) { my $f = File->new_remote("file-$_"); - $f->set_attr(); push @files, $f; } @@ -352,15 +359,11 @@ sub test_extraction_normal { } sub test_extraction_include { - - say "TEST: extraction -- backup and restore included paths"; - my @all_files; my @inc_files; for(qw(file_inc inc/b inc/c inc/dir/foo dir_ex/d zob)) { my $f = File->new_remote($_); - $f->set_attr(); push @all_files, $f; push @inc_files, $f if /inc/; } @@ -377,15 +380,11 @@ sub test_extraction_include { } sub test_extraction_exclude { - - say "TEST: extraction -- backup and restore without excluded paths"; - my @all_files; my @inc_files; for(qw(file_exc exc/b exc/c exc/dir/foo dir_ex/d zob)) { my $f = File->new_remote($_); - $f->set_attr(); push @all_files, $f; push @inc_files, $f if !/exc/; } @@ -403,13 +402,10 @@ sub test_extraction_exclude { sub test_creation_include { - say "TEST: extraction -- explicit include"; - my @files; for(qw(file_inc inc/b inc/c inc/dir/foo dir_ex/d zob)) { my $f = File->new_remote($_); - $f->set_attr(); push @files, $f if /inc/; } @@ -418,13 +414,10 @@ sub test_creation_include { } sub test_creation_exclude { - say "TEST: extraction -- explicit exclude"; - my @files; for(qw(file_ex ex/b ex/c ex/dir/foo foo/bar zob)) { my $f = File->new_remote($_); - $f->set_attr(); push @files, $f if !/ex/; } @@ -433,13 +426,10 @@ sub test_creation_exclude { } sub test_creation_list { - say "TEST: creation -- filelist"; - my @inc_files; for(qw(file_inc inc/b inc/c inc/dir/foo foo/bar zob)) { my $f = File->new_remote($_); - $f->set_attr(); push @inc_files, $f if /inc/; } @@ -448,13 +438,7 @@ sub test_creation_list { return check_tar($TAR, \@inc_files); } -sub tardump { - system sprintf q{tar tf %s 2>&1 | grep -v '/$' | sort }, $TAR; -} - sub test_creation_wildcard { - say "TEST: creation -- include/exclude with wildcards"; - my @exts = qw(txt jpg exe); my @dirs = ('', "$DIR/", "$DIR/dir/"); my @all; @@ -467,7 +451,6 @@ sub test_creation_wildcard { my $fn = $dir . "file$nb." . $_; my $f = File->new_remote($fn, 'ABSPATH'); $f->delete_on_destruction(1); - $f->set_attr(); push @all, $f; $nb++; } @@ -525,8 +508,6 @@ sub test_creation_wildcard { } sub test_extraction_wildcard { - say "TEST: extraction -- include/exclude with wildcards"; - my @exts = qw(txt jpg exe); my @dirs = ('', "$DIR/", "$DIR/dir/"); my $nb; @@ -542,7 +523,6 @@ sub test_extraction_wildcard { my $fn = $dir . "file$nb." . $_; my $f = File->new_remote($fn, 'ABSPATH'); $f->delete_on_destruction(1); - $f->set_attr(); push @all, $f; $nb++; } @@ -578,14 +558,11 @@ sub test_extraction_wildcard { } sub test_extraction_list { - say "TEST: extraction -- filelist"; - my @inc_files; my @all_files; for(qw(file_inc inc/b inc/c inc/dir/foo foo/bar zob)) { my $f = File->new_remote($_); - $f->set_attr(); push @all_files, $f; push @inc_files, $f if /inc/; } @@ -602,14 +579,30 @@ sub test_extraction_list { return check_remote($DIR, \@inc_files); } -##### +################################# # IMPLEMENTATION +=head2 Useful functions + +Here are a list of useful functions and helpers to define tests. + +=cut + +# list test number and description +sub list_test { + my $i = 0; + for(@TESTS) { + my ($desc, $f, @args) = @$_; + printf "%2d.\t%s\n", $i++, $desc; + } +} + sub run_test { for(@_) { - my ($f, @args) = @$_; + my ($desc, $f, @args) = @$_; reset_env(); + say "TEST: $desc"; my $err = $f->(@args); print_res($err); print "\n"; @@ -626,7 +619,13 @@ sub print_res { } } -# return list of combinations of n-uplet +=head3 C<combine ( \@set, $n )> + +=head3 C<combine ( ['a', 'b', 'c'], 2 )> + +Return a list of all possible I<n>-uplet (or combinaison of C<$n> element) of C<@set>. + +=cut sub combine { my ($list, $n) = @_; die "Insufficient list members" if $n > @$list; @@ -644,21 +643,45 @@ sub combine { return @comb; } + +=head3 C<reset_remote( )> + +Remove all files in the server C<$DIR> (not root) + +=cut sub reset_remote { remove_tree($LOCALPATH . '/'. $DIR); make_path($LOCALPATH . '/'. $DIR); } +=head3 C<reset_tmp( )> + +Remove all files in the temp directory C<$TMP> + +=cut sub reset_tmp { remove_tree($TMP); make_path($TMP); } + +=head3 C<reset_env( )> + +Remove both temp and remote (C<$DIR>) files + +=cut sub reset_env { reset_tmp(); reset_remote(); } +=head3 C<file_list ( @files )> + +Make a multiline string of all the files remote path, one path per line. + +C<@files> must be a list of C<File> instance. + +=cut sub file_list { my @files = @_; my $s = ''; @@ -668,6 +691,15 @@ sub file_list { return $s; } +=head3 C<check_remote( $remotepath, \@files )> + +Check if C<$remotepath> has B<exactly> all the C<@files>. + +Print a summary on STDOUT. + +C<@files> must be a list of C<File> instance. + +=cut sub check_remote { my ($subpath, $files) = @_; my (%done, %expected); @@ -728,6 +760,15 @@ sub check_remote { return (@more + @less + @diff); # nb of errors } +=head3 C<check_tar( $path_to_tar, \@files )> + +Check if the archive C<$path_to_tar> has B<exactly> all the C<@files>. + +Print a summary on C<STDOUT>; + +C<@files> must be a list of C<File> instance. + +=cut sub check_tar { my ($tar, $files) = @_; my %done; @@ -793,7 +834,20 @@ sub check_tar { return (@more + @less + @diff); # nb of errors } -# call smbclient and return output +=head3 C<smb_client ( @args )> + +Run smbclient with C<@args> passed as argument and return output. + +Each element of C<@args> becomes one escaped argument of smbclient. + +Host, share, user, password and the additionnal arguments provided on +the command-line are already inserted. + +The output contains both the C<STDOUT> and C<STDERR>. + +Die if smbclient crashes or exits with an error code. + +=cut sub smb_client { my (@args) = @_; @@ -836,46 +890,146 @@ sub smb_cmd { return smb_client('-c', join(' ', @_)); } +=head3 C<smb_tar( $cmd, @args )> + +=head3 C<smb_tar( 'tarmode inc', '-Tc', $TAR, $DIR )> + +Run C<$cmd> command and use C<@args> as argument and return output. + +Wrapper around C<smb_client> for tar calls. + +=cut sub smb_tar { my ($cmd, @rest) = @_; printf " CMD: %s\n ARG: %s\n", $cmd, join(' ', @rest); smb_client((length($cmd) ? ('-c', $cmd) : ()), @rest); } +=head3 C<random( $min, $max )> + +Return integer in C<[ $min ; $max [> + +=cut sub random { my ($min, $max) = @_; ($min, $max) = ($max, $min) if($min > $max); $min + int(rand($max - $min)); } +################################# + package File; + +=head2 The File module + +All the test should use the C<File> class. It has nice functions and +methods to deal with paths, to create random files, to list the +content of the server, to change attributes, etc. + +There are 2 kinds of C<File>: remote and local. + +=over + +=item * Remote files are accessible on the server. + +=item * Local files are not. + +=back + +Thus, some methods only works on remote files. If they do not make +sense for local ones, they always return undef. + +=cut use File::Basename; use File::Path qw/make_path remove_tree/; use Digest::MD5 qw/md5_hex/; use Scalar::Util 'blessed'; -sub cleanpath { - my $p = shift; - $p =~ s{/+}{/}g; - $p =~ s{/$}{}; - $p; +=head3 Constructors + +=head4 C<< File->new_remote($path [, $abs]) >> + +Creates a file accessible on the server at C<$DIR/$path> ie. not at the +root of the share. + +If you want to remove the automatic prefix C<$DIR>, set C<$abs> to 1. + +The file is created without any DOS attributes. + +If C<$path> contains non-existent directories, they are automatically +created. + +=cut +sub new_remote { + my ($class, $path, $abs) = @_; + my ($file, $dir) = fileparse($path); + + $dir = '' if $dir eq './'; + my $loc; + + if($abs) { + $loc = cleanpath($main::LOCALPATH.'/'.$dir); + } else { + $dir = cleanpath($main::DIR.'/'.$dir); + $loc = cleanpath($main::LOCALPATH.'/'.$dir); + } + + make_path($loc); + + my $self = bless { + 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/}, + 'dir' => $dir, + 'name' => $file, + 'md5' => create_file($loc.'/'.$file), + 'remote' => 1, + }, $class; + + $self->set_attr(); + + $self; } -sub create_file { - my $fn = shift; - my $buf = ''; - unlink $fn if -e $fn; - my $size = main::random(512, 1024); - open my $out, '>', $fn or die "can't open $fn: $!\n"; - binmode $out; - for(1..$size) { - $buf .= pack('C', main::random(0, 256)); +=head4 C<< File->new_local($abs_path [, $data]) >> + +Creates a file at C<$abs_path> with $data in it on the system. +If $data is not provided, fill it with random bytes. + +=cut +sub new_local { + my ($class, $path, $data) = @_; + my ($file, $dir) = fileparse($path); + + make_path($dir); + + my $md5; + + if(defined $data) { + open my $f, '>', $path or die "can't write in $path: $!"; + print $f $data; + close $f; + $md5 = md5_hex($data); + } else { + $md5 = create_file($path); } - print $out $buf; - close $out; - return md5_hex($buf); + + my $self = { + 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/}, + 'dir' => $dir, + 'name' => $file, + 'md5' => $md5, + 'remote' => 0, + }; + + bless $self, $class; } +=head3 Methods + +=head4 C<< $f->localpath >> + +Return path on the system eg. F</opt/samba/share/test_tar_mode/file> + +=cut sub localpath { my $s = shift; if($s->{remote}) { @@ -886,29 +1040,71 @@ sub localpath { } } +=head4 C<< $f->remotepath >> + +Return path on the server share. + +Return C<undef> if the file is local. + +=cut sub remotepath { my ($s) = @_; return undef if !$s->{remote}; cleanpath(($s->{dir}.'/'.$s->{name}) =~ s{^/}{}r); } + +=head4 C<< $f->remotedir >> + +Return the directory path of the file on the server. + +Like C<< $f->remotepath >> but without the final file name. + +=cut sub remotedir { my $s = shift; return undef if !$s->{remote}; cleanpath($s->{dir}); } +=head4 C<< $f->tarpath >> + +Return path as it would appear in a tar archive. + +Like C<< $f->remotepath >> but prefixed with F<./> + +=cut sub tarpath { my $s = shift; return undef if !$s->{remote}; cleanpath('./'.$s->remotepath); } +=head4 C<< $f->delete_on_destruction( 0 ) >> + +=head4 C<< $f->delete_on_destruction( 1 ) >> + +By default, a C<File> is not deleted on the filesystem when it is not +referenced anymore in Perl memory. + +When set to 1, the destructor unlink the file if it is not already removed. +If the C<File> created directories when constructed, it does not remove them. + +=cut sub delete_on_destruction { my ($s, $delete) = @_; $s->{delete_on_destruction} = $delete; } +=head4 C<< $f->set_attr( ) >> + +=head4 C<< $f->set_attr( 'a' ) >> + +=head4 C<< $f->set_attr( 'a', 'r', 's', 'h' ) >> + +Remove all DOS attributes and only set the one provided. + +=cut sub set_attr { my ($s, @flags) = @_; return undef if !$s->{remote}; @@ -930,18 +1126,69 @@ sub set_attr { } } +=head4 C<< $f->attr_any( 'a' ) >> + +=head4 C<< $f->attr_any( 'a', 's', ... ) >> + +Return 1 if the file has any of the DOS attributes provided. + +=cut +sub attr_any { + my ($s, @flags) = @_; + for(@flags) { + return 1 if $s->{attr}{$_}; + } + 0; +} + + +=head4 C<< $f->attr( 'a' ) >> + +=head4 C<< $f->attr( 'a', 's', ... ) >> + +Return 1 if the file has all the DOS attributes provided. + +=cut +sub attr { + my ($s, @flags) = @_; + for(@flags) { + return 0 if !$s->{attr}{$_}; + } + 1; +} + +=head4 C<< $f->attr_str >> + +Return DOS attributes as a compact string. + + Read-only, hiden, system, archive => "rhsa" + +=cut sub attr_str { my $s = shift; return undef if !$s->{remote}; join('', map {$_ if $s->{attr}{$_}} qw/r h s a d n/); } +=head4 C<< $f->set_time($t) >> +Set modification and access time of the file to C<$t>. + +C<$t> must be in Epoch time (number of seconds since 1970/1/1). + +=cut sub set_time { my ($s, $t) = @_; utime $t, $t, $s->localpath; } +=head4 C<< $f->md5 >> + +Return md5 sum of the file. + +The result is cached. + +=cut sub md5 { my $s = shift; @@ -955,6 +1202,29 @@ sub md5 { return $s->{md5}; } +sub DESTROY { + my $s = shift; + if($s->{delete_on_destruction} && -f $s->localpath) { + if($main::DEBUG) { + say "DESTROY ".$s->localpath; + } + unlink $s->localpath; + } +} + +=head3 Functions + +=head4 C<< File::walk( \&function, @files) >> + +=head4 C<< File::walk( sub { ... }, @files) >> + +Iterate on file hierachy in C<@files> and return accumulated results. + +Use C<$_ in> the sub to access the current C<File>. + +The C<@files> must come from a call to the C<File::tree> function. + +=cut sub walk { my $fun = \&{shift @_}; @@ -971,29 +1241,15 @@ sub walk { return @res; } -sub tree { - my ($class, $d) = @_; - my @files; - - if(!defined $d) { - @files = File->list(); - } elsif(blessed $d) { - @files = File->list($d->remotepath); - } else { - @files = File->list($d); - } +=head4 C<< File::list( $remotepath ) >> - for my $f (@files) { - if($f->{attr}{d}) { - $f->{content} = [tree($class, $f)]; - } - } +Return list of file (C<File> instance) in C<$remotepath>. - return @files; -} +C<$remotepath> must be a directory. +=cut sub list { - my ($class, $path) = @_; + my ($path) = @_; $path ||= '/'; my @files; my $out = main::smb_client('-D', $path, '-c', 'ls'); @@ -1019,75 +1275,100 @@ sub list { 'd' => scalar ($attr =~ /D/), 'n' => scalar ($attr =~ /N/), }, - }, $class; + }, 'File'; } return @files; } -sub new_remote { - my ($class, $path, $abs) = @_; - my ($file, $dir) = fileparse($path); +=head4 C<< File::tree( $remotepath ) >> - $dir = '' if $dir eq './'; - my $loc; +Return recursive list of file in C<$remotepath>. - if($abs) { - $loc = cleanpath($main::LOCALPATH.'/'.$dir); +C<$remotepath> must be a directory. + +Use C<File::walk()> to iterate over all the files. + +=cut +sub tree { + my ($d) = @_; + my @files; + + if(!defined $d) { + @files = list(); + } elsif(blessed $d) { + @files = list($d->remotepath); } else { - $dir = cleanpath($main::DIR.'/'.$dir); - $loc = cleanpath($main::LOCALPATH.'/'.$dir); + @files = list($d); } - make_path($loc); + for my $f (@files) { + if($f->{attr}{d}) { + $f->{content} = [tree($f)]; + } + } - my $self = { - 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/}, - 'dir' => $dir, - 'name' => $file, - 'md5' => create_file($loc.'/'.$file), - 'remote' => 1, - }; + return @files; +} - bless $self, $class; +# remove trailing or duplicated slash +sub cleanpath { + my $p = shift; + $p =~ s{/+}{/}g; + $p =~ s{/$}{}; + $p; } -sub new_local { - my ($class, $path, $data) = @_; - my ($file, $dir) = fileparse($path); +# create random file at path local path $fn +sub create_file { + my $fn = shift; + my $buf = ''; + unlink $fn if -e $fn; + my $size = main::random(512, 1024); + open my $out, '>', $fn or die "can't open $fn: $!\n"; + binmode $out; + for(1..$size) { + $buf .= pack('C', main::random(0, 256)); + } + print $out $buf; + close $out; + return md5_hex($buf); +} - make_path($dir); - my $md5; +=head3 Examples - if(defined $data) { - open my $f, '>', $path or die "can't write in $path: $!"; - print $f $data; - close $f; - $md5 = md5_hex($data); - } else { - $md5 = create_file($path); - } + # create remote file in $DIR/foo/bar + my $f = File->new_remote("foo/bar/myfile"); + say $f->localpath; # /opt/share/$DIR/foo/bar/myfile + say $f->remotepath; # $DIR/foo/bar/myfile + say $f->remotedir; # $DIR/foo/bar - my $self = { - 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/}, - 'dir' => $dir, - 'name' => $file, - 'md5' => $md5, - 'remote' => 0, - }; - bless $self, $class; -} + # same but in root dir + my $f = File->new_remote("myfile", 1); + say $f->localpath; # /opt/share/myfile + say $f->remotepath; # myfile + say $f->remotedir; # -# a gate to hard to debug bugs... -sub DESTROY { - my $s = shift; - if($s->{delete_on_destruction} && -f $s->localpath) { - if($main::DEBUG) { - say "DESTROY ".$s->localpath; + + # create local random temp file in $TMP + my $f = File->new_local("$TMP/temp"); + say $f->remotepath; # undef because it's not on the server + + + # same but file contains "hello" + my $f = File->new_local("$TMP/temp", "hello"); + + + # list of files in $DIR (1 level) + for (File::list($DIR)) { + say $_->remotepath; } - unlink $s->localpath; - } -} + + + # list of all files in dir and subdir of $DIR + File::walk(sub { say $_->remotepath }, File::tree($DIR)); + +=cut 1; |