File Coverage

blib/lib/WWW/FBX.pm
Criterion Covered Total %
statement 33 86 38.3
branch 0 28 0.0
condition 0 24 0.0
subroutine 11 21 52.3
pod n/a
total 44 159 27.6


line stmt bran cond sub pod time code
1             package WWW::FBX;
2 23     23   301800 use Moose;
  23         6783503  
  23         144  
3 23     23   118491 use Carp::Clan qw/^(?:WWW::FBX|Moose|Class::MOP)/;
  23         30757  
  23         143  
4 23     23   12003 use JSON::MaybeXS;
  23         98669  
  23         1112  
5 23     23   106 use Scalar::Util qw/reftype/;
  23         29  
  23         841  
6 23     23   8515 use URI::Escape;
  23         22886  
  23         1074  
7 23     23   9206 use HTTP::Request::Common;
  23         346435  
  23         1265  
8 23     23   8407 use WWW::FBX::Error;
  23         64  
  23         991  
9 23     23   13636 use Encode qw/encode_utf8/;
  23         166898  
  23         1541  
10 23     23   116 use Try::Tiny;
  23         32  
  23         879  
11 23     23   12158 use LWP::UserAgent;
  23         357987  
  23         997  
12              
13             with 'WWW::FBX::Role::API::APIv3';
14             with 'WWW::FBX::Role::Auth';
15            
16 23     23   152 use namespace::autoclean;
  23         31  
  23         177  
17              
18             our $VERSION = "0.18";
19              
20             has base_url => ( isa => 'Str', is => 'ro', default => 'http://mafreebox.free.fr' );
21             has lwp_args => ( isa => 'HashRef', is => 'ro', default => sub { {} } );
22             has [ qw/app_id app_name app_version device_name/ ] => (
23             isa => 'Str', is => 'ro', required => 1 );
24             has ua => ( isa => 'LWP::UserAgent', is => 'rw', lazy => 1, builder => '_build_ua' );
25             has uar => ( isa => 'HashRef', is => 'rw' );
26             has uarh => ( isa => 'HTTP::Response', is => 'rw' );
27             has debug => ( isa => 'Bool', is => 'rw', default => 0, trigger => \&_set_debug );
28              
29             has _json_handler => (
30             is => 'rw',
31             default => sub { JSON->new->allow_nonref },
32             handles => { from_json => 'decode' },
33             );
34              
35             sub _set_debug {
36 0     0     my ( $self, $debug, $odebug) = @_ ;
37 0 0 0       if ( defined $odebug and $odebug != $debug or $debug ) {
      0        
38 0 0         if ($debug) {
39 0     0     $self->ua->add_handler("request_send", sub { print ">" x 25, "\n"; shift->dump; return });
  0            
  0            
  0            
40 0     0     $self->ua->add_handler("response_done", sub { print "<" x 25, "\n"; shift->dump; return });
  0            
  0            
  0            
41             } else {
42 0           $self->ua->remove_handler("request_send");
43 0           $self->ua->remove_handler("response_done");
44             }
45             }
46             }
47            
48             sub _build_ua {
49 0     0     my $self = shift;
50            
51 0           my $ua = LWP::UserAgent->new(%{$self->lwp_args});
  0            
52            
53 0           return $ua;
54             }
55            
56             sub _json_request {
57 0     0     my ($self, $http_method, $uri, $args, $content_type ) = @_;
58            
59 0           my $msg = $self->_prepare_request($http_method, $uri, $args, $content_type);
60 0           my $res = $self->_send_request($msg);
61              
62             #Store response content
63 0           $self->uar( $self->_parse_result ($res, $args ) );
64              
65             #And HTTP response RAW
66 0           $self->uarh( $res );
67              
68 0           return $self->uar->{result};
69             }
70            
71             sub _prepare_request {
72 0     0     my ($self, $http_method, $uri, $args, $content_type ) = @_;
73            
74 0           my $msg;
75            
76 0 0         if( $http_method eq 'PUT' ) {
    0          
    0          
77 0           $msg = PUT( $uri, Content => encode_json $args );
78             }
79             elsif ( $http_method =~ /^(?:GET|DELETE)$/ ) {
80 0 0         $uri->query($self->_query_string_for($args)) if keys %$args;
81 0           $msg = HTTP::Request->new($http_method, $uri);
82             }
83             elsif ( $http_method eq 'POST' ) {
84 0 0 0       if( !$content_type or $content_type eq 'application/json' ) {
    0          
85 0           $msg = POST( $uri, Content_Type => 'application/json', Content => encode_json $args );
86             }
87             elsif ( $content_type eq "form-data" ) {
88 0 0         $msg = POST($uri, Content_Type => 'form-data', Content => [ map { ref $_ ? $_ : encode_utf8 $_ } %$args ]);
  0            
89             }
90             else {
91 0           $msg = POST($uri, Content => $args);
92             }
93             }
94             else {
95 0           croak "unexpected HTTP method: $http_method";
96             }
97              
98 0           return $msg;
99             }
100              
101             sub _query_string_for {
102 0     0     my ( $self, $args ) = @_;
103              
104 0           my @pairs;
105 0           while ( my ($k, $v) = each %$args ) {
106 0           push @pairs, join '=', $k, $v;
107             }
108              
109 0           return join '&', @pairs;
110             }
111              
112 0     0     sub _send_request { shift->ua->request(shift) }
113              
114             sub _parse_result {
115 0     0     my ($self, $res, $args) = @_;
116              
117 0           my $content = $res->content;
118              
119 0 0   0     my $j_obj = length $content ? try { $self->from_json($content) } : {};
  0            
120              
121             #Die if message contains an API error (even on HTTP 200)
122 0 0 0       if ( ref $j_obj && reftype $j_obj eq 'HASH' && (exists $j_obj->{error_code} || exists $j_obj->{msg} ) ) {
      0        
      0        
123 0           die WWW::FBX::Error->new(fbx_error => $j_obj, http_response => $res);
124             }
125              
126             #If no API error and HTTP is 200 and answer is json
127 0 0 0       return $j_obj if $res->is_success && defined $j_obj;
128              
129             #API Download file does not return JSON!!
130             #If answer is 200 and not json, return unchanged (but still pack it in an HashRef for uar type check..)
131 0 0 0       return { result => { filename => $res->filename, content => $content } } if $res->filename and $res->is_success;
132              
133             #Else die on HTTP failures, which might contain a json response or not
134 0           my $error = WWW::FBX::Error->new(http_response => $res);
135 0 0         $error->fbx_error($j_obj) if ref $j_obj;
136              
137 0           die $error;
138             }
139              
140             __PACKAGE__->meta->make_immutable;
141              
142             1;
143             __END__
144              
145             =for html <a href="https://travis-ci.org/architek/WWW-FBX"><img src="https://travis-ci.org/architek/WWW-FBX.svg?branch=master"></a>
146              
147             =encoding utf-8
148              
149             =head1 NAME
150              
151             WWW::FBX - Freebox v6 OS Perl Interface
152              
153             =head1 FREEBOX SDK API 3.0
154              
155             This version provides the API 3.0 support through the APIv3 role but other version can be provided by creating a new role.
156              
157             =head1 AUTHENTICATION
158              
159             Authentication is provided through the Auth role but other authentication mechanism can be provided by creating a new role.
160              
161             =head1 SYNOPSIS
162              
163             use WWW::FBX;
164             use Scalar::Util 'blessed';
165              
166             my $res;
167             eval {
168             my $fbx = WWW::FBX->new(
169             app_id => "APP ID",
170             app_name => "APP NAME",
171             app_version => "1.0",
172             device_name => "MY DEVICE",
173             track_id => "48",
174             app_token => "2/g43EZYD8AO7tbnwwhmMxMuELtTCyQrV1goMgaepHWGrqWlloWmMRszCuiN2ftp",
175             base_url => "http://12.34.56.78:3333",
176             debug => 1,
177             );
178             print "You are now authenticated with track_id ", $fbx->track_id, " and app_token ", $fbx->app_token, "\n";
179             print "App permissions are:\n";
180             while ( my( $key, $value ) = each %{ $fbx->uar->{result}{permissions} } ) {
181             print "\t $key\n" if $value;
182             }
183              
184             $res = $fbx->connection;
185             print "Your ", $res->{media}, " internet connection state is ", $res->{state}, "\n";
186             $fbx->set_ftp_config( {enabled => \1} );
187             $fbx->reset_freeplug( "F4:CA:E5:DE:AD:BE/reset" );
188             };
189              
190             if ( my $err = $@ ) {
191             die $@ unless blessed $err && $err->isa('WWW::FBX::Error');
192              
193             warn "HTTP Response Code: ", $err->code, "\n",
194             "HTTP Message......: ", $err->message, "\n",
195             "API Error.........: ", $err->error, "\n",
196             "Error Code........: ", $err->fbx_error_code, "\n",
197             }
198              
199             =head1 DESCRIPTION
200              
201             This module provides a perl interface to the L<Freebox|https://en.wikipedia.org/wiki/Freebox#V6_generation.2C_Freebox_Revolution> v6 APIs.
202              
203             See L<http://dev.freebox.fr/sdk/os/> for a full description of the APIs.
204              
205             =head1 METHODS AND ARGUMENTS
206              
207             my $fbx = WWW::FBX->new( app_id => "APP ID", app_name => "APP NAME",
208             app_version => "1.0", device_name => "device" );
209              
210             my $fbx = WWW::FBX->new( app_id => "APP ID", app_name => "APP NAME",
211             app_version => "1.0", device_name => "device",
212             track_id => "48", app_token => "2/g43EZYD8AO7tbnwwhmMxMuELtTCyQrV1goMgaepHWGrqWlloWmMRszCuiN2ftp",
213             base_url => "http://12.34.56.78:3333" ,
214             debug => 1 );
215              
216             Mandatory constructor parameters are app_id, app_name, app_version, device_name.
217             When track_id and app_token are also provided, they will be used to authenticate.
218             Otherwise, new track_id and app_token will be given by the freebox. These can be then used for later access.
219             base_url defaults to http://mafreebox.free.fr which is the base uri when accessing the freebox from the LAN side.
220              
221             Note that adding the I<settings> or I<parental> permissions is only possible through the web interface (Paramètres de la Freebox -> Gestion des accès -> Applications)
222              
223             The constructor takes care of detecting the API version and authentication.
224              
225              
226             The return value of all api methods is the L<result|http://dev.freebox.fr/sdk/os/#APIResponse.result> structure of APIResponse, or undef if no result is returned.
227              
228             The full json response of the last request is available through the uar method (usefull when using the I<new> method) and the complete HTTP::Response is available through the uarh method.
229              
230             Api methods will I<die> if the APIResponse is an error. It is up to the caller to handle this exception.
231              
232             =head1 QUICK START
233              
234             The list of currently available services implemented in this module is given in L<WWW::FBX::Role::API::APIv3>.
235              
236             A script called fbx_test.pl is provided in script to show how to send commands and handle exceptions.
237              
238             It can also be used standalone to get tokens and send a command.
239              
240             On first call, you will be requested to physically authenticate on the freebox itself. Once done, the token is stored in the current directory in a file called app_token. You can then grant all permissions on the freebox web interface to allow all commands.
241              
242             When suffix parameter is required, pass it as a normal parameter.
243              
244             When more parameters are required, it is possible to send a json structure, see EXAMPLES. You need to escape the accolades though.
245              
246             =head1 EXAMPLES
247              
248             fbx-test.pl --help
249             fbx-test.pl connection
250             fbx-test.pl system
251             fbx-test.pl call_log
252             fbx-test.pl call_log 2053
253             fbx-test.pl reboot
254             fbx-test.pl reset_freeplug F4:CA:42:22:53:EF/reset
255             fbx-test.pl cp '{"files":["Disque dur/ds.txt"], "dst":"Disque dur/Temp", "mode":"both"}'
256              
257             =head1 LICENSE
258              
259             Copyright (C) Laurent Kislaire.
260              
261             This library is free software; you can redistribute it and/or modify
262             it under the same terms as Perl itself.
263              
264             =head1 AUTHOR
265              
266             Laurent Kislaire E<lt>teebeenator@gmail.comE<gt>
267              
268             =cut
269