File Coverage

blib/lib/Catalyst/Model/XML/Feed.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 Catalyst::Model::XML::Feed;
2              
3 2     2   41370 use warnings;
  2         5  
  2         66  
4 2     2   11 use strict;
  2         3  
  2         71  
5              
6 2     2   11 use base qw(Catalyst::Model Class::Accessor);
  2         14  
  2         3790  
7 2     2   6866 use Carp;
  2         4  
  2         134  
8 2     2   844 use XML::Feed;
  0            
  0            
9             use MRO::Compat;
10             use URI;
11             use Catalyst::Model::XML::Feed::Item;
12              
13             __PACKAGE__->mk_accessors(qw|ttl feeds|);
14              
15             =head1 NAME
16              
17             Catalyst::Model::XML::Feed - Use RSS/Atom feeds as a Catalyst Model
18              
19             =head1 VERSION
20              
21             Version 0.04
22              
23             =cut
24              
25             our $VERSION = '0.04';
26              
27             =head1 SYNOPSIS
28              
29             In your own model:
30              
31             package MyApp::Model::Feeds;
32             use base qw(Catalyst::Model::XML::Feed);
33            
34             Then from elsewhere in your application:
35            
36             $c->model('Feeds')->register('delicious', 'http://del.icio.us/rss');
37             $c->model('Feeds')->register('http://blog.jrock.us/');
38            
39             my @feeds = $c->model('Feeds')->get_all_feeds;
40             my $delicious = $c->model('Feeds')->get('delicious');
41              
42             You can also pre-register feeds from your config file:
43              
44             ---
45             Model::Feeds:
46             feeds:
47             - uri: http://blog.jrock.us/
48             - uri: http://search.cpan.org/
49             - title: delicious
50             uri: http://del.icio.us/rss/
51              
52             See CONFIGURATION below for details.
53              
54             =head1 DESCRIPTION
55              
56             Catalyst::Model::XML::Feed allows you to use XML feeds in your
57             Catalyst application. To use a feed, you need to register it with
58             the C<register> method.
59              
60             Once a feed is registered, it's automatically cached for you.
61              
62             =head1 CONFIGURATION
63              
64             Configuration is accepted via the standard Catalyst method:
65              
66             $c->config->{Model::Feeds}->{key} = $value;
67              
68             Valid keys include:
69              
70             =over 4
71              
72             =item ttl
73              
74             Time To Live, in seconds, for each feed. If a feed is older than this
75             value, it is refreshed from its source. Defaults to 3600 seconds, 1 hour.
76              
77             =item feeds
78              
79             An arrayref of hashes containing feeds to preload. The hash is
80             required to contain a key called "uri" or "location", specifing the
81             URL of the feed to load. It may optinally contain "name" or "title",
82             if you wish to override the feed's own title.
83              
84             =back
85              
86             Example config in MyApp.yml (assuming you call your feed model
87             C<Feeds>):
88              
89             Model::Feeds:
90             feeds:
91             - uri: http://blog.jrock.us/
92             - title: delicious
93             location: http://del.icio.us/rss/
94             ttl: 1337
95              
96             =head1 METHODS
97              
98             =head2 new
99              
100             Creates a new instance. Called for you by Catalyst. If your config
101             file contains invalid feeds the feed will be refetched when the feed
102             content is accessed. This allows your Catalyst app to start even in
103             the case of an external outage of an RSS feed.
104              
105             =cut
106              
107             sub new {
108             my $self = shift;
109             $self = $self->next::method(@_);
110             my @in_feeds = eval { @{$self->feeds} };
111             $self->feeds({});
112              
113             $self->ttl($self->ttl || 3600);
114             foreach my $feed (@in_feeds) {
115             my $name = $feed->{name} || $feed->{title};
116             my $uri = $feed->{uri} || $feed->{location};
117             #my $c = $_[0];
118             if($name){
119             #$c->log->debug("registering XML feed $uri as $name") if $c;
120             $self->register($name, $uri);
121             }
122             else {
123             #$c->log->debug("registering XML feed $uri") if $c;
124             my @names = $self->register($uri);
125             my $name = join q{,},@names;
126             #$c->log->debug("feed(s) at $uri created as $name") if $c;
127             }
128             }
129              
130             return $self;
131             }
132              
133             =head2 register($uri_of_feed)
134              
135             Registers a feed with the Model. If C<$uri_of_feed> points to a feed,
136             the feed is added under its own name. If $C<$uri_of_feed> points to
137             an HTML or XHTML document containing C<< <link> >> tags pointing to
138             feeds, all feeds are added by using their URIs as their names.
139              
140             Returns a list of the names of the feeds that were added.
141              
142             Warns if the C<$uri_of_feeds> doesn't contain a feed
143             or links to feeds, or it cannot be fetched.
144              
145             =head2 register($name, $uri_of_feed)
146              
147             Registers a feed with the Model. If C<$name> is already registered,
148             the old feed at C<$name> is forgotten and replaced with the new feed
149             at C<$uri_of_feed>. The C<title> of the feed is replaced with
150             C<$name>.
151              
152             Warns if C<$uri_of_feed> isn't an XML feed (or doesn't
153             contain a C<link> to one).
154              
155             Throws an exception if the C<$uri_of_feed> links to multiple feeds.
156              
157             =cut
158              
159             sub register {
160             my $self = shift;
161             my ($arg1, $arg2) = @_;
162              
163             my $name;
164             my $uri;
165              
166             if($arg2){
167             # get only one feed
168             $name = $arg1;
169             $uri = URI->new($arg2);
170             my $feed;
171             eval {
172             $feed = XML::Feed->parse($uri)
173             or die XML::Feed->errstr;
174             };
175             if($@){
176             my @feeds = XML::Feed->find_feeds($arg2);
177             if(@feeds > 1){
178             croak "$arg2 points to too many feeds";
179             }
180             if(!@feeds){
181             carp "$arg2 does not reference any feeds";
182             # register $uri as it is, but without the feed, in hope that it comes online later.
183             } else {
184             $uri = shift @feeds;
185             }
186             }
187              
188             return $self->_add_uri($uri, $name);
189             }
190             else {
191             $uri = URI->new($arg1);
192             my @feed_uris = XML::Feed->find_feeds($uri);
193             croak "$arg1 does not reference any feeds" if !@feed_uris;
194              
195             my @added;
196             foreach my $uri (@feed_uris){
197             $uri = URI->new($uri);
198             my $name = $self->_add_uri($uri);
199             push @added, $name if $name;
200             }
201             return @added;
202             }
203             }
204              
205             sub _add_uri {
206             my $self = shift;
207             my $uri = shift;
208             my $name = shift;
209             my $feed;
210              
211             eval {
212             $feed = XML::Feed->parse($uri)
213             or die XML::Feed->errstr;
214             };
215             if (my $err = $@) {
216             carp "Failed to parse feed $uri: $@";
217             my $key = $name || $uri;
218             # Create feed item without the parsed content then
219             $self->feeds->{$key} = Catalyst::Model::XML::Feed::Item->new(undef, $uri);
220             return $key;
221             }
222             $feed->title($name) if $name;
223             my $obj = Catalyst::Model::XML::Feed::Item->new($feed, $uri);
224             $name ||= $uri;
225              
226             $self->feeds->{$name} = $obj;
227             return $name;
228             }
229              
230             =head2 names
231              
232             Returns the names of all registered feeds.
233              
234             =cut
235              
236             sub names {
237             return keys %{$_[0]->feeds};
238             }
239              
240             =head2 get_all_feeds
241              
242             Returns a list of all registered feeds. The elements are
243             C<XML::Feed> objects.
244              
245             =cut
246              
247             sub get_all_feeds {
248             my $self = shift;
249             my @names = $self->names;
250             my @feeds;
251             foreach my $name (@names){
252             my $feed = $self->get($name);
253             push @feeds, $feed;
254             }
255             return @feeds;
256             }
257              
258             =head2 get($name)
259              
260             Returns the C<XML::Feed> object that corresponds to C<$name>. Throws
261             an exception if there is no feed that's named C<$name>.
262              
263             =cut
264              
265             sub get {
266             my $self = shift;
267             my $name = shift;
268             my $feed = $self->feeds->{$name};
269             croak "No feed named $name" if !ref $feed;
270              
271             # refresh the feed if it's too old or if previous fetch failed
272             if((time - $feed->updated > $self->ttl) or !defined($feed->feed)) {
273             $self->_refresh($name);
274             # must update the ref after the refresh for this run of the sub to return the fresh info.
275             $feed = $self->feeds->{$name};
276             }
277              
278             return $feed->feed;
279             }
280              
281             =head2 refresh([$name])
282              
283             Forces the feed C<$name> to be refreshed from the source. If C<$name>
284             is omitted, refreshes all registered feeds.
285              
286             =cut
287              
288             sub refresh {
289             my $self = shift;
290             my $name = shift;
291              
292             if($name){
293             $self->_refresh($name);
294             }
295             else {
296             foreach my $name (keys %{$self->feeds}){
297             $self->_refresh($name);
298             }
299             }
300              
301             return;
302             }
303              
304             sub _refresh {
305             my $self = shift;
306             my $name = shift;
307             my $feed = $self->feeds->{$name};
308             croak "No feed named $name" if !ref $feed;
309              
310             my $uri = $feed->uri;
311             return $self->_add_uri($uri, $name);
312             }
313              
314             =head1 DIAGNOSTICS
315              
316             =head2 %s does not reference any feeds
317              
318             The URI you passed to C<register> was not a feed, or did not
319             C<link> to any feeds.
320              
321             =head2 %s points to too many feeds
322              
323             The URI you passed to C<register> referenced more than one feed. If
324             you want to register all the feeds, use the one argument form of
325             C<register> instead of the two argument form.
326              
327             =head2 No feed named %s
328              
329             The feed that you requested does not exist. Try registering it first.
330              
331             =head1 AUTHOR
332              
333             Jonathan Rockway, C<< <jrockway at cpan.org> >>
334              
335             =head1 BUGS
336              
337             Please report any bugs or feature requests to
338             C<bug-catalyst-model-xml-feed at rt.cpan.org>, or through the web interface at
339             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-Model-XML-Feed>.
340             I will be notified, and then you'll automatically be notified of progress on
341             your bug as I make changes.
342              
343             =head1 SUPPORT
344              
345             You can find documentation for this module with the perldoc command.
346              
347             perldoc Catalyst::Model::XML::Feed
348              
349             You can also look for information at:
350              
351             =over 4
352              
353             =item * AnnoCPAN: Annotated CPAN documentation
354              
355             L<http://annocpan.org/dist/Catalyst-Model-XML-Feed>
356              
357             =item * CPAN Ratings
358              
359             L<http://cpanratings.perl.org/d/Catalyst-Model-XML-Feed>
360              
361             =item * RT: CPAN's request tracker
362              
363             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-Model-XML-Feed>
364              
365             =item * Search CPAN
366              
367             L<http://search.cpan.org/dist/Catalyst-Model-XML-Feed>
368              
369             =back
370              
371             =head1 SEE ALSO
372              
373             L<XML::Feed> and L<XML::Feed::Entry>
374              
375             =head1 ACKNOWLEDGEMENTS
376              
377             =head1 COPYRIGHT & LICENSE
378              
379             Copyright 2006 Jonathan Rockway, all rights reserved.
380              
381             This program is free software; you can redistribute it and/or modify it
382             under the same terms as Perl itself.
383              
384             =cut
385              
386             1; # End of Catalyst::Model::XML::Feed