File Coverage

blib/lib/KSx/IndexManager.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1 5     5   198757 use strict;
  5         13  
  5         171  
2 5     5   27 use warnings;
  5         9  
  5         201  
3              
4             package KSx::IndexManager;
5              
6 5     5   99 use 5.00.004; # KinoSearch requires this
  5         31  
  5         338  
7             our $VERSION = '0.004';
8 5     5   35 use base qw(Class::Accessor::Grouped);
  5         9  
  5         5863  
9              
10             __PACKAGE__->mk_group_accessors(simple => qw(root schema context _lock_fh));
11             __PACKAGE__->mk_group_accessors(inherited => qw(_plugins));
12             __PACKAGE__->mk_group_accessors(
13             component_class => qw(invindexer_class searcher_class schema_class),
14             );
15             __PACKAGE__->invindexer_class('KinoSearch::InvIndexer');
16             __PACKAGE__->searcher_class('KinoSearch::Searcher');
17              
18 5     5   141150 use KinoSearch::Searcher;
  5         360876  
  5         247  
19 5     5   6498 use KinoSearch::InvIndexer;
  0            
  0            
20             use KinoSearch::Schema;
21              
22             use Data::OptList;
23             use Carp ();
24             use Scalar::Util ();
25             use Fcntl qw(:DEFAULT :flock);
26              
27             sub plugins { @{ $_[0]->_plugins || $_[0]->_plugins([]) } }
28              
29             sub add_plugins {
30             my $class = shift;
31             if (Scalar::Util::blessed $class) {
32             Carp::croak "add_plugins is a class method, do not call it on $class";
33             }
34             my @plugins = $class->plugins;
35             for my $opt (@{ Data::OptList::mkopt([@_]) }) {
36             my ($plugin, $arg) = @$opt;
37             $plugin = "KSx::IndexManager::Plugin::$plugin"
38             unless $plugin =~ s/^\+//;
39             eval "require $plugin; 1" or die $@;
40             push @plugins, $plugin->new($arg);
41             }
42             $class->_plugins(\@plugins);
43             }
44              
45             sub call_plugins {
46             my ($self, $event, $arg, @rest) = @_;
47             for my $plugin ($self->plugins) {
48             #use Data::Dumper; warn Dumper($plugin);
49             $plugin->$event($arg, @rest);
50             }
51             }
52              
53             sub call_self_plugins {
54             my ($self, $event, $arg, @rest) = @_;
55             $arg ||= {};
56             for my $plugin ($self->plugins) {
57             $plugin->$event($self, $arg, @rest);
58             }
59             }
60              
61             sub new {
62             my ($class, $arg) = @_;
63             $arg ||= {};
64             $class->call_plugins(before_new => $arg);
65             unless ($arg->{schema} ||= $class->schema_class) {
66             Carp::croak "schema is mandatory for $class->new";
67             }
68             my $self = bless $arg => $class;
69             $self->call_self_plugins('after_new');
70             return $self;
71             }
72              
73             sub path {
74             my $self = shift;
75             my $path = $self->root;
76             $self->call_self_plugins(alter_path => \$path);
77             return $path;
78             }
79              
80             sub open { shift->invindexer({ mode => 'open' }) }
81             sub clobber { shift->invindexer({ mode => 'clobber' }) }
82              
83             sub invindexer {
84             my ($self, $opt) = @_;
85             Carp::croak "'mode' argument is mandatory for $self->invindexer"
86             unless $opt->{mode};
87             my $meth = $opt->{mode};
88             return $self->invindexer_class->new(
89             invindex => $self->schema->$meth($self->path),
90             );
91             }
92              
93             sub _load {
94             my ($self, $i, $opt) = @_;
95             $i->{$self->path} ||= $self->invindexer($opt);
96             }
97              
98             sub to_doc {
99             my ($self, $obj) = @_;
100             return $obj;
101             }
102              
103             sub _add_one_doc {
104             my ($self, $i, $obj, $opt) = @_;
105             my $inv = $self->_load($i, $opt);
106             $self->call_self_plugins(before_add_doc => $obj);
107             for my $doc ($self->to_doc($obj)) {
108             $inv->add_doc($doc);
109             }
110             # wish we could call it with the new document or something
111             $self->call_self_plugins(after_add_doc => $obj);
112             }
113              
114             sub add_docs {
115             my ($self, $opt, $docs) = @_;
116             my $created = 0;
117             my $i = {}; # invindex cache by path
118             if (ref $docs eq 'ARRAY') {
119             $created = @$docs;
120             for my $obj (@$docs) {
121             $self->_add_one_doc($i, $obj, $opt);
122             }
123             } elsif (eval { $docs->can('next') }) {
124             while (my $obj = $docs->next) {
125             $self->_add_one_doc($i, $obj, $opt);
126             $created++;
127             }
128             } else {
129             die "unhandled argument: $docs";
130             }
131             return 0 if $opt->{mode} eq 'open' and not $created;
132             $_->finish(
133             optimize => $opt->{optimize} || 0,
134             ) for values %$i;
135             return $created;
136             }
137              
138             sub append { shift->add_docs({ mode => 'open' }, @_) }
139             sub write { shift->add_docs({ mode => 'clobber' }, @_) }
140              
141             sub lockfile { File::Spec->catfile(shift->path, 'mgr.lock') }
142              
143             sub lock {
144             my $self = shift;
145             my $file = $self->lockfile;
146             my $fh;
147             File::Path::mkpath($self->path);
148             unless (sysopen($fh, $file, O_RDWR|O_CREAT|O_EXCL)) {
149             my $err = $!;
150             if (-e $file) {
151             sysopen($fh, $file, O_RDWR) or die "can't sysopen $file: $!";
152             } else {
153             die "can't sysopen $file: $err";
154             }
155             }
156             flock($fh, LOCK_EX|LOCK_NB) or die "$file is already locked";
157             $self->_lock_fh($fh);
158             }
159              
160             sub unlock {
161             my $self = shift;
162             my $file = $self->lockfile;
163             die "$file is not locked" unless $self->_lock_fh;
164             flock($self->_lock_fh, LOCK_UN) or die "can't unlock $file: $!";
165             }
166              
167             sub search {
168             my $self = shift;
169             return $self->searcher->search(@_);
170             }
171              
172             sub searcher {
173             my $self = shift;
174             return $self->searcher_class->new(
175             invindex => $self->schema->read($self->path),
176             );
177             }
178              
179             1;
180              
181             __END__