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 )