package Tie::TSHashDir;

# Tie::TSHashDir, a Perl hash tie module with timestamps from directories.
# Copyright (C) 2001 Tom Rathborne
# Copyright (C) 2001 Convoke Communications Corp.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

require 5.005;
use strict;
require IO::File;

####### Documentation ########################################################

=head1 NAME

Tie::TSHashDir - manipulate files in a directory like a hash, with special
timestamp keys.

=head1 SYNOPSIS

my (%tsdir, %cache, %tsmir);

tie %tsdir, 'Tie::TSHashDir', '/path/to/files', '.suffix';
tie %cache, 'Tie::SomeFasterThing', $cache;

tie %tsmir, 'Tie::TSHashMirror', \%tsdir, \%cache, 30;

=head1 DESCRIPTION

Tie::TSHashDir gives you hash access to a directory full of files. The TIEHASH
constructor needs two arguments - the directory being accesses and the suffix
of files in that directory. When you access the hash (for read or write), the
corresponding file in the directory is accessed.

For any given filename foo, if you retrieve _TS_foo, you will get the last
modified date of the file. This is of course only useful in combination with
Tie::TSHashMirror (q.v.).

=head1 AUTHOR

Tom Rathborne <tomr@convoke.com>

=cut

####### Public methods #######################################################

sub TIEHASH ($$) { # creates new directory bin
    my $class = shift;
    my $dir = shift || die 'No directory specified';
    my $suffix = shift || '';
    die "'$dir' is not a directory" unless -d "$dir/.";
    die "'$dir' is not readable"    unless -r _;
    die "'$dir' is not executable"  unless -x _;

    my $self = {
        'DIR' => $dir,
        'SUF' => $suffix,
    };

    return bless $self, $class;
}

sub FETCH ($$) {
    my $self = shift;
    my $fname = shift;

    if ($fname =~ /^_TS_(.*)$/) {
        if (-f "$self->{'DIR'}/$1$self->{'SUF'}") {
            return (stat(_))[9];
        } else {
            return undef;
        }
    }

    # implicit else

    my $file = '';

    if (my $fh = new IO::File ("<$self->{'DIR'}/$fname$self->{'SUF'}")) {
        $file = join ('', $fh->getlines);
        $fh->close;
    }

    return $file;
}

sub STORE ($$$) {
    my $self = shift;
    my $fname = shift;
    my $value = shift;

    open (TSFILE, ">$self->{'DIR'}/$fname$self->{'SUF'}") || die;
    print TSFILE $value;
    close TSFILE;
}

sub DELETE ($$) {
    my $self = shift;
    my $fname = shift;

    unlink "$self->{'DIR'}/$fname$self->{'SUF'}";
}

sub CLEAR ($) {
    my $self = shift;

    my @files = $self->FLIST;

    foreach my $file (@files) {
        unlink "$self->{'DIR'}/$file";
    }
}

sub EXISTS ($$) {
    my $self = shift;
    my $fname = shift;

    if ($fname =~ /^_TS_(.*)$/) {
        return (-e "$self->{'DIR'}/$1$self->{'SUF'}" and -s _ and -r _);
    }

    return (-e "$self->{'DIR'}/$fname$self->{'SUF'}" and -s _ and -r _);
}

sub FIRSTKEY ($) {
    my $self = shift;

    return ($self->FLIST)[0];
}

sub NEXTKEY ($$) {
    my $self = shift;
    my $lastkey = shift;

    my @files = $self->FLIST();

    while (my $file = shift @files) {
        return shift @files if $file eq $lastkey;
    }

    return undef;
}

####### Private methods ######################################################

sub FLIST ($) {
    my $self = shift;

    opendir TSDIR, $self->{'DIR'} || die;
    my @files = sort (grep s/$self->{'SUF'}$//, (readdir TSDIR));
    closedir TSDIR;

    return @files;
}

####### End of Code ##########################################################

1; # Return true

####### EOF ##################################################################
