Index: /trunk/include/ws_functions.inc.php
===================================================================
--- /trunk/include/ws_functions.inc.php	(revision 2462)
+++ /trunk/include/ws_functions.inc.php	(revision 2463)
@@ -914,4 +914,104 @@
   }
   return $affected_rows;
+}
+
+function ws_images_add($params, &$service)
+{
+  global $conf;
+  
+  // name
+  // category_id
+  // file_content
+  // file_sum
+  // thumbnail_content
+  // thumbnail_sum
+  
+  // $fh_log = fopen('/tmp/php.log', 'w');
+  // fwrite($fh_log, time()."\n");
+  // fwrite($fh_log, 'input:  '.$params['file_sum']."\n");
+  // fwrite($fh_log, 'input:  '.$params['thumbnail_sum']."\n");
+
+  // current date
+  list($dbnow) = mysql_fetch_row(pwg_query('SELECT NOW();'));
+  list($year, $month, $day) = preg_split('/[^\d]/', $dbnow, 4);
+  
+  $upload_dir = sprintf(
+    PHPWG_ROOT_PATH.'upload/%s/%s/%s',
+    $year,
+    $month,
+    $day
+    );
+
+  fwrite($fh_log, $upload_dir."\n");
+  
+  if (!is_dir($upload_dir)) {
+    umask(0000);
+    $recursive = true;
+    mkdir($upload_dir, 0777, $recursive);
+  }
+
+  $date_string = preg_replace('/[^\d]/', '', $dbnow);
+  $random_string = substr($params['file_sum'], 0, 8);
+
+  $filename_wo_ext = $date_string.'-'.$random_string;
+  
+  $file_path = $upload_dir.'/'.$filename_wo_ext.'.jpg';
+  $fh_file = fopen($file_path, 'w');
+  fwrite($fh_file, base64_decode($params['file_content']));
+  fclose($fh_file);
+
+  // check dumped file md5sum with expected md5sum
+
+  $thumbnail_dir = $upload_dir.'/thumbnail';
+  if (!is_dir($thumbnail_dir)) {
+    umask(0000);
+    mkdir($thumbnail_dir, 0777);
+  }
+  
+  $thumbnail_path = sprintf(
+    '%s/%s%s.%s',
+    $thumbnail_dir,
+    $conf['prefix_thumbnail'],
+    $filename_wo_ext,
+    'jpg'
+    );
+  $fh_thumbnail = fopen($thumbnail_path, 'w');
+  fwrite($fh_thumbnail, base64_decode($params['thumbnail_content']));
+  fclose($fh_thumbnail);
+
+  // check dumped thumbnail md5
+
+  // fwrite($fh_log, 'output: '.md5_file($file_path)."\n");
+  // fwrite($fh_log, 'output: '.md5_file($thumbnail_path)."\n");
+
+  // database registration
+  $insert = array(
+    'file' => $filename_wo_ext.'.jpg',
+    'date_available' => $dbnow,
+    'tn_ext' => 'jpg',
+    'name' => $params['name'],
+    'path' => $file_path,
+    );
+
+  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
+  mass_inserts(
+    IMAGES_TABLE,
+    array_keys($insert),
+    array($insert)
+    );
+
+  $image_id = mysql_insert_id();
+
+  $insert = array(
+    'category_id' => $params['category_id'],
+    'image_id'=> $image_id,
+    );
+  mass_inserts(
+    IMAGE_CATEGORY_TABLE,
+    array_keys($insert),
+    array($insert)
+    );
+  
+  // fclose($fh_log);
 }
 
Index: /trunk/tools/piwigo_remote.pl
===================================================================
--- /trunk/tools/piwigo_remote.pl	(revision 2463)
+++ /trunk/tools/piwigo_remote.pl	(revision 2463)
@@ -0,0 +1,118 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use JSON;
+use LWP::UserAgent;
+use Getopt::Long;
+
+my %opt = ();
+GetOptions(
+    \%opt,
+    qw/action=s file=s thumbnail=s category_id=i name=s/
+);
+
+our $ua = LWP::UserAgent->new;
+$ua->cookie_jar({});
+
+my %conf;
+$conf{base_url} = 'http://localhost/~pierrick/piwigo/trunk';
+$conf{partner_key} = 'youhou';
+$conf{response_format} = 'json';
+$conf{username} = 'pierrick';
+$conf{password} = 'z0rglub';
+$conf{limit} = 10;
+
+my $result = undef;
+my $query = undef;
+
+binmode STDOUT, ":encoding(utf-8)";
+
+# TODO : don't connect at each script call, use the session duration instead.
+my $form = {
+    method => 'pwg.session.login',
+    username => $conf{username},
+    password => $conf{password},
+};
+
+$result = $ua->post(
+    $conf{base_url}.'/ws.php?partner='.$conf{partner_key}.'&format=json',
+    $form
+);
+
+# print "\n", $ua->cookie_jar->as_string, "\n";
+
+if ($opt{action} eq 'pwg.images.add') {
+    use MIME::Base64 qw(encode_base64);
+    use Digest::MD5::File qw/file_md5_hex/;
+    use File::Slurp;
+
+    my $file_content = encode_base64(read_file($opt{file}));
+    my $file_sum = file_md5_hex($opt{file});
+
+    my $thumbnail_content = encode_base64(read_file($opt{thumbnail}));
+    my $thumbnail_sum = file_md5_hex($opt{thumbnail});
+
+    $form = {
+        method => 'pwg.images.add',
+        file_sum => $file_sum,
+        file_content => $file_content,
+        thumbnail_sum => $thumbnail_sum,
+        thumbnail_content => $thumbnail_content,
+        category_id => $opt{category_id},
+        name => $opt{name},
+    };
+
+    $result = $ua->post(
+        $conf{base_url}.'/ws.php?partner='.$conf{partner_key}.'&format=json',
+        $form
+    );
+}
+
+if ($opt{action} eq 'pwg.tags.list') {
+    use Text::ASCIITable;
+
+    $query = pwg_ws_get_query(
+        method => 'pwg.tags.getList',
+        sort_by_counter => 'true',
+    );
+
+    $result = $ua->get($query);
+    my $tag_result = from_json($result->content);
+    my $t = Text::ASCIITable->new({ headingText => 'Tags' });
+    $t->setCols('id','counter','name');
+
+    my $tag_number = 1;
+    foreach my $tag_href (@{ $tag_result->{result}{tags} }) {
+        $t->addRow(
+            $tag_href->{id},
+            $tag_href->{counter},
+            $tag_href->{name}
+        );
+
+        last if $tag_number++ >= $conf{limit};
+    }
+    print $t;
+}
+
+$query = pwg_ws_get_query(
+    method => 'pwg.session.logout'
+);
+$ua->get($query);
+
+sub pwg_ws_get_query {
+    my %params = @_;
+
+    my $query = $conf{base_url}.'/ws.php?format='.$conf{response_format};
+
+    if (defined $conf{partner_key}) {
+        $query .= '&partner='.$conf{partner_key};
+    }
+
+    foreach my $key (keys %params) {
+        $query .= '&'.$key.'='.$params{$key};
+    }
+
+    return $query;
+}
Index: /trunk/ws.php
===================================================================
--- /trunk/ws.php	(revision 2462)
+++ /trunk/ws.php	(revision 2463)
@@ -129,10 +129,14 @@
       'Returns elements for the corresponding query search.'
     );
-  $service->addMethod('pwg.images.setPrivacyLevel', 'ws_images_setPrivacyLevel',
-      array(
-        'image_id' => array('flags'=>WS_PARAM_FORCE_ARRAY),
-        'level' => array('maxValue'=>$conf['available_permission_levels']),
-      ),
-      'sets the privacy levels for the images' );
+  
+  $service->addMethod(
+    'pwg.images.setPrivacyLevel',
+    'ws_images_setPrivacyLevel',
+    array(
+      'image_id' => array('flags'=>WS_PARAM_FORCE_ARRAY),
+      'level' => array('maxValue'=>$conf['available_permission_levels']),
+      ),
+    'sets the privacy levels for the images'
+    );
 
   $service->addMethod('pwg.session.getStatus', 'ws_session_getStatus', null, '' );
@@ -168,4 +172,18 @@
       'Returns elements for the corresponding tags. Note that tag_id, tag_url_name, tag_name an be arrays. Fill at least one of them. '
     );
+
+  $service->addMethod(
+    'pwg.images.add',
+    'ws_images_add',
+    array(
+      'name',
+      'category_id',
+      'file_content',
+      'file_sum',
+      'thumbnail_content',
+      'thumbnail_sum'
+      ),
+    'POST method only'
+    );
 }
 
