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

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

Feature 1052 added : photo re-upload management. When uploading an already added photo, the user is warned and has a choice on action for files, simple properties and multi-values properties.

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