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

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

Feature 1609 added : Verify upload access while connecting and before upload.

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