source: extensions/pLoader/trunk/src/Uploader/PWG/WebServices.pm @ 4779

Last change on this file since 4779 was 4779, checked in by ronosman, 14 years ago

Change copyright notice to add 2010.

  • Property svn:eol-style set to LF
File size: 17.0 KB
Line 
1# +-----------------------------------------------------------------------+
2# | pLoader - a Perl photo uploader for Piwigo                            |
3# +-----------------------------------------------------------------------+
4# | Copyright(C) 2008-2010 Piwigo Team                  http://piwigo.org |
5# +-----------------------------------------------------------------------+
6# | This program is free software; you can redistribute it and/or modify  |
7# | it under the terms of the GNU General Public License as published by  |
8# | the Free Software Foundation                                          |
9# |                                                                       |
10# | This program is distributed in the hope that it will be useful, but   |
11# | WITHOUT ANY WARRANTY; without even the implied warranty of            |
12# | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
13# | General Public License for more details.                              |
14# |                                                                       |
15# | You should have received a copy of the GNU General Public License     |
16# | along with this program; if not, write to the Free Software           |
17# | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
18# | USA.                                                                  |
19# +-----------------------------------------------------------------------+
20package Uploader::PWG::WebServices;
21 
22use strict;
23use warnings;
24use MIME::Base64 qw(encode_base64); 
25use JSON;
26use LWP::UserAgent;
27use Data::Dumper;
28use Digest::MD5::File qw/file_md5_hex md5_hex/;
29use File::Slurp;
30use File::Spec;
31use POSIX qw(ceil floor);
32use base qw/
33           Uploader::Object
34           Class::Accessor::Fast
35           /;
36
37__PACKAGE__->mk_accessors( 
38    qw/
39           uagent
40           urlbase
41           username
42           password
43           qry_list_categories
44           qry_add_categories
45           qry_list_tags
46           items
47           tags
48           categories
49           site_high_file
50           site_resized_file
51           site_thumb_file
52           site_image_name
53           site_tags
54           rank
55           site_author
56           site_comment
57           site_img_date_creation
58           uagent_response
59           login_result
60           action_result
61           upload_high
62           chunk_size
63           sum
64           image_id
65           typecode
66           reupload_action_files
67           reupload_action_properties
68           reupload_action_properties_m
69           single_value_mode
70           multiple_value_mode
71           privacy_level
72      / 
73);
74
75$|=1;
76
77sub Init {
78    my ( $self, $version ) = @_ ;
79
80    # to transform a type_code into a typename
81    $self->typecode(
82        {
83             file => 'file',
84             thumbnail => 'thumb',
85             high => 'high',   
86        }
87    );
88   
89    $self->single_value_mode(
90        {
91            1 => 'fill_if_empty',
92            2 => 'replace',
93        }
94    );
95
96    $self->multiple_value_mode(
97        {
98            1 => 'append',
99            2 => 'replace',
100        }
101    );
102   
103    $self->uagent(
104        LWP::UserAgent->new(
105            agent => sprintf("Mozilla/pLoader %s", $version)       
106        )
107    );
108   
109    $self->uagent->cookie_jar({});   
110
111    $self->urlbase(
112        $self->{site_url}
113    );
114   
115    $self->username(
116        $self->{site_username}
117    );
118   
119    $self->password(
120        $self->{site_password}
121    );
122   
123    $self->chunk_size(
124        $self->{chunk_size}
125    );
126
127   
128    $self->uagent->default_headers->authorization_basic(
129        $self->{http_username}||$self->username, 
130        $self->{http_password}||$self->password
131    );
132   
133   
134    $self->qry_list_categories( sprintf
135        "%s/ws.php?format=json&method=%s&recursive=%s",
136        $self->urlbase,
137#        'pwg.categories.getAdminList',
138        'pwg.categories.getList',
139        'true',
140    );
141
142    $self->qry_list_tags( sprintf
143        "%s/ws.php?format=json&method=%s",
144        $self->urlbase,
145        'pwg.tags.getAdminList',
146    );
147
148
149    my $form = {
150        method => 'pwg.session.login',
151        username => $self->username,
152        password => $self->password,
153    };
154 
155    $self->_execute_post(
156        $form
157    );   
158
159    $self->login_result(
160        $self->_json_response_content
161    );
162
163}
164
165sub _json_response_content {
166    my ( $self ) = @_;
167
168    my $hresult = {} ;
169
170    if($self->uagent_response->is_success){
171        eval {
172            $hresult = from_json(
173                $self->uagent_response->content
174            );
175        };
176        if($@){
177            # when server response has warnings, the content response is not a valid json string
178            # find the json response
179            $self->uagent_response->content =~ /(\{.+\})/;
180            #printf("json response %s\n", $1);
181            eval {
182                $hresult = from_json(
183                    $1
184                );
185            };
186        }       
187    }
188    else{
189        $hresult = {
190            'message' => $self->uagent_response->message,
191            'stat'    => 'fail',
192        };
193    }
194   
195    $hresult;
196}
197
198sub _execute_get {
199    my ( $self, $query ) = @_;
200
201    eval {
202        $self->uagent_response(
203            $self->uagent->get(
204                $query
205            )
206        );
207    };
208
209    if($@){
210        printf("An error occured in query execution %s\n%s",
211            $query,
212            $@,
213        );   
214    }
215}
216
217sub _execute_post {
218    my ( $self, $form ) = @_;
219
220    my $result;
221    eval {
222        $result = $self->uagent_response(
223            $self->uagent->post(
224                $self->urlbase.'/ws.php?format=json',
225                $form
226            )
227        );
228    };
229
230    if($@){
231        printf("An error occured in post execution %s\n%s",
232            $form->{method},
233            $@,
234        );   
235    }
236   
237    return ( $result->is_success, $result->status_line );
238}
239sub GetCategories {
240    my ( $self ) = @_;
241
242 
243    $self->_execute_get(
244        $self->qry_list_categories
245    );
246    $self->_json_response_content->{result}{categories};
247}
248
249sub GetTags {
250    my ( $self ) = @_;
251
252    $self->_execute_get(
253        $self->qry_list_tags   
254    );
255    $self->_json_response_content->{result}{tags};
256}
257
258sub AddTags {
259    my ( $self, $name ) = @_;
260
261    my $form = {
262        method            => 'pwg.tags.add',
263        name              => $name,
264       
265    };
266
267    return ( $self->_execute_post($form), $self->_json_response_content );
268} 
269
270
271sub UploadImage {
272    my ( $self, $progress ) = @_;
273   
274    return if $progress->{stop_processing};
275
276    $self->image_id(
277        $self->_exists($progress->{original_sum})
278    );
279
280    my $status = 1;
281    my $status_line ="OK";
282    my $content = {};
283    my $doubleCheck;
284    my $form;
285    UPLOAD: while(1){
286        # first upload
287        if(!defined($self->image_id)){ 
288            $doubleCheck = 1;
289            $self->_checksum_files($progress);
290            my @types = ('file', 'thumb');
291            #printf("WS upload_high %s\n", $self->upload_high);
292            push @types, 'high' if $self->upload_high;
293            map{
294                my $rval = $self->_send_chunks($_, $progress);
295                $status_line = $rval->{message};
296                last UPLOAD if !$rval->{ok};
297            } @types;
298         
299           
300            $form = {
301                method            => 'pwg.images.add',
302                original_sum      => $self->sum->{original},
303                file_sum          => $self->sum->{file},
304                thumbnail_sum     => $self->sum->{thumb},
305                categories        => $self->categories,
306                name              => $self->site_image_name,
307                author            => $self->site_author,
308                comment           => $self->site_comment,
309                date_creation     => $self->site_img_date_creation,
310                tag_ids           => $self->site_tags,
311               
312            };
313
314           $form->{high_sum} = $self->sum->{high} if $self->upload_high;
315           
316           $progress->{yield}->();
317           ( $status, $status_line ) = $self->_execute_post($form);
318        }
319        # re-upload
320        else{
321            # need to check if files have changed
322            # and update image info
323            if($self->reupload_action_files){
324                $self->_checksum_files($progress);
325                my $files = $self->_check_files();
326                if(defined($files)){
327                    $self->_add_files($files, $progress);   
328                }
329            }
330
331            $form = {
332                    method        => 'pwg.images.setInfo',
333                    image_id      => $self->image_id,
334            };
335            # update metadata info
336            # simple value metadata
337            if($self->reupload_action_properties){     
338                $form->{name}          = $self->site_image_name;
339                $form->{author}        = $self->site_author;
340                $form->{comment}       = $self->site_comment;
341                $form->{date_creation} = $self->site_img_date_creation;
342                $form->{single_value_mode}  = $self->single_value_mode->{$self->reupload_action_properties};
343                $form->{level} = $self->privacy_level ? 2**($self->privacy_level - 1) : 0;
344            }
345            # multi value metadata
346            if($self->reupload_action_properties_m){     
347                $form->{tag_ids} = $self->site_tags if $self->site_tags;
348                $form->{categories} = $self->categories;
349                $form->{multiple_value_mode} = $self->multiple_value_mode->{$self->reupload_action_properties_m};
350            };
351            $progress->{yield}->();
352            ( $status, $status_line ) = $self->_execute_post($form); 
353
354        }
355
356        delete $form->{tag_ids} unless defined $self->site_tags;
357        delete $form->{tag_ids} if '' eq $self->site_tags;
358
359        $progress->{yield}->();
360        # for first upload
361        # make sure the image is uploaded by querying for its existence
362        if($doubleCheck){
363            $self->image_id(
364                $self->_exists($progress->{original_sum})
365            );
366            $content->{stat} = !defined $self->image_id  ? 'fail' : 'ok'; 
367            if(defined $self->image_id and defined $self->privacy_level){
368                ( $status, $status_line, $content ) = $self->_set_privacy_level;
369            }
370        }
371
372        last UPLOAD;
373    }# UPLOAD
374   
375    return ( $status,  $status_line, $content);
376}
377
378sub _set_privacy_level {
379    my ( $self ) = @_;
380       
381    my $form = {
382        method        => 'pwg.images.setPrivacyLevel',
383        image_id      => $self->image_id,
384        level         => $self->privacy_level ? 2**($self->privacy_level - 1) : 0,
385    };
386
387    my ( $status, $status_line ) = $self->_execute_post($form); 
388    my $hresult = $self->_json_response_content;
389
390    ($status, $status_line, $hresult );
391}
392
393sub _checksum_files {
394    my ( $self, $progress ) = @_;
395
396    $self->sum(
397        {
398            file => $self->_checksum(
399                        $self->site_resized_file,
400                        $progress
401                    ),
402            thumb => $self->_checksum(
403                             $self->site_thumb_file,
404                             $progress
405                         ),
406            original => $progress->{original_sum}
407        }
408    ); 
409
410    $self->sum->{high} = $self->_checksum(
411                        $self->site_high_file,
412                        $progress
413    ) if $self->upload_high ;
414}
415
416sub _check_files {
417    my ( $self ) = @_;
418
419    my $form = {
420        method   => 'pwg.images.checkFiles',
421        image_id => $self->image_id,
422    };
423   
424    @$form{'thumbnail_sum', 'file_sum' } = (
425        $self->sum->{thumb},
426        $self->sum->{file},
427    );
428
429    if($self->upload_high){
430        $form->{high_sum} = $self->sum->{high};
431    }
432
433    $self->_execute_post($form);   
434    my $hresult = $self->_json_response_content;
435
436    my $rval = 'ok' eq $hresult->{stat} ? $hresult->{result} : undef;
437   
438    $rval;
439}
440
441# $files is returned by _check_files
442# {
443#     thumbnail => 'equals', 'differs', 'missing'
444#     file => 'equals', 'differs', 'missing'
445#     high => 'equals', 'differs', 'missing'
446#}
447
448sub _add_files {
449    my ( $self, $files, $progress ) = @_;
450
451    map{
452        $self->_add_file($_, $progress);
453    }
454    map{
455        $self->typecode->{$_};
456    } grep { 'equals' ne $files->{$_} } keys %$files ;
457       
458}
459
460sub _add_file {
461    my ( $self, $type_code, $progress ) = @_;
462
463    $self->_send_chunks(
464        $type_code,
465        $progress,
466    );
467
468    my $form = {
469        method => 'pwg.images.addFile',
470        image_id => $self->image_id,
471        type     => $type_code,
472        sum      => $self->sum->{$type_code},       
473    };   
474
475    $self->_execute_post($form);   
476    my $hresult = $self->_json_response_content;
477
478    my $rval = 'ok' eq $hresult->{stat} ? $hresult->{result} : undef;
479   
480    $rval;
481}
482
483sub IsAlreadyUploaded {
484    my ( $self, $md5_sums ) = @_;
485   
486    # md5_sums is an array ref
487    $self->_execute_post({
488        method      => 'pwg.images.exist',
489        md5sum_list => join(',', @$md5_sums)
490        }
491    );
492
493    my $sums = $self->_json_response_content->{result};
494
495    $sums;
496}
497
498sub _exists {
499    my ( $self, $md5_sum ) = @_;
500
501    my $form = {
502        method            => 'pwg.images.exist',
503        md5sum_list       => $md5_sum,
504    };
505
506    $self->_execute_post($form);   
507    my $hresult = $self->_json_response_content;
508
509    $hresult->{result} = {} if 'HASH' ne ref $hresult->{result};
510    my $id = 'ok' eq $hresult->{stat} ? $hresult->{result}{$md5_sum} : undef ;
511
512    $id;
513} 
514
515sub _checksum {
516    my ( $self, $file, $progress ) = @_;
517    my $file_sum;
518
519    my $yield = $progress->{yield};
520
521    $yield->();
522    $progress->{msg_details}->(
523        sprintf(
524            "%s : %s", 
525            $progress->{checksum_msg}, 
526            $file
527        )
528    );
529
530    eval {
531        $file_sum = file_md5_hex(
532            $file
533        );
534    };
535    $yield->();
536
537    $file_sum;
538}
539
540sub _send_chunks {
541    my ( $self, $type_code, $progress ) = @_;
542
543    my $msg = {
544        thumb=>'thumbnail_msg',
545        file=>'resized_msg',
546        high=>'highdef_msg',
547    };
548
549    my $filepath = {
550        thumb=>$self->site_thumb_file,
551        file=>$self->site_resized_file,
552        high=>$self->site_high_file,
553    };
554
555    $progress->{current_msg} = $progress->{$msg->{$type_code}};
556    $progress->{yield}->();
557
558    my $params = {
559         filepath      => $filepath->{$type_code},
560         type          => $type_code,
561         original_sum  => $self->sum->{original},
562    };
563    #print Dumper $params;
564    $self->send_chunks(
565       $params,
566       $progress,
567    );
568    $progress->{yield}->();
569
570    $params;
571}
572
573sub AddCategories{
574    my ( $self, $name, $parentid ) = @_;
575
576    my $form = {
577        method            => 'pwg.categories.add',
578        name              => $name,
579        parent            => $parentid,
580       
581    };
582   
583    return ( $self->_execute_post($form), $self->_json_response_content );
584}
585
586sub SetInfoCategories{
587    my ( $self, $name, $comment, $parentid ) = @_;
588
589    my $form = {
590        method            => 'pwg.categories.setInfo',
591        name              => $name,
592        comment           => $comment,
593        category_id       => $parentid,
594       
595    };
596
597    return ( $self->_execute_post($form), $self->_json_response_content );
598}
599
600
601sub send_chunks {
602    my ( $self, $params, $progress ) = @_;
603
604    my $yield = $progress->{yield};
605    my ( $vol, $dir, $filename ) = File::Spec->splitpath($params->{filepath});
606
607    $yield->();
608    $progress->{bar}->(0);
609    $yield->();
610    $progress->{msg_details}->(
611        sprintf(
612            "%s : %s", 
613            $progress->{current_msg}, 
614            $filename
615        )
616    );
617
618
619    $yield->();
620    my $content = read_file(
621        $params->{filepath},
622        binmode => ':raw',
623    );
624    $yield->();
625
626    my $content_length = length($content);
627    my $nb_chunks = ceil($content_length / $self->chunk_size);
628
629    my $chunk_pos = 0;
630    my $chunk_id = 1;
631
632
633    while ($chunk_pos < $content_length) {
634
635        my $chunk = substr(
636            $content,
637            $chunk_pos,
638            $self->chunk_size
639        );
640        $chunk_pos += $self->chunk_size;
641        my $response = $self->uagent->post(
642            $self->urlbase.'/ws.php?format=json',
643            {
644                method       => 'pwg.images.addChunk',
645                data         => encode_base64($chunk),
646                original_sum => $params->{original_sum},
647                position     => $chunk_id,
648                type         => $params->{type},
649            }
650        );
651        $yield->();
652        $progress->{bar}->(100*($chunk_pos/$content_length));
653        $progress->{msg_details}->(
654            sprintf(
655                "%s : %s", 
656                $progress->{current_msg}, 
657                $filename
658            )
659        );
660        $params->{ok} = 1;
661        if ($response->code != 200) {
662            printf("response code    : %u\n", $response->code);
663            printf("response message : %s\n", $response->message);
664            $params->{ok} = 0;
665            $params->{message} = $response->message;
666            $params->{code} = $response->code;
667            last;
668        }
669
670        $chunk_id++;
671    }
672}
673
674 
6751;
676   
Note: See TracBrowser for help on using the repository browser.