File Coverage

blib/lib/CloudApp/REST.pm
Criterion Covered Total %
statement 21 127 16.5
branch 0 34 0.0
condition 0 27 0.0
subroutine 7 19 36.8
pod 8 8 100.0
total 36 215 16.7


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