File Coverage

blib/lib/AnyEvent/Cron.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package AnyEvent::Cron;
2 1     1   31348 use warnings;
  1         3  
  1         37  
3 1     1   8 use strict;
  1         2  
  1         38  
4 1     1   1838 use DateTime;
  1         245781  
  1         48  
5 1     1   3117 use AnyEvent;
  1         7522  
  1         42  
6 1     1   495 use Moose;
  0            
  0            
7             use Try::Tiny;
8             use DateTime::Event::Cron;
9             use v5.12;
10              
11             our $VERSION = '0.03';
12              
13              
14             # map of expiration formats to their respective time in seconds
15             my %_Expiration_Units = ( map(($_, 1), qw(s second seconds sec)),
16             map(($_, 60), qw(m minute minutes min)),
17             map(($_, 60*60), qw(h hour hours)),
18             map(($_, 60*60*24), qw(d day days)),
19             map(($_, 60*60*24*7), qw(w week weeks)),
20             map(($_, 60*60*24*30), qw(M month months)),
21             map(($_, 60*60*24*365), qw(y year years)) );
22              
23             has interval =>
24             ( is => 'rw' , isa => 'Int' , default => sub { 1 } );
25              
26             has verbose =>
27             ( is => 'rw' , isa => 'Bool' , default => sub { 0 } );
28              
29             has debug =>
30             ( is => 'rw' , isa => 'Bool' , default => sub { 0 } );
31              
32             # TODO:
33             has ignore_floating =>
34             ( is => 'rw', isa => 'Bool' , default => sub { 0 } );
35              
36             has jobs =>
37             traits => ['Array'],
38             handles => {
39             add_job => 'push'
40             },
41             is => 'rw',
42             isa => 'ArrayRef' ,
43             default => sub { [ ] }
44              
45             ;
46              
47              
48             has timers => ( is => 'rw', isa => 'ArrayRef' , default => sub { [ ] } );
49              
50             use Scalar::Util qw(refaddr);
51              
52             sub add {
53             my $self = shift;
54             my ( $timespec, $cb , %args ) = @_;
55              
56             # try to create with crontab format
57             try {
58             my $cron_event = DateTime::Event::Cron->new($timespec);
59             $self->add_job({
60             event => $cron_event,
61             cb => $cb,
62             %args
63             });
64             }
65             catch {
66             given ( $timespec ) {
67             # hour:minute per day
68             when( m{^(\d+):(\d+)$} ) {
69             my ( $hour, $minute ) = ( $1, $2 );
70             $self->add_job({
71             time => { hour => $hour, minute => $minute },
72             cb => $cb,
73             %args,
74             });
75             }
76             when( m{^\s*(\d+)\s*(\w+)} ) {
77             my ( $number, $unit ) = ( $1, $2 );
78             my $seconds = $number * $_Expiration_Units{$unit};
79             $self->add_job({
80             seconds => $seconds,
81             cb => $cb,
82             %args
83             });
84             # $self->create_interval_event( { second => $seconds, callback => $cb } );
85             }
86             default {
87             die 'time string format is not supported.';
88             }
89             }
90             };
91             return $self;
92             }
93              
94             sub _call_event {
95             my ( $self, $e, $dt ) = @_;
96             unless ( $e->{triggered} ) {
97             print $e->{name} . " triggered\n" if $self->verbose;
98             $e->{callback}->( $self, $e, $dt );
99             $e->{triggered} = 1;
100             }
101             }
102              
103             sub _schedule {
104             my ($self,$job) = @_;
105              
106             AnyEvent->now_update();
107             my $now_epoch = AnyEvent->now;
108             my $next_epoch;
109             my $delay;
110             my $name = $job->{name};
111             my $debug = $job->{debug};
112              
113             if( $job->{event} ) {
114             my $event = $job->{event};
115             $next_epoch = $event->next->epoch; # set next schedule time
116             $delay = $next_epoch - $now_epoch;
117             warn "delay:",$delay if $debug;
118             }
119             elsif( $job->{seconds} ) {
120             $next_epoch = $now_epoch + $job->{seconds};
121             $delay = $next_epoch - $now_epoch;
122             warn "delay:",$delay if $debug;
123             }
124             elsif( $job->{time} ) {
125             my $time = $job->{time};
126             my $now = DateTime->from_epoch( epoch => $now_epoch ); # depends on now
127             my $next = $now->clone;
128             $next->set( %$time );
129              
130             # now > the scheduled time
131             if( DateTime->compare( $now, $next ) == 1 ) {
132             if( $time->{month} ) {
133             $next->add( years => 1 );
134             }
135             elsif( $time->{day} ) {
136             $next->add( months => 1 );
137             }
138             elsif( $time->{hour} ) {
139             $next->add( days => 1 );
140             }
141             elsif( $time->{minute} ) {
142             $next->add( hours => 1 );
143             }
144             elsif( $time->{second} ) {
145             $next->add( minutes => 1 );
146             }
147             else {
148             die 'unknown spec';
149             }
150             }
151             }
152              
153             $job->{next}{ $next_epoch } = 1;
154             $job->{watchers}{$next_epoch} = AnyEvent->timer(
155             after => $delay,
156             cb => sub {
157             $self->{_cv}->begin;
158             delete $job->{watchers}{$next_epoch};
159              
160             $self->_schedule($job) unless $job->{once};
161              
162             if ( $job->{single} && $job->{running}++ ) {
163             print STDERR "Skipping job '$name' - still running\n"
164             if $debug;
165             }
166             else {
167             eval { $job->{cb}->( $self->{_cv}, $job ); 1 }
168             or warn $@ || 'Unknown error';
169             delete $job->{running};
170             print STDERR "Finished job '$name'\n"
171             if $debug;
172             }
173             $self->{_cv}->end;
174             }
175             );
176             }
177              
178             sub run {
179             my $self = shift;
180             my $cv = $self->{_cv} = AnyEvent->condvar;
181             for my $job ( @{ $self->jobs } ) {
182             $self->_schedule($job);
183             }
184             }
185              
186              
187             1;
188             __END__
189              
190             =head1 NAME
191              
192             AnyEvent::Cron - Crontab in AnyEvent! provide an interface to register event on specified time.
193              
194             =head1 SYNOPSIS
195              
196             my $cron = AnyEvent::Cron->new(
197             verbose => 1,
198             debug => 1,
199             ignore_floating => 1
200             );
201              
202             # 00:00 (hour:minute)
203             $cron->add("00:00" => sub { warn "zero"; })
204             ->add( '* * * * *' => sub { } )
205             ->add( '1 seconds' => sub { } )
206             ->add( '3 days' => sub { } )
207             ->run();
208              
209             my $cv = AnyEvent->condvar;
210             $cv->recv;
211              
212              
213             =head1 METHODS
214              
215             =head2 add( "12:36" => sub { } )
216              
217             =head2 add( DateTime->now => sub { } )
218              
219             =head1 AUTHOR
220              
221             Cornelius, C<< <cornelius.howl_at_gmail.com> >>
222              
223             =head1 BUGS
224              
225             Please report any bugs or feature requests to C<bug-anyevent-cron at rt.cpan.org>, or through
226             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-Cron>. I will be notified, and then you'll
227             automatically be notified of progress on your bug as I make changes.
228              
229             =head1 SUPPORT
230              
231             You can find documentation for this module with the perldoc command.
232              
233             perldoc AnyEvent::Cron
234              
235              
236             You can also look for information at:
237              
238             =over 4
239              
240             =item * RT: CPAN's request tracker
241              
242             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-Cron>
243              
244             =item * AnnoCPAN: Annotated CPAN documentation
245              
246             L<http://annocpan.org/dist/AnyEvent-Cron>
247              
248             =item * CPAN Ratings
249              
250             L<http://cpanratings.perl.org/d/AnyEvent-Cron>
251              
252             =item * Search CPAN
253              
254             L<http://search.cpan.org/dist/AnyEvent-Cron/>
255              
256             =back
257              
258              
259             =head1 ACKNOWLEDGEMENTS
260              
261              
262             =head1 LICENSE AND COPYRIGHT
263              
264             Copyright 2010 Cornelius.
265              
266             This program is free software; you can redistribute it and/or modify it
267             under the terms of either: the GNU General Public License as published
268             by the Free Software Foundation; or the Artistic License.
269              
270             See http://dev.perl.org/licenses/ for more information.
271              
272              
273             =cut
274              
275             1; # End of AnyEvent::Cron