File Coverage

blib/lib/App/MonM/Notifier/Store.pm
Criterion Covered Total %
statement 72 225 32.0
branch 0 68 0.0
condition 0 45 0.0
subroutine 24 39 61.5
pod 15 15 100.0
total 111 392 28.3


line stmt bran cond sub pod time code
1             package App::MonM::Notifier::Store; # $Id: Store.pm 60 2019-07-14 09:57:26Z abalama $
2 1     1   5 use strict;
  1         2  
  1         24  
3 1     1   4 use utf8;
  1         2  
  1         4  
4              
5             =encoding utf8
6              
7             =head1 NAME
8              
9             App::MonM::Notifier::Store - monotifier store class
10              
11             =head1 VERSION
12              
13             Version 1.01
14              
15             =head1 SYNOPSIS
16              
17             use App::MonM::Notifier::Store;
18              
19             my $store = new App::MonM::Notifier::Store(
20             dsn => "DBI:mysql:database=monotifier;host=mysql.example.com",
21             user => "username",
22             password => "password",
23             set => [
24             "RaiseError 0",
25             "PrintError 0",
26             "mysql_enable_utf8 1",
27             ],
28             );
29              
30             die($store->error) unless $store->status;
31              
32             =head1 DESCRIPTION
33              
34             This module provides store methods.
35              
36             CREATE TABLE IF NOT EXISTS `monotifier` (
37             `id` int(11) NOT NULL COMMENT 'ID',
38             `to` char(255) DEFAULT NULL COMMENT 'Recipient name',
39             `channel` char(255) DEFAULT NULL COMMENT 'Recipient channel',
40             `subject` text COMMENT 'Message subject',
41             `message` text COMMENT 'Message content',
42             `pubdate` int(11) DEFAULT NULL COMMENT 'Date (unixtime) of the publication',
43             `expires` int(11) DEFAULT NULL COMMENT 'Date (unixtime) of the expire',
44             `status` char(32) DEFAULT NULL COMMENT 'Status of transaction',
45             `comment` char(255) DEFAULT NULL COMMENT 'Comment',
46             `errcode` int(11) DEFAULT NULL COMMENT 'Error code',
47             `errmsg` text COMMENT 'Error message',
48             PRIMARY KEY (`id`),
49             KEY `I_ID` (`id`)
50             ) ENGINE=MyISAM DEFAULT CHARSET=utf8
51              
52             =head2 new
53              
54             my $store = new App::MonM::Notifier::Store(
55             dsn => "DBI:mysql:database=monotifier;host=mysql.example.com",
56             user => "username",
57             password => "password",
58             set => [
59             "RaiseError 0",
60             "PrintError 0",
61             "mysql_enable_utf8 1",
62             ],
63             );
64              
65             Creates DBI object
66              
67             =head2 add
68              
69             $store->add(
70             to => $user,
71             channel => $ch_name,
72             subject => $subject,
73             message => $message,
74             ) or die($store->error);
75              
76             Adds new recored
77              
78             =head2 clean
79              
80             $store->clean or die($store->error);
81              
82             Delete incorrect records (that are expired, skipped or failed)
83              
84             =head2 del
85              
86             $store->del($id) or die($store->error);
87              
88             Delete record by id
89              
90             =head2 dsn
91              
92             my $dsn = $store->dsn;
93              
94             Returns DSN string of current database connection
95              
96             =head2 error
97              
98             my $error = $store->error;
99              
100             Returns error message
101              
102             my $status = $store->error( "Error message" );
103              
104             Sets error message if argument is provided.
105             This method in "set" context returns status of the operation as status() method.
106              
107             =head2 get
108              
109             my %data = $store->get($id);
110              
111             Returns data from database by id
112              
113             =head2 getall
114              
115             my @table = $store->getall();
116             my @table_100 = $store->getall(100);
117              
118             Returns data from database with limit supporting
119              
120             =head2 getByName
121              
122             my %data = $store->getByName($username, $ch_name);
123              
124             Returns data from database by username and channel name
125              
126             =head2 is_sqlite
127              
128             print $store->is_sqlite ? "Is SQLite" : "Is not SQLite"
129              
130             Returns true if type of current database is SQLite
131              
132             =head2 ping
133              
134             $store->ping ? 'OK' : 'Database session is expired';
135              
136             Checks the connection to database
137              
138             =head2 setError
139              
140             $store->setError($id, 102, "Error string")
141             or die($store->error);
142              
143             Sets error code and error message by id. See L
144              
145             =head2 setStatus
146              
147             $store->setStatus($id, JOB_EXPIRED, "Comment")
148             or die($store->error);
149              
150             Sets new status by id. See L
151              
152             =head2 status
153              
154             my $status = $store->status;
155             my $status = $store->status( 1 ); # Sets the status value and returns it
156              
157             Get/set BOOL status of the operation
158              
159             =head2 truncate
160              
161             $store->truncate or die($store->error);
162              
163             Delete all records
164              
165             =head1 HISTORY
166              
167             See C file
168              
169             =head1 TO DO
170              
171             See C file
172              
173             =head1 BUGS
174              
175             * none noted
176              
177             =head1 SEE ALSO
178              
179             L, L, L
180              
181             =head1 AUTHOR
182              
183             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
184              
185             =head1 COPYRIGHT
186              
187             Copyright (C) 1998-2019 D&D Corporation. All Rights Reserved
188              
189             =head1 LICENSE
190              
191             This program is free software; you can redistribute it and/or
192             modify it under the same terms as Perl itself.
193              
194             See C file and L
195              
196             =cut
197              
198 1     1   43 use vars qw/$VERSION/;
  1         2  
  1         36  
199             $VERSION = '1.01';
200              
201 1     1   4 use Carp;
  1         2  
  1         37  
202 1     1   350 use CTK::DBI;
  1         21830  
  1         31  
203 1     1   7 use CTK::Util qw/ touch /;
  1         2  
  1         39  
204 1     1   5 use CTK::ConfGenUtil;
  1         2  
  1         52  
205 1     1   6 use CTK::TFVals qw/ :ALL /;
  1         2  
  1         175  
206 1     1   5 use File::Spec;
  1         2  
  1         27  
207              
208 1     1   5 use App::MonM::Const;
  1         2  
  1         50  
209 1     1   5 use App::MonM::Util qw/ set2attr /;
  1         2  
  1         35  
210              
211 1     1   6 use App::MonM::Notifier::Const qw/ :jobs :functions EXPIRES /;
  1         2  
  1         101  
212 1     1   367 use App::MonM::Notifier::Util;
  1         2  
  1         61  
213              
214             use constant {
215 1         83 DB_FILENAME => '.monotifier.db',
216             DEFAULT_DSN_MASK => 'dbi:SQLite:dbname=%s',
217             DEFAULT_DBI_ATTR => {
218             dsn => '', # See DEFAULT_DSN_MASK
219             user => '',
220             password => '',
221             set => [
222             'RaiseError 0',
223             'PrintError 0',
224             'sqlite_unicode 1',
225             ],
226             },
227 1     1   6 };
  1         2  
228              
229 1     1   6 use constant MONOTIFIER_DDL => <<'DDL';
  1         3  
  1         52  
230             CREATE TABLE IF NOT EXISTS monotifier (
231             `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
232             `to` CHAR(255), -- Recipient name
233             `channel` CHAR(255), -- Recipient channel
234             `subject` TEXT, -- Message subject
235             `message` TEXT, -- Message content
236             `pubdate` BIGINT(20), -- Date (unixtime) of the publication
237             `expires` BIGINT(20), -- Date (unixtime) of the expire
238             `status` CHAR(32), -- Status of transaction
239             `comment` CHAR(255), -- Comment
240             `errcode` INT(11), -- Error code
241             `errmsg` TEXT -- Error message
242             )
243             DDL
244              
245 1     1   6 use constant MONOTIFIER_ADD => <<'DML';
  1         2  
  1         51  
246             INSERT INTO monotifier
247             (`to`,`channel`,`subject`,`message`,`pubdate`,`expires`,`status`,`comment`,`errcode`,`errmsg`)
248             VALUES
249             (?,?,?,?,?,?,?,?,?,?)
250             DML
251              
252 1     1   22 use constant MONOTIFIER_GET_BY_NAME => <<'DML';
  1         2  
  1         51  
253             SELECT `id`,`to`,`channel`,`subject`,`message`,`pubdate`,`expires`,`status`,`comment`,`errcode`,`errmsg`
254             FROM monotifier
255             WHERE `status` = ? AND `to` = ? AND `channel` = ?
256             DML
257              
258 1     1   5 use constant MONOTIFIER_SET_STATUS => <<'DML';
  1         2  
  1         39  
259             UPDATE monotifier
260             SET `status` = ?, `comment` = ?
261             WHERE `id` = ?
262             DML
263              
264 1     1   5 use constant MONOTIFIER_SET_ERROR => <<'DML';
  1         2  
  1         44  
265             UPDATE monotifier
266             SET `status` = ?, `errcode` = ?, `errmsg` = ?
267             WHERE `id` = ?
268             DML
269              
270 1     1   5 use constant MONOTIFIER_DEL => <<'DML';
  1         16  
  1         44  
271             DELETE FROM monotifier WHERE `id` = ?
272             DML
273              
274 1     1   6 use constant MONOTIFIER_GET => <<'DML';
  1         2  
  1         46  
275             SELECT `id`,`to`,`channel`,`subject`,`message`,`pubdate`,`expires`,`status`,`comment`,`errcode`,`errmsg`
276             FROM monotifier
277             WHERE `id` = ?
278             DML
279              
280 1     1   6 use constant MONOTIFIER_GETALL => <<'DML';
  1         1  
  1         38  
281             SELECT `id`,`to`,`channel`,`subject`,`pubdate`,`expires`,`status`,`comment`,`errcode`,`errmsg`
282             FROM monotifier
283             ORDER BY `id` DESC
284             DML
285              
286 1     1   5 use constant MONOTIFIER_CLEAN => <<'DML';
  1         1  
  1         47  
287             DELETE FROM monotifier WHERE `status` IN ('EXPIRED', 'SKIP', 'ERROR') OR `expires` <= ?
288             DML
289              
290 1     1   6 use constant MONOTIFIER_TRUNCATE => <<'DML';
  1         6  
  1         1504  
291             DELETE FROM monotifier
292             DML
293              
294             sub new {
295 0     0 1   my $class = shift;
296 0           my %args = @_;
297 0 0         unless ($args{dsn}) {
298 0           my $dda = DEFAULT_DBI_ATTR;
299 0           foreach (%$dda) {
300 0   0       $args{$_} //= $dda->{$_}
301             }
302             }
303 0   0       my $file = $args{file} || DB_FILENAME;
304 0   0       my $dsn = $args{dsn} || sprintf(DEFAULT_DSN_MASK, $file);
305              
306             # DB
307             my $db = new CTK::DBI(
308             -dsn => $dsn,
309             -debug => 0,
310             -username => $args{'user'},
311             -password => $args{'password'},
312             -attr => set2attr($args{'set'}),
313             $args{timeout} ? (
314             -timeout_connect => $args{timeout},
315             -timeout_request => $args{timeout},
316 0 0         ) : (),
317             );
318 0 0         my $dbh = $db->connect if $db;
319              
320             # SQLite
321 0           my $fnew = 0;
322 0           my $issqlite = 0;
323 0 0 0       if ($dbh && $dsn =~ /SQLite/i) {
324 0           $file = $dbh->sqlite_db_filename();
325 0 0 0       unless ($file && (-e $file) && !(-z $file)) {
      0        
326 0           touch($file);
327 0           chmod(0666, $file);
328 0           $fnew = 1;
329             }
330 0           $issqlite = 1;
331             }
332              
333 0           my $status = 1;
334 0           my $error = "";
335 0 0         if (!$db) {
    0          
    0          
336 0           $error = sprintf("Can't init database \"%s\"", $dsn);
337 0           $status = 0;
338             } elsif (!$dbh) {
339 0   0       $error = sprintf("Can't connect to database \"%s\": %s", $dsn, $DBI::errstr || "unknown error");
340 0           $status = 0;
341             } elsif ($fnew) {
342 0           $db->execute(MONOTIFIER_DDL);
343 0           $error = $dbh->errstr();
344 0 0         $status = 0 if $dbh->err;
345             }
346 0 0         unless ($error) {
347 0 0         unless ($dbh->ping) {
348 0   0       $error = sprintf("Can't init database \"%s\". Ping failed: %s",
349             $dsn, $dbh->errstr() || "unknown error");
350 0           $status = 0;
351             }
352             }
353              
354             my $self = bless {
355             file => $file,
356             issqlite=> $issqlite,
357             dsn => $dsn,
358             error => $error,
359             dbi => $db,
360 0   0       expires => $args{expires} || EXPIRES,
361             status => $status,
362             }, $class;
363              
364 0           return $self;
365             }
366             sub status {
367 0     0 1   my $self = shift;
368 0           my $value = shift;
369 0 0         return fv2zero($self->{status}) unless defined($value);
370 0 0         $self->{status} = $value ? 1 : 0;
371 0           return $self->{status};
372             }
373             sub error {
374 0     0 1   my $self = shift;
375 0           my $value = shift;
376 0 0         return uv2null($self->{error}) unless defined($value);
377 0           $self->{error} = $value;
378 0 0         $self->status($value ne "" ? 0 : 1);
379 0           return $value;
380             }
381             sub ping {
382 0     0 1   my $self = shift;
383 0 0         return 0 unless $self->{dsn};
384 0           my $dbi = $self->{dbi};
385 0 0         return 0 unless $dbi;
386 0           my $dbh = $dbi->{dbh};
387 0 0         return 0 unless $dbh;
388 0 0         return 1 unless $dbh->can('ping');
389 0           return $dbh->ping();
390             }
391             sub dsn {
392 0     0 1   my $self = shift;
393 0           return $self->{dsn};
394             }
395             sub is_sqlite {
396 0     0 1   my $self = shift;
397 0 0         return $self->{issqlite} ? 1 : 0;
398             }
399             sub add {
400 0     0 1   my $self = shift;
401 0           my %data = @_;
402 0 0         return unless $self->ping;
403 0           $self->error("");
404 0           my $dbi = $self->{dbi};
405              
406             # Data
407 0   0       my $pubdate = $data{pubdate} || time();
408 0           my $expires = $pubdate + $self->{expires};
409              
410             # Delete too old records
411 0           $dbi->execute('DELETE FROM monotifier WHERE `expires` <= ?', time());
412 0 0         if ($dbi->connect->err) {
413 0           $self->error(sprintf("Can't delete old records: %s", uv2null($dbi->connect->errstr)));
414 0           return;
415             }
416              
417             # Добавляем запись в БД
418             $dbi->execute(MONOTIFIER_ADD,
419             $data{to},
420             $data{channel},
421             $data{subject}, $data{message},
422             $pubdate, $expires,
423             $data{status} || JOB_NEW,
424             $data{comment},
425 0   0       0, getErr(0),
426             );
427 0 0         if ($dbi->connect->err) {
428 0           $self->error(sprintf("Can't insert new record: %s", uv2null($dbi->connect->errstr)));
429 0           return;
430             }
431              
432 0           return 1;
433             }
434             sub getByName {
435 0     0 1   my $self = shift;
436 0           my ($name, $channel) = @_;
437 0           $self->error("");
438 0           my $dbi = $self->{dbi};
439              
440             # Get table
441 0           my %tbl = $dbi->tableh("id", MONOTIFIER_GET_BY_NAME, JOB_NEW, $name, $channel);
442 0 0         if ($dbi->connect->err) {
443 0           $self->error(sprintf("Can't select records: %s", uv2null($dbi->connect->errstr)));
444 0           return;
445             }
446              
447             # Update to PROGRESS status
448 0           my $summary = 1;
449 0           foreach my $id (keys %tbl) {
450 0 0         $self->setStatus($id, JOB_PROGRESS) or do {$summary = 0};
  0            
451             }
452 0 0         return unless $summary;
453              
454 0           return %tbl;
455             }
456             sub setStatus {
457 0     0 1   my $self = shift;
458 0   0       my $id = shift || 0;
459 0   0       my $status = shift || JOB_SKIP;
460 0   0       my $comment = shift || sprintf("Modified at %s", scalar(localtime(time())));
461 0           $self->error("");
462 0           my $dbi = $self->{dbi};
463 0           $dbi->execute(MONOTIFIER_SET_STATUS, $status, $comment, $id);
464 0 0         if ($dbi->connect->err) {
465 0           $self->error(sprintf("Can't change status: %s", uv2null($dbi->connect->errstr)));
466 0           return 0;
467             }
468 0           return 1;
469             }
470             sub setError {
471 0     0 1   my $self = shift;
472 0   0       my $id = shift || 0;
473 0   0       my $code = shift || 1;
474 0           my $error = sprintf(getErr($code), @_);
475 0           $self->error("");
476 0           my $dbi = $self->{dbi};
477              
478 0           $dbi->execute(MONOTIFIER_SET_ERROR, JOB_ERROR, $code, $error, $id);
479 0 0         if ($dbi->connect->err) {
480 0           $self->error(sprintf("Can't set error: %s", uv2null($dbi->connect->errstr)));
481 0           return 0;
482             }
483 0           return 1;
484             }
485             sub del {
486 0     0 1   my $self = shift;
487 0   0       my $id = shift || 0;
488 0           my $dbi = $self->{dbi};
489 0           $self->error("");
490              
491 0           $dbi->execute(MONOTIFIER_DEL, $id);
492 0 0         if ($dbi->connect->err) {
493 0           $self->error(sprintf("Can't delete record: %s", uv2null($dbi->connect->errstr)));
494 0           return 0;
495             }
496 0           return 1;
497             }
498             sub get {
499 0     0 1   my $self = shift;
500 0   0       my $id = shift || 0;
501 0           my $dbi = $self->{dbi};
502 0           $self->error("");
503              
504 0           my %rec = $dbi->recordh(MONOTIFIER_GET, $id);
505 0 0         if ($dbi->connect->err) {
506 0           $self->error(sprintf("Can't get record: %s", uv2null($dbi->connect->errstr)));
507 0           return ();
508             }
509 0           return %rec;
510             }
511             sub getall {
512 0     0 1   my $self = shift;
513 0   0       my $limit = shift || 0;
514 0           my $dbi = $self->{dbi};
515 0           $self->error("");
516              
517 0 0         my @tbl = $dbi->table(sprintf("%s%s", MONOTIFIER_GETALL, $limit ? " LIMIT $limit" : "" ));
518 0 0         if ($dbi->connect->err) {
519 0           $self->error(sprintf("Can't get records: %s", uv2null($dbi->connect->errstr)));
520 0           return ();
521             }
522 0           return @tbl;
523             }
524             sub clean {
525 0     0 1   my $self = shift;
526 0           my $dbi = $self->{dbi};
527 0           $self->error("");
528              
529 0           $dbi->execute(MONOTIFIER_CLEAN, time);
530 0 0         if ($dbi->connect->err) {
531 0           $self->error(sprintf("Can't cleaning up: %s", uv2null($dbi->connect->errstr)));
532 0           return 0;
533             }
534 0           return 1;
535             }
536             sub truncate {
537 0     0 1   my $self = shift;
538 0           my $dbi = $self->{dbi};
539 0           $self->error("");
540              
541 0           $dbi->execute(MONOTIFIER_TRUNCATE);
542 0 0         if ($dbi->connect->err) {
543 0           $self->error(sprintf("Can't truncate table: %s", uv2null($dbi->connect->errstr)));
544 0           return 0;
545             }
546 0           return 1;
547             }
548              
549             1;
550              
551             __END__