The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Gallery::Remote;

# An implementation of the gallery remote protocol as defined by
# http://gallery.menalto.com/modules.php?op=modload&name=GalleryDocs&file=index&page=gallery-remote.protocol.php
#
# Copyright (C) 2004, Tanner Lovelace <lovelace@cpan.org>
#
# This library 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; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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 perl module; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

use strict;
use Carp;
use vars qw(
	$VERSION
	$REVISION
	@ISA
	@EXPORT
	@EXPORT_OK
	@configs
);

require Exporter;
use LWP::UserAgent;
use HTTP::Cookies;
use HTTP::Request::Common;
#use HTTP::Response;

use constant PROTOCOL_VERSION => '2.7';
use constant PROTOCOL_MAJOR   => '2';
use constant PROTOCOL_MINOR   => '7';
use constant RESPONSE_BEGINNING => '#__GR2PROTO__\n';

# Response Status Codes
#
# Status name
# Code
# Description
#
# GR_STAT_SUCCESS
# 0
# The command the client sent in the request completed
# successfully. The data (if any) in the response should be considered
# valid.
#
# GR_STAT_PROTO_MAJ_VER_INVAL
# 101
# The protocol major version the client is using is not supported.
#
# GR_STAT_PROTO_MIN_VER_INVAL
# 102
# The protocol minor version the client is using is not supported.
#
# GR_STAT_PROTO_VER_FMT_INVAL
# 103
# The format of the protocol version string the client sent in the
# request is invalid.
#
# GR_STAT_PROTO_VER_MISSING
# 104
# The request did not contain the required protocol_version key.
#
# GR_STAT_PASSWD_WRONG
# 201
# The password and/or username the client send in the request is
# invalid.
#
# GR_STAT_LOGIN_MISSING
# 202
# The client used the login command in the request but failed to
# include either the username or password (or both) in the request.
#
# GR_STAT_UNKNOWN_CMD
# 301
# The value of the cmd key is not valid.
#
# GR_STAT_NO_ADD_PERMISSION
# 401
# The user does not have permission to add an item to the gallery.
#
# GR_STAT_NO_FILENAME
# 402
# No filename was specified.
#
# GR_STAT_UPLOAD_PHOTO_FAIL
# 403
# The file was received, but could not be processed or added to the
# album.
#
# GR_STAT_NO_WRITE_PERMISSION
# 404
# No write permission to destination album.
#
# GR_STAT_NO_CREATE_ALBUM_PERMISSION
# 501
# A new album could not be created because the user does not have
# permission to do so.
#
# GR_STAT_CREATE_ALBUM_FAILED
# 502
# A new album could not be created, for a different reason (name
# conflict).
use constant GR_STAT_SUCCESS                    => '0';
use constant GR_STAT_PROTO_MAJ_VER_INVAL        => '101';
use constant GR_STAT_PROTO_MIN_VER_INVAL        => '102';
use constant GR_STAT_PROTO_VER_FMT_INVAL        => '103';
use constant GR_STAT_PROTO_VER_MISSING          => '104';
use constant GR_STAT_PASSWD_WRONG               => '201';
use constant GR_STAT_LOGIN_MISSING              => '202';
use constant GR_STAT_UNKNOWN_CMD                => '301';
use constant GR_STAT_NO_ADD_PERMISSION          => '401';
use constant GR_STAT_NO_FILENAME                => '402';
use constant GR_STAT_UPLOAD_PHOTO_FAIL          => '403';
use constant GR_STAT_NO_WRITE_PERMISSION        => '404';
use constant GR_STAT_NO_CREATE_ALBUM_PERMISSION => '501';
use constant GR_STAT_CREATE_ALBUM_FAILED        => '502';


@ISA = qw(Exporter);
# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
@EXPORT = qw(
);
$VERSION  = '0.2';
$REVISION = (qw$Rev: 13 $)[-1];

# Preloaded methods go here.

sub new {
    my $classname = shift;
    my $self      = {};
    bless($self, $classname);
    $self->_init(@_);
    return $self;
}

# 
# Intenal methods
#
sub _init
{
    my $self = shift;

    $self->{LOGGEDIN} = 0;

    if (@_) {
	my %extra = @_;
	@$self{keys %extra} = values %extra;
    }
    if ($self->{URL}) {
	$self->{REMOTE_URL} = $self->{URL} . "gallery_remote2.php";
    }
}

sub _check_response
{
    my $self     = shift;
    my $response = shift;
    my $url      = shift;

    my $response_code = $response->code;
    my $response_text = $self->_get_response_text($response);

    # Error!
    if ($response_code != 200) { 
	if ($response_code == 404) {
	    die "Remote gallery not found at " . $url ."\n" . 
		"Code   : " . $response_code . "\n" .
		"Message: " . $response_text . "\n";
	} else {
	    die "Could not log in to remote gallery at " . $url ."\n" . 
		"Code   : " . $response_code . "\n" .
		"Message: " . $response_text . "\n";
	}
    }
    return 1;
}

sub _get_response_text
{
    my $self     = shift;
    my $response = shift;
    if ($response->is_error) {
	return $response->error_as_HTML;
    }
    return $response->content;
}

sub _fill_in_response_fields
{
    my $self         = shift;
    my $field_string = shift;

    my $fields = {};

    print STDERR "Response string:\n$field_string\n" if ($self->{DEBUG});

    # Strip off the the stuff before responses.
    my $resp_beg = RESPONSE_BEGINNING;
    $field_string =~ s/.*$resp_beg//s;
    print STDERR "Response string (header stripped off):\n$field_string\n" if ($self->{DEBUG});
    my @field_lines = split('\n',$field_string);
    foreach my $line (@field_lines) {
	#print STDERR "Response line: $line\n" if ($self->{DEBUG});
	my ($field_key, $field_value) = split('=', $line);
	print STDERR "Response field: $field_key = $field_value\n" if ($self->{DEBUG});
	$fields->{$field_key} = $field_value if ($field_value ne "");
    }

    return $fields;
}

sub _fill_in_album_data
{
    my $self            = shift;
    my $response_fields = shift;
    # Array reference for returning album data
    my $album_data = ();

    for (my $i = 1; $i <= $response_fields->{album_count}; $i++) {
	my $album_entry = {};
	$album_entry->{name}       = $response_fields->{"album.name.$i"};
	$album_entry->{title}      = $response_fields->{"album.title.$i"};
	$album_entry->{parent}     = $response_fields->{"album.parent.$i"};
	$album_entry->{add}        = ($response_fields->{"album.perms.add.$i"} eq 'true' ? 1 : 0);
	$album_entry->{write}      = ($response_fields->{"album.perms.write.$i"} eq 'true' ? 1 : 0);
	$album_entry->{del_item}   = ($response_fields->{"album.perms.del_item.$i"} eq 'true' ? 1 : 0);
	$album_entry->{del_alb}    = ($response_fields->{"album.perms.del_alb.$i"} eq 'true' ? 1 : 0);
	$album_entry->{create_sub} = ($response_fields->{"album.perms.create_sub.$i"} eq 'true' ? 1 : 0);

	print STDERR "Response fields: \n" .
	             " album.name.$i             = " . $response_fields->{"album.name.$i"} . "\n" .
	             " album.title.$i            = " . $response_fields->{"album.title.$i"} . "\n" .
	             " album.parent.$i           = " . $response_fields->{"album.parent.$i"} . "\n" .
		     " album.perms.add.$i        = " . $response_fields->{"album.perms.add.$i"} . "\n" .
		     " album.perms.write.$i      = " . $response_fields->{"album.perms.write.$i"} . "\n" .
		     " album.perms.del_item.$i   = " . $response_fields->{"album.perms.del_item.$i"} . "\n" .
		     " album.perms.del_alb.$i    = " . $response_fields->{"album.perms.del_alb.$i"} . "\n" .
		     " album.perms.create_sub.$i = " . $response_fields->{"album.perms.create_sub.$i"} . "\n"
		     if ($self->{DEBUG});
		     
	push @$album_data, $album_entry;
    }
    
    return $album_data;
}

#
# External methods
#
sub set_server
{
    my $self = shift;

    if ($self->{LOGGEDIN}) { $self->logout(); }

    # Setup values.
    $self->{URL}      = shift;
    $self->{USERNAME} = shift;
    $self->{PASSWORD} = shift;

    $self->{REMOTE_URL} = $self->{URL} . "gallery_remote2.php";
}

sub login
{
    my $self = shift;

    die "URL must be set to login to remote gallery" unless $self->{REMOTE_URL};
    die "USERNAME must be set to login to remote gallery" unless $self->{USERNAME};
    die "PASSWORD must be set to login to remote gallery" unless $self->{PASSWORD};

    $self->{_UA} = LWP::UserAgent->new;
    $self->{_UA}->cookie_jar(HTTP::Cookies->new(file => 'cookie_jar', autosave => 1));

    my $response  = $self->{_UA}->request(POST $self->{REMOTE_URL},
					  Content_Type => 'form-data',
					  Content      => [ protocol_version => PROTOCOL_VERSION,
							    cmd => "login",
							    uname => $self->{USERNAME},
							    password => $self->{PASSWORD}
							  ] );

    my $response_text = $self->_get_response_text($response);

    $self->_check_response($response,$self->{URL});

    # Ok, the file was there.  Parse the response codes.
    my $response_fields = $self->_fill_in_response_fields($response_text);

    print STDERR "Response status: $response_fields->{status}\n" if ($self->{DEBUG});

    if ($response_fields->{status} eq GR_STAT_SUCCESS) {
	# Make sure our version number is high enough
	my ($major, $minor) = split('\.', $response_fields->{server_version});
	if ($major != PROTOCOL_MAJOR) {
	    die "Major protocol mismatch.  Was expecting " . PROTOCOL_MAJOR . ".  Got: $major";
	}
	if ($minor < PROTOCOL_MINOR) {
	    die "Need a minor protocol of at least " . PROTOCOL_MINOR . ". Got: $minor";
	}
	$self->{LOGGEDIN} = 1;
	print "Login successful\n" if ($self->{VERBOSE});
    } else {
	die "Could not log in to remote gallery at " . $self->{URL} ."\n" . 
	    "Status: " . $response_fields->{status} . "\n" .
	    "Text  : " . $response_fields->{status_text} . "\n";
    }
}

sub logout
{
    my $self = shift;

    # No specific logout command, so reset variables

    $self->{LOGGEDIN} = 0;
    $self->{_UA} = undef;
}


# Getting a list of albums
#
# The fetch-albums command asks the server to return a list of all
# albums (visible with the client's logged in user permissions).
#
# Context
#
# A request with the login command must be made before the
# fetch-albums command is used.
#
# Form data
#
# cmd=fetch-albums
# protocol_version=2.0
#
# Results
#
#           #__GR2PROTO__
#          status=result-code
#          status_text=explanatory-text
#         /album.name.ref-num=album-url-name
#         |album.title.ref-num=album-display-name
#         |album.summary.ref-num=album-summary [since 2.8]
#         |album.parent.ref-num=parent-ref-num
#         |album.resize_size.ref-num=image-resize-size [since 2.8]
#   0...n |album.perms.add.ref-num=boolean
#         |album.perms.write.ref-num=boolean
#         |album.perms.del_item.ref-num=boolean
#         |album.perms.del_alb.ref-num=boolean
#         |album.perms.create_sub.ref-num=boolean
#         \album.info.extrafields.ref-num=extra-fields [since 2.3]
#          album_count=number-of-albums
#          can_create_root=yes/no [since 2.11]
#
# If the result-code is equal to GR_STAT_SUCCESS, the album data was
# fetched successfully.
#
# If successful, a response to the fetch-albums command returns
# several keys for each album in the gallery, where
#
# ref-num is a reference number,
#
# album-url-name is the name of the partial URL for the gallery,
#
# album-display-name is the gallery's visual name,
#
# album-summary is the summary of the album,
#
# parent-ref-num refers to some other album's ref-num. A
#   parent-ref-num of 0 (zero) indicates that the album is a "top-level"
#   album (it has no parent).
#
# image-resize-size is the intermediate size of images created when a
#   large image is added to an album,
#
# extra-fields is a comma-separated list of extra fields names, and
#   boolean represents a boolean value. true is considered true, any
#   other value false.
#
# Several "permissions" are reported for each album. The reported
# permissions are the effective permissions of the currently logged in
# user:
#
# the add permission allows the user to add a picture to the album.
#
# the write permission allows the user to add and change pictures in
# the album.
#
# the del_item permission allows the user remove pictures from the
# album.
#
# the del_alb permission allows the user to delete the album.
#
# the create_sub permission allows the user to create nested albums in
# the album.
#
# The number of albums in the response is returned as
# number-of-albums.
#
# can_create_root will be set to either yes or no depending on the
# user's permissions to create albums at the root level.
sub fetch_albums
{
    my $self = shift;

    die "Must log in before fetching albums" unless ($self->{LOGGEDIN});

    my $response  = $self->{_UA}->request(POST $self->{REMOTE_URL},
					  Content_Type => 'form-data',
					  Content      => [ protocol_version => PROTOCOL_VERSION,
							    cmd => "fetch-albums",
							  ] );

    my $response_text = $self->_get_response_text($response);

    $self->_check_response($response,$self->{URL});

    # Ok, the file was there.  Parse the response codes.
    my $response_fields = $self->_fill_in_response_fields($response_text);

    if ($response_fields->{status} ne GR_STAT_SUCCESS) {
	die "Could not fetch-albums from remote gallery at " . $self->{URL} ."\n" . 
	    "Status: " . $response_fields->{status} . "\n" .
	    "Text  : " . $response_fields->{status_text} . "\n";
    }

    return($self->_fill_in_album_data($response_fields));
}


# Getting a list of albums v2, more efficient [since 2.2]
#
# The fetch-albums-prune command asks the server to return a list of
# all albums that the user can either write to, or that are visible to
# the user and contain a sub-album that is writable (including
# sub-albums several times removed).
#
# The reason for this slightly altered version of fetch-albums is
# two-fold: the previous version was slow on the server-side, because
# of the way it was structured, and limitation in the Gallery mode of
# operation; it returns all albums the the user can read, even if
# writing is not allowed. This new version is faster, because it uses
# a more efficient algorithm to find albums; it is more efficient
# because it only sends albums that are useful to the client. It also
# doesn't parse the pictures database, which makes it run much faster
# on the server.
#
# Context
#
# A request with the login command must be made before the
# fetch-albums-prune command is used.
#
# Form data
#
# cmd=fetch-albums-prune
# protocol_version=2.2
# check_writeable=yes/no [since 2.13]
#
# Results
#
#           #__GR2PROTO__
#          status=result-code
#          status_text=explanatory-text
#         /album.name.ref-num=album-url-name
#         |album.title.ref-num=album-display-name
#         |album.summary.ref-num=album-summary [since 2.8]
#         |album.parent.ref-num=parent-ref-num
#         |album.resize_size.ref-num=image-resize-size [since 2.8]
#         |album.thumb_size.ref-num=image-thumb-size [since 2.9]
#   0...n |album.perms.add.ref-num=boolean
#         |album.perms.write.ref-num=boolean
#         |album.perms.del_item.ref-num=boolean
#         |album.perms.del_alb.ref-num=boolean
#         |album.perms.create_sub.ref-num=boolean
#         \album.info.extrafields.ref-num=extra-fields [since 2.3]
#          album_count=number-of-albums
#          can_create_root=yes/no [since 2.11]
#
# If the result-code is equal to GR_STAT_SUCCESS, the album data was
# fetched successfully.
#
# If successful, a response to the fetch-albums-prune command returns
# several keys for each album in the gallery, where
#
# ref-num is a reference number,
#
# album-url-name is the name of the partial URL for the gallery,
#
# album-display-name is the gallery's visual name,
#
# album-summary is the summary of the album,
#
# parent-ref-num refers to some other album's ref-num. A
# parent-ref-num of 0 (zero) indicates that the album is a "top-level"
# album (it has no parent).
#
# image-resize-size is the intermediate size of images created when a
# large image is added to an album,
#
# extra-fields is a comma-separated list of extra fields names,
#
# and boolean represents a boolean value. true is considered true, any
# other value false.
#
# Several "permissions" are reported for each album. The reported
# permissions are the effective permissions of the currently logged in
# user:
#
# the add permission allows the user to add a picture to the album.
#
# the write permission allows the user to add and change pictures in
# the album.
#
# the del_item permission allows the user remove pictures from the
# album.
#
# the del_alb permission allows the user to delete the album.
#
# the create_sub permission allows the user to create nested albums in
# the album.
#
# The number of albums in the response is returned as number-of-albums.
#
# can_create_root will be set to either yes or no depending on the
# user's permissions to create albums at the root level.
sub fetch_albums_prune
{
    my $self = shift;

    die "Must log in before fetching albums" unless ($self->{LOGGEDIN});

    my $response  = $self->{_UA}->request(POST $self->{REMOTE_URL},
					  Content_Type => 'form-data',
					  Content      => [ protocol_version => PROTOCOL_VERSION,
							    cmd => "fetch-albums-prune",
							  ] );

    my $response_text = $self->_get_response_text($response);

    $self->_check_response($response,$self->{URL});

    # Ok, the file was there.  Parse the response codes.
    my $response_fields = $self->_fill_in_response_fields($response_text);

    if ($response_fields->{status} ne GR_STAT_SUCCESS) {
	die "Could not fetch-albums-prune from remote gallery at " . $self->{URL} ."\n" . 
	    "Status: " . $response_fields->{status} . "\n" .
	    "Text  : " . $response_fields->{status_text} . "\n";
    }

    return($self->_fill_in_album_data($response_fields));
}

# Uploading a photo to an album
#
# The add-item command asks the server to add a photo to a specified
# album.
#
# Context
#
# A request with the login command must be made before the add-item
# command is used.
#
# Form data
#
# cmd=add-item
# protocol_version=2.0
# set_albumName=album name
# userfile=form-data-encoded image data [since 2.0] or URL of image [since 2.12]
# userfile_name=file name (usually inserted automatically by HTTP library, which is why we also have force_filename
# caption=caption (optional) [since 2.0]
# force_filename=name of the file on the server (optional) [since 2.0]
# auto_rotate=yes/no (optional) [since 2.5]
# extrafield.fieldname=value of the extra field fieldname (optional) [since 2.3]
#
# Multiple extrafield lines with different fieldname values can be
# used.
#
# Only gallery administrators can specify a URL as the userfile.
#
# Results
#
# #__GR2PROTO__
# status=result-code
# status_text=explanatory-text
#
# If the result-code is equal to GR_STAT_SUCCESS, the file upload
# succeeded.
sub add_item
{
    my $self = shift;

    die "Must log in before getting album properties" unless ($self->{LOGGEDIN});

    my %params = @_;

    if (!$params{set_albumName}) {
	die "Must specify album to add items to it.";
    }
    if (!$params{userfile}) {
	die "Must specify an image file or url for add_item.";
    }
    if (!$params{userfile_name}) {
	die "Must specify an image file name or url for add_item.";
    }

    my $content = { protocol_version => PROTOCOL_VERSION,
		    cmd => "add-item",
		  };

    # Set everything at once.
    @$content{keys %params} = values %params;

    my $response  = $self->{_UA}->request(POST $self->{REMOTE_URL},
					  Content_Type => 'form-data',
					  Content      => $content );

    my $response_text = $self->_get_response_text($response);

    $self->_check_response($response,$self->{URL});

    # Ok, the file was there.  Parse the response codes.
    my $response_fields = $self->_fill_in_response_fields($response_text);

    if ($response_fields->{status} ne GR_STAT_SUCCESS) {
	die "Could not execute album-properties command on remote gallery at " . $self->{URL} ."\n" . 
	    "Status: " . $response_fields->{status} . "\n" .
	    "Text  : " . $response_fields->{status_text} . "\n";
    }

    # XXX - Do we want to return this for all methods?
    return $response_fields;
}

# Getting information about an album [since 2.0]
#
# The album-properties command asks the server for information about
# an album.
#
# Context
#
# A request with the login command must be made before the
# album-properties command is used.
#
# Form data
#
# cmd=album-properties
# protocol_version=2.0
# set_albumName=album name
#
# Results
#
# #__GR2PROTO__
# status=result-code
# status_text=explanatory-text
# auto_resize=resize-dimension
# add_to_beginning=yes/no
#
# If the result-code is equal to GR_STAT_SUCCESS, the request
# succeeded.
#
# If an image is uploaded such that its largest dimension is greater
# than resize-dimension, the server will resize it. Otherwise, the
# server will use the original image that was uploaded for both the
# full-sized and the resized size. In all cases a thumbnail-sized
# image will be created. The creation of a thumbnail is highly
# dependant on the size of the image that was uploaded.
#
# If the value is 0 (zero), the Gallery server does not intend to
# resize uploaded images.
#
# add_to_beginning will contain yes or no based on whether the album
# will add images to the beginning or the end of the album. [since
# 2.10]
sub album_properties
{
    my $self = shift;

    die "Must log in before getting album properties" unless ($self->{LOGGEDIN});

    my %params = @_;

    if (!$params{set_albumName}) {
	die "Must specify album to get its properties";
    }

    my $content = { protocol_version => PROTOCOL_VERSION,
		    cmd => "album-properties",
		  };

    # Set everything at once.
    @$content{keys %params} = values %params;

    my $response  = $self->{_UA}->request(POST $self->{REMOTE_URL},
					  Content_Type => 'form-data',
					  Content      => $content );

    my $response_text = $self->_get_response_text($response);

    $self->_check_response($response,$self->{URL});

    # Ok, the file was there.  Parse the response codes.
    my $response_fields = $self->_fill_in_response_fields($response_text);

    if ($response_fields->{status} ne GR_STAT_SUCCESS) {
	die "Could not execute album-properties command on remote gallery at " . $self->{URL} ."\n" . 
	    "Status: " . $response_fields->{status} . "\n" .
	    "Text  : " . $response_fields->{status_text} . "\n";
    }

    # XXX - Do we want to return this for all methods?
    return $response_fields;
}

# Creating a new album [since 2.1]
#
# The new-album command asks the server to add a new album to the
# gallery installation.
#
# Context
#
# A request with the login command must be made before the new-album
# command is used.
#
# Form data
#
# cmd=new-album
# protocol_version=2.1
# set_albumName=parent-album-name
# newAlbumName=album-name (optional)
# newAlbumTitle=album-title (optional)
# newAlbumDesc=album-description (optional)
#
# parent-album-name is the name of the gallery that the new album
# should be created under, or 0 to create the album at the top-level;
#
# album-name is the new album's desired name. The name must be unique
# within the Gallery. If it is not, then Gallery will assign an
# automatically-generated name. An automatically generated name will
# also be used if this parameter is not provided or is empty;
#
# album-title is the new album's desired title;
#
# album-description is the new album's desired description.
#
# Results
#
# #__GR2PROTO__
# status=result-code
# status_text=explanatory-text
# album_name=actual-name [since 2.5]
#
# If the result-code is equal to GR_STAT_SUCCESS, the request
# succeeded.
#
# If the result-code is equal to GR_STAT_NO_CREATE_ALBUM_PERMISSION,
# the logged-in user doesn't have permission to create an album in the
# specified location.
#
# If an album is created with the same name as an already existing
# album or album-title is left blank, gallery will automatically
# generate an album name. actual-name will return the name of the
# newly created album.
sub new_album
{
    my $self = shift;

    die "Must log in before creating albums" unless ($self->{LOGGEDIN});

    my %params = @_;

    my $content = { protocol_version => PROTOCOL_VERSION,
		    cmd => "new-album",
		  };

    # Set everything at once.
    @$content{keys %params} = values %params;

    my $response  = $self->{_UA}->request(POST $self->{REMOTE_URL},
					  Content_Type => 'form-data',
					  Content      => $content );

    my $response_text = $self->_get_response_text($response);

    $self->_check_response($response,$self->{URL});

    # Ok, the file was there.  Parse the response codes.
    my $response_fields = $self->_fill_in_response_fields($response_text);

    if ($response_fields->{status} ne GR_STAT_SUCCESS) {
	die "Could not execute new-album command on remote gallery at " . $self->{URL} ."\n" . 
	    "Status: " . $response_fields->{status} . "\n" .
	    "Text  : " . $response_fields->{status_text} . "\n";
    }

    return $response_fields->{album_name};
}

# Getting the list of photos in an album [since 2.4]
#
# The fetch-album-images command asks the server to return information
# about all the images in an album. It ignores sub-albums.
#
# Context
#
# A request with the login command can be made before the
# fetch-album-images command is used, but since viewing photos in an
# album is generally (but not always) open to non logged-in users, a
# login is not always necessary.
#
# Form data
#
# cmd=fetch-album-images
# protocol_version=2.4
# set_albumName=album-name
# albums_too=yes/no [since 2.13]
#
# If set_albumName empty, the root albums are listed. Of course, this
# only works if albums_too is also used [since 2.13]
#
# Results
#
#          #__GR2PROTO__
#          status=result-code
#          status_text=explanatory-text
#         /image.name.ref_num=filename of the image
#         |image.raw_width.ref_num=the width of the full-sized image
#         |image.raw_height.ref_num=the height of the full-sized image
#         |image.resizedName.ref_num=filename of the resized image, if there is one
#         |image.resized_width.ref_num=the width of the resized image, if there is one [since 2.9]
#         |image.resized_height.ref_num=the height of the resized image, if there is one [since 2.9]
#         |image.thumbName.ref_num=filename of the thumbnail [since 2.9]
#         |image.thumb_width.ref_num=the width of the thumbnail [since 2.9]
#         |image.thumb_height.ref_num=the height of the thumbnail [since 2.9]
#         |image.raw_filesize.ref_num=size of the full-sized image
#         |image.caption.ref_num=caption associated with the image
#   0...n |image.extrafield.fieldname.ref_num=value of the extra field of key fieldname
#         |image.clicks.ref_num=number of clicks on the image
#         |image.capturedate.year.ref_num=date of capture of the image (year)
#         |image.capturedate.mon.ref_num=date of capture of the image (month)
#         |image.capturedate.mday.ref_num=date of capture of the image (day of the month)
#         |image.capturedate.hours.ref_num=date of capture of the image (hour)
#         |image.capturedate.minutes.ref_num=date of capture of the image (minute)
#         \image.capturedate.seconds.ref_num=date of capture of the image (second)
#   OR     album.name.ref_num=name of the album [since 2.13]
#          image_count=total number of images in the album
#          baseurl=URL of the album
#
# If the result-code is equal to GR_STAT_SUCCESS, the request
# succeeded.
#
# The baseurl contains a fully-qualified URL. A URL to each image can
# be obtained by appending the filename of the image to this.
#
# The name and resizedName include the type (extension), but do not
# include any path information.
#
# Multiple extrafield lines with different fieldname values can be
# used.
#
# If albums_too is yes, the list of results can contain album
# references. In this case, none of the image.*.ref_num fields will be
# present. Instead, album.name.ref_num will provide the reference to
# the sub-album. This allows reursive getting of all images in an
# album hierarchy.
sub fetch_album_images
{
    my $self = shift;

    die "XXX - fetch_album_images not implemented yet!";
}

# Moving an album [since 2.7]
#
# The move-album command asks the server to move an album to a new
# location within the photo gallery.
#
# Context
#
# A request with the login command must be made before the move-album
# command is used.
#
# Form data
#
# cmd=move-album
# protocol_version=2.7
# set_albumName=source-album
# set_destalbumName=destination-album
#
# source-album is the name of the album that you intend to move;
#
# destination-album is the name of the album that the source-album
# will be moved into, or 0 if the source-album should be moved to the
# root level;
#
# Results
#
# #__GR2PROTO__
# status=result-code
# status_text=explanatory-text
#
# If the result-code is equal to GR_STAT_SUCCESS, the album move succeeded.
sub move_album
{
    my $self = shift;

    die "Must log in before creating albums" unless ($self->{LOGGEDIN});

    my %params = @_;

    if (!$params{set_albumName}) {
	die "Must specify album to move";
    }
    if (!$params{set_destalbumName}) {
	die "Must specify destination for move";
    }

    my $content = { protocol_version => PROTOCOL_VERSION,
		    cmd => "move-album",
		  };

    # Set everything at once.
    @$content{keys %params} = values %params;

    my $response  = $self->{_UA}->request(POST $self->{REMOTE_URL},
					  Content_Type => 'form-data',
					  Content      => $content );

    my $response_text = $self->_get_response_text($response);

    $self->_check_response($response,$self->{URL});

    # Ok, the file was there.  Parse the response codes.
    my $response_fields = $self->_fill_in_response_fields($response_text);

    if ($response_fields->{status} ne GR_STAT_SUCCESS) {
	die "Could not execute move-album command on remote gallery at " . $self->{URL} ."\n" . 
	    "Status: " . $response_fields->{status} . "\n" .
	    "Text  : " . $response_fields->{status_text} . "\n";
    }

    return $response_fields->{status};
}

1;
__END__
# This documentation is still incomplete and needs to be finished.

=head1 NAME

Gallery::Remote - Perl extension for interacting with the Gallery remote protocol.

=head1 SYNOPSIS

  use Gallery::Remote;

  # Instatiate a new Gallery::Remote object
  my $remote_gallery  = Gallery::Remote->new(URL => 'http://www.example.com/gallery/',
                                             USERNAME => 'admin',
                                             PASSWORD => 'password');

  $remote_gallery->login();

  # Get an array of hash information about remote albums.
  my $album_data = $remote_gallery->fetch_albums_prune();

  if ($album_data) {
    print "Albums found: " . scalar(@$album_data) . "\n";
  } else {
    print "No albums found.\n";
  }

  # Go through and find all album data.
  foreach my $album_entry (@$album_data) {
    foreach my $key (keys %$album_entry) {
      print "Found: album_entry{$key} = $$album_entry{$key}\n";
    }
  }

  my $parms = {};
  my $picparms = {};

  $$parms{name} = "test";
  $$parms{title} = "A test of Gallery::Remote";
  $$parms{desc} = "I'm testing out my perl script";

  my $parent_album = $remote_gallery->new_album( %$parms );

  $parms = {};
  $$parms{parent} = $parent_album;
  $$parms{title} = "Test Album";
  $$parms{desc} = "Sub album test";
  $$parms{name} = "test2";
  my $new_album_name = $remote_gallery->new_album( %$parms );
  print "Created new album: $new_album_name under parent album $parent_album\n";

  $$picparms{set_albumName} = $new_album_name;
  $$picparms{userfile} = [ "./example.jpg" ];
  $$picparms{userfile_name} = "example.jpg";
  $$picparms{caption} = "Testing Gallery::Remote";

  $remote_gallery->add_item( %$picparms );


=head1 DESCRIPTION

B<Gallery::Remote> is a perl module that allows remote access to
a remote gallery.

=head1 METHODS

=over 2

=item B<new( URL => "http://gallery.example.com/", USERNAME => "admin", PASSWORD => "password", VERBOSE => 0, DEBUG => 0, )>

The B<new()> method specifies the remote gallery and a username
and pasword combination to log in with. You can optionally
specify verbose operation or debug (which will print out a lot
of information).

=back

=head1 AUTHOR

Tanner Lovelace <lovelace@wayfarer.org>

=head1 SEE ALSO

Gallery - http://gallery.sf.net/

=cut