r1024@llin: dpavlin | 2006-09-26 16:00:56 +0200
[webpac2] / lib / WebPAC / Store.pm
1 package WebPAC::Store;
2
3 use warnings;
4 use strict;
5
6 use base 'WebPAC::Common';
7 use Storable;
8 use File::Path;
9 use Data::Dump qw/dump/;
10
11 =head1 NAME
12
13 WebPAC::Store - Store WebPAC data on disk
14
15 =head1 VERSION
16
17 Version 0.12
18
19 =cut
20
21 our $VERSION = '0.12';
22
23 =head1 SYNOPSIS
24
25 This module provides disk storage for normalised data and lookups.
26
27 It is one of newer components of WebPAC, so it will change from time to
28 time.
29
30 I will try to keep backward compatiblity by providing multiple back-ends,
31 but this can't be garanteed. In other words, don't delete your input
32 databases just yet :-)
33
34 This has additional advantage. I can create single place to plugin other
35 file formats which provide better performance for particular type of data.
36
37 For now, this is a prototype version.
38
39     use WebPAC::Store;
40
41     my $foo = WebPAC::Store->new();
42     ...
43
44 =head1 FUNCTIONS
45
46 =head2 new
47
48 Create new normalised database object
49
50   my $db = new WebPAC::Store(
51         path => '/path/to/cache/ds/',
52         database => 'name',
53         read_only => 1,
54   );
55
56 Optional parameter C<path> defines path to directory
57 in which cache file for C<data_structure> call will be created.
58
59 If called with C<read_only> it will not disable caching if
60 called without write permission (but will die on C<save_ds>).
61
62 Optional parametar C<database> will be used used as subdirectory in path if no
63 database in specified when calling other functions.
64
65 =cut
66
67 sub new {
68         my $class = shift;
69         my $self = {@_};
70         bless($self, $class);
71
72         my $log = $self->_get_logger();
73
74         foreach my $p (qw/path/) {
75                 $log->logconfess("need $p") unless ($self->{$p});
76         }
77
78         $self->path( $self->{'path'} );
79
80         $self ? return $self : return undef;
81 }
82
83 =head2 path
84
85 Check if specified cache directory exist, and if not, disable caching.
86
87  $db->path('./cache/ds/');
88
89 If you pass false or zero value to this function, it will disable
90 cacheing.
91
92 You can also call this function to get current cache path.
93
94  my $cache_path = $db->path;
95
96 =cut
97
98 sub path {
99         my $self = shift;
100
101         my $dir = shift;
102         
103         return $self->{path} unless defined($dir);
104
105         my $log = $self->_get_logger();
106
107         if ($dir) {
108                 my $msg;
109                 if (! -e $dir) {
110                         if ($self->{'read_only'}) {
111                                 $msg = "doesn't exist";
112                         } else {
113                                 $log->info("creating $dir");
114                                 mkpath $dir;
115                         }
116                 } elsif (! -d $dir) {
117                         $msg = "is not directory";
118                 } elsif (! -w $dir) {
119                         $msg = "not writable" unless ($self->{'read_only'});
120                 }
121
122                 if ($msg) {
123                         $log->warn("cache path $dir $msg, disabling...");
124                         undef $self->{'path'};
125                 } else {
126                         $log->debug("using cache dir $dir");
127                         $self->{'path'} = $dir;
128                 }
129         } else {
130                 $log->debug("disabling cache");
131                 undef $self->{'path'};
132         }
133 }
134
135 =head2 load_ds
136
137 Retrive from disk one data_structure records usually using field 000 as key
138
139   my $ds = $db->load_ds(
140                 database => 'ps',
141                 input => 'name',
142                 id => 42,
143   );
144
145 This function will also perform basic sanity checking on returned
146 data and disable caching if data is corrupted (or changed since last
147 update).
148
149 C<input> is used to differenciate different source input databases
150 which are indexed in same database.
151
152 C<database> if B<optional> argument which will override database name used when creating
153 C<WebPAC::Store> object (for simple retrival from multiple databases).
154
155 Returns hash or undef if cacheing is disabled or unavailable.
156
157 =cut
158
159 sub load_ds {
160         my $self = shift;
161
162         my $log = $self->_get_logger;
163
164         my $cache_path = $self->{'path'};
165
166         if (! $cache_path) {
167                 $log->warn("path not set, ignoring load_ds");
168                 return;
169         }
170
171         $log->logconfess("arguments for load_ds must be HASH") unless (ref(\@_) eq 'ARRAY' && ($#_ % 2 == 1));
172
173         my $args = {@_};
174         my $id = $args->{id};
175
176         $log->logconfess("got hash, but without id") unless (defined($id));
177         $log->logconfess("got hash, but id [$id] isn't number") unless ($id =~ /^\d+$/);
178
179         my $database = $args->{database} || $self->{database} || $log->logconfess("no database?");
180
181         my $input = $args->{input} || '';
182
183         my $cache_file = "$cache_path/$database/$input/$id";
184         $cache_file =~ s#//#/#go;
185
186         $log->debug("using cache_file $cache_file");
187
188         if (-r $cache_file) {
189                 my $ds_ref = retrieve($cache_file);
190                 if ($ds_ref) {
191                         $log->debug("cache hit: $cache_file");
192                         if ($ds_ref->{'ds'}) {
193                                 return $ds_ref->{'ds'};
194                         } else {
195                                 $log->warn("cache entry $cache_file corrupt. Use rm $cache_path/* to re-create it on next run!");
196                                 undef $self->{'path'};
197                         }
198                 }
199         } else {
200                 #$log->warn("cache entry $cache_file doesn't exist");
201                 return undef;
202         }
203
204         return undef;
205 }
206
207 =head2 save_ds
208
209 Store data_structure on disk.
210
211   $db->save_ds(
212         database => 'name',
213         input => 'name',
214         id => $ds->{000}->[0],
215         ds => $ds,
216   );
217
218 B<Totally broken, but fast.>
219
220 Depends on filename generated by C<load_ds>.
221
222 =cut
223
224 sub save_ds {
225         my $self = shift;
226
227         die "can't write to database in read_only mode!" if ($self->{'read_only'});
228
229         return unless($self->{'path'});
230
231         my $args = {@_};
232
233         my $log = $self->_get_logger;
234         $log->debug("save_ds arguments:", dump( \@_ ));
235
236         foreach my $f (qw/id ds/) {
237                 $log->logconfess("need $f") unless (defined($args->{$f}));
238         }
239
240         my $database = $args->{database} || $self->{database};
241         $log->logconfess("can't find database name") unless (defined($database));
242
243         my $input = $args->{input} || '';
244
245         my $cache_file = $self->{path} . "/$database/$input/";
246         $cache_file =~ s#//#/#go;
247
248         mkpath($cache_file) unless (-d $cache_file);
249
250         $cache_file .= $args->{id};
251
252         $log->debug("creating storable cache file $cache_file");
253
254         return store {
255                 ds => $args->{ds},
256                 id => $args->{id},
257         }, $cache_file;
258
259 }
260
261 =head2 load_lookup
262
263   $data = $db->load_lookup(
264         database => $database,
265         input => $input,
266         key => $key,
267   );
268
269 =cut
270
271 sub load_lookup {
272         my $self = shift;
273         my $args = {@_};
274
275         my $log = $self->_get_logger;
276
277         foreach my $r (qw/input key/) {
278                 $log->logconfess("need '$r'") unless defined($args->{$r});
279         }
280
281         my $database = $args->{database} || $self->{database} || $log->logconfess("no database?");
282
283         my $path = $self->{path} . "/lookup/$database/" . $args->{input} . '/' . $args->{key};
284
285         if (! -e $path) {
286                 $log->warn("lookup $path doesn't exist, skipping");
287                 return;
288         }
289
290         if (my $data = retrieve($path)) {
291                 $log->info("loaded lookup $path");
292                 return $data;
293         } else {
294                 $log->logwarn("can't load lookup $database/", $args->{input}, "/", $args->{key}, " from $path: $!");
295                 return undef;
296         }
297 }
298
299 =head2 save_lookup
300
301   $db->save_lookup(
302         database => $database,
303         input => $input,
304         key => $key,
305         data => $lookup,
306   );
307
308 =cut
309
310 sub save_lookup {
311         my $self = shift;
312         my $args = {@_};
313
314         my $log = $self->_get_logger;
315
316         foreach my $r (qw/input key data/) {
317                 $log->logconfess("need '$r'") unless defined($args->{$r});
318         }
319
320         my $database = $args->{database} || $self->{database} || $log->logconfess("no database?");
321
322         my $path = $self->{path} . "/lookup/$database/" . $args->{input};
323
324         mkpath($path) unless (-d $path);
325
326         $path .= "/" . $args->{key};
327
328         if (store $args->{data}, $path) {
329                 $log->info("saved lookup $path");
330                 return 1;
331         } else {
332                 $log->logwarn("can't save lookup $database/", $args->{input}, "/", $args->{key}, " in $path: $!");
333                 return undef;
334         }
335 }
336
337
338 =head1 AUTHOR
339
340 Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>
341
342 =head1 COPYRIGHT & LICENSE
343
344 Copyright 2005 Dobrica Pavlinusic, All Rights Reserved.
345
346 This program is free software; you can redistribute it and/or modify it
347 under the same terms as Perl itself.
348
349 =cut
350
351 1; # End of WebPAC::Store