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;