File Coverage

blib/lib/Net/DAV/LockManager/DB.pm
Criterion Covered Total %
statement 64 67 95.5
branch 15 16 93.7
condition n/a
subroutine 13 13 100.0
pod 0 7 0.0
total 92 103 89.3


line stmt bran cond sub pod time code
1             package Net::DAV::LockManager::DB;
2              
3 2     2   63349 use strict;
  2         14  
  2         50  
4              
5 2     2   2319 use DBI;
  2         25587  
  2         92  
6 2     2   439 use File::Temp qw(tmpnam);
  2         14180  
  2         89  
7 2     2   299 use Net::DAV::Lock;
  2         5  
  2         1231  
8              
9             our $VERSION = '1.304';
10             $VERSION = eval $VERSION;
11              
12             #
13             # This provides a listing of all database schema required to initialize
14             # a database from an empty state. Note that this is an array, as each
15             # schema definition must be executed separately due to limitations in
16             # the SQLite database driver.
17             #
18             my @schema = (
19             qq/
20             create table lock (
21             uuid CHAR(36) PRIMARY KEY,
22             expiry INTEGER,
23             creator CHAR(128),
24             owner CHAR(128),
25             depth CHAR(32),
26             scope CHAR(32),
27             path CHAR(512)
28             )
29             /
30             );
31              
32             #
33             # Create a new lock manager database context. Optionally accepts a
34             # parameter representing a DBI-formatted Data Source Name (DSN). If no
35             # DSN is provided, then a temporary SQLite database is used by default.
36             #
37             sub new {
38 5     5 0 510 my $class = shift;
39 5 100       15 my $dsn = $_[0]? $_[0]: undef;
40 5         10 my $tmp = undef;
41              
42 5 100       15 unless ($dsn) {
43 3         12 $tmp = File::Temp::tmpnam('/tmp', '.webdav-locks');
44 3         689 $dsn = 'dbi:SQLite:dbname='. $tmp;
45             }
46              
47 5         28 my $self = bless {
48             'db' => DBI->connect($dsn, '', '')
49             }, $class;
50              
51             #
52             # In the event no data source name was passed, take note of this fact in
53             # the new object instance so that a proper cleanup can happen at destruction
54             # time.
55             #
56 5 100       17223 if ($tmp) {
57 3         11 $self->{'tmp'} = $tmp;
58             }
59              
60             #
61             # Perform any database initializations that may be required prior to
62             # returning the newly-constructed object.
63             #
64 5         17 $self->_initialize();
65              
66 5         33 return $self;
67             }
68              
69             #
70             # Called from the constructor to initialize state (including database
71             # file and schema) prior to returning a newly-instantiated object.
72             #
73             sub _initialize {
74 6     6   283 my ($self) = @_;
75              
76             #
77             # Enable transactions for the duration of this method. Enable
78             # error reporting.
79             #
80 6         28 $self->{'db'}->{'AutoCommit'} = 0;
81 6         26 $self->{'db'}->{'RaiseError'} = 1;
82              
83             #
84             # Only perform initialization if the table definition is missing.
85             # We can use the internal SQLite table SQLITE_MASTER to verify
86             # the presence of our lock table.
87             #
88             # If the schema has already been applied to the current database,
89             # then we can safely return.
90             #
91 6 100       33 if ($self->{'db'}->selectrow_hashref(q/select name from sqlite_master where name = 'lock'/)) {
92             #
93             # Disable transactions and raised errors to revert to default
94             # state.
95             #
96 2         570 $self->{'db'}->{'AutoCommit'} = 1;
97 2         12 $self->{'db'}->{'RaiseError'} = 0;
98 2         7 return;
99             }
100              
101             #
102             # The schema has not been applied. Instantiate it.
103             #
104 4         1817 eval {
105 4         10 foreach my $definition (@schema) {
106 4         22 $self->{'db'}->do($definition);
107             }
108              
109 4         59176 $self->{'db'}->commit();
110             };
111              
112             #
113             # Gracefully recover from any errors in instantiating the schema,
114             # in this case by throwing another error describing the situation.
115             #
116 4 50       34 if ($@) {
117 0         0 warn("Unable to initialize database schema: $@");
118              
119 0         0 eval {
120 0         0 $self->{'db'}->rollback();
121             };
122             }
123              
124             #
125             # Disable transactions and raised errors to revert to default
126             # state.
127             #
128 4         52 $self->{'db'}->{'AutoCommit'} = 1;
129 4         33 $self->{'db'}->{'RaiseError'} = 0;
130             }
131              
132             #
133             # Intended to be dispatched by the caller whenever the database is no
134             # longer required. This method will remove any temporary, one-time
135             # use databases which may have been created at object instantiation
136             # time.
137             #
138             sub close {
139 8     8 0 1023 my ($self) = @_;
140              
141 8         316 $self->{'db'}->disconnect();
142              
143             #
144             # If the name of a temporary database was stored in this object,
145             # be sure to unlink() said file.
146             #
147 8 100       87 if ($self->{'tmp'}) {
148 5         313 unlink($self->{'tmp'});
149             }
150             }
151              
152             #
153             # Garbage collection hook to perform tidy cleanup prior to deallocation.
154             #
155             sub DESTROY {
156 5     5   543 my ($self) = @_;
157              
158 5         15 $self->close();
159             }
160              
161             #
162             # Given a normalized string representation of a resource path, return
163             # the first lock found. Return undef if no object was found in the
164             # database.
165             #
166             sub get {
167 3     3 0 455 my ($self, $path) = @_;
168              
169 3         17 my $row = $self->{'db'}->selectrow_hashref(q/select * from lock where path = ?/, {}, $path);
170              
171 3 100       473 return $row? Net::DAV::Lock->reanimate($row): undef;
172             }
173              
174             #
175             # Given a path string, return any lock objects whose paths are descendants
176             # of the specified path, excluding the current path.
177             #
178             sub list_descendants {
179 3     3 0 485 my ($self, $path) = @_;
180              
181 3 100       13 if ($path eq '/') {
182             return map {
183 4         235 Net::DAV::Lock->reanimate($_)
184 1         2 } @{$self->{'db'}->selectall_arrayref(q(select * from lock where path != '/'), { 'Slice' => {} })};
  1         10  
185             }
186              
187 2         6 my $sql = q/select * from lock where path like ?/;
188              
189             return map {
190 4         563 Net::DAV::Lock->reanimate($_)
191 2         4 } @{$self->{'db'}->selectall_arrayref($sql, { 'Slice' => {} }, "$path/%")};
  2         35  
192             }
193              
194             #
195             # Given an instance of Net::DAV::Lock, update any entries in the
196             # database whose path corresponds to the value provided in the
197             # object.
198             #
199             sub update {
200 1     1 0 3 my ($self, $lock) = @_;
201              
202 1         3 $self->{'db'}->do(q/update lock set expiry = ? where path = ?/, {},
203             $lock->expiry,
204             $lock->path
205             );
206              
207 1         11858 return $lock;
208             }
209              
210             #
211             # Insert the data passed in an instance of Net::DAV::Lock into the
212             # database, and return that reference.
213             #
214             sub add {
215 6     6 0 13 my ($self, $lock) = @_;
216              
217 6         10 my $sql = qq{
218             insert into lock (
219             uuid, expiry, creator, owner, depth, scope, path
220             ) values (
221             ?, ?, ?, ?, ?, ?, ?
222             )
223             };
224              
225 6         16 $self->{'db'}->do($sql, {},
226             $lock->uuid,
227             $lock->expiry,
228             $lock->creator,
229             $lock->owner,
230             $lock->depth,
231             $lock->scope,
232             $lock->path
233             );
234              
235 6         74522 return $lock;
236             }
237              
238             #
239             # Given a Net::DAV::Lock object, the database record which contains the
240             # corresponding path.
241             #
242             sub remove {
243 1     1 0 4 my ($self, $lock) = @_;
244              
245 1         5 $self->{'db'}->do(q/delete from lock where path = ?/, {}, $lock->path);
246             }
247              
248             1;
249              
250             __END__