package WebPAC::Store;

use warnings;
use strict;

use WebPAC::Common;
use base qw/WebPAC::Common Class::Accessor/;
__PACKAGE__->mk_accessors(qw/database/);

use Storable;
use File::Path;
use Data::Dump qw/dump/;

=head1 NAME

WebPAC::Store - Store WebPAC data on disk

=head1 VERSION

Version 0.15

=cut

our $VERSION = '0.15';

=head1 SYNOPSIS

This module provides disk storage for normalised data and lookups.

It is one of newer components of WebPAC, so it will change from time to
time.

I will try to keep backward compatiblity by providing multiple back-ends,
but this can't be garanteed. In other words, don't delete your input
databases just yet :-)

This has additional advantage. I can create single place to plugin other
file formats which provide better performance for particular type of data.

=head1 FUNCTIONS

=head2 new

Create new normalised database object

  my $store = new WebPAC::Store({
	database => 'name',
  });

Optional parameter C<path> defines path to directory
in which cache file for C<data_structure> call will be created.

Optional parametar C<database> will be used used as subdirectory in path if no
database in specified when calling other functions.

=cut

=head2 load_ds

Retrive from disk one data_structure records usually using field 000 as key

  my $ds = $store->load_ds(
		database => 'ps',
		input => 'name',
		id => 42,
  );

This function will also perform basic sanity checking on returned
data and disable caching if data is corrupted (or changed since last
update).

C<input> is used to differenciate different source input databases
which are indexed in same database.

C<database> if B<optional> argument which will override database name used when creating
C<WebPAC::Store> object (for simple retrival from multiple databases).

Returns hash or undef if cacheing is disabled or unavailable.

=cut

sub load_ds {
	my $self = shift;

	my $log = $self->_get_logger;

	$log->logconfess("arguments for load_ds must be HASH") unless (ref(\@_) eq 'ARRAY' && ($#_ % 2 == 1));

	my $args = {@_};
	my $id = $args->{id};

	$log->logconfess("got hash, but without id") unless (defined($id));
	$log->logconfess("got hash, but id [$id] isn't number") unless ($id =~ /^\d+$/);

	my $database = $args->{database} || $self->database || $log->logconfess("no database?");

	my $input = $args->{input} || '';

	my $cache_file = $self->var_path( 'ds', $database, $input, $id );

	$log->debug("using cache_file $cache_file");

	if (-r $cache_file) {
		my $ds_ref = retrieve($cache_file);
		if ($ds_ref) {
			$log->debug("cache hit: $cache_file");
			if ($ds_ref->{'ds'}) {
				return $ds_ref->{'ds'};
			} else {
				$log->warn("cache entry $cache_file corrupt. Use rm $cache_file to re-create it on next run!");
			}
		}
	} else {
		#$log->warn("cache entry $cache_file doesn't exist");
		return undef;
	}

	return undef;
}

=head2 save_ds

Store data_structure on disk.

  $store->save_ds(
  	database => 'name',
	input => 'name',
  	id => $ds->{000}->[0],
	ds => $ds,
  );

C<database> and C<input> are optional.

=cut

sub save_ds {
	my $self = shift;

	my $args = {@_};

	my $log = $self->_get_logger;
	$log->debug("save_ds arguments:", sub { dump( \@_ ) });

	foreach my $f (qw/id ds/) {
		$log->logconfess("need $f") unless (defined($args->{$f}));
	}

	my $database = $args->{database} || $self->{database};
	$log->logconfess("can't find database name") unless (defined($database));

	my $input = $args->{input} || '';

	my $cache_file = $self->var_path( 'ds', $database, $input );

	mkpath($cache_file) unless (-d $cache_file);

	$cache_file = $self->var_path( 'ds', $database, $input, $args->{id} );

	$log->debug("creating storable cache file $cache_file");

	return store {
		ds => $args->{ds},
		id => $args->{id},
	}, $cache_file;

}

=head2 load_lookup

Loads lookup hash from file

  $data = $store->load_lookup(
  	database => $database,
	input => $input,
	key => $key,
  );

C<database> is optional.

=cut

sub load_lookup {
	my $self = shift;
	my $args = {@_};

	my $log = $self->_get_logger;

	foreach my $r (qw/input key/) {
		$log->logconfess("need '$r'") unless defined($args->{$r});
	}

	my $database = $args->{database} || $self->{database} || $log->logconfess("no database?");

	my $path = $self->var_path( 'lookup', $database, $args->{input}, $args->{key} );

	if (! -e $path) {
		$log->error("lookup $path doesn't exist, lookups will be disabled. Try re-indexing $database/", $args->{input});
		return;
	}

	if (my $data = retrieve($path)) {
		$log->info("loaded lookup $path ", -s $path, " bytes");
		return $data;
	} else {
		$log->logwarn("can't load lookup $database/", $args->{input}, "/", $args->{key}, " from $path: $!");
		return undef;
	}
}

=head2 save_lookup

Save lookup data to file.

  $store->save_lookup(
  	database => $database,
	input => $input,
	key => $key,
	data => $lookup,
  );

C<database> is optional.

=cut

sub save_lookup {
	my $self = shift;
	my $args = {@_};

	my $log = $self->_get_logger;

	foreach my $r (qw/input key data/) {
		$log->logconfess("need '$r'") unless defined($args->{$r});
	}

	my $database = $args->{database} || $self->{database} || $log->logconfess("no database?");

	my $path = $self->var_path( 'lookup', $database, $args->{input} );

	mkpath($path) unless (-d $path);

	$path .= "/" . $args->{key};

	my $t = time();

	if (store $args->{data}, $path) {
		$log->info(sprintf("saved lookup %s %d bytes in %.2fs", $path, -s $path, time() - $t));
		return 1;
	} else {
		$log->logwarn("can't save lookup to $path: $!");
		return undef;
	}
}

=head2 load_row

Loads row from input database cache (used for lookups)

  $row = $store->load_row(
  	database => $database,
	input => $input,
	id => 42,
  );

C<database> is optional.

=cut

sub load_row {
	my $self = shift;
	my $args = {@_};

	my $log = $self->_get_logger;

	foreach my $r (qw/input id/) {
		$log->logconfess("need '$r'") unless defined($args->{$r});
	}

	my $database = $args->{database} || $self->{database} || $log->logconfess("no database?");

	my $path = $self->var_path( 'row', $database, $args->{input}, $args->{id} );

	if (! -e $path) {
		$log->warn("input row $path doesn't exist, skipping");
		return;
	}

	if (my $data = retrieve($path)) {
		$log->debug("loaded row $path");
		return $data;
	} else {
		$log->logwarn("can't load row from $path: $!");
		return undef;
	}
}

=head2 save_row

Save row data to file.

  $store->save_row(
  	database => $database,
	input => $input,
	id => $mfn,
	row => $lookup,
  );

C<database> is optional.

=cut

sub save_row {
	my $self = shift;
	my $args = {@_};

	my $log = $self->_get_logger;

	foreach my $r (qw/input id row/) {
		$log->logconfess("need '$r'") unless defined($args->{$r});
	}

	my $database = $args->{database} || $self->{database} || $log->logconfess("no database?");

	my $path = $self->var_path( 'row', $database, $args->{input} );

	mkpath($path) unless (-d $path);

	$path .= "/" . $args->{id};

	if (store $args->{row}, $path) {
		$log->debug("saved row $path");
		return 1;
	} else {
		$log->logwarn("can't save row to $path: $!");
		return undef;
	}
}


=head1 AUTHOR

Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>

=head1 COPYRIGHT & LICENSE

Copyright 2005-2006 Dobrica Pavlinusic, All Rights Reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

1; # End of WebPAC::Store
