File Coverage

blib/lib/POE/Component/DirWatch/Object.pm
Criterion Covered Total %
statement 68 74 91.8
branch 15 26 57.6
condition n/a
subroutine 16 19 84.2
pod 5 5 100.0
total 104 124 83.8


line stmt bran cond sub pod time code
1             package POE::Component::DirWatch::Object;
2 7     7   20021 use strict;
  7         12  
  7         245  
3 7     7   30 use warnings;
  7         8  
  7         174  
4 7     7   1826 use Moose;
  7         1166315  
  7         52  
5              
6             our $VERSION = "0.1200";
7 7     7   38646 use File::Spec;
  7         12  
  7         176  
8 7     7   31 use Carp;
  7         11  
  7         495  
9 7     7   684 use POE;
  7         32788  
  7         43  
10             #use IO::AIO;
11              
12             #--------#---------#---------#---------#---------#---------#---------#---------#
13             has 'alias' => (is => 'rw', isa => 'Str', required => 1,
14             default => 'dirwatch');
15              
16             has 'next_poll' => (is => 'rw', isa => 'Int', required => 0,
17             clearer => 'clear_next_poll', predicate => 'has_next_poll');
18             has 'callback' => (is => 'rw', isa => 'Ref', required => 1);
19             has 'directory' => (is => 'rw', isa => 'Str', required => 1);
20             has 'interval' => (is => 'rw', isa => 'Int', required => 1, default => 1);
21             has 'filter' => (is => 'rw', isa => 'CodeRef', required => 1,
22             default => sub { sub{ -f $_[1]; } }); #holler
23              
24             sub BUILD{
25 5     5 1 8814 my ($self, $args) = @_;
26              
27             #my $s =
28 5         89 POE::Session->create
29             (
30             object_states =>
31             [
32             $self, {
33             _start => '_start',
34             _pause => '_pause',
35             _resume => '_resume',
36             shutdown => '_shutdown',
37             poll => '_poll',
38             callback => '_callback',
39             dispatch => '_dispatch',
40             },
41             ]
42             );
43             }
44              
45             sub session{
46 0     0 1 0 my $self = shift;
47 0         0 return $poe_kernel->alias_resolve( $self->alias );
48             }
49              
50             #--------#---------#---------#---------#---------#---------#---------#---------#
51              
52             sub _start{
53 5     5   989 my ($self, $kernel) = @_[OBJECT, KERNEL];
54              
55             # set alias for ourselves and remember it
56 5         216 $kernel->alias_set($self->alias);
57             # set up polling
58 5         318 $self->next_poll( $kernel->delay_set(poll => $self->interval) );
59             }
60              
61             sub _pause{
62 3     3   316 my ($self, $kernel, $until) = @_[OBJECT, KERNEL, ARG0];
63 3 50       133 $kernel->alarm_remove($self->next_poll) if $self->has_next_poll;
64 3         364 $self->clear_next_poll;
65 3 100       13 return unless defined $until;
66              
67 1         2 my $t = time;
68 1 50       4 $until += $t if $t > $until;
69 1         4 $self->next_poll( $kernel->alarm_set(poll => $until) );
70              
71             }
72              
73             sub _resume{
74 2     2   138 my ($self, $kernel, $when) = @_[OBJECT, KERNEL, ARG0];
75 2 50       98 $kernel->alarm_remove($self->next_poll) if $self->has_next_poll;
76 2         89 $self->clear_next_poll;
77 2 50       8 $when = 0 unless defined $when;
78              
79 2         3 my $t = time;
80 2 50       5 $when += $t if $t > $when;
81 2         41 $self->next_poll( $kernel->alarm_set(poll => $when) );
82             }
83              
84             #--------#---------#---------#---------#---------#---------#---------#---------#
85              
86             sub pause{
87 0     0 1 0 my ($self, $until) = @_;
88 0         0 $poe_kernel->call($self->alias, _pause => $until);
89             }
90              
91             sub resume{
92 0     0 1 0 my ($self, $when) = @_;
93 0         0 $poe_kernel->call($self->alias, _resume => $when);
94             }
95              
96             sub shutdown{
97 1     1 1 1985 my ($self) = @_;
98 1 50       49 $poe_kernel->alarm_remove($self->next_poll) if $self->has_next_poll;
99 1         126 $self->clear_next_poll;
100 1         32 $poe_kernel->post($self->alias, 'shutdown');
101             }
102              
103             #--------#---------#---------#---------#---------#---------#---------#---------#
104             sub _poll{
105 19     19   17083301 my ($self, $kernel) = @_[OBJECT, KERNEL];
106 19         1199 $self->clear_next_poll;
107              
108             #AIO?? maybe one day...
109             #aio_readdir($self->directory, sub{ $self->_aio_callback(@_) } );
110              
111             #until i figure out AIO this will have to be good enough
112 19         47 my @files;
113 19 50       34 eval {
114 19 50       717 opendir(DIR, $self->directory) ||
115             die "Failed to open '".$self->directory."': $!";
116 19         538 @files = grep { $_ !~ /^\.\.?$/ } readdir(DIR);
  72         283  
117 19         1310 closedir DIR;
118             } || carp($@);
119              
120 19         130 $self->_aio_callback(\@files);
121             }
122              
123             sub _aio_callback{
124 19     19   38 my ($self, $files) = @_;
125              
126 19         1034 $self->next_poll( $poe_kernel->delay_set(poll => $self->interval) );
127 19 50       78 return unless ref $files;
128              
129             $poe_kernel->yield(dispatch => $_, File::Spec->catfile($self->directory, $_))
130 19         789 foreach (@$files);
131             }
132              
133             sub _dispatch {
134 10     10   1299 my ($self, $kernel, $fname, $fpath) = @_[OBJECT, KERNEL, ARG0, ARG1];
135 10 50       500 $kernel->yield(callback => [$fname, $fpath])
136             if $self->filter->($fname,$fpath);
137             }
138              
139             sub _callback{
140 13     13   1903 my ($self, $args) = @_[OBJECT, ARG0];
141 13         555 my $cb = $self->callback;
142              
143 13 100       43 if( ref $cb eq 'ARRAY' ){
144 2         6 my ($obj, $method) = @$cb;
145 2         8 $obj->$method(@$args);
146 2         3430 return;
147             }
148              
149 11 50       64 $cb->(@$args) if( ref $cb eq 'CODE');
150 11         13680 return;
151             }
152              
153             #--------#---------#---------#---------#---------#---------#---------#---------#
154             sub _shutdown {
155 5     5   995569 my ($self, $kernel, $heap) = @_[OBJECT, KERNEL, HEAP];
156              
157             #cleaup heap, alias, alarms (no lingering refs n ish)
158 5         20 %$heap = ();
159 5         409 $kernel->alias_remove($self->alias);
160 5         220 $kernel->alarm_remove_all();
161             }
162              
163             #--------#---------#---------#---------#---------#---------#---------#---------#
164              
165             1;
166              
167             __END__;
168              
169             =head1 NAME
170              
171             POE::Component::DirWatch::Object - POE directory watcher object
172              
173             =head1 SYNOPSIS
174              
175             use POE::Component::DirWatch::Object;
176              
177             #$watcher is a PoCo::DW:Object
178             my $watcher = POE::Component::DirWatch::Object->new
179             (
180             alias => 'dirwatch',
181             directory => '/some_dir',
182             filter => sub { $_[0] =~ /\.gz$/ && -f $_[1] },
183             callback => \&some_sub,
184             # OR
185             callback => [$obj, 'some_sub'], #if you want $obj->some_sub
186             interval => 1,
187             );
188              
189             $poe_kernel->run;
190              
191             =head1 DESCRIPTION
192              
193             POE::Component::DirWatch::Object watches a directory for files. Upon finding
194             a file it will invoke the user-supplied callback function.
195              
196             This module was primarily designed as an L<Moose>-based replacement for
197             L<POE::Component::Dirwatch>. While all known functionality of the original is
198             meant to be covered in a similar way there is some subtle differences.
199              
200             Its primary intended use is processing a "drop-box" style
201             directory, such as an FTP upload directory.
202              
203             Apparently the original DirWatch no longer exists. Yes, I know Moose is a bit heavy
204             but I don't really care. The original is still on BackPAN if you don't like my
205             awesome replacement.
206              
207             =head1 Public Methods
208              
209             =head2 new( \%attrs)
210              
211             See SYNOPSIS and Accessors / Attributes below.
212              
213             =head2 session
214              
215             Returns a reference to the actual POE session.
216             Please avoid this unless you are subclassing. Even then it is recommended that
217             it is always used as C<$watcher-E<gt>session-E<gt>method> because copying the object
218             reference around could create a problem with lingering references.
219              
220             =head2 pause [$until]
221              
222             Synchronous call to _pause. This just posts an immediate _pause event to the kernel.
223             Safe for use outside of POEish land (doesnt use @_[KERNEL, ARG0...])
224              
225             =head2 resume [$when]
226              
227             Synchronous call to _resume. This just posts an immediate _resume event to the kernel.
228             Safe for use outside of POEish land (doesnt use @_[KERNEL, ARG0...])
229              
230             =head2 shutdown
231              
232             Convenience method that posts a FIFO shutdown event.
233              
234             =head1 Accessors / Attributes
235              
236             =head2 alias
237              
238             The alias for the DirWatch session. Defaults to C<dirwatch> if not
239             specified. You can NOT rename a session at runtime.
240              
241             =head2 directory
242              
243             This is a required argument during C<new>.
244             The path of the directory to watch.
245              
246             =head2 interval
247              
248             The interval waited between the end of a directory poll and the start of another.
249             Default to 1 if not specified.
250              
251             WARNING: This is number NOT the interval between polls. A lengthy blocking callback,
252             high-loads, or slow applications may delay the time between polls. You can see:
253             L<http://poe.perl.org/?POE_Cookbook/Recurring_Alarms> for more info.
254              
255             =head2 callback
256              
257             This is a required argument during C<new>.
258             The code to be called when a matching file is found.
259              
260             The code called will be passed 2 arguments, the $filename and $filepath.
261             This may take 2 different values. A 2 element arrayref or a single coderef.
262             When given an arrayref the first item will be treated as an object and the
263             second as a method name. See the SYNOPSYS.
264              
265             It usually makes most sense to process the file and remove it from the directory.
266              
267             #Example
268             callback => sub{ my($filename, $fullpath) = @_ }
269             # OR
270             callback => [$obj, 'mymethod']
271              
272             #Where my method looks like:
273             sub mymethod {
274             my ($self, $filename, $fullpath) = @_;
275             ...
276              
277             =head2 filter
278              
279             A reference to a subroutine that will be called for each file
280             in the watched directory. It should return a TRUE value if
281             the file qualifies as found, FALSE if the file is to be
282             ignored.
283              
284             This subroutine is called with two arguments: the name of the
285             file, and its full pathname.
286              
287             If not specified, defaults to C<sub { -f $_[1] }>.
288              
289             =head2 next_poll
290              
291             The ID of the alarm for the next scheduled poll, if any. Has clearer
292             and predicate methods named C<clear_next_poll> and C<has_next_poll>.
293             Please note that clearing the C<next_poll> just clears the next poll id,
294             it does not remove the alarm, please use C<pause> for that.
295              
296             =head1 Private methods
297              
298             These methods are documented here just in case you subclass. Please
299             do not call them directly. If you are wondering why some are needed it is so
300             Moose's C<before> and C<after> work.
301              
302             =head2 _start
303              
304             Runs when C<$poe_kernel-E<gt>run> is called. It will create a new DirHandle watching
305             to C<$watcher-E<gt>directory>, set the session's alias and schedule the first C<poll> event.
306              
307             =head2 _poll
308              
309             Triggered by the C<poll> event this is the re-occurring action. _poll will use get a
310             list of all files in the directory and call C<_aio_callback> with the list of filenames (if any)
311              
312             I promise I will make this async soon, it's just that IO::AIO doesnt work on FreeBSD.
313              
314             =head2 _aio_callback
315              
316             Schedule the next poll and dispatch any files found.
317              
318             =head2 _dispatch
319              
320             Triggered by the C<dispatch> event, it recieves a filename in ARG0, it then proceeds to
321             run the file through the filter and schedule a callback.
322              
323             =head2 _callback
324              
325             Triggered by the C<callback> event, it derefernces the argument list that is passed to
326             it in ARG0 and calls the appropriate coderef or object-method pair with
327             $filename and $fullpath in @_;
328              
329             =head2 _pause [$until]
330              
331             Triggered by the C<_pause> event this method will remove the alarm scheduling the
332             next directory poll. It takes an optional argument of $until, which dictates when the
333             polling should begin again. If $until is an integer smaller than the result of time()
334             it will treat $until as the number of seconds to wait before polling. If $until is an
335             integer larger than the result of time() it will treat $until as an epoch timestamp
336             and schedule the poll alarm accordingly.
337              
338             #these two are the same thing
339             $watcher->pause( time() + 60);
340             $watcher->pause( 60 );
341              
342             #this is one also the same
343             $watcher->pause;
344             $watcher->resume( 60 );
345              
346              
347             =head2 _resume [$when]
348              
349             Triggered by the C<_resume> event this method will remove the alarm scheduling the
350             next directory poll (if any) and schedule a new poll alarm. It takes an optional
351             argument of $when, which dictates when the polling should begin again. If $when is
352             an integer smaller than the result of time() it will treat $until as the number of
353             seconds to wait before polling. If $until is an integer larger than the result of
354             time() it will treat $when as an epoch timestamp and schedule the poll alarm
355             accordingly. If not specified, the alarm will be scheduled with a delay of zero.
356              
357             =head2 _shutdown
358              
359             Delete the C<heap>, remove the alias we are using and remove all set alarms.
360              
361             =head2 BUILD
362              
363             Constructor. C<create()>s a L<POE::Session> and stores it in C<$self-E<gt>session>.
364              
365             =head2 meta
366              
367             Test Happiness.
368              
369             =head1 TODO
370              
371             =over 4
372              
373             =item C<IO::AIO> is b0rken on FreeBSD so I can't add support until it works
374              
375             =item Use C<Win32::ChangeNotify> on Win32 platforms for better performance.
376              
377             =item Allow user to change the directory watched during runtime.
378              
379             =item ImproveDocs
380              
381             =item Write some tests. (after I read PDN and learn how)
382              
383             =item Figure out why taint mode fails
384              
385             =back
386              
387             =head1 Subclassing
388              
389             Please see L<Moose> for the proper way to subclass this. And please remember to
390             shift $self out of @_ on any functions called by POE directly so that you don't screw
391             up the named @_ positions (@_[KERNEL, HEAP, ...])
392              
393             Also check out L<POE::Component::DirWatch::Object::NewFile> for a simple example of
394             how to extend functionality.
395              
396             =head1 SEE ALSO
397              
398             L<POE>, L<POE::Session>, L<POE::Component>, L<POE::Component::DirWatch>, L<Moose>
399              
400             =head1 AUTHOR
401              
402             Guillermo Roditi, <groditi@cpan.org>
403              
404             Based on the L<POE::Component::Dirwatch> code by:
405             Eric Cholet, <cholet@logilune.com>
406             (I also copy pasted some POD)
407              
408             Currently maintained by Robert Rothenberg <rrwo@thermeon.com>
409              
410             =head1 BUGS
411              
412             Holler?
413              
414             Please report any bugs or feature requests to
415             C<bug-poe-component-dirwatch-object at rt.cpan.org>, or through the web interface at
416             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=POE-Component-DirWatch-Object>.
417             I will be notified, and then you'll automatically be notified of progress on
418             your bug as I make changes.
419              
420             =head1 CONTRIBUTING
421              
422             The git repository can be found at
423             L<https://github.com/robrwo/POE-Component-DirWatch-Object>
424              
425             Bugs can be reported
426             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=POE-Component-DirWatch-Object>
427              
428             =head1 ACKNOWLEDGEMENTS
429              
430             People who answered way too many questions from an inquisitive idiot:
431              
432             =over 4
433              
434             =item #PoE & #Moose
435              
436             =item Matt S Trout <mst@shadowcatsystems.co.uk>
437              
438             =item Rocco Caputo
439              
440             =item Charles Reiss
441              
442             =item Stevan Little
443              
444             =back
445              
446             =head1 COPYRIGHT
447              
448             Copyright 2006 Guillermo Roditi. All Rights Reserved. This is
449             free software; you may redistribute it and/or modify it under the same
450             terms as Perl itself.
451              
452             =cut