File Coverage

blib/lib/Gallery/Remote/API.pm
Criterion Covered Total %
statement 140 153 91.5
branch 39 54 72.2
condition 11 18 61.1
subroutine 25 35 71.4
pod 13 13 100.0
total 228 273 83.5


line stmt bran cond sub pod time code
1             package Gallery::Remote::API;
2              
3 2     2   24616 use strict;
  2         5  
  2         75  
4 2     2   12 use warnings;
  2         2  
  2         63  
5              
6 2     2   1809 use version 0.77; our $VERSION = qv('v0.1.4');
  2         5249  
  2         16  
7              
8 2     2   185 use base qw(Class::Accessor);
  2         4  
  2         2054  
9             Gallery::Remote::API->mk_ro_accessors(qw(
10             url username password version _remoteurl _cookiejar _useragent
11             ));
12             Gallery::Remote::API->mk_accessors(qw(response result));
13              
14 2     2   6863 use Carp;
  2         5  
  2         138  
15 2     2   985 use URI;
  2         8120  
  2         52  
16 2     2   1641 use URI::QueryParam;
  2         1496  
  2         52  
17 2     2   1089 use LWP::UserAgent;
  2         67216  
  2         66  
18 2     2   2093 use HTTP::Cookies;
  2         20820  
  2         61  
19 2     2   2397 use File::Temp;
  2         38335  
  2         175  
20 2     2   1521 use Config::Properties;
  2         10485  
  2         73  
21 2     2   1764 use Data::Diver qw(Dive);
  2         1495  
  2         121  
22 2     2   10 use Sub::Name;
  2         3  
  2         91  
23 2     2   22 use Scalar::Util qw(blessed);
  2         4  
  2         87  
24 2     2   10 use Fcntl qw(:seek);
  2         2  
  2         296  
25              
26              
27             #constants
28              
29 2     2   12 use constant PROTOCOL_VERSION => 2.9;
  2         2  
  2         136  
30              
31 2         85 use constant ACCESSPAGE => {
32             1 => 'gallery_remote2.php',
33             2 => 'main.php'
34 2     2   9 };
  2         4  
35              
36             ## actions
37              
38             ## we auto-build methods for each known Protocol command
39              
40             BEGIN {
41            
42 2     2   9 no strict 'refs';
  2         3  
  2         246  
43              
44 2     2   6 foreach (qw(
45             fetch_albums
46             fetch_albums_prune
47             add_item
48             album_properties
49             new_album
50             fetch_album_images
51             move_album
52             increment_view_count
53             image_properties
54             no_op
55             )) {
56 20         26 my $cmd = $_; $cmd =~ s/\_/\-/g;
  20         57  
57             my $method = sub {
58 0     0 1 0 my ($self,$params) = @_;
        0 1    
        0 1    
        0 1    
        0 1    
        0 1    
        0 1    
        0 1    
        0 1    
        0 1    
59 0         0 return $self->execute_command("$cmd",$params);
60 20         60 };
61 20         55 my $methname = "Gallery::Remote::API::$_";
62 20         98 subname($methname,$method);
63 20         20 *{$methname} = $method;
  20         2069  
64             }
65             };
66              
67             #except for login, for which we want the user/pass goodness
68              
69             sub login {
70 4     4 1 3382 my $self = shift;
71              
72 4 100       11 croak "Must define username during object construction to login"
73             unless my $u = $self->username;
74 3 100       29 croak "Must define password during object construction to login"
75             unless my $p = $self->password;
76              
77 2         21 return $self->execute_command('login', {
78             uname => $u, password => $p
79             });
80             }
81              
82             # the big boy
83              
84             sub execute_command {
85 3     3 1 1707 my ($self,$command,$params) = @_;
86              
87             #clear any previous response and result
88 3         9 $self->response(undef);
89 3         38 $self->result(undef);
90              
91 3 50       26 croak "Must pass a command" unless $command;
92 3 100       9 $params = {} unless defined $params;
93              
94 3   50     15 $params->{protocol_version} ||= PROTOCOL_VERSION;
95             #if you try and send this, I'm just going to overwrite
96 3         5 $params->{cmd} = $command;
97              
98 3         4 my $useparams = {};
99 3 50       15 if ($self->version == 2) {
100 3         47 foreach (keys %$params) {
101 10 50       18 next if $_ =~ /^userfile/;
102 10         27 $useparams->{"g2_form[$_]"} = $params->{$_};
103             }
104              
105             #hack these goofy exceptions
106             # see: http://codex.gallery2.org/Gallery_Remote:Protocol#G2_support
107 3 50       11 if (my $uf = $params->{userfile}) {
108             #also do the arrayref bit here so lwp knows to read the file
109 0         0 $useparams->{g2_userfile} = [$uf];
110             }
111 3 50       8 if (my $ufn = $params->{userfile_name}) {
112 0         0 $useparams->{g2_userfile_name} = $ufn;
113             }
114             }
115             else {
116 0         0 $useparams = $params;
117             }
118              
119             #do it!
120 3 50       11 my $res = $self->_useragent->post(
121             $self->_remoteurl,
122             Content_Type => $command eq 'add-item' ?
123             'multipart/form-data' : 'application/x-www-form-urlencoded',
124             Content => $useparams
125             );
126            
127 3 100       223 if ($res->is_success) {
128 2         88 $self->response($res->content);
129 2         89 return $self->_parse_response;
130             }
131              
132             #carp "Server Error: ".$res->status_line."\n";
133             # fake an error in the same style as those returned by the protocol
134             # throw in the response object itself in case anyone finds it useful
135 1         2392 $self->result({status => 'server_error', status_text => $res->message, response => $res});
136 1         2360 return;
137             }
138              
139             sub _parse_response {
140 2     2   3 my $self = shift;
141 2 50       5 if (my $response = $self->response) {
142              
143             #drop anything before the proto tag
144 2         17 $response =~ s/^(.*)#__GR2PROTO/#__GR2PROTO/;
145              
146             #this is stupid. They return a Java Properties stream. We
147             #want Config::Properties to deserialize it for us, but that
148             #module wants to load from a filehandle, it doesn't look like
149             #you can just pass it data. Hence...
150 2         4 my $virtualfile = '';
151 2 50   1   50 open(my $fh, '+>', \$virtualfile)
  1         8  
  1         2  
  1         7  
152             || croak "Failed to open virtual file: $!";
153 2         1345 print $fh $response;
154 2         5 seek($fh,0,SEEK_SET);
155              
156 2         15 my $cp = new Config::Properties;
157 2         80 $cp->load($fh);
158              
159 2         256 my $result = $cp->splitToTree;
160              
161             #now let's improve deserialization on a few things where we get a csv
162             #list that ought to be an array (does Properties not serialize
163             #on the "value" side? I don't see any way to differentiate a csv from
164             #ordinary data with a comma in it)
165              
166             # use DataDiver so as not to autovivify if not there
167              
168             #from fetch-albums & fetch-abums-prune
169 2 50       92 if (my $ef = Dive($result, qw( album info extrafields ))) {
170 0         0 foreach (keys %$ef) {
171 0         0 $result->{album}->{info}->{extrafields}->{$_} = [
172             split(',',$result->{album}->{info}->{extrafields}->{$_})
173             ];
174             }
175             }
176             #from album-properties
177 2 50       64 if (my $ef = Dive($result, qw(extrafields))) {
178 0         0 $result->{extrafields} = [ split(',',$ef) ];
179             }
180              
181 2         48 $self->result($result);
182 2 50       25 unless (exists $result->{status}) {
183 0         0 $result->{status} = 'unknown_error';
184 0   0     0 $result->{status_text} = $result->{Error} || 'unknown error';
185             }
186              
187 2 50       5 unless ($result->{status}) { #success is 0, don't do on fail
188             #add/replace the security token, if present
189 2 50       6 if (my $newtoken = $result->{auth_token}) {
190 0         0 $self->_remoteurl->query_param(g2_authToken => $newtoken);
191             }
192 2         21 return $result
193             };
194             }
195 0         0 return;
196             }
197              
198              
199             #constructor
200              
201             # Override parent C:A's new method to do some validation and default
202             # as needed before passing arguments into the RO accessors
203              
204             sub new {
205 8     8 1 3537 my ($class,$args) = @_;
206 8   33     34 $class = ref $class || $class;
207              
208 8         16 my $cleanargs = $class->_parse_constructor_args($args);
209 4         25 my $self = $class->SUPER::new($cleanargs);
210 4         44 bless $self,$class;
211              
212 4         14 return $self;
213             }
214              
215             sub _parse_constructor_args {
216 8     8   9 my ($self,$args) = @_;
217              
218 8 100       18 unless (ref $args eq 'HASH') {
219 1         198 croak "Must pass arguments as a hashref; 'url' required at minimum";
220             }
221              
222 7         7 my %cleanargs;
223 7   100     25 $args->{version} ||= 2;
224 7         20 foreach (keys %$args) {
225 15 100 66     7422 if (($_ eq 'url') && (my $u = $args->{url})) {
    100          
    50          
226              
227 6 100 100     45 if (ref $u && blessed $u && $u->isa('URI')) {
    100 66        
228 1         5 $cleanargs{$_} = $u;
229             }
230             elsif (ref $u) {
231 1         111 croak "url must be a URI object, or a string";
232             }
233             else {
234 4 50       12 $u = "http://$u" unless (substr($u,0,7) eq 'http://');
235 4         23 $cleanargs{$_} = URI->new($u);
236             }
237             }
238             elsif ($_ eq 'version') {
239 6 100       19 if ($args->{$_} =~ /^[12]$/) {
240 5         11 $cleanargs{$_} = $args->{$_};
241             }
242             else {
243 1         154 croak "Accepted values for Gallery version are '1' or '2'";
244             }
245             }
246             elsif ($self->can($_)) {
247 3         7 $cleanargs{$_} = $args->{$_};
248             }
249             else {
250 0         0 carp "Unkown argument '$_'";
251             }
252             }
253              
254 5 100       165 if (my $u = $cleanargs{url}) {
255 4         24 my $v = $cleanargs{version};
256 4         14 $cleanargs{_remoteurl} = URI->new($u->canonical . ACCESSPAGE->{$v});
257 4 100       626 if ($v == 2) {
258 3         17 $cleanargs{_remoteurl}->query_param(g2_controller => 'remote:GalleryRemote');
259             }
260             }
261             else {
262 1         115 croak "'url' to the gallery installation is a required argument";
263             }
264              
265 4         324 my $cj = File::Temp->new->filename;
266 4         1640 $cleanargs{_cookiejar} = $cj;
267              
268 4         872 my $ua = new LWP::UserAgent;
269 4         28 $ua->cookie_jar(HTTP::Cookies->new(file => $cj, autosave => 1));
270 4         415 $cleanargs{_useragent} = $ua;
271              
272 4         10 return \%cleanargs;
273             }
274              
275              
276             1; # Magic true value required at end of module
277             __END__