File Coverage

blib/lib/CloudApp/REST.pm
Criterion Covered Total %
statement 21 123 17.0
branch 0 34 0.0
condition 0 23 0.0
subroutine 7 18 38.8
pod 7 7 100.0
total 35 205 17.0


line stmt bran cond sub pod time code
1             package CloudApp::REST;
2              
3 2     2   47182 use Moose;
  2         580127  
  2         13  
4 2     2   10101 use MooseX::Types::URI qw(Uri);
  2         276571  
  2         13  
5              
6 2     2   3802 use LWP::UserAgent;
  2         56628  
  2         58  
7 2     2   14 use HTTP::Request;
  2         4  
  2         40  
8 2     2   1116 use JSON::XS;
  2         7333  
  2         95  
9 2     2   745 use Module::Load;
  2         1489  
  2         9  
10 2     2   1067 use Data::Dumper;
  2         9114  
  2         2370  
11              
12             =head1 NAME
13              
14             CloudApp::REST - Perl Interface to the CloudApp REST API
15              
16             =head1 VERSION
17              
18             Version 0.01_05
19              
20             =cut
21              
22             our $VERSION = '0.01_05';
23              
24             has useragent => (
25             is => 'ro',
26             required => 0,
27             isa => 'LWP::UserAgent',
28             lazy => 1,
29             default => sub {
30             my $self = shift;
31             my $ua = LWP::UserAgent->new;
32             $ua->agent($self->agent_name);
33             $ua->proxy('http', $self->proxy) if $self->proxy;
34             return $ua;
35             },
36             clearer => '_reset_useragent',
37             );
38              
39             has debug => (is => 'rw', required => 0, isa => 'Bool', default => 0);
40              
41             has agent_name => (is => 'rw', required => 0, isa => 'Str', default => __PACKAGE__ . "/" . $VERSION);
42             has private_base_url => (is => 'rw', required => 0, isa => Uri, coerce => 1, default => sub { to_Uri('http://my.cl.ly/') });
43             has public_base_url => (is => 'rw', required => 0, isa => Uri, coerce => 1, default => sub { to_Uri('http://cl.ly/') });
44             has fileupload_url => (is => 'rw', required => 0, isa => Uri, coerce => 1, default => sub { to_Uri('http://f.cl.ly') });
45              
46             has auth_netloc => (is => 'rw', required => 0, isa => 'Str', default => 'my.cl.ly:80');
47             has auth_realm => (is => 'rw', required => 0, isa => 'Str', default => 'Application');
48              
49             has username => (is => 'rw', required => 0, isa => 'Str');
50             has password => (is => 'rw', required => 0, isa => 'Str');
51              
52             has proxy => (is => 'rw', required => 0, isa => Uri, coerce => 1);
53              
54             =head1 SYNOPSIS
55              
56             This is a Perl Interface to the CloudApp REST API. You can find more information about
57             CloudApp at L<http://www.getcloudapp.com/>.
58              
59             Here's an example on how to retrieve the last 5 items:
60              
61             use CloudApp::REST;
62            
63             my $cl = CloudApp::REST->new;
64            
65             $cl->username('email@example.com');
66             $cl->password('my_supersafe_secret');
67            
68             my $items = $cl->get_items;
69              
70             =head1 SUBROUTINES/METHODS
71              
72             =head2 new
73              
74             Creates and returns a new instance.
75              
76             =head2 username
77              
78             Parameters:
79              
80             =over
81              
82             =item C<$username>
83              
84             =back
85              
86             Sets the username for requests that need authentication. Unless you only use L</get_item>
87             a username is required.
88              
89             =head2 password
90              
91             Parameters:
92              
93             =over
94              
95             =item C<$password>
96              
97             =back
98              
99             Sets the password for requests that need authentication. Unless you only use L</get_item>
100             a password is required.
101              
102             =head2 get_item
103              
104             Parameters:
105              
106             =over
107              
108             =item C<\%params>
109              
110             =back
111              
112             Gets a single item from CloudApp and returns the appropriate C<CloudApp::REST::Item::*> module.
113             Only one of the following parameters should be given. However, if C<uri> is given, C<slug>
114             is ignored.
115              
116             =over 4
117              
118             =item I<uri =E<gt> $uri>
119              
120             The URI to the CloudApp item, eg. C<http://cl.ly/abc123>.
121              
122             Basically this can be an arbitraty URI pointing anywhere, as long as the app behind it
123             supports the CloudApp API.
124              
125             =item I<slug =E<gt> $slug>
126              
127             The so called C<slug> of an CloudApp Item, eg. C<abc123> for the item at C<http://cl.ly/abc123>.
128              
129             =back
130              
131             =cut
132              
133             sub get_item {
134 0     0 1   my $self = shift;
135 0           my $params = shift;
136              
137 0   0       my $uri = $params->{uri} || ($params->{slug} ? $self->public_base_url . $params->{slug} : die "No 'uri' or 'slug' given");
138              
139 0           $self->authenticate;
140 0           my $item_attrs = $self->_get_response({ uri => $uri });
141              
142 0           return $self->_build_item($item_attrs);
143             }
144              
145             =head2 get_items
146              
147             Parameters:
148              
149             =over
150              
151             =item C<\%params>
152              
153             =back
154              
155             Gets some or all items from CloudApp, depending on the parameters you pass in. Returns an arrayref
156             or array (depending on your context) of appropriate C<CloudApp::REST::Item::*> objects.
157              
158             =over 4
159              
160             =item I<per_page =E<gt> $n>
161              
162             =item I<limit =E<gt> $n>
163              
164             Sets the maximum count of items per page and/or the maximum items you want to retrieve. If C<per_page>
165             is given, C<limit> is ignored.
166              
167             If not present, defaults to C<5>.
168              
169             =item I<page =E<gt> $n>
170              
171             Sets the current page you want to retrieve items from.
172              
173             Example: If C<per_page> or C<limit> is C<5> and C<page> is C<2>, you will retrieve a maximum of C<5> items
174             starting at number C<6> (1-based). If there are no such items, an empty arrayref is returned.
175             I<B<Note:> this behavior fully depends on the behaviour of the API!>
176              
177             If C<page> and C<offset> are not present, C<page> defaults to C<1>.
178              
179             =item I<offset =E<gt> $n>
180              
181             As an alternative to C<page> you can define an offset. If C<page> is not given but C<offset> is, C<offset>
182             is divided by C<per_page> and then converted to an integer. The result is then used as C<page>.
183              
184             =item I<type =E<gt> $type>
185              
186             If you want to get only a specific type of items, set C<type> to an appropriate value. The value should
187             be the last part of the module name of the appropriate C<CloudApp::REST::Item::*> class in lower case, eg.
188             C<archive> for C<CloudApp::REST::Item::Archive>. If you set C<type> to a value that is not an item type,
189             an empty list will be returned by this method.
190              
191             =item I<deleted =E<gt> $bool>
192              
193             Set to a true value if you want only items from the trash. Defaults to C<false>. You may want
194             to use the shortcut L</get_trash> instead.
195              
196             =back
197              
198             =cut
199              
200             sub get_items {
201 0     0 1   my $self = shift;
202 0           my $params = shift;
203              
204 0   0       my $per_page = $params->{per_page} || $params->{limit} || 5;
205 0   0       my $page = $params->{page} || ($params->{offset} ? int($params->{offset} / $per_page) : 1);
206 0 0         my $type = $params->{type} ? "&type=" . $params->{type} : '';
207 0 0         my $deleted = $params->{deleted} ? 'true' : 'false';
208              
209 0           $self->authenticate;
210 0           my $hashed_items = $self->_get_response({ uri => $self->private_base_url . "items?page=$page&per_page=$per_page&deleted=$deleted" . $type });
211              
212 0           return $self->_build_items($hashed_items);
213             }
214              
215             =head2 get_trash
216              
217             Parameters:
218              
219             =over
220              
221             =item C<\%params>
222              
223             =back
224              
225             Accepts the same parameters as L</get_items>, except for C<deleted>. L</get_trash> is
226             nly a small wrapper around L</get_items>.
227              
228             =cut
229              
230             sub get_trash {
231 0     0 1   my $self = shift;
232 0           my $params = shift;
233              
234 0           $params->{deleted} = 1;
235 0           return $self->get_items($params);
236             }
237              
238             =head2 create_bookmark
239              
240             Parameters:
241              
242             =over
243              
244             =item C<\%params>
245              
246             =back
247              
248             Creates a bookmark at CloudApp and returns the newly created bookmark as a L<CloudApp::REST::Item::Bookmark> object.
249              
250             =over 4
251              
252             =item I<name =E<gt> $name>
253              
254             I<Required.>
255              
256             The name of the bookmark, eg. C<12. Deutscher Perl Workshop>.
257              
258             =item I<uri =E<gt> $uri>
259              
260             I<Required.>
261              
262             The URI of the bookmark, eg. C<http://conferences.yapceurope.org/gpw2010/>.
263              
264             =back
265              
266             =cut
267              
268             sub create_bookmark {
269 0     0 1   my $self = shift;
270 0           my $params = shift;
271              
272 0 0 0       die "Provide 'name' and 'uri'" unless $params->{name} && $params->{uri};
273              
274 0           $self->authenticate;
275             my $bookmark = $self->_get_response(
276             {
277             uri => $self->private_base_url . "items",
278             params => {
279             item => {
280             name => $params->{name},
281             redirect_url => $params->{uri},
282             }
283             }
284             }
285 0           );
286              
287 0           return $self->_build_item($bookmark);
288             }
289              
290             =head2 create_file
291              
292             Parameters:
293              
294             =over
295              
296             =item C<\%params>
297              
298             =back
299              
300             Uploads a local file to CloudApp and returns the corresponding C<CloudApp::REST::Item::*> object.
301              
302             =over 4
303              
304             =item I<file =E<gt> $path_to_file>
305              
306             I<Required.>
307              
308             The path to the file that will be uploaded. If the file is not accessible or does not exist,
309             L</create_file> dies before trying to upload.
310              
311             =back
312              
313             =cut
314              
315             sub create_file {
316 0     0 1   my $self = shift;
317 0           my $params = shift;
318              
319 0 0         die "Provide 'file'" unless $params->{file};
320 0 0         die "File " . $params->{file} . " does not exist" unless -f $params->{file};
321              
322 0           $self->authenticate;
323 0           my $req_params = $self->_get_response({ uri => $self->private_base_url . "items/new" });
324 0           $req_params->{params}->{file} = $params->{file};
325              
326 0           my $res = $self->_get_response({ uri => $req_params->{url}, params => $req_params->{params} });
327              
328 0 0         return ref $res eq 'ARRAY' ? $self->_build_items($res) : $self->_build_item($res);
329             }
330              
331             =head2 delete_item
332              
333             Parameters:
334              
335             =over
336              
337             =item C<$item>
338              
339             =back
340              
341             Deletes an item at CloudApp. C<$item> has to be an C<CloudApp::REST::Item::*> object.
342              
343             Usually this method is called via L<CloudApp::REST::Item/delete>
344             of a C<CloudApp::REST::Item::*> module object.
345              
346             =cut
347              
348             sub delete_item {
349 0     0 1   my $self = shift;
350 0           my $item = shift;
351              
352 0           $self->authenticate;
353 0           $self->_get_response({ method => 'DELETE', uri => $item->href->path });
354              
355 0           return 1;
356             }
357              
358             =head2 authenticate
359              
360             Parameters:
361              
362             =over
363              
364             =item C<\%params>
365              
366             =back
367              
368             Instead of using L</username> and L</password> directly you can
369             pass along both parameters to L</authenticate> to set the user data.
370              
371             If one of the following parameters are not given, L</authenticate> tries to find them in
372             L</username> or L</password>. If either parameter cannot be found,
373             L</authenticate> dies.
374              
375             =over 4
376              
377             =item I<username =E<gt> $username>
378              
379             =item I<user =E<gt> $username>
380              
381             Username to authenticate with. Use one of them to access L</username>.
382              
383             =item I<password =E<gt> $password>
384              
385             =item I<pass =E<gt> $password>
386              
387             Password to authenticate with. Use one of them to access L</password>.
388              
389             =back
390              
391             B<Note:> the credentails passed through L</authenticate> are B<not> saved within the instance
392             data of L<CloudApp::REST>. As result only one request is handled with authentication, all
393             following will be processed without it. Note that some API calles require authentication
394             and if this data is not present when calling such a method, that method will die.
395              
396             =cut
397              
398             sub authenticate {
399 0     0 1   my $self = shift;
400 0           my $params = shift;
401              
402 0   0       my $user = $params->{username} || $params->{user} || $self->username || die "You have to provide a username";
403 0   0       my $pass = $params->{password} || $params->{pass} || $self->password || die "You have to provide a password";
404              
405 0           $self->useragent->credentials($self->auth_netloc, $self->auth_realm, $user, $pass);
406              
407 0           return 1;
408             }
409              
410             =head1 FLAGS, ATTRIBUTES AND SETTINGS
411              
412             You can control some behaviour by setting different flags or change some attributes
413             or settings. Use them as methods.
414              
415             =over 4
416              
417             =item debug
418              
419             Parameters:
420              
421             =over
422              
423             =item C<$bool>
424              
425             =back
426              
427             Activates the debug mode by passing a true value. Defaults to C<0>. Debug messages are
428             printed with C<warn>.
429              
430             =item agent_name
431              
432             Parameters:
433              
434             =over
435              
436             =item C<$new_name>
437              
438             =back
439              
440             Redefines the name of the user agent, defaults to module name and version.
441              
442             =item private_base_url
443              
444             Parameters:
445              
446             =over
447              
448             =item C<$url>
449              
450             =back
451              
452             The hostname and the scheme of the private area (when auth is needed). Defaults
453             to C<http://my.cl.ly/>. I<Usually there is no need to change this!>
454              
455             =item public_base_url
456              
457             Parameters:
458              
459             =over
460              
461             =item C<$url>
462              
463             =back
464              
465             The hostname and the scheme of the public area (when auth is not needed). Defaults
466             to C<http://cl.ly/>. I<Usually there is no need to change this!>
467              
468             =item auth_netloc
469              
470             Parameters:
471              
472             =over
473              
474             =item C<$netloc>
475              
476             =back
477              
478             The so called C<netloc> for authentication, as L<LWP::UserAgent> requires. Defaults
479             to C<my.cl.ly:80>. I<Usually there is no need to change this!>
480              
481             =item auth_realm
482              
483             Parameters:
484              
485             =over
486              
487             =item C<$real>
488              
489             =back
490              
491             The so-called C<realm> for authentication, as required by L<LWP::UserAgent> and the
492             CloudApp API. Defaults to C<Application>. I<Usually there is no need to change this!>
493              
494             =item proxy
495              
496             Parameters:
497              
498             =over
499              
500             =item C<$proxy_url>
501              
502             =back
503              
504             If you need to set a proxy, use this method. Pass in a proxy URL and port for
505             an C<http> proxy. If not set, no proxy is used.
506              
507             =back
508              
509             =head1 INTERNAL METHODS
510              
511             =head2 _build_item
512              
513             Parameters:
514              
515             =over
516              
517             =item C<\%item>
518              
519             =back
520              
521             Expects an hashref of an item and returns the
522             appropriate C<CloudApp::REST::Item::*> module.
523              
524             =cut
525              
526             sub _build_item {
527 0     0     my $self = shift;
528 0           my $item_attrs = shift;
529              
530 0           my $type = $item_attrs->{item_type};
531              
532 0           $item_attrs->{_REST} = $self;
533 0           foreach (keys %$item_attrs) {
534 0 0         delete $item_attrs->{$_} unless defined $item_attrs->{$_};
535             }
536              
537 0           my $module = __PACKAGE__ . '::Item::' . ucfirst($type);
538 0           load $module;
539              
540 0           my $item_instance = $module->new($item_attrs);
541              
542 0           return $item_instance;
543             }
544              
545             =head2 _build_items
546              
547             Parameters:
548              
549             =over
550              
551             =item C<\@items>
552              
553             =back
554              
555             Expects an arrayref of items and returns a list
556             of appropriate C<CloudApp::REST::Item::*> objects as arrayref or array,
557             depending on your context.
558              
559             =cut
560              
561             sub _build_items {
562 0     0     my $self = shift;
563 0           my $hashed_items = shift;
564              
565 0           my @items;
566 0           foreach my $item_attrs (@$hashed_items) {
567 0           push @items, $self->_build_item($item_attrs);
568             }
569              
570 0 0         return wantarray ? @items : \@items;
571             }
572              
573             =head2 _get_response
574              
575             Parameters:
576              
577             =over
578              
579             =item C<\%params>
580              
581             =back
582              
583             Executes each request and communicates with the CloudApp API.
584              
585             =over 4
586              
587             =item I<uri =E<gt> $uri>
588              
589             The URI that is requested, eg. C<http://my.cl.ly/items?page=1&per_page=5>.
590              
591             =item I<method =E<gt> $method>
592              
593             The HTTP method of the request type. If the parameter C<params> to L</_get_response>
594             is set, C<method> is ignored and set to C<POST>, otherwise to the value of C<method>. Defaults
595             to C<GET> in all other cases.
596              
597             =item I<params =E<gt> \%params>
598              
599             If C<params> is set, the keys and values are used as C<POST> parameters with their values,
600             the HTTP method is set to C<POST>.
601              
602             If C<params> has a key C<file>, this method tries to upload that file. However, it is not
603             checked if the file exists (you need to do this by yourself if you use this method directly).
604              
605             =item I<noredirect =E<gt> $bool>
606              
607             If C<noredirect> is set to a true value, this method won't follow any redirects.
608              
609             =back
610              
611             I<Some notes:>
612              
613             =over 4
614              
615             =item
616              
617             After each call, the current user agent instance is destroyed. This is done to
618             reset the redirect status so that the next request won't contain auth data
619             unless required.
620              
621             =item
622              
623             This method handles all HTTP status codes that are considered as C<successful>
624             (all C<2xx> codes) and the codes C<302> and C<303>. If other status codes are returned,
625             the request is considered an error and the method dies.
626              
627             =back
628              
629             =cut
630              
631             sub _get_response {
632 0     0     my $self = shift;
633 0           my $params = shift;
634              
635 0   0       my $uri = $params->{uri} || die "No URI given!";
636 0           my $method = $params->{method};
637 0 0         my %body = $params->{params} ? %{ $params->{params} } : ();
  0            
638              
639 0 0         $self->useragent->requests_redirectable([]) if $params->{noredirect};
640              
641 0           my $res;
642 0 0         unless (exists $body{file}) {
643 0           $self->_debug("New request, URI is $uri");
644 0           my $req = HTTP::Request->new;
645 0           $req->header(Accept => 'application/json');
646 0           $req->content_type('application/json');
647 0           $req->uri($uri);
648              
649 0           $req->method('GET');
650 0 0         if (%body) {
651 0           $self->_debug("Have content, method will be POST");
652              
653 0           my $body_json = encode_json \%body;
654 0           $req->content($body_json);
655 0           $req->method('POST');
656             }
657 0 0 0       if (defined $method && $method) {
658 0           $self->_debug("Explicit method $method");
659 0           $req->method($method);
660             }
661              
662 0           $res = $self->useragent->request($req);
663             } else {
664 0           my $file = delete $body{file};
665 0           $res = $self->useragent->post($uri, [%body, file => [$file]], Content_Type => 'form-data');
666             }
667              
668 0           $self->_reset_useragent;
669              
670 0 0 0       if ($res->is_success) {
    0          
671 0           $self->_debug("Request successful: " . $res->code);
672 0           $self->_debug("Content: '" . $res->content . "'");
673 0 0         if ($res->content !~ /^\s*$/) {
674 0           return decode_json($res->content);
675             } else {
676 0           return undef;
677             }
678             } elsif ($res->code == 303 || $res->code == 302) {
679 0           $self->authenticate;
680 0           my $location = to_Uri($res->header('Location'));
681 0           my %params = map { $_ => $location->query_param($_) } $location->query_param;
  0            
682 0           return $self->_get_response({ uri => $res->header('Location'), noredirect => 1 });
683             } else {
684 0           die "Request error: " . $res->status_line . Dumper($res);
685             }
686             }
687              
688             =head2 _debug
689              
690             Parameters:
691              
692             =over
693              
694             =item C<@msgs>
695              
696             =back
697              
698             Small debug message handler that C<warn>s C<@msgs> joined with a line break. Only prints if C<debug> set to C<true>.
699              
700             =cut
701              
702             sub _debug {
703 0     0     my $self = shift;
704 0 0         warn join("\n", @_) . "\n" if $self->debug;
705             }
706              
707             =head1 BUGS
708              
709             Please report any bugs or feature requests to C<bug-cloudapp-api at rt.cpan.org>, or through
710             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CloudApp-REST>. I will be notified, and then you'll
711             automatically be updated on the progress of your report as I make changes.
712              
713             =head1 ACKNOWLEDGEMENTS
714              
715             Thanks to linebreak L<http://www.bylinebreak.com/> for making such a cool application,
716             CloudApp. Go get yourself an account at L<http://www.getcloudapp.com/>!
717              
718             =head1 SEE ALSO
719              
720             L<CloudApp::REST::Item>
721              
722             L<CloudApp::REST::Item::Archive>
723              
724             L<CloudApp::REST::Item::Audio>
725              
726             L<CloudApp::REST::Item::Bookmark>
727              
728             L<CloudApp::REST::Item::Image>
729              
730             L<CloudApp::REST::Item::Pdf>
731              
732             L<CloudApp::REST::Item::Text>
733              
734             L<CloudApp::REST::Item::Unknown>
735              
736             L<CloudApp::REST::Item::Video>
737              
738             =head1 AUTHOR
739              
740             Matthias Dietrich, C<< <perl@rainboxx.de> >>
741              
742             L<http://www.rainboxx.de>
743              
744             =head1 LICENSE AND COPYRIGHT
745              
746             Copyright 2010 Matthias Dietrich.
747              
748             This program is free software; you can redistribute it and/or modify it
749             under the terms of either: the GNU General Public License as published
750             by the Free Software Foundation; or the Artistic License.
751              
752             See http://dev.perl.org/licenses/ for more information.
753              
754              
755             =cut
756              
757             1; # End of CloudApp::REST