The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

# ----------------------------------------------------------------------------
# simple file cache to handle more "open" files than the system allows
# the cache gets created with an explicit limit, which gets automatically
# decreased if a file open fails
# handles only files for writing, which gets opened the first time with '>'
# and later with '>>'
# ----------------------------------------------------------------------------

package privFileCache;
use fields qw(open closed max n);
use Symbol 'gensym';

sub new {
    my ($class,$max) = @_;
    my $self = fields::new($class);
    $self->{n} = 0;
    $self->{max} = $max||128;
    $self->{open} = {};
    $self->{closed} = {};
    return $self;
}

sub create {
    my ($self,$fname) = @_;
    $self->_get($fname,1) or return;
    my $fh = gensym();
    tie *$fh,'privFileCache::HANDLE',$self,$fname;
    return $fh;
}

sub _get {
    my ($self,$fname,$create) = @_;
    my $fh = $self->{open}{$fname};
    $fh = $fh && $fh->[0];
    if ($create) {
	_expire($self) if ! $fh;
	$fh = _open($self,'>',$fname) or do {
	    delete $self->{open}{$fname};
	    return;
	};
    }
    if ( ! $fh ) {
	$self->{closed}{$fname} or die "$fname not in pool";
	$fh = _open($self,'>>',$fname) or return;
	delete $self->{closed}{$fname};
    }

    $self->{open}{$fname} = [ $fh,$self->{n}++ ];
    return $fh;
}

sub _del {
    my ($self,$fname) = @_;
    delete $self->{open}{$fname};
    delete $self->{closed}{$fname};
}


sub _open {
    my ($self,$what,$fname) = @_;
    my $fh;
    while ( ! open( $fh,$what,$fname )) {
	if ( $!{ENFILE} || $!{EMFILE} || $!{ENOMEM} ) {
	    $self->{max}-- >1 or die "pool to small";
	    _expire($self);
	} else {
	    return;
	}
    }
    return $fh;
}

sub _expire {
    my ($self) = @_;
    my @fn = keys %{$self->{open}};
    @fn > $self->{max} or return;
    @fn = sort { $self->{open}{$a}[1] <=> $self->{open}{$b}[1] } @fn;
    while (@fn > $self->{max}) {
	my $fn = shift(@fn);
	delete $self->{open}{$fn};
	$self->{closed}{$fn} = 1;
    }
}

package privFileCache::HANDLE;
use strict;
use Errno 'EBADF';
use Scalar::Util 'weaken';

sub TIEHANDLE {
    my ($class, $fcache, $fname) = @_;
    weaken($fcache);
    bless { fcache => $fcache, fname => $fname }, $class;
}

sub PRINT {
    my $self = shift;
    my $fh = $self->{fcache}->_get($self->{fname}) or return;
    print $fh @_;
};

sub PRINTF {
    my $self = shift;
    my $fh = $self->{fcache}->_get($self->{fname}) or return;
    printf $fh @_;
};
    
*WRITE = \&PRINT;
*READ = *READLINE = *GETC = *FILENO = *TELL = sub { die "not implemented" };
sub BINMODE  { return 0 }

sub CLOSE {
    my $self = shift;
    $self->{fcache}->_del($self->{fname}) or return;
}


1;