--- trunk/lib/WebPAC/Store.pm 2005/12/05 17:46:57 209 +++ trunk/lib/WebPAC/Store.pm 2006/10/02 09:31:25 735 @@ -5,26 +5,31 @@ use base 'WebPAC::Common'; use Storable; +use File::Path; +use Data::Dump qw/dump/; =head1 NAME -WebPAC::Store - Store normalized data on disk +WebPAC::Store - Store WebPAC data on disk =head1 VERSION -Version 0.03 +Version 0.14 =cut -our $VERSION = '0.03'; +our $VERSION = '0.14'; =head1 SYNOPSIS -This module provides disk storage for normalised data. +This module provides disk storage for normalised data and lookups. -It is newest component of WebPAC, so it will change quite often or be in -flux. However, I will try to keep backward compatiblity by providing -multiple back-ends. +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. @@ -33,7 +38,7 @@ use WebPAC::Store; - my $foo = WebPAC::Store->new(); + my $store = WebPAC::Store->new(); ... =head1 FUNCTIONS @@ -42,8 +47,9 @@ Create new normalised database object - my $db = new WebPAC::Store( + my $store = new WebPAC::Store( path => '/path/to/cache/ds/', + database => 'name', read_only => 1, ); @@ -53,12 +59,21 @@ If called with C it will not disable caching if called without write permission (but will die on C). +Optional parametar C will be used used as subdirectory in path if no +database in specified when calling other functions. + =cut sub new { my $class = shift; - my $self = {@_}; - bless($self, $class); + my $self = {@_}; + bless($self, $class); + + my $log = $self->_get_logger(); + + foreach my $p (qw/path/) { + $log->logconfess("need $p") unless ($self->{$p}); + } $self->path( $self->{'path'} ); @@ -69,12 +84,14 @@ Check if specified cache directory exist, and if not, disable caching. - $db->path('./cache/ds/'); + $store->path('./cache/'); If you pass false or zero value to this function, it will disable cacheing. -You can also example C<< $db->{path} >> to get current cache path. +You can also call this function to get current cache path. + + my $cache_path = $store->path; =cut @@ -82,13 +99,20 @@ my $self = shift; my $dir = shift; + + return $self->{path} unless defined($dir); my $log = $self->_get_logger(); if ($dir) { my $msg; if (! -e $dir) { - $msg = "doesn't exist"; + if ($self->{'read_only'}) { + $msg = "doesn't exist"; + } else { + $log->info("creating $dir"); + mkpath $dir; + } } elsif (! -d $dir) { $msg = "is not directory"; } elsif (! -w $dir) { @@ -110,18 +134,24 @@ =head2 load_ds -Retrive from disk one data_structure records using field 000 as key +Retrive from disk one data_structure records usually using field 000 as key - my $ds = $db->load_ds( 42 ); - -There is also a more verbose form, similar to C - - my $ds = $db->load_ds( id => 42 ); + 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 is used to differenciate different source input databases +which are indexed in same database. + +C if B argument which will override database name used when creating +C object (for simple retrival from multiple databases). + Returns hash or undef if cacheing is disabled or unavailable. =cut @@ -129,47 +159,46 @@ sub load_ds { my $self = shift; - return unless $self->{'path'}; - my $log = $self->_get_logger; my $cache_path = $self->{'path'}; - my $id = shift; - if (lc($id) eq 'id') { - $id = shift; - $log->logconfess("got hash, but without key id") unless (defined($id)); - $log->logconfess("got hash, but id isn't number") unless ($id =~ /^\d+$/); + if (! $cache_path) { + $log->warn("path not set, ignoring load_ds"); + return; } - if (! defined($id)) { - $log->warn("called without id"); - return undef; - } else { - my $cache_file = "$cache_path/$id"; - if (-r $cache_file) { - my $ds_ref = retrieve($cache_file); - if ($ds_ref) { - $log->debug("cache hit: $cache_file"); - my $ok = 1; -# foreach my $f (qw(current_filename headline)) { -# if ($ds_ref->{$f}) { -# $self->{$f} = $ds_ref->{$f}; -# } else { -# $ok = 0; -# } -# }; - if ($ok && $ds_ref->{'ds'}) { - return $ds_ref->{'ds'}; - } else { - $log->warn("cache entry $cache_file corrupt. Use rm $cache_path/* to re-create it on next run!"); - undef $self->{'path'}; - } + $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 = "$cache_path/ds/$database/$input/$id"; + $cache_file =~ s#//#/#go; + + $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_path/* to re-create it on next run!"); + undef $self->{'path'}; } - } else { - #$log->warn("cache entry $cache_file doesn't exist"); - return undef; } + } else { + #$log->warn("cache entry $cache_file doesn't exist"); + return undef; } return undef; @@ -179,14 +208,14 @@ Store data_structure on disk. - $db->save_ds( + $store->save_ds( + database => 'name', + input => 'name', id => $ds->{000}->[0], ds => $ds, ); -B - -Depends on filename generated by C. +C and C are optional. =cut @@ -197,32 +226,212 @@ return unless($self->{'path'}); - my $arg = {@_}; + my $args = {@_}; my $log = $self->_get_logger; + $log->debug("save_ds arguments:", dump( \@_ )); foreach my $f (qw/id ds/) { - $log->logconfess("need $f") unless ($arg->{$f}); + $log->logconfess("need $f") unless (defined($args->{$f})); } - my $cache_file = $self->{path} . '/' . $arg->{id}; + my $database = $args->{database} || $self->{database}; + $log->logconfess("can't find database name") unless (defined($database)); + + my $input = $args->{input} || ''; + + my $cache_file = $self->{path} . "/ds/$database/$input/"; + $cache_file =~ s#//#/#go; + + mkpath($cache_file) unless (-d $cache_file); + + $cache_file .= $args->{id}; $log->debug("creating storable cache file $cache_file"); return store { - ds => $arg->{ds}, - id => $arg->{id}, + 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 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->{path} . "/lookup/$database/" . $args->{input} . '/' . $args->{key}; + + if (! -e $path) { + $log->warn("lookup $path doesn't exist, skipping"); + return; + } + + if (my $data = retrieve($path)) { + $log->info("loaded lookup $path"); + 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 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->{path} . "/lookup/$database/" . $args->{input}; + + mkpath($path) unless (-d $path); + + $path .= "/" . $args->{key}; + + if (store $args->{data}, $path) { + $log->info("saved lookup $path"); + 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 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->{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 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->{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<< >> =head1 COPYRIGHT & LICENSE -Copyright 2005 Dobrica Pavlinusic, All Rights Reserved. +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.