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

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

Bug 1610 fixed : new chunk size not used until reload. Add a number validator.

  • Property svn:eol-style set to LF
File size: 17.3 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_original_filename
50           site_high_file
51           site_resized_file
52           site_thumb_file
53           site_image_name
54           site_tags
55           rank
56           site_author
57           site_comment
58           site_img_date_creation
59           uagent_response
60           login_result
61           action_result
62           upload_high
63           chunk_size
64           sum
65           image_id
66           typecode
67           reupload_action_files
68           reupload_action_properties
69           reupload_action_properties_m
70           single_value_mode
71           multiple_value_mode
72           privacy_level
73      / 
74);
75
76$|=1;
77
78sub Init {
79    my ( $self, $version ) = @_ ;
80
81    # to transform a type_code into a typename
82    $self->typecode(
83        {
84             file => 'file',
85             thumbnail => 'thumb',
86             high => 'high',   
87        }
88    );
89   
90    $self->single_value_mode(
91        {
92            1 => 'fill_if_empty',
93            2 => 'replace',
94        }
95    );
96
97    $self->multiple_value_mode(
98        {
99            1 => 'append',
100            2 => 'replace',
101        }
102    );
103   
104    $self->uagent(
105        LWP::UserAgent->new(
106            agent => sprintf("Mozilla/pLoader %s", $version)       
107        )
108    );
109   
110    $self->uagent->cookie_jar({});   
111
112    $self->urlbase(
113        $self->{site_url}
114    );
115   
116    $self->username(
117        $self->{site_username}
118    );
119   
120    $self->password(
121        $self->{site_password}
122    );
123   
124    $self->chunk_size(
125        $self->{chunk_size}
126    );
127
128   
129    $self->uagent->default_headers->authorization_basic(
130        $self->{http_username}||$self->username, 
131        $self->{http_password}||$self->password
132    );
133   
134   
135    $self->qry_list_categories( sprintf
136        "%s/ws.php?format=json&method=%s&recursive=%s",
137        $self->urlbase,
138#        'pwg.categories.getAdminList',
139        'pwg.categories.getList',
140        'true',
141    );
142
143    $self->qry_list_tags( sprintf
144        "%s/ws.php?format=json&method=%s",
145        $self->urlbase,
146        'pwg.tags.getAdminList',
147    );
148
149
150    my $form = {
151        method => 'pwg.session.login',
152        username => $self->username,
153        password => $self->password,
154    };
155 
156    $self->_execute_post(
157        $form
158    );   
159
160    $self->login_result(
161        $self->_json_response_content
162    );
163
164}
165
166sub _json_response_content {
167    my ( $self ) = @_;
168
169    my $hresult = {} ;
170
171    if($self->uagent_response->is_success){
172        eval {
173            $hresult = from_json(
174                $self->uagent_response->content
175            );
176        };
177        if($@){
178            # when server response has warnings, the content response is not a valid json string
179            # find the json response
180            $self->uagent_response->content =~ /(\{.+\})/;
181            #printf("json response %s\n", $1);
182            eval {
183                $hresult = from_json(
184                    $1
185                );
186            };
187        }       
188    }
189    else{
190        $hresult = {
191            'message' => $self->uagent_response->message,
192            'stat'    => 'fail',
193        };
194    }
195   
196    $hresult;
197}
198
199sub _execute_get {
200    my ( $self, $query ) = @_;
201
202    eval {
203        $self->uagent_response(
204            $self->uagent->get(
205                $query
206            )
207        );
208    };
209
210    if($@){
211        printf("An error occured in query execution %s\n%s",
212            $query,
213            $@,
214        );   
215    }
216}
217
218sub _execute_post {
219    my ( $self, $form ) = @_;
220
221    my $result;
222    eval {
223        $result = $self->uagent_response(
224            $self->uagent->post(
225                $self->urlbase.'/ws.php?format=json',
226                $form
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                if (!$rval->{ok}){
297                    $status = 0;
298                    last UPLOAD ;
299                }
300            } @types;
301         
302           
303            $form = {
304                method            => 'pwg.images.add',
305                original_sum      => $self->sum->{original},
306                original_filename => $self->site_original_filename,
307                file_sum          => $self->sum->{file},
308                thumbnail_sum     => $self->sum->{thumb},
309                categories        => $self->categories,
310                name              => $self->site_image_name,
311                author            => $self->site_author,
312                comment           => $self->site_comment,
313                date_creation     => $self->site_img_date_creation,
314                tag_ids           => $self->site_tags,
315               
316            };
317
318           $form->{high_sum} = $self->sum->{high} if $self->upload_high;
319           
320           $progress->{yield}->();
321           ( $status, $status_line ) = $self->_execute_post($form);
322        }
323        # re-upload
324        else{
325            # need to check if files have changed
326            # and update image info
327            if($self->reupload_action_files){
328                $self->_checksum_files($progress);
329                my $files = $self->_check_files();
330                if(defined($files)){
331                    $self->_add_files($files, $progress);   
332                }
333            }
334
335            $form = {
336                    method        => 'pwg.images.setInfo',
337                    image_id      => $self->image_id,
338            };
339            # update metadata info
340            # simple value metadata
341            if($self->reupload_action_properties){     
342                $form->{name}          = $self->site_image_name;
343                $form->{author}        = $self->site_author;
344                $form->{comment}       = $self->site_comment;
345                $form->{date_creation} = $self->site_img_date_creation;
346                $form->{single_value_mode}  = $self->single_value_mode->{$self->reupload_action_properties};
347                $form->{level} = $self->privacy_level ? 2**($self->privacy_level - 1) : 0;
348            }
349            # multi value metadata
350            if($self->reupload_action_properties_m){     
351                $form->{tag_ids} = $self->site_tags if $self->site_tags;
352                $form->{categories} = $self->categories;
353                $form->{multiple_value_mode} = $self->multiple_value_mode->{$self->reupload_action_properties_m};
354            };
355            $progress->{yield}->();
356            ( $status, $status_line ) = $self->_execute_post($form); 
357
358        }
359
360        delete $form->{tag_ids} unless defined $self->site_tags;
361        delete $form->{tag_ids} if '' eq $self->site_tags;
362
363        $progress->{yield}->();
364        # for first upload
365        # make sure the image is uploaded by querying for its existence
366        if($doubleCheck){
367            $self->image_id(
368                $self->_exists($progress->{original_sum})
369            );
370            $content->{stat} = !defined $self->image_id  ? 'fail' : 'ok'; 
371            if(defined $self->image_id and defined $self->privacy_level){
372                ( $status, $status_line, $content ) = $self->_set_privacy_level;
373            }
374        }
375
376        last UPLOAD;
377    }# UPLOAD
378   
379    return ( $status,  $status_line, $content);
380}
381
382sub _set_privacy_level {
383    my ( $self ) = @_;
384       
385    my $form = {
386        method        => 'pwg.images.setPrivacyLevel',
387        image_id      => $self->image_id,
388        level         => $self->privacy_level ? 2**($self->privacy_level - 1) : 0,
389    };
390
391    my ( $status, $status_line ) = $self->_execute_post($form); 
392    my $hresult = $self->_json_response_content;
393
394    ($status, $status_line, $hresult );
395}
396
397sub _checksum_files {
398    my ( $self, $progress ) = @_;
399
400    $self->sum(
401        {
402            file => $self->_checksum(
403                        $self->site_resized_file,
404                        $progress
405                    ),
406            thumb => $self->_checksum(
407                             $self->site_thumb_file,
408                             $progress
409                         ),
410            original => $progress->{original_sum}
411        }
412    ); 
413
414    $self->sum->{high} = $self->_checksum(
415                        $self->site_high_file,
416                        $progress
417    ) if $self->upload_high ;
418}
419
420sub _check_files {
421    my ( $self ) = @_;
422
423    my $form = {
424        method   => 'pwg.images.checkFiles',
425        image_id => $self->image_id,
426    };
427   
428    @$form{'thumbnail_sum', 'file_sum' } = (
429        $self->sum->{thumb},
430        $self->sum->{file},
431    );
432
433    if($self->upload_high){
434        $form->{high_sum} = $self->sum->{high};
435    }
436
437    $self->_execute_post($form);   
438    my $hresult = $self->_json_response_content;
439
440    my $rval = 'ok' eq $hresult->{stat} ? $hresult->{result} : undef;
441   
442    $rval;
443}
444
445# $files is returned by _check_files
446# {
447#     thumbnail => 'equals', 'differs', 'missing'
448#     file => 'equals', 'differs', 'missing'
449#     high => 'equals', 'differs', 'missing'
450#}
451
452sub _add_files {
453    my ( $self, $files, $progress ) = @_;
454
455    map{
456        $self->_add_file($_, $progress);
457    }
458    map{
459        $self->typecode->{$_};
460    } grep { 'equals' ne $files->{$_} } keys %$files ;
461       
462}
463
464sub _add_file {
465    my ( $self, $type_code, $progress ) = @_;
466
467    $self->_send_chunks(
468        $type_code,
469        $progress,
470    );
471
472    my $form = {
473        method => 'pwg.images.addFile',
474        image_id => $self->image_id,
475        type     => $type_code,
476        sum      => $self->sum->{$type_code},       
477    };   
478
479    $self->_execute_post($form);   
480    my $hresult = $self->_json_response_content;
481
482    my $rval = 'ok' eq $hresult->{stat} ? $hresult->{result} : undef;
483   
484    $rval;
485}
486
487sub IsAlreadyUploaded {
488    my ( $self, $md5_sums ) = @_;
489   
490    # md5_sums is an array ref
491    $self->_execute_post({
492        method      => 'pwg.images.exist',
493        md5sum_list => join(',', @$md5_sums)
494        }
495    );
496
497    my $sums = $self->_json_response_content->{result};
498
499    $sums;
500}
501
502sub _exists {
503    my ( $self, $md5_sum ) = @_;
504
505    my $form = {
506        method            => 'pwg.images.exist',
507        md5sum_list       => $md5_sum,
508    };
509
510    $self->_execute_post($form);   
511    my $hresult = $self->_json_response_content;
512
513    $hresult->{result} = {} if 'HASH' ne ref $hresult->{result};
514    my $id = 'ok' eq $hresult->{stat} ? $hresult->{result}{$md5_sum} : undef ;
515
516    $id;
517} 
518
519sub _checksum {
520    my ( $self, $file, $progress ) = @_;
521    my $file_sum;
522
523    my $yield = $progress->{yield};
524
525    $yield->();
526    $progress->{msg_details}->(
527        sprintf(
528            "%s : %s", 
529            $progress->{checksum_msg}, 
530            $file
531        )
532    );
533
534    eval {
535        $file_sum = file_md5_hex(
536            $file
537        );
538    };
539    $yield->();
540
541    $file_sum;
542}
543
544sub _send_chunks {
545    my ( $self, $type_code, $progress ) = @_;
546
547    my $msg = {
548        thumb=>'thumbnail_msg',
549        file=>'resized_msg',
550        high=>'highdef_msg',
551    };
552
553    my $filepath = {
554        thumb=>$self->site_thumb_file,
555        file=>$self->site_resized_file,
556        high=>$self->site_high_file,
557    };
558
559    $progress->{current_msg} = $progress->{$msg->{$type_code}};
560    $progress->{yield}->();
561
562    my $params = {
563         filepath      => $filepath->{$type_code},
564         type          => $type_code,
565         original_sum  => $self->sum->{original},
566    };
567    #print Dumper $params;
568    $self->send_chunks(
569       $params,
570       $progress,
571    );
572    $progress->{yield}->();
573
574    $params;
575}
576
577sub AddCategories{
578    my ( $self, $name, $parentid ) = @_;
579
580    my $form = {
581        method            => 'pwg.categories.add',
582        name              => $name,
583        parent            => $parentid,
584       
585    };
586   
587    return ( $self->_execute_post($form), $self->_json_response_content );
588}
589
590sub SetInfoCategories{
591    my ( $self, $name, $comment, $parentid ) = @_;
592
593    my $form = {
594        method            => 'pwg.categories.setInfo',
595        name              => $name,
596        comment           => $comment,
597        category_id       => $parentid,
598       
599    };
600
601    return ( $self->_execute_post($form), $self->_json_response_content );
602}
603
604
605sub send_chunks {
606    my ( $self, $params, $progress ) = @_;
607
608    my $yield = $progress->{yield};
609    my ( $vol, $dir, $filename ) = File::Spec->splitpath($params->{filepath});
610
611    $yield->();
612    $progress->{bar}->(0);
613    $yield->();
614    $progress->{msg_details}->(
615        sprintf(
616            "%s : %s", 
617            $progress->{current_msg}, 
618            $filename
619        )
620    );
621
622
623    $yield->();
624    my $content = read_file(
625        $params->{filepath},
626        binmode => ':raw',
627    );
628    $yield->();
629
630    my $content_length = length($content);
631    my $nb_chunks = ceil($content_length / $self->chunk_size->());
632
633    my $chunk_pos = 0;
634    my $chunk_id = 1;
635
636
637    while ($chunk_pos < $content_length) {
638
639        my $chunk = substr(
640            $content,
641            $chunk_pos,
642            $self->chunk_size->()
643        );
644        $chunk_pos += $self->chunk_size->();
645        #print "pwg.images.addChunk\n";
646        my $data = encode_base64($chunk);
647        #printf("chunk : %s, data %s\n", length $chunk, length $data);
648        my $response = $self->uagent->post(
649            $self->urlbase.'/ws.php?format=json',
650            {
651                method       => 'pwg.images.addChunk',
652                data         => $data,
653                original_sum => $params->{original_sum},
654                position     => $chunk_id,
655                type         => $params->{type},
656            }
657        );
658#print Dumper $response;
659        $yield->();
660        $progress->{bar}->(100*($chunk_pos/$content_length));
661        $progress->{msg_details}->(
662            sprintf(
663                "%s : %s", 
664                $progress->{current_msg}, 
665                $filename
666            )
667        );
668        $params->{ok} = 1;
669        if ($response->code != 200) {
670            printf("response code    : %u\n", $response->code);
671            printf("response message : %s\n", $response->message);
672            $params->{ok} = 0;
673            $params->{message} = $response->message;
674            $params->{code} = $response->code;
675            last;
676        }
677
678        $chunk_id++;
679    }
680}
681
682 
6831;
684   
Note: See TracBrowser for help on using the repository browser.