397ae8cd8437cd51df51cc68825cfa5a5a869c64
[onis.git] / lib / Onis / Data / Persistent / Dbm.pm
1 package Onis::Data::Persistent::Dbm;
2
3 use strict;
4 use warnings;
5
6 BEGIN
7 {
8         @AnyDBM_File::ISA = (qw(GDBM_File DB_File GDBM_File SDBM_File NDBM_File ODBM_File));
9 }
10
11 use Carp qw(carp confess);
12 use Fcntl (qw(O_RDWR O_CREAT));
13 use AnyDBM_File;
14
15 use Onis::Config (qw(get_config));
16
17 =head1 NAME
18
19 Onis::Data::Persistent::Dbm - Storage backend using AnyDBM_File.
20
21 =head1 DESCRIPTION
22
23 Storage backend that uses DBM files for storing data permanently.
24
25 =head1 CONFIGURATION OPTIONS
26
27 =over 4
28
29 =item B<dbm_directory>: I<E<lt>dirE<gt>>
30
31 Directory in which the DBM-files are kept.
32
33 =back
34
35 =cut
36
37 our $DBMDirectory = get_config ('dbm_directory') || 'var';
38 $DBMDirectory =~ s#/$##g;
39
40 if (!$DBMDirectory or !-d $DBMDirectory)
41 {
42         print STDERR <<ERROR;
43 The directory ``$DBMDirectory'' does not exist or is not useable. Please
44 create it before running onis.
45 ERROR
46         exit (1);
47 }
48
49 our $Alarm = chr (7);
50 our %Objects = ();
51
52 if ($::DEBUG & 0x0200)
53 {
54         require Data::Dumper;
55 }
56
57 return (1);
58
59 sub new
60 {
61         my $pkg    = shift;
62         my $name   = shift;
63         my $key    = shift;
64         my @fields = @_;
65         my $caller = caller ();
66         my $obj    = {};
67         my %hash;
68         my $i = 0;
69         my $filename;
70         
71         my $id = $caller . ':' . $name;
72         $id =~ s#/##g;
73
74         $filename = "$DBMDirectory/$id.dbm";
75         
76         if (exists ($Objects{$id}))
77         {
78                 print STDERR $/, __FILE__, ": Name $name has been used in context $caller before.";
79                 return (undef);
80         }
81
82         no strict (qw(subs));
83         tie (%hash, 'AnyDBM_File', $filename, O_RDWR | O_CREAT, 0666) or die ("tie: $!");
84
85         $obj->{'data'} = tied %hash;
86         $obj->{'key'} = $key;
87         $obj->{'fields'} = [@fields];
88         $obj->{'num_fields'} = scalar (@fields);
89         $obj->{'field_index'} = {map { $_ => $i++ } (@fields)};
90         $obj->{'id'} = $id;
91         $obj->{'cache'} = {};
92
93         if ($::DEBUG & 0x0200)
94         {
95                 my $prefix = __FILE__ . ': ';
96                 my $dbg = Data::Dumper->Dump ([$obj], ['obj']);
97                 $dbg =~ s/^/$prefix/mg; chomp ($dbg);
98                 print STDOUT $/, $dbg;
99         }
100         
101         $Objects{$id} = bless ($obj, $pkg);
102         return ($Objects{$id});
103 }
104
105 sub put
106 {
107         my $obj    = shift;
108         my $key    = shift;
109         my @fields = @_;
110
111         if ($obj->{'num_fields'} != scalar (@fields))
112         {
113                 my $id = $obj->{'id'};
114                 carp ("Number of fields do not match ($id).");
115                 return;
116         }
117
118         if ($::DEBUG & 0x0200)
119         {
120                 print STDOUT $/, __FILE__, ': PUT(', $obj->{'id'}, ', ', $key, ') = (' . join (', ', @fields) . ')';
121         }
122
123         $obj->{'cache'}{$key} = [@fields];
124 }
125
126 sub get
127 {
128         my $obj = shift;
129         my $key = shift;
130         my $val;
131         my @ret;
132         my $db = $obj->{'data'};
133
134         if (!exists ($obj->{'cache'}{$key}))
135         {
136                 $val = $db->FETCH ($key);
137                 if (!defined ($val))
138                 {
139                         $obj->{'cache'}{$key} = undef;
140                 }
141                 else
142                 {
143                         $obj->{'cache'}{$key} = [split ($Alarm, $val)];
144                 }
145         }
146
147         if (!defined ($obj->{'cache'}{$key}))
148         {
149                 return (qw());
150         }
151         else
152         {
153                 @ret = @{$obj->{'cache'}{$key}};
154         }
155
156         if ($::DEBUG & 0x0200)
157         {
158                 print STDOUT $/, __FILE__, ': GET(', $obj->{'id'}, ', ', $key, ') = (' . join (', ', @ret) . ')';
159         }
160
161         return (@ret);
162 }
163
164 sub keys
165 {
166         my $obj = shift;
167         my @fields = @_;
168         my @field_indizes = ();
169         my $db = $obj->{'data'};
170         my $key;
171         my $val;
172
173         no strict (qw(subs));
174         for (($key, $val) = $db->FIRSTKEY (); ($key, $val) = $db->NEXTKEY ($key);)
175         {
176                 die unless (defined ($key));
177                 next if (defined ($obj->{'cache'}{$key}));
178
179                 $obj->{'cache'}{$key} = [split ($Alarm, $val)];
180         }
181
182         if (!@fields)
183         {
184                 return (keys %{$obj->{'cache'}});
185         }
186
187         for (@fields)
188         {
189                 my $field = $_;
190                 if (!defined ($obj->{'field_index'}{$field}))
191                 {
192                         my $id = $obj->{'id'};
193                         print STDERR $/, __FILE__, ": $field is not a valid field ($id).";
194                         next;
195                 }
196                 push (@field_indizes, $obj->{'field_index'}{$field});
197         }
198
199         return (sort
200         {
201                 for (@field_indizes)
202                 {
203                         my $d = $obj->{'cache'}{$a}[$_] cmp $obj->{'cache'}{$b}[$_];
204                         return ($d) if ($d);
205                 }
206         } (keys %{$obj->{'cache'}}));
207 }
208
209 sub del
210 {
211         my $obj = shift;
212         my $key = shift;
213         my $db = $obj->{'data'};
214
215         if (exists ($obj->{'cache'}{$key}))
216         {
217                 if (defined ($obj->{'cache'}{$key}))
218                 {
219                         $db->DELETE ($key);
220                         $obj->{'cache'}{$key} = undef;
221                 }
222                 # It's known that the key doesn't exist..
223         }
224         else
225         {
226                 $db->DELETE ($key);
227                 $obj->{'cache'}{$key} = undef;
228         }
229 }
230
231 sub sync
232 {
233         my $obj = shift;
234         my $db = $obj->{'data'};
235
236         for (CORE::keys %{$obj->{'cache'}})
237         {
238                 my $key = $_;
239                 next unless (defined ($obj->{'cache'}{$key}));
240
241                 my $val = join ($Alarm, @{$obj->{'cache'}{$key}});
242
243                 $db->STORE ($key, $val);
244                 delete ($obj->{'cache'}{$key});
245         }
246
247         $db->sync ();
248 }
249
250 END
251 {
252         for (CORE::keys (%Objects))
253         {
254                 my $key = $_;
255                 my $obj = $Objects{$key};
256                 $obj->sync ();
257         }
258 }
259
260 =head1 AUTHOR
261
262 Florian octo Forster, L<octo at verplant.org>
263
264 =cut