App-MonM-Notifier
view release on metacpan or search on metacpan
lib/App/MonM/Notifier/Store.pm view on Meta::CPAN
package App::MonM::Notifier::Store; # $Id: Store.pm 81 2022-09-16 10:21:57Z abalama $
use strict;
use utf8;
=encoding utf8
=head1 NAME
App::MonM::Notifier::Store - monotifier store class
=head1 VERSION
Version 1.02
=head1 SYNOPSIS
use App::MonM::Notifier::Store;
my $store = App::MonM::Notifier::Store->new(
dsn => "DBI:mysql:database=monotifier;host=mysql.example.com",
user => "username",
password => "password",
set => [
"RaiseError 0",
"PrintError 0",
"mysql_enable_utf8 1",
],
expires => 3600*24*7,
maxtime => 300,
);
die($store->error) if $store->error;
=head1 DESCRIPTION
DBI interface for monotifier store. This module provides store methods
=head2 new
my $store = App::MonM::Notifier::Store->new(
dsn => "DBI:mysql:database=monotifier;host=mysql.example.com",
user => "username",
password => "password",
set => [
"RaiseError 0",
"PrintError 0",
"mysql_enable_utf8 1",
],
expires => 3600*24*7,
maxtime => 300,
);
Creates DBI object
=over 8
=item B<expires>
Time in seconds of life of database record
=item B<maxtime>
Max time in seconds to sending one message
=back
=head2 cleanup
my $st = $store->cleanup;
Removes permanently queue entities based on how old they are
=head2 dequeue
my $st = $store->dequeue(
id => 1,
);
Dequeues the element by setting success status (STATUS_SENT)
=head2 delById
$store->delById($id) or die($store->error);
Delete record by id
=head2 dsn
my $dsn = $store->dsn;
Returns DSN string of current database connection
=head2 enqueue
$store->enqueue(
to => $user,
channel => $ch_name,
subject => $subject,
message => $message,
attributes => $ch, # Channel attributes
) or die($store->error);
Adds a new element at the end of the current queue
lib/App/MonM/Notifier/Store.pm view on Meta::CPAN
=head1 SEE ALSO
L<CTK::DBI>, L<App::MonM>
=head1 AUTHOR
Serż Minus (Sergey Lepenkov) L<https://www.serzik.com> E<lt>abalama@cpan.orgE<gt>
=head1 COPYRIGHT
Copyright (C) 1998-2022 D&D Corporation. All Rights Reserved
=head1 LICENSE
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
See C<LICENSE> file and L<https://dev.perl.org/licenses/>
=cut
use vars qw/$VERSION/;
$VERSION = '1.02';
use File::Spec;
use MIME::Base64 qw/encode_base64 decode_base64/;
use CTK::DBI;
use CTK::Util qw/ read_attributes touch /;
use CTK::ConfGenUtil;
use CTK::TFVals qw/ :ALL /;
use CTK::Serializer;
use App::MonM::Const;
use App::MonM::Util qw/ set2attr /;
use constant {
EXPIRES => 30*24*60*60, # 30 days max (how time to hold of messages)
MAXTIME => 300, # 5 min
JSON_ATTRS => [
{ # For serialize
utf8 => 0,
pretty => 1,
allow_nonref => 1,
allow_blessed => 1,
},
{ # For deserialize
utf8 => 0,
allow_nonref => 1,
allow_blessed => 1,
},
],
# Database
DB_FILENAME_NASK => 'monotifier-%s.db', # username
DEFAULT_DSN_MASK => 'dbi:SQLite:dbname=%s',
DEFAULT_DBI_ATTR => {
dsn => '', # See DEFAULT_DSN_MASK
user => '',
password => '',
set => [
'RaiseError 0',
'PrintError 0',
'sqlite_unicode 1',
],
},
# Statuses
STATUS_NEW => 'NEW',
STATUS_BUSY => 'BUSY',
STATUS_FAIL => 'FAIL', # See Attempt
STATUS_SENT => 'SENT',
};
use constant MONOTIFIER_DDL => <<'DDL';
CREATE TABLE IF NOT EXISTS monotifier (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
`to` CHAR(255), -- Recipient name
`channel` CHAR(255), -- Recipient channel
`subject` TEXT, -- Message subject
`message` TEXT, -- Message content (BASE64)
`attributes` TEXT, -- Message attributes (JSON)
`published` BIGINT(20), -- The publication time (unixtime)
`scheduled` BIGINT(20), -- The scheduled time (unixtime)
`expired` BIGINT(20), -- The expiration time (unixtime)
`sent` BIGINT(20), -- The send time
`attempt` INTEGER DEFAULT 0, -- Count of failed attempts
`status` CHAR(32), -- Status of transaction
`errcode` INT(11), -- Error code
`errmsg` TEXT -- Error message
)
DDL
use constant MONOTIFIER_ADD => <<'DML';
INSERT INTO monotifier
(`to`,`channel`,`subject`,`message`,`attributes`,`published`,`scheduled`,`expired`,`sent`,`attempt`,`status`,`errcode`,`errmsg`)
VALUES
(?,?,?,?,?,?,?,?,?,?,?,?,?)
DML
use constant MONOTIFIER_GET_NEXT => <<'DML';
SELECT `id`,`to`,`channel`,`subject`,`message`,`attributes`,`published`,`scheduled`,`expired`,`sent`,`attempt`,`status`,`errcode`,`errmsg`
FROM `monotifier`
WHERE `scheduled` <= ? AND `status` = ?
LIMIT 1
DML
use constant MONOTIFIER_UPDATE_STATUS => <<'DML';
UPDATE `monotifier`
SET `status` = ?, `scheduled` = ?, `sent` = ?, `attempt` = ?, `errcode` = ?, `errmsg` = ?
WHERE `id` = ?
DML
use constant MONOTIFIER_UPDATE_ERROR => <<'DML';
UPDATE `monotifier`
SET `status` = ?, `errcode` = ?, `errmsg` = ?
WHERE `id` = ?
DML
use constant MONOTIFIER_CLEANUP => <<'DML';
DELETE FROM `monotifier`
WHERE `expired` <= ?
DML
use constant MONOTIFIER_FLUSH => <<'DML';
UPDATE `monotifier`
SET `status` = ?, `errcode` = ?, `errmsg` = ?
WHERE (`status` = ? OR `status` = ?) AND `scheduled` < ?
DML
use constant MONOTIFIER_PURGE => <<'DML';
DELETE FROM monotifier
DML
use constant MONOTIFIER_GET_ALL => <<'DML';
SELECT `id`,`to`,`channel`,`subject`,`message`,`attributes`,`published`,`scheduled`,`expired`,`sent`,`attempt`,`status`,`errcode`,`errmsg`
FROM monotifier
ORDER BY `id` DESC
DML
use constant MONOTIFIER_GET_BY_ID => <<'DML';
SELECT `id`,`to`,`channel`,`subject`,`message`,`attributes`,`published`,`scheduled`,`expired`,`sent`,`attempt`,`status`,`errcode`,`errmsg`
FROM monotifier
WHERE `id` = ?
DML
use constant MONOTIFIER_DEL_BY_ID => <<'DML';
DELETE FROM monotifier WHERE `id` = ?
DML
sub new {
my $class = shift;
my %args = @_;
unless ($args{dsn}) {
my $dda = DEFAULT_DBI_ATTR;
foreach (%$dda) {
$args{$_} //= $dda->{$_}
}
}
my $username = getlogin() || (getpwuid($>))[0] || $ENV{LOGNAME} || $ENV{USER} || "anonymous";
my $filename = sprintf(DB_FILENAME_NASK, $username);
my $file = $args{file} || File::Spec->catfile(File::Spec->tmpdir(), $filename);
my $dsn = $args{dsn} || sprintf(DEFAULT_DSN_MASK, $file);
# DB
my $db = CTK::DBI->new(
-dsn => $dsn,
-debug => 0,
-username => $args{'user'},
-password => $args{'password'},
-attr => set2attr($args{'set'}),
$args{timeout} ? (
-timeout_connect => $args{timeout},
-timeout_request => $args{timeout},
) : (),
);
my $dbh = $db->connect if $db;
# SQLite
my $fnew = 0;
my $issqlite = 0;
if ($dbh && $dsn =~ /SQLite/i) {
$file = $dbh->sqlite_db_filename();
unless ($file && (-e $file) && !(-z $file)) {
touch($file);
chmod(0666, $file);
$fnew = 1;
}
$issqlite = 1;
}
# Errors
my $error = "";
if (!$db) {
$error = sprintf("Can't init database \"%s\"", $dsn);
} elsif (!$dbh) {
$error = sprintf("Can't connect to database \"%s\": %s", $dsn, $DBI::errstr || "unknown error");
} elsif ($fnew) {
$db->execute(MONOTIFIER_DDL);
$error = $dbh->errstr() if $dbh->err;
}
unless ($error) {
$error = sprintf("Can't init database \"%s\". Ping failed: %s",
$dsn, $dbh->errstr() || "unknown error") unless $dbh->ping;
}
my $self = bless {
file => $file,
issqlite=> $issqlite,
dsn => $dsn,
error => $error,
dbi => $db,
expires => $args{expires} || EXPIRES,
maxtime => $args{maxtime} || MAXTIME,
serializer => CTK::Serializer->new('json', attrs => { json => JSON_ATTRS }),
}, $class;
return $self;
}
sub error {
my $self = shift;
my $err = shift;
return $self->{error} unless defined $err;
$self->{error} = $err;
return $self->{error};
}
sub ping {
my $self = shift;
return 0 unless $self->{dsn};
my $dbi = $self->{dbi};
( run in 0.853 second using v1.01-cache-2.11-cpan-39bf76dae61 )