package Mogstored::FIDStatter; use strict; use warnings; use Carp qw(croak); # on_fid => sub { my ($fidid, $size) = @_; ... } # t_stat => sub { my $fid = shift } sub new { my ($class, %opts) = @_; my $self = bless {}, $class; foreach (qw(dir from to on_fid t_stat)) { $self->{$_} = delete $opts{$_}; } croak("unknown opts") if %opts; $self->{on_fid} ||= sub {}; $self->{t_stat} ||= sub {}; return $self; } sub run { my $self = shift; # min/max dirs we could possibly care about format: "n/nnn/nnn/" my $min_dir = dir($self->{from}); my $max_dir = dir($self->{to}); # our start/end fid ranges, zero-padded to 25 or so digits, to be # string-comparable, avoiding integer math (this might be a 32-bit # machine, with a 64-bit mogilefsd/clients) my $min_zpad = zeropad($self->{from}); my $max_zpad = zeropad($self->{to}); my $dir_in_range = sub { my $dir = shift; # "n/[nnn/[nnnn/]]" return 0 if max_subdir($dir) lt $min_dir; return 0 if min_subdir($dir) gt $max_dir; return 1; }; my $file_in_range = sub { my $fid = zeropad(shift); return $fid ge $min_zpad && $fid le $max_zpad; }; foreach_dentry($self->{dir}, qr/^\d$/, sub { my ($bdir, $dir) = @_; return unless $dir_in_range->("$bdir/"); foreach_dentry($dir, qr/^\d{3}$/, sub { my ($mdir, $dir) = @_; return unless $dir_in_range->("$bdir/$mdir/"); foreach_dentry($dir, qr/^\d{3}$/, sub { my ($tdir, $dir) = @_; return unless $dir_in_range->("$bdir/$mdir/$tdir/"); foreach_dentry($dir, qr/^\d+\.fid$/, sub { my ($file, $fullfile) = @_; my ($fid) = ($file =~ /^0*(\d+)\.fid$/); return unless $file_in_range->($fid); $self->{t_stat}->($fid); my $size = (stat($fullfile))[9]; $self->{on_fid}->($fid, $size) if $size; }); }); }); }); } sub zeropad { my $fid = shift; return "0"x(25-length($fid)) . $fid; } sub foreach_dentry { my ($dir, $re, $code) = @_; opendir(my $dh, $dir) or die "Failed to open $dir: $!"; $code->($_, "$dir/$_") foreach sort grep { /$re/ } readdir($dh); } # returns directory that a fid will be in # $fid may or may not have leading zeroes. sub dir { my $fid = shift; $fid =~ s!^0*!!; $fid = "0"x(10-length($fid)) . $fid if length($fid) < 10; my ($b, $mmm, $ttt) = $fid =~ m{^(\d)(\d{3})(\d{3})}; return "$b/$mmm/$ttt/"; } sub max_subdir { pad_dir($_[0], "999"); } sub min_subdir { pad_dir($_[0], "000"); } sub pad_dir { my ($dir, $pad) = @_; if (length($dir) == 2) { return "$dir$pad/$pad/" } if (length($dir) == 6) { return "$dir$pad/" } if (length($dir) == 10) { return $dir } Carp::confess("how do I pad '$dir' ?"); } 1;