# +-----------------------------------------------------------------------+ # | pLoader - a Perl photo uploader for Piwigo | # +-----------------------------------------------------------------------+ # | Copyright(C) 2008 Piwigo Team http://piwigo.org | # +-----------------------------------------------------------------------+ # | This program is free software; you can redistribute it and/or modify | # | it under the terms of the GNU General Public License as published by | # | the Free Software Foundation | # | | # | This program is distributed in the hope that it will be useful, but | # | WITHOUT ANY WARRANTY; without even the implied warranty of | # | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | # | General Public License for more details. | # | | # | You should have received a copy of the GNU General Public License | # | along with this program; if not, write to the Free Software | # | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | # | USA. | # +-----------------------------------------------------------------------+ package Uploader::PWG::WebServices; use strict; use warnings; use MIME::Base64 qw(encode_base64); use JSON; use LWP::UserAgent; use Data::Dumper; use Digest::MD5::File qw/file_md5_hex md5_hex/; use File::Slurp; use File::Spec; use POSIX qw(ceil floor); use base qw/ Uploader::Object Class::Accessor::Fast /; __PACKAGE__->mk_accessors( qw/ uagent urlbase username password qry_list_categories qry_add_categories qry_list_tags items tags categories site_high_file site_resized_file site_thumb_file site_image_name site_tags rank site_author site_comment site_img_date_creation uagent_response login_result action_result upload_high chunk_size sum image_id typecode reupload_action_files reupload_action_properties reupload_action_properties_m single_value_mode multiple_value_mode privacy_level / ); $|=1; sub Init { my ( $self, $version ) = @_ ; # to transform a type_code into a typename $self->typecode( { file => 'file', thumbnail => 'thumb', high => 'high', } ); $self->single_value_mode( { 1 => 'fill_if_empty', 2 => 'replace', } ); $self->multiple_value_mode( { 1 => 'append', 2 => 'replace', } ); $self->uagent( LWP::UserAgent->new( agent => sprintf("Mozilla/pLoader %s", $version) ) ); $self->uagent->cookie_jar({}); $self->urlbase( $self->{site_url} ); $self->username( $self->{site_username} ); $self->password( $self->{site_password} ); $self->chunk_size( $self->{chunk_size} ); $self->uagent->default_headers->authorization_basic( $self->{http_username}||$self->username, $self->{http_password}||$self->password ); $self->qry_list_categories( sprintf "%s/ws.php?format=json&method=%s&recursive=%s", $self->urlbase, # 'pwg.categories.getAdminList', 'pwg.categories.getList', 'true', ); $self->qry_list_tags( sprintf "%s/ws.php?format=json&method=%s", $self->urlbase, 'pwg.tags.getAdminList', ); my $form = { method => 'pwg.session.login', username => $self->username, password => $self->password, }; $self->_execute_post( $form ); $self->login_result( $self->_json_response_content ); } sub _json_response_content { my ( $self ) = @_; my $hresult = {} ; if($self->uagent_response->is_success){ # when server response has warnings, the content response is not a valid json string # find the json response $self->uagent_response->content =~ /(\{.+\})/; #printf("json response %s\n", $1); eval { $hresult = from_json( $1 ); }; } else{ $hresult = { 'message' => $self->uagent_response->message, 'stat' => 'fail', }; } $hresult; } sub _execute_get { my ( $self, $query ) = @_; eval { $self->uagent_response( $self->uagent->get( $query ) ); }; if($@){ printf("An error occured in query execution %s\n%s", $query, $@, ); } } sub _execute_post { my ( $self, $form ) = @_; my $result; eval { $result = $self->uagent_response( $self->uagent->post( $self->urlbase.'/ws.php?format=json', $form ) ); }; if($@){ printf("An error occured in post execution %s\n%s", $form->{method}, $@, ); } return ( $result->is_success, $result->status_line ); } sub GetCategories { my ( $self ) = @_; $self->_execute_get( $self->qry_list_categories ); $self->_json_response_content->{result}{categories}; } sub GetTags { my ( $self ) = @_; $self->_execute_get( $self->qry_list_tags ); $self->_json_response_content->{result}{tags}; } sub AddTags { my ( $self, $name ) = @_; my $form = { method => 'pwg.tags.add', name => $name, }; return ( $self->_execute_post($form), $self->_json_response_content ); } sub UploadImage { my ( $self, $progress ) = @_; $self->image_id( $self->_exists($progress->{original_sum}) ); my $status = 1; my $status_line ="OK"; my $content = {}; my $doubleCheck; my $form; UPLOAD: while(1){ # first upload if(!defined($self->image_id)){ $doubleCheck = 1; $self->_checksum_files($progress); my @types = ('file', 'thumb'); #printf("WS upload_high %s\n", $self->upload_high); push @types, 'high' if $self->upload_high; map{ my $rval = $self->_send_chunks($_, $progress); $status_line = $rval->{message}; last UPLOAD if !$rval->{ok}; } @types; $form = { method => 'pwg.images.add', original_sum => $self->sum->{original}, file_sum => $self->sum->{file}, thumbnail_sum => $self->sum->{thumb}, categories => $self->categories, name => $self->site_image_name, author => $self->site_author, comment => $self->site_comment, date_creation => $self->site_img_date_creation, tag_ids => $self->site_tags, }; $form->{high_sum} = $self->sum->{high} if $self->upload_high; $progress->{yield}->(); ( $status, $status_line ) = $self->_execute_post($form); } # re-upload else{ # need to check if files have changed # and update image info if($self->reupload_action_files){ $self->_checksum_files($progress); my $files = $self->_check_files(); if(defined($files)){ $self->_add_files($files, $progress); } } $form = { method => 'pwg.images.setInfo', image_id => $self->image_id, }; # update metadata info if($self->reupload_action_properties){ #printf("reupload_action_properties %s\n", $self->reupload_action_properties); $form->{name} = $self->site_image_name; $form->{author} = $self->site_author; $form->{comment} = $self->site_comment; $form->{date_creation} = $self->site_img_date_creation; $form->{single_value_mode} = $self->single_value_mode->{$self->reupload_action_properties}; $form->{level} = $self->privacy_level ? 2**($self->privacy_level - 1) : 0; } if($self->reupload_action_properties_m){ #printf("reupload_action_properties_m %s\n", $self->reupload_action_properties_m); $form->{tag_ids} = $self->site_tags if $self->site_tags; $form->{categories} = $self->categories; $form->{multiple_value_mode} = $self->multiple_value_mode->{$self->reupload_action_properties_m}; }; $progress->{yield}->(); ( $status, $status_line ) = $self->_execute_post($form); } delete $form->{tag_ids} unless defined $self->site_tags; delete $form->{tag_ids} if '' eq $self->site_tags; $progress->{yield}->(); # for first upload if($doubleCheck){ $self->image_id( $self->_exists($progress->{original_sum}) ); $content->{stat} = !defined $self->image_id ? 'fail' : 'ok'; if(defined $self->image_id and defined $self->privacy_level){ ( $status, $status_line, $content ) = $self->_set_privacy_level; } } last UPLOAD; }# UPLOAD return ( $status, $status_line, $content); } sub _set_privacy_level { my ( $self ) = @_; my $form = { method => 'pwg.images.setPrivacyLevel', image_id => $self->image_id, level => $self->privacy_level ? 2**($self->privacy_level - 1) : 0, }; print Dumper $form; my ( $status, $status_line ) = $self->_execute_post($form); my $hresult = $self->_json_response_content; ($status, $status_line, $hresult ); } sub _checksum_files { my ( $self, $progress ) = @_; $self->sum( { file => $self->_checksum( $self->site_resized_file, $progress ), thumb => $self->_checksum( $self->site_thumb_file, $progress ), original => $progress->{original_sum} } ); high => $self->_checksum( $self->site_high_file, $progress ), $self->sum->{high} = $self->_checksum( $self->site_high_file, $progress ) if $self->upload_high ; } sub _check_files { my ( $self ) = @_; my $form = { method => 'pwg.images.checkFiles', image_id => $self->image_id, }; @$form{'thumbnail_sum', 'file_sum' } = ( $self->sum->{thumb}, $self->sum->{file}, ); if($self->upload_high){ $form->{high_sum} = $self->sum->{high}; } $self->_execute_post($form); my $hresult = $self->_json_response_content; my $rval = 'ok' eq $hresult->{stat} ? $hresult->{result} : undef; $rval; } # $files is returned by _check_files # { # thumbnail => 'equals', 'differs', 'missing' # file => 'equals', 'differs', 'missing' # high => 'equals', 'differs', 'missing' #} sub _add_files { my ( $self, $files, $progress ) = @_; map{ $self->_add_file($_, $progress); } map{ $self->typecode->{$_}; } grep { 'equals' ne $files->{$_} } keys %$files ; } sub _add_file { my ( $self, $type_code, $progress ) = @_; $self->_send_chunks( $type_code, $progress, ); my $form = { method => 'pwg.images.addFile', image_id => $self->image_id, type => $type_code, sum => $self->sum->{$type_code}, }; $self->_execute_post($form); my $hresult = $self->_json_response_content; my $rval = 'ok' eq $hresult->{stat} ? $hresult->{result} : undef; $rval; } sub IsAlreadyUploaded { my ( $self, $md5_sums ) = @_; # md5_sums is an array ref $self->_execute_post({ method => 'pwg.images.exist', md5sum_list => join(',', @$md5_sums) } ); my $sums = $self->_json_response_content->{result}; $sums; } sub _exists { my ( $self, $md5_sum ) = @_; my $form = { method => 'pwg.images.exist', md5sum_list => $md5_sum, }; $self->_execute_post($form); my $hresult = $self->_json_response_content; $hresult->{result} = {} if 'HASH' ne ref $hresult->{result}; my $id = 'ok' eq $hresult->{stat} ? $hresult->{result}{$md5_sum} : undef ; $id; } sub _checksum { my ( $self, $file, $progress ) = @_; my $file_sum; my $yield = $progress->{yield}; $yield->(); $progress->{msg_details}->( sprintf( "%s : %s", $progress->{checksum_msg}, $file ) ); eval { $file_sum = file_md5_hex( $file ); }; $yield->(); $file_sum; } sub _send_chunks { my ( $self, $type_code, $progress ) = @_; my $msg = { thumb=>'thumbnail_msg', file=>'resized_msg', high=>'highdef_msg', }; my $filepath = { thumb=>$self->site_thumb_file, file=>$self->site_resized_file, high=>$self->site_high_file, }; $progress->{current_msg} = $progress->{$msg->{$type_code}}; $progress->{yield}->(); my $params = { filepath => $filepath->{$type_code}, type => $type_code, original_sum => $self->sum->{original}, }; #print Dumper $params; $self->send_chunks( $params, $progress, ); $progress->{yield}->(); $params; } sub AddCategories{ my ( $self, $name, $parentid ) = @_; my $form = { method => 'pwg.categories.add', name => $name, parent => $parentid, }; return ( $self->_execute_post($form), $self->_json_response_content ); } sub SetInfoCategories{ my ( $self, $name, $comment, $parentid ) = @_; my $form = { method => 'pwg.categories.setInfo', name => $name, comment => $comment, category_id => $parentid, }; return ( $self->_execute_post($form), $self->_json_response_content ); } sub send_chunks { my ( $self, $params, $progress ) = @_; my $yield = $progress->{yield}; my ( $vol, $dir, $filename ) = File::Spec->splitpath($params->{filepath}); $yield->(); $progress->{bar}->(0); $yield->(); $progress->{msg_details}->( sprintf( "%s : %s", $progress->{current_msg}, $filename ) ); $yield->(); my $content = read_file( $params->{filepath}, binmode => ':raw', ); $yield->(); my $content_length = length($content); my $nb_chunks = ceil($content_length / $self->chunk_size); my $chunk_pos = 0; my $chunk_id = 1; while ($chunk_pos < $content_length) { my $chunk = substr( $content, $chunk_pos, $self->chunk_size ); $chunk_pos += $self->chunk_size; my $response = $self->uagent->post( $self->urlbase.'/ws.php?format=json', { method => 'pwg.images.addChunk', data => encode_base64($chunk), original_sum => $params->{original_sum}, position => $chunk_id, type => $params->{type}, } ); $yield->(); $progress->{bar}->(100*($chunk_pos/$content_length)); $progress->{msg_details}->( sprintf( "%s : %s", $progress->{current_msg}, $filename ) ); $params->{ok} = 1; if ($response->code != 200) { printf("response code : %u\n", $response->code); printf("response message : %s\n", $response->message); $params->{ok} = 0; $params->{message} = $response->message; $params->{code} = $response->code; last; } $chunk_id++; } } 1;