File Coverage

blib/lib/Crashplan/Client.pm
Criterion Covered Total %
statement 72 206 34.9
branch 19 68 27.9
condition 1 28 3.5
subroutine 16 35 45.7
pod 20 20 100.0
total 128 357 35.8


line stmt bran cond sub pod time code
1             package Crashplan::Client;
2              
3 8     8   245826 use warnings;
  8         20  
  8         276  
4 8     8   47 use strict;
  8         16  
  8         271  
5              
6 8     8   40 use Carp;
  8         20  
  8         707  
7 8     8   9140 use MIME::Base64;
  8         8287  
  8         612  
8 8     8   8226 use REST::Client;
  8         661251  
  8         277  
9 8     8   11167 use JSON;
  8         137607  
  8         51  
10              
11 8     8   6513 use Crashplan::Client::Org;
  8         22  
  8         348  
12 8     8   4276 use Crashplan::Client::User;
  8         20  
  8         220  
13 8     8   4894 use Crashplan::Client::Computer;
  8         18  
  8         228  
14 8     8   5661 use Crashplan::Client::ComputerUsage;
  8         21  
  8         214  
15 8     8   4373 use Crashplan::Client::MountPoint;
  8         19  
  8         208  
16 8     8   4870 use Crashplan::Client::ServerStatistics;
  8         24  
  8         223  
17              
18 8     8   8708 use Data::Dumper;
  8         61284  
  8         20091  
19              
20             =head1 NAME
21              
22             Crashplan::Client - Client to the Crashplan PROe server
23              
24             =head1 VERSION
25              
26             Version 0.003.0
27              
28             =cut
29              
30             our $VERSION = '0.003_0';
31              
32             =head1 SYNOPSIS
33              
34             Crashplan::Client allow you to access an Crashplan PROe server (hopefully) in a easy way.
35              
36             This version only provides a low level API matching part of the server REST API.
37              
38             This version (0.3.0) extends the new highlevel API which add syntaxic sugar.
39              
40             What you can do now :
41              
42             use Crashplan::Client;
43              
44             my $client = Crashplan::Client->new();
45            
46             # Get all Orgs entity
47             my @orgs = $client->orgs;
48             my $org = shift @orgs;
49              
50             # Get all Orgs active entity entity
51             my @orgs = $client->orgs(status => 'Active');
52             my $org = shift @orgs;
53              
54             # Modify an org and update the server
55             $org->name('My Org');
56             $org->update;
57              
58             # Create an org and modify it
59             use Crashplan::Client::Org;
60             my $neworg = Crashplan::Client::Org->new(name => 'New Org', parentId => 3);
61             $client->create($neworg);
62             $neworg->name('No longer new Org');
63             $neworg->update;
64            
65             ...
66              
67             It's planned to offer (NOT IMPLEMENTED YET) something more like :
68              
69             use Crashplan::Client;
70              
71             my $client = Crashplan::Client->new();
72            
73             my $org = $client->orgs->first;
74              
75             ...
76              
77              
78             The first lowlevel API is still present
79              
80             use Crashplan::Client;
81              
82             my $client = Crashplan::Client->new();
83            
84             $client->GET('/rest/orgs');
85             my @orgs = $client->parse_response;
86             my $org = shift @orgs;
87              
88             ...
89              
90              
91             =head1 SUBROUTINES/METHODS - Highlevel API
92              
93             =head2 new ()
94              
95             Constructor for the Crashplan::Client class
96              
97             =cut
98              
99             sub new {
100 6     6 1 3865 my $class = shift;
101 6         17 my $params = shift;
102              
103 6         23 my $self = bless {}, $class;
104              
105 6         33 for my $key ( keys %$params ) {
106 0 0       0 if ( $key =~ /^server$/i ) { $self->{'server'} = $params->{$key}; next }
  0         0  
  0         0  
107 0 0       0 if ( $key =~ /^user$/i ) { $self->{'user'} = $params->{$key}; next }
  0         0  
  0         0  
108 0 0       0 if ( $key =~ /^password$/i ) {
109 0         0 $self->{'password'} = $params->{$key};
110 0         0 next;
111             }
112 0         0 carp "Unknown paramseter $key";
113 0         0 return undef;
114             }
115              
116 6 50       55 if ( $self->{server} ) {
117 0         0 $self->{rest} = REST::Client->new();
118 0   0     0 my $user = $self->{user} || '';
119 0   0     0 my $pass = $self->{password} || '';
120 0         0 my $creds = encode_base64($user.":".$pass);
121 0         0 $self->set_header(Authorization => "Basic $creds");
122 0         0 $self->set_header(Accept => "application/json");
123             # Automatically follow redirect
124 0         0 $self->{rest}->setFollow(1);
125 0         0 $self->{rest}->setHost($self->{server});
126             }
127              
128 6         27 return $self
129             }
130              
131             =head2 create
132              
133             Create an entity entry in the database
134              
135             =cut
136              
137             sub create {
138 0     0 1 0 my $self = shift;
139 0         0 my $entity = shift;
140              
141             # Filter out non REST attribute from the current object
142 0         0 my @attributes = grep {!/^rest|rest_header$/} keys %$entity;
  0         0  
143              
144 0         0 my $body = encode_json( { map {$_ => $entity->{$_}} @attributes} );
  0         0  
145              
146 0         0 $self->POST($entity->url,$body);
147              
148 0         0 my $return = undef;
149 0         0 my $param = decode_json($self->responseContent);
150              
151 0         0 $return = $entity->new($param);
152              
153             }
154              
155             =head2 users ([$property => $value])
156              
157             Return all the users entity from the server
158              
159             Input : $property the property to be used to filter the result list
160             (currently this can be : id, email, status, username, firstName, lastName, orgId)
161             $value only the entity matching $property = $value will be returned
162              
163             Output : An array of Crashplan::Client::User
164              
165             =cut
166              
167             sub users {
168 0     0 1 0 my $self = shift;
169 0         0 my ($property, $value) = @_;
170              
171 0         0 my $filter = '';
172              
173 0 0       0 if (lc $property eq 'id') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
174 0         0 $filter = "/$value";
175             } elsif (lc $property eq 'email') {
176 0         0 $filter = "?email=$value";
177             } elsif (lc $property eq 'username') {
178 0         0 $filter = "?username=$value";
179             } elsif (lc $property eq 'status') {
180 0         0 $filter = "?status=$value";
181             } elsif (lc $property eq 'firstname') {
182 0         0 $filter = "?firstName=$value";
183             } elsif (lc $property eq 'lastname') {
184 0         0 $filter = "?lastName=$value";
185             } elsif (lc $property eq 'orgid') {
186 0         0 $filter = "?orgId=$value";
187             } elsif (defined $property) {
188 0         0 croak "Unrecognized filter in users() : $property";
189             }
190            
191 0         0 $self->GET('/rest/users'.$filter);
192              
193 0         0 return $self->parse_response;
194             }
195              
196             =head2 orgs ([$property => $value])
197              
198             Return all the orgs entity from the server
199              
200             Input : $property the property to be used to filter the result list
201             (currently this can be : id, name, status, parentId)
202             $value only the entity matching $property = $value will be returned
203              
204             Output : An array of Crashplan::Client::Org
205              
206             =cut
207              
208             sub orgs {
209 0     0 1 0 my $self = shift;
210 0         0 my ($property, $value) = @_;
211              
212 0         0 my $filter = '';
213              
214 0 0       0 if (lc $property eq 'id') {
    0          
    0          
    0          
215 0         0 $filter = "/$value";
216             } elsif (lc $property eq 'name') {
217 0         0 $filter = "?name=$value";
218             } elsif (lc $property eq 'parentid') {
219 0         0 $filter = "?parentId=$value";
220             } elsif (defined $property) {
221 0         0 croak "Unrecognized filter in orgs() : $property";
222             }
223            
224 0         0 $self->GET('/rest/orgs'.$filter);
225              
226 0         0 return $self->parse_response;
227             }
228              
229             =head2 computers ()
230              
231             Return all the computers entity from the server
232              
233             Input : $property the property to be used to filter the result list
234             (currently this can be : id, name, status, guid, $userid)
235             $value only the entity matching $property = $value will be returned
236              
237             Output : An array of Crashplan::Client::Computer
238              
239             =cut
240              
241             sub computers {
242 0     0 1 0 my $self = shift;
243 0         0 my ($property, $value) = @_;
244              
245 0         0 my $filter = '';
246              
247 0 0       0 if (lc $property eq 'id') {
    0          
    0          
    0          
    0          
    0          
248 0         0 $filter = "/$value";
249             } elsif (lc $property eq 'status') {
250 0         0 $filter = "?status=$value";
251             } elsif (lc $property eq 'name') {
252 0         0 $filter = "?name=$value";
253             } elsif (lc $property eq 'guid') {
254 0         0 $filter = "?guid=$value";
255             } elsif (lc $property eq 'userid') {
256 0         0 $filter = "?userId=$value";
257             } elsif (defined $property) {
258 0         0 croak "Unrecognized filter in computers() : $property";
259             }
260            
261 0         0 $self->GET('/rest/computers'.$filter);
262              
263 0         0 return $self->parse_response;
264             }
265              
266             =head2 serverstatistics ()
267              
268             Return all the serverStatistics entity from the server
269              
270             Input : None
271              
272             Output : An array of Crashplan::Client::Computer
273              
274             =cut
275              
276             sub serverstatistics {
277 0     0 1 0 my $self = shift;
278            
279 0         0 $self->GET('/rest/serverStats');
280              
281 0         0 return $self->parse_response;
282             }
283              
284             =head2 user ($id)
285              
286             Return the user entity whose id is passed as parameter
287              
288             Input : None
289              
290             Output : A Crashplan::Client::User object
291              
292             =cut
293              
294             sub user {
295 0     0 1 0 my $self = shift;
296 0         0 my $id = shift;
297            
298 0         0 $self->GET("/rest/users/$id");
299              
300 0         0 return $self->parse_response;
301             }
302              
303             =head2 computer ($id)
304              
305             Return the computer entity whose id is passed as parameter
306              
307             Input : None
308              
309             Output : A Crashplan::Client::Computer object
310              
311             =cut
312              
313             sub computer {
314 0     0 1 0 my $self = shift;
315 0         0 my $id = shift;
316            
317 0         0 $self->GET("/rest/computers/$id");
318              
319 0         0 return $self->parse_response;
320             }
321              
322             =head2 org ($id)
323              
324             Return the org entity whose id is passed as parameter
325              
326             Input : None
327              
328             Output : A Crashplan::Client::Org object
329              
330             =cut
331              
332             sub org {
333 0     0 1 0 my $self = shift;
334 0         0 my $id = shift;
335            
336 0         0 $self->GET("/rest/orgs/$id");
337              
338 0         0 return $self->parse_response;
339             }
340              
341             =head1 SUBROUTINES/METHODS - Lowlevel API
342              
343             =head2 get_full_header ()
344              
345             Get the REST header use by the inner REST::Client
346              
347             Input : None
348              
349             Output : A hashref to the current REST header
350              
351             =cut
352              
353             sub get_full_header {
354 0     0 1 0 my $self = shift;
355 0         0 my $key = shift;
356 0         0 my $value = shift;
357              
358 0         0 return $self->{rest_header};
359             }
360              
361              
362             =head2 set_header ($key, $value)
363              
364             Set a rest header
365              
366             Input : header, value the name and the value of the header to be set
367              
368             Output : None
369              
370             =cut
371              
372             sub set_header{
373 0     0 1 0 my $self = shift;
374 0         0 my $key = shift;
375 0         0 my $value = shift;
376              
377 0         0 $self->{rest_header}{$key} = $value;
378             }
379              
380             =head2 unset_header ($key)
381              
382             Unset a rest header
383              
384             Input : $key the name of the header to be unset
385              
386             Output : None
387              
388             =cut
389              
390             sub unset_header {
391 0     0 1 0 my $self = shift;
392 0         0 my $key = shift;
393 0         0 my $value = shift;
394              
395 0         0 delete $self->{rest_header}{$key};
396             }
397              
398             =head2 request ($method, $url [,$content, $header_ref])
399              
400             Request against the rest API
401              
402             Input : $method the method to be used (GET, POST, PUT, DELETE)
403             $url the url to be used with the server
404             $content (OPTIONAL) content of the request
405             $header (OPTIONAL) hash reference of a header
406              
407             Output : None
408             Will set internal attributes responseCode and responseContent
409              
410             =cut
411              
412             sub request {
413 0     0 1 0 my $self = shift;
414 0         0 my $method = shift;
415 0         0 my $url = shift;
416 0   0     0 my $content = shift || '' ;
417 0   0     0 my $header = shift || $self->get_full_header;
418              
419 0         0 $self->{'rest'}->request( $method, $url, $content, $header );
420             }
421              
422             =head2 responseContent ()
423              
424             Get the response content (for the previous request)
425              
426             Input : None
427              
428             Output : A string with the response as a JSON structure
429              
430             =cut
431              
432             sub responseContent {
433 0     0 1 0 my $self = shift;
434              
435 0         0 return $self->{'rest'}->responseContent;
436             }
437              
438             =head2 responseCode ()
439              
440             Get the response code (for the previous request)
441              
442             Input : None
443              
444             Output : An integer
445              
446             =cut
447              
448             sub responseCode {
449 0     0 1 0 my $self = shift;
450              
451 0         0 return $self->{'rest'}->responseCode;
452             }
453              
454             =head2 default_header ()
455              
456             Build a default header based on $self object attribute
457             In particular user and password attributes are used to
458             build the Basic Authentication credentials.
459              
460             Input : None
461              
462             Output : A hash ref
463              
464             =cut
465              
466             sub default_header {
467 0     0 1 0 my $self = shift;
468              
469 0   0     0 my $user = $self->{user} || '';
470 0   0     0 my $pass = $self->{password} || '';
471              
472 0         0 my $creds = encode_base64($user.":".$pass);
473 0         0 $self->set_header(Authorization => "Basic $creds");
474 0         0 $self->set_header(Accept => "application/json");
475            
476 0         0 return $self->get_full_header;
477             #return { Authorization => "Basic $creds", "Accept" => "application/json" };
478              
479             }
480              
481             =head2 GET ($url [,$header])
482              
483             GET request against the REST server
484              
485             Input : $url the url to be requested
486              
487             Output : None
488             The state of the response is store in the internal 'rest' attribute which is
489             currently an REST::Client object
490              
491             =cut
492              
493             sub GET {
494 0     0 1 0 my $self = shift;
495 0         0 my $url = shift;
496 0   0     0 my $header = shift || $self->get_full_header;
497            
498 0         0 $self->{rest}->GET( $url, $header );
499             }
500              
501              
502             =head2 POST ($url [, $body [,$header]])
503              
504             POST request against the REST server
505              
506             Input : $url the url to be requested, $body the content in JSON format
507              
508             Output : None
509             The state of the response is store in the internal 'rest' attribute which is
510             currently an REST::Client object
511              
512             =cut
513              
514             sub POST {
515 0     0 1 0 my $self = shift;
516 0         0 my $url = shift;
517 0         0 my $body = shift;
518              
519 0         0 $self->set_header("Content-Type","application/json");
520 0   0     0 my $header = shift || $self->get_full_header;
521              
522 0         0 $self->{rest}->POST( $url, $body, $header );
523             }
524              
525             =head2 PUT ($url, [$body [,$header]])
526              
527             PUT request against the REST server
528              
529             Input : $url the url to be requested, $body the content in JSON format
530              
531             Output : None
532             The state of the response is store in the internal 'rest' attribute which is
533             currently an REST::Client object
534              
535             =cut
536              
537             sub PUT {
538 0     0 1 0 my $self = shift;
539 0         0 my $url = shift;
540 0         0 my $body = shift;
541            
542 0         0 $self->set_header("Content-Type","application/json");
543 0   0     0 my $header = shift || $self->get_full_header;
544              
545 0         0 $self->{rest}->PUT( $url, $body, $header );
546             }
547              
548             =head2 DELETE ($url)
549              
550             DELETE request against the REST server
551              
552             Input : $url the url to be requested
553              
554             Output : None
555             The state of the response is store in the internal 'rest' attribute which is
556             currently an REST::Client object
557              
558             =cut
559              
560             sub DELETE {
561 0     0   0 my $self = shift;
562 0         0 my $url = shift;
563 0         0 my $body = shift;
564            
565 0         0 $self->set_header("Content-Type","application/json");
566 0   0     0 my $header = shift || $self->get_full_header;
567              
568 0         0 $self->{rest}->DELETE( $url, $body, $header );
569             }
570              
571             =head2 parse_response ()
572              
573             Parse a server response to populate Crashplan objects
574              
575             Input : None (use $self->responseContent)
576              
577             Output : Array or single Crashplan::Client:: object based on the
578             previous request answer
579              
580             =cut
581              
582             sub parse_response {
583 6     6 1 4856 my $self = shift;
584 6   33     33 my $response = shift || $self->responseContent;
585              
586 6 50       38 return undef if $response eq 'Not Found';
587              
588 6         39 $self->{responses} = from_json($response);
589              
590              
591             #
592             # If a list is returned 2 keys at least are available metadata and the requested entity
593             #
594 6         508 for my $entity (keys %{$self->{responses}}) {
  6         31  
595 31 100       194 if ($entity =~ /orgs/) {
    100          
    100          
    100          
    100          
596 1         5 return _populate('Crashplan::Client::Org', $self->{responses}{$entity},$self);
597             } elsif ($entity =~ /users/) {
598 1         6 return _populate('Crashplan::Client::User', $self->{responses}{$entity}, $self);
599             } elsif ($entity =~ /computers/) {
600 1         7 return _populate('Crashplan::Client::Computer', $self->{responses}{$entity}, $self);
601             } elsif ($entity =~ /computerUsages/) {
602 1         4 return _populate('Crashplan::Client::ComputerUsage', $self->{responses}{$entity}, $self);
603             } elsif ($entity =~ /mountPoints/) {
604 1         5 return _populate('Crashplan::Client::MountPoint', $self->{responses}{$entity}, $self);
605             }
606             }
607             #
608             # If only a single entity try to guess type based on specific attribute
609             #
610              
611 1 50       18 if (exists $self->{responses}{parentId}) {
    50          
    50          
    50          
612 0         0 return _populate('Crashplan::Client::Org', $self->{responses}, $self);
613             } elsif ($self->{responses}{mountPointId}) {
614 0         0 return _populate('Crashplan::Client::Computer', $self->{responses}, $self);
615             } elsif ($self->{responses}{email}) {
616 0         0 return _populate('Crashplan::Client::User', $self->{responses}, $self);
617             } elsif ($self->{responses}{cpuUtilization}) {
618 1         6 return _populate('Crashplan::Client::ServerStatistics', $self->{responses}, $self);
619             };
620              
621            
622             }
623              
624             =head2 _populate ($entity_name, $hashref, $crashplanclien)
625              
626             Return an array of Crashplan::Client::$entity_name objects from the $hashref.
627              
628             Input : $entity_name = The class name
629             $hashref = The hash ref used by the constructor
630             $crashplanclient = the Crashplan::Client reference
631              
632             Output : Array of object
633              
634             =cut
635              
636             sub _populate {
637 6     6   14 my $class = shift;
638 6         15 my $ref = shift;
639 6         13 my $cc = shift; #Crashplan::Client
640              
641 6         12 my @result;
642 6 100       39 if (ref($ref) =~ /ARRAY/) {
    50          
643 5         12 foreach my $entity (@$ref) {
644 25         109 my $object = $class->new($entity);
645 25         65 $object->{rest} = $cc;
646 25         54 push @result, $object;
647             }
648 5         33 return @result;
649             } elsif (ref($ref) =~ /HASH/) {
650 1         11 my $object = $class->new($ref);
651 1         6 $object->{rest} = $cc;
652 1         5 return $object;
653             }
654              
655             }
656              
657             =head1 TESTING
658              
659             To enable testing against a Crashplan server,
660             set the following environment variables before running 'make test' .
661              
662             TEST_SERVER, TEST_USER, TEST_PASSWORD
663              
664              
665             =head1 AUTHOR
666              
667             Arnaud (Arhuman) ASSAD, C<< >>
668              
669             =head1 BUGS
670              
671             Please report any bugs or feature requests to C, or through
672             the web interface at L. I will be notified, and then you'll
673             automatically be notified of progress on your bug as I make changes.
674              
675             =head1 SUPPORT
676              
677             You can find documentation for this module with the perldoc command.
678              
679             perldoc Crashplan::Client
680              
681              
682             You can also look for information at:
683              
684             =over 4
685              
686             =item * RT: CPAN's request tracker
687              
688             L
689              
690             =item * AnnoCPAN: Annotated CPAN documentation
691              
692             L
693              
694             =item * CPAN Ratings
695              
696             L
697              
698             =item * Search CPAN
699              
700             L
701              
702             =back
703              
704              
705             =head1 ACKNOWLEDGEMENTS
706              
707              
708             =head1 LICENSE AND COPYRIGHT
709              
710             Copyright 2011 Arnaud (Arhuman) ASSAD.
711              
712             This program is free software; you can redistribute it and/or modify it
713             under the terms of either: the GNU General Public License as published
714             by the Free Software Foundation; or the Artistic License.
715              
716             See http://dev.perl.org/licenses/ for more information.
717              
718              
719             =head1 SEE ALSO
720              
721             For a detailed API description,
722             see http://support.crashplanpro.com/doku.php/api
723              
724             =cut
725              
726             1; # End of Crashplan::Client