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   314805 use Moose;
  23         6902451  
  23         136  
3 23     23   121459 use Carp::Clan qw/^(?:WWW::FBX|Moose|Class::MOP)/;
  23         31317  
  23         138  
4 23     23   11863 use JSON::MaybeXS;
  23         101387  
  23         1169  
5 23     23   115 use Scalar::Util qw/reftype/;
  23         27  
  23         891  
6 23     23   8583 use URI::Escape;
  23         22605  
  23         1144  
7 23     23   9082 use HTTP::Request::Common;
  23         361731  
  23         1415  
8 23     23   8837 use WWW::FBX::Error;
  23         72  
  23         1178  
9 23     23   14007 use Encode qw/encode_utf8/;
  23         172479  
  23         1610  
10 23     23   123 use Try::Tiny;
  23         32  
  23         913  
11 23     23   12350 use LWP::UserAgent;
  23         365180  
  23         1045  
12              
13             with 'WWW::FBX::Role::API::APIv3';
14             with 'WWW::FBX::Role::Auth';
15            
16 23     23   162 use namespace::autoclean;
  23         28  
  23         202  
17              
18             our $VERSION = "0.19";
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 the script directory.
237              
238             You should first call it without argument to store a token in app_token on the disk. Once physically authenticated on the freebox itself, the token file will be reused for subsequent call. You can then grant all permissions on the freebox web interface if you will.
239              
240             Witout parameter, a simple connection check is done, app permissions are shows and status of the internet connection is displayed.
241              
242             Commands requiring a suffix can be send by adding a simple parameters on the command line. When more parameters are required, it is possible to send a json structure, see EXAMPLES. You need to escape the accolades in that case.
243              
244             =head1 EXAMPLES
245              
246             fbx-test.pl --help
247             fbx-test.pl --debug connection
248             fbx-test.pl system
249             fbx-test.pl call_log
250             fbx-test.pl call_log 2053
251             fbx-test.pl reboot
252             fbx-test.pl reset_freeplug F4:CA:42:22:53:EF/reset
253             fbx-test.pl cp '{"files":["Disque dur/ds.txt"], "dst":"Disque dur/Temp", "mode":"both"}'
254              
255             =head1 LICENSE
256              
257             Copyright (C) Laurent Kislaire.
258              
259             This library is free software; you can redistribute it and/or modify
260             it under the same terms as Perl itself.
261              
262             =head1 AUTHOR
263              
264             Laurent Kislaire E<lt>teebeenator@gmail.comE<gt>
265              
266             =cut
267