File: Store.pm

package info (click to toggle)
libbot-basicbot-pluggable-perl 0.98-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 564 kB
  • ctags: 281
  • sloc: perl: 3,112; makefile: 16
file content (267 lines) | stat: -rw-r--r-- 6,581 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
package Bot::BasicBot::Pluggable::Store;
{
  $Bot::BasicBot::Pluggable::Store::VERSION = '0.98';
}
use strict;
use warnings;
use Carp qw( croak );
use Data::Dumper;
use Storable qw( nfreeze thaw );
use Try::Tiny;
use Module::Load qw();
use Log::Log4perl;

use base qw( );

sub new {
    my $class = shift;
    my $self;
    my $logger = Log::Log4perl->get_logger($class);
    if ( @_ % 2 == 0 ) {
        $self = bless {@_} => $class;
    }
    elsif ( @_ == 1 and ref $_[0] eq 'HASH' ) {
        $self = $class->new_from_hashref( $_[0] );
    }
    elsif ( @_ == 1 and !ref $_[0] ) {
        $self = $class->new_from_hashref( { type => $_[0] } );
    }
    elsif ( !@_ ) {
        $self = bless {} => $class;
    }
    else {
        $logger->warn(
"Argument to new() is neither an argument list, a hashref, a string nor empty"
        );
    }
    $self->init();
    $self->load();
    return $self;
}

sub new_from_hashref {
    my ( $class, $args ) = @_;
    my $logger = Log::Log4perl->get_logger($class);

    if ( ref($args) ne 'HASH' ) {
        $logger->warn('Argument to store_from_hashref must be a hashref');
    }

    my $store_class = delete $args->{type} || 'Memory';

    $store_class = "Bot::BasicBot::Pluggable::Store::$store_class"
      unless $store_class =~ /::/;

    # load the store class
    try { Module::Load::load $store_class; }
    catch { $logger->warn("Couldn't load $store_class - $_"); };

    my $store = $store_class->new( %{$args} );

    croak "Couldn't init a $store_class store\n" unless $store;

    return $store;
}

sub init { undef }

sub load { undef }

sub save { }

sub keys {
    my ( $self, $namespace, %opts ) = @_;
    my $mod = $self->{store}{$namespace} || {};
    return $self->_keys_aux( $mod, $namespace, %opts );
}

sub count_keys {
    my ( $self, $namespace, %opts ) = @_;
    $opts{_count_only} = 1;
    $self->keys( $namespace, %opts );
}

sub _keys_aux {
    my ( $self, $mod, $namespace, %opts ) = @_;

    my @res = ( exists $opts{res} ) ? @{ $opts{res} } : ();

    return CORE::keys %$mod unless @res;

    my @return;
    my $count = 0;
  OUTER: while ( my ($key) = each %$mod ) {
        for my $re (@res) {

            # limit matches
            $re = "^" . lc($namespace) . "_.*${re}.*"
              if $re =~ m!^[^\^].*[^\$]$!;
            next OUTER unless $key =~ m!$re!;
        }
        push @return, $key if ( !$opts{_count_only} );
        last if $opts{limit} && ++$count >= $opts{limit};

    }

    return ( $opts{_count_only} ) ? $count : @return;
}

sub get {
    my ( $self, $namespace, $key ) = @_;
    return $self->{store}{$namespace}{$key};
}

sub set {
    my ( $self, $namespace, $key, $value ) = @_;
    $self->{store}{$namespace}{$key} = $value;
    $self->save($namespace);
    return $self;
}

sub unset {
    my ( $self, $namespace, $key ) = @_;
    delete $self->{store}{$namespace}{$key};
    $self->save($namespace);
    return $self;
}

sub namespaces {
    my $self = shift;
    return CORE::keys( %{ $self->{store} } );
}

sub dump {
    my $self = shift;
    my $data = {};
    for my $n ( $self->namespaces ) {
        warn "Dumping namespace '$n'.\n";
        for my $k ( $self->keys($n) ) {
            $data->{$n}{$k} = $self->get( $n, $k );
        }
    }
    return nfreeze($data);
}

sub restore {
    my ( $self, $dump ) = @_;
    my $data = thaw($dump);
    for my $n ( CORE::keys(%$data) ) {
        warn "Restoring namespace '$n'.\n";
        for my $k ( CORE::keys( %{ $data->{$n} } ) ) {
            $self->set( $n, $k, $data->{$n}{$k} );
        }
    }
    warn "Complete.\n";
}

1;
__END__

=head1 NAME

Bot::BasicBot::Pluggable::Store - base class for the back-end pluggable store

=head1 VERSION

version 0.98

=head1 SYNOPSIS

  my $store = Bot::BasicBot::Pluggable::Store->new( option => "value" );

  my $namespace = "MyModule";

  for ( $store->keys($namespace) ) {
    my $value = $store->get($namespace, $_);
    $store->set( $namespace, $_, "$value and your momma." );
  }

Store classes should subclass this and provide some persistent way of storing things.

=head1 METHODS

=over 4

=item new()

Standard C<new> method, blesses a hash into the right class and
puts any key/value pairs passed to it into the blessed hash. If
called with an hash argument as its first argument, new_from_hashref
will be run with the hash as its only argument. See L</new_from_hashref>
for the possible keys and values. You can also pass a string and
it will try to call new_from_hashref with a hash reference { type
=> $string }. Calls C<load()> to load any internal variables, then
C<init>, which you can also override in your module.

=item new_from_hashref( $hashref )

Intended to be called as class method to dynamically create a store
object. It expects a hash reference as its only argument. The only
required hash element is a string specified by I<type>. This should
be either a fully qualified classname or a colonless string that
is appended to I<Bot::BasicBot::Pluggable::Store>. All other arguments
are passed down to the real object constructor.

=item init()

Called as part of new class construction, before C<load()>.

=item load()

Called as part of new class construction, after C<init()>.

=item save()

Subclass me. But, only if you want to. See ...Store::Storable.pm as an example.

=item keys($namespace,[$regex])

Returns a list of all store keys for the passed C<$namespace>.

If you pass C<$regex> then it will only pass the keys matching C<$regex>

=item get($namespace, $variable)

Returns the stored value of the C<$variable> from C<$namespace>.

=item set($namespace, $variable, $value)

Sets stored value for C<$variable> to C<$value> in C<$namespace>. Returns store object.

=item unset($namespace, $variable)

Removes the C<$variable> from the store. Returns store object.

=item namespaces()

Returns a list of all namespaces in the store.

=item dump()

Dumps the complete store to a huge Storable scalar. This is mostly so
you can convert from one store to another easily, i.e.:

  my $from = Bot::BasicBot::Pluggable::Store::Storable->new();
  my $to   = Bot::BasicBot::Pluggable::Store::DBI->new( ... );
  $to->restore( $from->dump );

C<dump> is written generally so you don't have to re-implement it in subclasses.

=item restore($data)

Restores the store from a L<dump()>.

=back

=head1 AUTHOR

Mario Domgoergen <mdom@cpan.org>

This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Bot::BasicBot::Pluggable>

L<Bot::BasicBot::Pluggable::Module>