## package RefDB::Client;
## RefDB Client module

## markus@mhoenicka.de 2003-03-03

##   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; either version 2 of the License, or
##   (at your option) any later version.
##   
##   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 main documentation

=head1 NAME

RefDB::Client - Perl extension for talking to a RefDB server

=head1 SYNOPSIS

    use RefDB::Client;


=head1 DESCRIPTION

RefDB::Client provides functions to talk to a refdbd process like the RefDB C clients do. This module isn't a wrapper for the C clients but uses native Perl code to talk to the server.

=head1 FEEDBACK

    Send bug reports, questions, and comments to the refdb-users mailing list at:

    refdb-users@lists.sourceforge.net

    For list information and archives, please visit:

http://lists.sourceforge.net/lists/listinfo/refdb-users


=head1 AUTHOR

    Markus Hoenicka, markus@mhoenicka.de

=head1 SEE ALSO

    This module is part of the RefDB package, a reference manager and bibliography tool for markup languages. Please visit http://refdb.sourceforge.net for further information.

=cut




######################################################################
######################################################################
## defines a class to deal with RIS data

package RefDB::Risdata;

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


=head2 new

    Title   : new

    Usage   : new RefDB::Risdata();

Function: Creates a new Risdata object

=cut

######################################################################
## new(): creates a new Risdata element
######################################################################
    sub new() {
	my $class = shift;
	my $self = {};

	## the list that holds the datasets
	$self->{sets} = ();

	bless $self, $class;
	return $self;
    }

=head2 read_ris

    Title   : read_ris

    Usage   : $data->read_ris($file);

Function: loads RIS data from a file

Parameter: $file: path of file

=cut

######################################################################
## read_ris(): loads RIS data from a file
######################################################################
    sub read_ris {
	my ($self, $file) = @_;

	my $instring;

	open IN, "< $file";
	while (<IN>) {
	    $instring .= $_;
	    if ($_ =~ /^ER  - /) {
		push(@{$self->{sets}}, $instring);
		$instring = "";
	    }
	}
	close IN;

	$self->{sets};
    }

=head2 get_ris

    Title   : get_ris

    Usage   : $data->get_ris();

Function: returns previously loaded RIS data

=cut

######################################################################
## get_ris(): returns previously loaded RIS data
######################################################################
    sub get_ris {
	my ($self) = @_;

	$self->{sets};
    }

######################################################################
######################################################################
## defines a helper class for retrieving simple lists from the server

package RefDB::Simplelist;

use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
use constant REFDB_PROTOCOL_VERSION => 6;
use constant COMMAND_INBUF_LENGTH => 4096;
use constant OUTBUF_LEN => 4096;


=head2 new

    Title   : new

    Usage   : new RefDB::Simplelist;

Function: creates a new Simplelist element

=cut

######################################################################
## new(): creates a new Simplelist element
######################################################################
    sub new() {
	my $class = shift;
	my $self = {};

	## the buffer that holds the command sent to the server
	$self->{cmd} = undef;

	## the buffer that holds the message received from the server
	$self->{inbuffer} = undef;

	## the buffer that holds the summary received from the server
	#$self->{summary} = undef;

	## the data in list format
	$self->{result} = ();

	## whether or not to close
	$self->{close} = "t";

	bless $self, $class;
	return $self;
    }

######################################################################
######################################################################
## defines a helper class for password encryption

package RefDB::Enigma;

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


=head2 new

    Title   : new

    Usage   : new RefDB::Enigma;

Function: creates a new Enigma element

=cut

######################################################################
## new(): creates a new Enigma element
######################################################################
    sub new() {
	my $class = shift;
	my $self = {};

	## the buffer that holds the raw password string
	$self->{rawpass} = undef;

	## the buffer that receives the encoded password string
	$self->{encodedpass} = undef;

	## the buffer that receives the numberized encoded password string
	$self->{numberpass} = undef;

	## the buffer that holds the scramble string
	$self->{scramble} = undef;

	## this array maps the available rotors 0-2 to the slots 0-2
	@{$self->{rotormap}} = ();

	## this array holds the rotor positions for the rotors in the slots 0-2
	@{$self->{rotorpos}} = ();

	## the rotor wirings for the rotors 0-2
	@{$self->{rotor}} = (
			     # Input "!\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
			     ["0", "u", "V", "`", "\$", "/", "z", "G", "7", "3", "L", "a", "*", "N", "[", ";", "U", "F", "Z", "^", "l", "t", "m", "{", "]", "\"", "s", "=", "b", "Q", "+", "?", "O", "A", "g", "w", "T", "5", "c", "W", "v", "#", "~", "}", ")", "o", "M", "S", "\'", "_", "\\", "@", "K", "(", "d", "D", "p", "|", "y", "8", "B", "%", "Y", "x", "J", "2", ":", "9", "&", "r", "4", "!", "k", "f", "-", "j", ".", "h", "1", "6", "I", "C", "i", ",", "R", "n", "e", "<", "E", "P", "q", ">", "X", "H"], # 0
			     ["E", "P", "l", "b", "(", "S", "O", "\'", "D", "p", "|", "y", "8", "B", "x", "t", "J", "2", ":", "j", ".", "h", "f", "-", "I", "C", "i", ",", "R", "n", "e", "0", "G", "\"", "1", "6", "9", "&", "%", "Y", "_", "\\", "k", "7", "3", "u", "V", "\$", "/", "z", "N", "@", "F", "Z", "^", "m", "{", "]", "s", "w", "X", "U", "T", "5", "c", "W", "v", "#", "=", "H", "q", "r", "4", "!", "[", ";", "<", ">", "g", "~", "}", ")", "Q", "+", "L", "a", "*", "d", "?", "A", "K", "`", "o", "M"], # 1
			     ["r", "4", "!", "[", ";", "<", "/", "z", "\$", "s", "`", "w", "X", "U", "T", "q", "{", "]", ">", "S", "5", ")", "@", "F", "Z", "Q", "D", "M", "|", "y", "8", "c", "W", "0", "G", "\"", "u", "v", "R", "Y", "_", "\\", "#", "=", "p", "a", "*", "d", "?", "A", "K", "+", "L", "E", "P", "l", "b", "(", "\'", "H", "1", "6", "t", "J", "2", ":", "j", ".", "n", "e", "9", "&", "%", "h", "f", "-", "I", "C", "i", "m", "o", "B", "x", ",", "k", "7", "O", "3", "V", "g", "~", "}", "N", "^"] # 2
			     );

	# the reflector array must be symmetrical, e.g. if ! maps to /, / must also map to !
	@{$self->{reflector}} = (
				 "/", "-", "Q", "=", "~", "E", "1", ".", "6", "4", "2", "H", "\"", "(", "!", "?", "\'", "+", "X", "*", ">", ")", "S", "c", "v", "@", "<", ";", "\$", "5", "0", ":", "R", "u", "n", "Z", "&", "r", "{", ",", "Y", "M", "s", "p", "J", "^", "z", "q", "#", "A", "7", "g", "\\", "f", "h", "3", "I", "D", "e", "U", "}", "N", "y", "w", "d", "o", "8", "a", "[", "V", "T", "W", "|", "m", "x", "t", "j", "C", "b", "L", "P", "F", "K", "l", "B", "9", "`", "k", "_", "O", "G", "i", "]", "%"
				 );

	bless $self, $class;
	return $self;
    }

######################################################################
## init(): set the parameters
## Arguments: - raw password
##            - scramble string
######################################################################
sub init {
    my ($self, $rawpass, $scramble) = @_;

    $self->{rawpass} = $rawpass;
    $self->{scramble} = $scramble;
}

######################################################################
## get_pass(): get the numberized encoded password
######################################################################
sub get_pass {
    my ($self) = @_;
    
    return $self->{numberpass};
}

######################################################################
## eenc(): encode or decode a password string
##         returns the numberized encoded password
######################################################################
sub eenc {
    my ($self) = @_;

    # set initial wheel sequence and wheel positions
    $self->_set_wheels();

    # split the raw password into individual characters
    my @pw = split(/ */, $self->{rawpass});

    my $scrambledchar = undef;

    # this array will receive the permuted characters
    my @encchar = ();

    # loop over all characters in the raw password
    foreach my $pwchar (@pw) {

	# first send the character through all rotors in the forward direction
	$scrambledchar = $self->_wheel($pwchar, 0);
	$scrambledchar = $self->_wheel($scrambledchar, 1);
	$scrambledchar = $self->_wheel($scrambledchar, 2);

	# send the character through the reflector
	$scrambledchar = $self->_reflect($scrambledchar);

	# send the character through all rotors in the reverse direction
	$scrambledchar = $self->_rwheel($scrambledchar, 2);
	$scrambledchar = $self->_rwheel($scrambledchar, 1);
	$scrambledchar = $self->_rwheel($scrambledchar, 0);

	# advance all wheels one position
	$self->_move_wheels();

	# add the permuted character to the encoded password
	push @encchar, $scrambledchar;
    }

    # glue the permuted characters together
    $self->{encodedpass} = join('', @encchar);

    # numberize the encoded password and return it
    $self->_numberize();
}

######################################################################
## _set_wheels(): set the wheel position
######################################################################
sub _set_wheels {
    my ($self) = @_;

    # sanity check
    if (length($self->{scramble}) < 12) {
	return 1;
    }

    # use first three characters of the scramble string as rotor map
    $self->{rotormap}[0] = ord(substr($self->{scramble}, 0, 1)) - 48;
    $self->{rotormap}[1] = ord(substr($self->{scramble}, 1, 1)) - 48;
    $self->{rotormap}[2] = ord(substr($self->{scramble}, 2, 1)) - 48;

    # slot values must be between 0 and 2 and we don't use
    # a wheel twice
    if ($self->{rotormap}[0] == $self->{rotormap}[1]
	|| $self->{rotormap}[1] == $self->{rotormap}[2]
	|| $self->{rotormap}[0] == $self->{rotormap}[2]
	|| $self->{rotormap}[0] < 0
	|| $self->{rotormap}[0] > 2
	|| $self->{rotormap}[1] < 0
	|| $self->{rotormap}[1] > 2
	|| $self->{rotormap}[2] < 0
	|| $self->{rotormap}[2] > 2) {
	return 1;
    }

    # loop over slots
    foreach my $i (0 .. 2) {
	# retrieve wheel positions from scramble string
	$self->{rotorpos}->[$i] = substr($self->{scramble}, ($i*3)+4, 2);
	if ($self->{rotorpos}->[$i] < 0
	    || $self->{rotorpos}->[$i] > 94) {
	    return 1; # out of range
	}
    }

    return 0;
}

######################################################################
## _wheel(): performs character permutation on a wheel in the forward
##           direction
######################################################################
sub _wheel {
    my ($self, $char, $wheel) = @_;

    my $outchar = undef;

    $outchar = $self->{rotor}[$self->{rotormap}[$wheel]][(ord($char)-33+$self->{rotorpos}->[$wheel]) % 94];

    return $outchar;
}

######################################################################
## _reflect(): performs character permutation on the reflector
######################################################################
sub _reflect {
    my ($self, $char) = @_;

    my $outchar = undef;
    my $conv = ord($char)-33;

    $outchar = $self->{reflector}[ord($char)-33];
}


######################################################################
## _rwheel(): performs character permutation on a wheel in the reverse
##           direction
######################################################################
sub _rwheel {
    my ($self, $char, $wheel) = @_;

    my $outchar = undef;
    my $charindex = 0;

    # loop over all characters in the rotor to do a reverse lookup
    foreach my $wheelchar (@{$self->{rotor}[$self->{rotormap}[$wheel]]}) {
	if ($char eq $wheelchar) {
	    $outchar = $charindex + 33 - $self->{rotorpos}[$wheel];
	    while ($outchar < 33) {
		$outchar += 94;
	    }
	    last; # jump out when the character is found
	}
	$charindex++;
    }

#    $outchar = sprintf "%c", $outchar;
    $outchar = chr($outchar);
}

######################################################################
## _move_wheels(): advance the wheels one position
######################################################################
sub _move_wheels {
    my ($self) = @_;
    
    $self->{rotorpos}[0] = ($self->{rotorpos}[0]+1) % 94;
    $self->{rotorpos}[1] = ($self->{rotorpos}[1]+1) % 94;
    $self->{rotorpos}[2] = ($self->{rotorpos}[2]+1) % 94;

}

######################################################################
## _numberize(): converts the encoded password to a numerical form
######################################################################
sub _numberize {
    my ($self) = @_;
    
    # split password into characters and loop over them
    foreach my $char (split(/ */, $self->{encodedpass})) {
	$self->{numberpass} .= sprintf("%03d", ord($char));
    }
    return $self->{numberpass};
}


######################################################################
######################################################################
## defines the main class to talk to the refdbd server

package RefDB::Client;

use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
use constant REFDB_PROTOCOL_VERSION => 6;
use constant COMMAND_INBUF_LENGTH => 4096;
use constant OUTBUF_LEN => 4096;
use constant COMMAND_INBUF_LEN => 4096;
use constant XMLPARSE_CHUNKSIZE => 4096;
use constant TERM_YES => 1;
use constant TERM_NO => 0;
use constant TERM_LEN => 4;
use constant STATUS_LEN => 3;
use Socket;

$VERSION = "1.18";

=head2 new

    Title   : new

    Usage   : new RefDB::Client;

Function: creates a new Client element

=cut

######################################################################
## new(): creates a new Client element
######################################################################
    sub new() {
	my $class = shift;
	my $self = {};

	## the socket used for communication
	$self->{socket} = undef;

	## the IP address of the host
	$self->{server_ip} = undef;

	## the port address of the host
	$self->{port_address} = undef;

	## the username
	$self->{username} = undef;

	## the raw password
	$self->{passwd} = undef;

	## the numberized, scrambled password
	$self->{numberized_passwd} = undef;

	## the scramble string used for the current connection
	$self->{scramble_string} = undef;

	## the database name
	$self->{db} = undef;

	## the pdfroot (directory or base URL)
	$self->{pdf_root} = undef;

	## the URL of a CSS style sheet for the getref html output
	$self->{css_url} = undef;

	## the time in seconds after which a stale connection times out
	$self->{timeout} = 180;

	## the client/server message terminator
	$self->{CS_TERM} = "\x00\x00\x00\x00";

	## the server status
	$self->{server_status} = undef;

	## the data returned by a client command are stored here
	$self->{server_data} = undef;

	## the summary returned by a client command is stored here
	$self->{server_summary} = undef;

	## the style specification returned by the getbib command is stored here
	$self->{stylespec} = undef;

	## the status messages
	%{$self->{refstat}} = (
			       "000" => "ok",
			       "001" => "error",
			       "100" => "void",
			       "101" => "incorrect scramble string",
			       "102" => "client and server protocols do not match",
			       "103" => "invalid client request",
			       "104" => "incomplete client command",
			       "105" => "missing client command",
			       "106" => "missing client command option",
			       "107" => "unknown client command option",
			       "108" => "could not descramble password",
			       "109" => "timeout while reading",
			       "110" => "timeout while writing",
			       "111" => "missing client command argument",
			       "112" => "client aborted command",
			       "200" => "void",
			       "201" => "main database is missing",
			       "202" => "could not open main database",
			       "203" => "main database is too old or corrupt",
			       "204" => "could not open reference database",
			       "205" => "could not connect to database server",
			       "206" => "main database version is not supported",
			       "207" => "could not create result from database query",
			       "208" => "could not retrieve reference database metadata",
			       "209" => "could not create reference database",
			       "210" => "could not create reference database metadata",
			       "211" => "create t_meta failed",
			       "212" => "create t_refdb failed",
			       "213" => "create t_author failed",
			       "214" => "create t_keyword failed",
			       "215" => "create t_periodical failed",
			       "216" => "create t_note failed",
			       "217" => "create t_user failed",
			       "218" => "create t_xauthor failed",
			       "219" => "create t_xkeyword failed",
			       "220" => "create t_xuser failed",
			       "221" => "create t_xnote failed",
			       "222" => "could not create user group",
			       "223" => "could not grant user permissions",
			       "224" => "access control not supported",
			       "225" => "not a RefDB database",
			       "226" => "database does not exist",
			       "227" => "begin transaction failed",
			       "228" => "cannot lock tables",
			       "229" => "failed to remove keyword",
			       "230" => "failed to remove author",
			       "231" => "failed to remove periodical",
			       "232" => "failed to update main reference data",
			       "233" => "inserting reference data failed",
			       "234" => "select failed",
			       "235" => "database successfully created",
			       "236" => "assume localhost as host",
			       "237" => "grant user permissions successful",
			       "238" => "revoke user permissions successful",
			       "239" => "could not revoke user permissions",
			       "240" => "switched to database",
			       "241" => "failed to access style data",
			       "242" => "create temporary table failed",
			       "243" => "delete temporary table failed",
			       "244" => "incomplete reference data",
			       "245" => "failed to remove note xlink",
			       "246" => "failed to delete main note data",
			       "247" => "failed to remove user",
			       "248" => "failed to delete main reference data",
			       "249" => "failed to delete database",
			       "250" => "could not delete user group",
			       "251" => "database successfully deleted",
			       "252" => "personal interest list is empty",
			       "253" => "failed to detach dataset from user",
			       "254" => "sucessfully detached dataset from user",
			       "255" => "failed to attach dataset to user",
			       "256" => "sucessfully attached dataset to user",
			       "257" => "create t_link failed",
			       "258" => "create t_xlink failed",
			       "259" => "failed to remove ulink",
			       "260" => "failed to update journal names",
			       "261" => "failed to create citation key",
			       "262" => "failed to create personal list",
			       "263" => "successfully created personal list",
			       "264" => "failed to delete personal list",
			       "265" => "successfully deleted personal list",
			       "266" => "personal list not owned by current user",
			       "267" => "personal list does not exist",
			       "268" => "create t_temp_xdup failed",
			       "300" => "void",
			       "301" => "missing argument",
			       "302" => "unknown output format",
			       "400" => "void",
			       "401" => "no more data available",
			       "402" => "finished transferring data",
			       "403" => "chunk added successfully",
			       "404" => "finished transferring dataset",
			       "405" => "finished adding dataset",
			       "406" => "citation key",
			       "407" => "refused to overwrite dataset",
			       "408" => "dataset added successfully",
			       "409" => "numerical id ignored",
			       "410" => "numerical id nonexistent",
			       "411" => "citation key nonexistent",
			       "412" => "ID and citation key missing",
			       "413" => "dataset updated successfully",
			       "414" => "failed to add dataset",
			       "415" => "missing link target",
			       "416" => "incorrect link type",
			       "417" => "dataset not found",
			       "418" => "link already exists",
			       "419" => "dataset removed successfully",
			       "420" => "failed to remove dataset",
			       "421" => "link added successfully",
			       "422" => "only owner can fiddle with dataset",
			       "423" => "dataset is still in use",
			       "424" => "dataset is already attached to user",
			       "425" => "periodical name changed successfully",
			       "426" => "reference type changed",
			       "700" => "void",
			       "701" => "failed to initialize character set conversion",
			       "702" => "character set conversion failed",
			       "703" => "remote administration disabled",
			       "704" => "administration is not restricted",
			       "705" => "administration not permitted",
			       "706" => "administration permitted",
			       "707" => "process ID",
			       "708" => "application server stop submitted",
			       "709" => "set server IP submitted",
			       "710" => "set timeout submitted",
			       "711" => "set logfile submitted",
			       "712" => "set logdest submitted",
			       "713" => "set loglevel submitted",
			       "800" => "void",
			       "801" => "out of memory",
			       "802" => "failed to load cgi templates",
			       "803" => "command partially processed, aborted after unrecoverable error",
			       "804" => "suffix pool exhausted",
			       "805" => "REFNUMBER formatting failed",
			       "806" => "AUTHORLIST formatting failed",
			       "807" => "EDITORLIST formatting failed",
			       "808" => "SEDITORLIST formatting failed",
			       "809" => "PUBDATE formatting failed",
			       "810" => "PUBDATESEC formatting failed",
			       "811" => "TITLE formatting failed",
			       "812" => "BOOKTITLE formatting failed",
			       "813" => "SERIESTITLE formatting failed",
			       "814" => "JOURNALNAME formatting failed",
			       "815" => "VOLUME formatting failed",
			       "816" => "ISSUE formatting failed",
			       "817" => "PAGES formatting failed",
			       "818" => "PUBLISHER formatting failed",
			       "819" => "PUBPLACE formatting failed",
			       "820" => "SERIAL formatting failed",
			       "821" => "ADDRESS formatting failed",
			       "822" => "URL formatting failed",
			       "823" => "USERDEF1 formatting failed",
			       "824" => "USERDEF2 formatting failed",
			       "825" => "USERDEF3 formatting failed",
			       "826" => "USERDEF4 formatting failed",
			       "827" => "USERDEF5 formatting failed",
			       "828" => "MISC1 formatting failed",
			       "829" => "MISC2 formatting failed",
			       "830" => "MISC3 formatting failed",
			       "831" => "LINK1 formatting failed",
			       "832" => "LINK2 formatting failed",
			       "833" => "LINK3 formatting failed",
			       "834" => "LINK4 formatting failed",
			       "835" => "ABSTRACT formatting failed",
			       "836" => "NOTES formatting failed",
			       "837" => "SEPARATOR formatting failed",
			       "838" => "remote administration failed",
			       "839" => "child->parent communication failure",
			       "840" => "FIFO write error",
			       "841" => "unknown command",
			       "999" => "summary",
			       );

	bless $self, $class;
	return $self;
    }

=head2 set_conninfo

    Title   : set_conninfo

    Usage   : $client->set_conninfo($server_ip, $port_address, $username, $password, $database, $pdf_root, $css_url, $timeout);

Function: sets the initial connection parameters of a Client object

Parameter: $server_ip: IP address or hostname of the server that runs refdbd

Parameter: $port_address: Port address at which refdbd listens

Parameter: $username: Username for database password authentication

Parameter: $password: Password for database password authentication

Parameter: $database: Name of the reference database

Parameter: $pdf_root: Path of the root directory of all electronic offprints

Parameter: $css_url: URL of a Cascading Stylesheets file for (X)HTML output

Parameter: $timeout: time in seconds after which a stale connection is dropped

=cut

######################################################################
## set_conninfo(): sets the initial connection parameters of a Client object
######################################################################
    sub set_conninfo {
	my ($self, $server_ip, $port_address, $username, $passwd, $db, $pdf_root, $css_url, $timeout) = @_;

	$self->{server_ip} = $server_ip;
	$self->{port_address} = $port_address;
	$self->{username} = $username;
	$self->{passwd} = $passwd;
	$self->{db} = $db;
	$self->{pdf_root} = $pdf_root;
	$self->{css_url} = $css_url;
	$self->{timeout} = $timeout;
    }

sub id_from_aux {
    my ($self, $file) = @_;

    my $idstring = undef;
    my @idlist;

    # in order to avoid duplicate citations we add all citation keys
    # to a list, then we sort the list and eliminate consecutive
    # duplicates

    open IN, "< $file";
    while (<IN>) {
	my $line = $_;
	if ($line =~ s/^\\citation\{(.*)\}\s/$1/) {
	    push(@idlist, $line);
	}
    }
    close IN;
    
    @idlist = sort @idlist;

    my $previtem = undef;

    foreach my $item (@idlist) {
	unless ($previtem eq $item) {
	    $idstring .= $item . " ";
	    $previtem = $item;
	}
    }

    # this is now a space-separated list of IDs/citation keys
    $idstring;
}

######################################################################
## _connect_to_server(): establishes server connection via socket
######################################################################
sub _connect_to_server {
    my ($self) = @_;

    if ($self->{port_address} =~ /\D/) {
	$self->{port_address} = getservbyname($self->{port_address}, 'tcp');
    }
    die "No port" unless $self->{port_address};


    my $iaddr = inet_aton($self->{server_ip}) || die "no host: $self->{server_ip}";
    my $paddr = sockaddr_in($self->{port_address}, $iaddr);

    my $proto = getprotobyname('tcp');

    $self->{socket} = eval { local *SOCK; socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "socket: $!"; connect(SOCK, $paddr) or die "connect: $!"; *SOCK{IO}};
    die "could not set up socket connection" unless $self->{socket};
}

######################################################################
## _init_dialog(): starts client/server dialog with password authentication
######################################################################
sub _init_dialog {
    my ($self) = @_;

    my $inbuffer;

    $self->_connect_to_server();

    my $socket = $self->{socket};

    # send protocol version
    my $proto = REFDB_PROTOCOL_VERSION;

    # syswrite($socket, "$proto");
    $self->_iwrite("$proto", TERM_YES);

    # read server status
    $self->_read_status();
    unless (defined($self->{server_status})) {
	die "could not read from server";
    }

    # check for server error status
    if ($self->{server_status} ne "000") {
	my $msg = $self->get_status_msg();
	die "server error: $msg\n";
    }
    
    # read password scramble string
    unless (defined($self->{scramble_string} = $self->_tread(OUTBUF_LEN))) {
	die "could not read from server";
    }

    if (length($self->{passwd}) > 0) {
	my $enigma = new RefDB::Enigma;
	$enigma->init($self->{passwd}, $self->{scramble_string});
	$self->{numberized_passwd} = $enigma->eenc();
    }
    else {
	$self->{numberized_passwd} = "";
    }
    #print $self->{numberized_passwd};

}

######################################################################
## _tread(): reads $len bytes from a socket or less if a NULL is found
######################################################################
sub _tread {
    my ($self, $len) = @_;

#    my $socket = $self->{socket};

    my $result;

    eval {
	# use an alarm to timeout a stale connection
	local $SIG{ALRM} = sub { die "alarm\n" };

	alarm $self->{timeout};
	while (sysread($self->{socket}, my $line, $len)) {
	    $result .= $line;
	    if ($line =~ /$self->{CS_TERM}/) {
		last;
	    }
	}
	alarm 0;
    };
    
    if ($@) {
	# propagate errors not caused by the eval code
	die unless $@ eq "alarm\n";

	# return an empty string
	return undef;
    }
    $result;
}    

######################################################################
## _iread(): reads $len bytes from a socket
######################################################################
sub _iread {
    my ($self, $len) = @_;

#    my $socket = $self->{socket};

    my $result;

    eval {
	# use an alarm to timeout a stale connection
	local $SIG{ALRM} = sub { die "alarm\n" };

	alarm $self->{timeout};
	while (sysread($self->{socket}, my $line, $len)) {
	    $result .= $line;
	    if (length($line) == $len ) {
		last;
	    }
	}
	alarm 0;
    };
    
    if ($@) {
	# propagate errors not caused by the eval code
	die unless $@ eq "alarm\n";

	# return an empty string
	return undef;
    }
    $result;
}    


######################################################################
## _iwrite(): sends $len bytes to a socket
######################################################################
sub _iwrite {
    my ($self, $buffer, $term) = @_;

    my $result;

    eval {
	# use an alarm to timeout a stale connection
	local $SIG{ALRM} = sub { die "alarm\n" };

	alarm $self->{timeout};

	if ($term) {
	    syswrite($self->{socket}, $buffer . $self->{CS_TERM});
	}
	else {
	    syswrite($self->{socket}, $buffer);
	}
	alarm 0;
    };
    
    if ($@) {
	# propagate errors not caused by the eval code
	die unless $@ eq "alarm\n";

	# return an empty string
	return 1;
    }
    return 0;
}    


######################################################################
## _send_status(): sends the status bytes preceding each message
######################################################################
sub _send_status {
    my ($self, $status, $term) = @_;
    # print "sent status code $status<<\n";
    $self->_iwrite($status, $term);
}

######################################################################
## _read_status(): reads the status bytes preceding each message
######################################################################
sub _read_status {
    my ($self) = @_;

    $self->{server_status} = $self->_iread(STATUS_LEN);
}

=head2 get_status

    Title   : get_status

    Usage   : $client->get_status();

Function: returns the numerical server status

=cut

######################################################################
## get_status(): returns the server status 
######################################################################
    sub get_status {
	my ($self) = @_;

	$self->{server_status};
    }

=head2 get_status_msg

    Title   : get_status_msg

    Usage   : $client->get_status_msg();

Function: returns the server status message

=cut

######################################################################
## get_status_msg(): returns the server status 
######################################################################
    sub get_status_msg {
	my ($self) = @_;

	$self->{refstat}{$self->{server_status}};
    }

=head2 translate_status

    Title   : translate_status

    Usage   : $client->translate_status($status);

Function: translates a given server status into a human readable message

=cut

######################################################################
## translate_status(): translates a status into a message
######################################################################
    sub translate_status {
	my ($self, $status) = @_;

	$self->{refstat}{$status};
    }

=head2 get_data

    Title   : get_data

    Usage   : $client->get_data();

Function: returns the data of the most recent command

=cut

######################################################################
## get_data(): returns the server data 
######################################################################
    sub get_data {
	my ($self) = @_;

	$self->{server_data};
    }

=head2 get_summary

    Title   : get_summary

    Usage   : $client->get_summary();

Function: returns the summary of the most recent command

=cut

######################################################################
## get_summary(): returns the server summary
######################################################################
    sub get_summary {
	my ($self) = @_;

	$self->{server_summary};
    }

######################################################################
## _listvalue(): retrieves a list from the server
######################################################################
sub _listvalue {
    my ($self, $cmd) = @_;

    $self->_init_dialog();

    my $sl = new RefDB::Simplelist;

    $sl->{cmd} = $cmd;

    if (length($self->{username}) > 0) {
	$sl->{cmd} .= " -u $self->{username}";
    }

    if (length($self->{numberized_passwd}) > 0) {
	$sl->{cmd} .= " -w $self->{numberized_passwd}";
    }

    $self->{server_data} = $self->_getsimplelist($sl);

    # return result summary
    $self->{summary} = $sl->{summary};
}

######################################################################
## _getsimplelist(): sends command to the server to retrieve a simple list
######################################################################
sub _getsimplelist {
    my ($self, $sl) = @_;

    $self->_send_status("000", TERM_NO);

    $self->_iwrite($sl->{cmd}, TERM_YES);
    #print $sl->{cmd};
    my $inbuffer;


    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	$sl->{inbuffer} .= $inbuffer;
	if ($sl->{inbuffer} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    # print "sl inbuffer went to:$sl->{inbuffer}<<\n";

    $self->_send_status("000", TERM_NO);

    # retrieve command summary
    $self->_read_status();

    if ($self->{server_status} ne "000"
	&& $self->{server_status} ne "418") {
	return $self->get_status_msg();
    }

    while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	$sl->{summary} .= $inbuffer;
	if ($sl->{summary} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }
    $self->_send_status("000", TERM_NO);

    # print "sl summary went to:$sl->{summary}<<\n";


    if ($self->{close} eq "t") {
	close ($self->{socket});
    }

    $self->{server_summary} = $sl->{summary};

    # return data
    $sl->{inbuffer};
}

######################################################################
## _addref(): adds or updates references
######################################################################
sub _addref {
    my ($self, $cmd, $refdata, $type) = @_;

    $self->_send_status("000", TERM_NO);

    $self->_iwrite($cmd, TERM_YES);

    my $result;

    # reset data
    $self->{server_data} = "";
    $self->{server_summary} = "";
    
    $self->_read_status();
    
    if ($self->{server_status} ne "000") {
	if ($self->{server_status} eq "701") {
	    # charset conversion error, treated as non-fatal
	    $self->{server_data} .= $self->get_status_msg();
	}
	else {
	    return $self->get_status_msg();
	}
    }

    if ($type eq "ris") {
	# now loop over the datasets and send them one at a time
	foreach my $dataset (@{$refdata->{sets}}) {
	    # Phase 1: transmit length of next dataset
	    $self->_send_status("000", TERM_NO);
	    my $datalength = length($dataset)+TERM_LEN;
	    #print "datalength went to:$datalength<<\n";
	    $self->_iwrite("$datalength", TERM_YES);
	    
	    # Phase 2: read acknowledgement from server
	    $self->_read_status();

	    if ($self->{server_status} ne "000") {
		return $self->get_status_msg();
	    }

	    # Phase 3: send dataset
	    $self->_iwrite($dataset, TERM_YES);
	    
	    # Phase 4: read server reply
	    $result = undef;
	    $self->_read_status();
	    
	    if ($self->{server_status} eq "400"
		|| $self->{server_status} eq "407"
		|| $self->{server_status} eq "408"
		|| $self->{server_status} eq "413"
		|| $self->{server_status} eq "414"
		|| $self->{server_status} eq "702") {
		# retrieve server-generated status/error message
		while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		    $self->{server_data} .= $inbuffer;
		    if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
			last;
		    }
		}

		if ($self->{server_status} eq "400"
		    || $self->{server_status} eq "702") {
		    return $self->{server_data};
		}
	    }
	}
    }
    else { # assume risx
	$self->{server_data} .= $self->_send_xml($refdata);
    }

    # Phase 5: signal server that we're done
    $self->_send_status("402", TERM_NO);

    $self->_read_status();

    if ($self->{server_status} ne "403") {
	if ($self->{server_status} eq "400") {
	    # retrieve server-generated status/error message
	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }
	}
	else {
	    $self->{server_data}.= $self->get_status_msg();
	}
    }

    # read data
    $result = undef;
    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_data} .= $inbuffer;
	if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }
    #print $result . "\n";
    
    # don't ask me why this is necessary
#    $self->{server_data} =~ s/$self->{CS_TERM}//;

    $self->_send_status("000", TERM_NO);

    # read summary
    $self->_read_status();

    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_summary} .= $inbuffer;
	if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    $self->_send_status("000", TERM_NO);

    $self->{server_summary};
}

######################################################################
## _addnote(): adds or updates notes
######################################################################
sub _addnote {
    my ($self, $cmd, $xnotedata) = @_;

    $self->_send_status("000", TERM_NO);
    $self->_iwrite($cmd, TERM_YES);

    # reset data
    $self->{server_data} = "";
    $self->{server_summary} = "";

    $self->_read_status();

    if ($self->{server_status} ne "000") {
	if ($self->{server_status} eq "701") {
	    # charset conversion error, treated as non-fatal
	    $self->{server_data} .= $self->get_status_msg();
	}
	else {
	    return $self->get_status_msg();
	}
    }

    $self->{server_data} .= $self->_send_xml($xnotedata);

    # Phase 5: signal server that we're done
    $self->_send_status("402", TERM_NO);

    $self->_read_status();

    if ($self->{server_status} ne "403") {
	if ($self->{server_status} eq "400") {
	    # retrieve server-generated status/error message
	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }
	}
	else {
	    $self->{server_data} .= $self->get_status_msg();
	}
    }

    # read data
    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_data} .= $inbuffer;
	if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    $self->_send_status("000", TERM_NO);
    
    # read summary
    $self->_read_status();

    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_summary} .= $inbuffer;
	if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    $self->_send_status("000", TERM_NO);

    $self->{server_summary};
}

######################################################################
## _adduser(): adds new users to the database
######################################################################
sub _adduser {
    my ($self, $host, $database, $newuserpasswd, $is_add, $arg) = @_;

    $self->_init_dialog();

    my $cmd = "adduser ";

    if (length($self->{username}) > 0) {
	$cmd .= "-u $self->{username} ";
    }

    if (length($self->{numberized_passwd}) > 0) {
	$cmd .= "-w $self->{numberized_passwd} ";
    }

    # if no database is provided, use the database we're currently
    # connected to
    if (length($database) > 0) {
	$cmd .= "-d $database ";
    }
    elsif (length($self->{db}) > 0) {
	$cmd .= "-d $self->{db} ";
    }

    if (length($host) > 0) {
	$cmd .= "-H $host ";
    }

    if (length($newuserpasswd) > 0) {
	my $enigma = new RefDB::Enigma;
	$enigma->init($newuserpasswd, $self->{scramble_string});
	my $numberized_passwd = $enigma->eenc();

	$cmd .= "-W  $numberized_passwd ";
    }

    unless ($is_add eq "t") {
	$cmd .= "-r ";
    }

    $self->_send_status("000", TERM_NO);
    $self->_iwrite("$cmd", TERM_YES);

    # reset data
    $self->{server_data} = "";
    $self->{server_summary} = "";

    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    my $index = 0;
    my $arglength = length($arg);

    $self->_send_status("000", TERM_NO);
    $self->_iwrite($arg, TERM_YES);

    # read data
    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_data} .= $inbuffer;
	if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    $self->_send_status("000", TERM_NO);

    # read summary
    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_summary} .= $inbuffer;
	if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    $self->_send_status("000", TERM_NO);

    close ($self->{socket});

    $self->{server_summary};
}

######################################################################
## _addlink(): links notes to database objects
######################################################################
sub _addlink {
    my ($self, $remove, $arg) = @_;

    my $cmd;

    $cmd = "addlink -d $self->{db} ";

    if ($remove eq "t") {
	$cmd .= "-r ";
    }

    $cmd .= "\"" . $arg . "\"";

    # reset data
    $self->{server_data} = "";
    $self->{server_summary} = "";

    $self->_listvalue($cmd);
}

######################################################################
## _addword(): adds/removes a reserved word
######################################################################
sub _addword {
    my ($self, $is_remove, $arg) = @_;

    $self->_init_dialog();

    my $cmd = "addword ";

    if (length($self->{username}) > 0) {
	$cmd .= "-u $self->{username} ";
    }

    if (length($self->{numberized_passwd}) > 0) {
	$cmd .= "-w $self->{numberized_passwd} ";
    }

    if ($is_remove eq "t") {
	$cmd .= "-r ";
    }

    # send command
    $self->_send_status("000", TERM_NO);
    $self->_iwrite("$cmd", TERM_YES);

    # reset data
    $self->{server_data} = "";
    $self->{server_summary} = "";

    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    # send argument (word list)
    $self->_send_status("000", TERM_NO);
    $self->_iwrite($arg, TERM_YES);


    # read data
    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_data} .= $inbuffer;
	if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    $self->_send_status("000", TERM_NO);

    # read summary

    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	$self->{server_summary} .= $inbuffer;
	if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
	    last;
	}
    }

    # finish dialog
    $self->_send_status("000", TERM_NO);

    close ($self->{socket});

    $self->{server_summary};
}

######################################################################
## _pickref(): adds/removes references from personal reference list
######################################################################
sub _pickref {
    my ($self, $arg, $is_remove, $listname) = @_;

    my $cmd = "pickref -d $self->{db} ";

    $self->_init_dialog();

    if (length($self->{username}) > 0) {
	$cmd .= "-u $self->{username} ";
    }

    if (length($self->{numberized_passwd}) > 0) {
	$cmd .= "-w $self->{numberized_passwd} ";
    }

    if (length($listname) > 0) {
	$cmd .= "-b $listname ";
    }

    if ($is_remove eq "t") {
	$cmd .= "-r ";
    }

    my $arglength = length($arg) + length($self->{CS_TERM});

    $cmd .= "$arglength";

    $self->_send_status("000", TERM_NO);
    $self->_iwrite("$cmd", TERM_YES);

    # reset data
    $self->{server_data} = "";
    $self->{server_summary} = "";

    # read data
    $self->_read_status();

    if ($self->{server_status} ne "000") {
	return $self->get_status_msg();
    }

    my $sl = new RefDB::Simplelist;

    $sl->{cmd} = $arg;

    # return the result as a list of lines
    $self->{server_data} = $self->_getsimplelist($sl);

    # return result summary
    $self->{summary} = $sl->{summary};
}

######################################################################
## _send_xml(): helper to send XML data to the server
######################################################################
sub _send_xml {
    my ($self, $styledata) = @_;

    my $index = 0;
    my $stylelength = length($styledata);
    my $result_pool;

    # now loop over XMLPARSE_CHUNKSIZE chunks and send them one at a time
    while ($stylelength > 0) {
	# Phase 1: transmit length of next dataset
	my $datalength = ($stylelength > XMLPARSE_CHUNKSIZE) ? XMLPARSE_CHUNKSIZE : $stylelength;
	$self->_send_status("000", TERM_NO);

	$self->_iwrite("$datalength", TERM_YES);

	# Phase 2: read acknowledgement from server
	my $result = undef;

	$self->_read_status();

	if ($self->{server_status} ne "000") {
	    $result_pool .= $self->get_status_msg();
	    return;
	}

	# Phase 3: send dataset
	$self->_iwrite(substr($styledata, $index, XMLPARSE_CHUNKSIZE), TERM_NO);

	# Phase 4: read server reply
	$result = undef;

	$self->_read_status();

	if ($self->{server_status} ne "403") {
	    $result_pool .= $self->_tread(OUTBUF_LEN);
	    return;
	}

	$stylelength -= $datalength;
	$index += $datalength;
    }
    return $result_pool;
}

######################################################################
## client commands

=head2 refdb_addstyle

    Title   : refdb_addstyle

    Usage   : $client->refdb_addstyle($styledata);

Function: adds a citation/bibliography style to the database

Parameter: $styledata: XML data representing the bibliography style

=cut

######################################################################
## refdb_addstyle(): adds a citation/bibliography style to the database
######################################################################
    sub refdb_addstyle {
	my ($self, $styledata) = @_;

	my $cmd = "addstyle ";

	$self->_init_dialog();

	if (defined($self->{username})) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	$self->_send_status("000", TERM_NO);
	$self->_iwrite("$cmd", TERM_YES);

	my $result;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_read_status();

	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}

	# Phases 1 through 4 of client/server dialog
	$self->{server_data} .= $self->_send_xml($styledata);

	if ($self->{server_status} ne "403") {
	    return "999:0";
	}

	# Phase 5: signal server that we're done
	$self->_send_status("402", TERM_NO);

	# read data
	$self->_read_status();

	if ($self->{server_status} eq "400") {
	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }
	    return "999:0";
	}
	
	while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_data} .= $inbuffer;
	    if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}

	$self->_send_status("000", TERM_NO);

	# read summary
	$self->_read_status();
	while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}

	$self->_send_status("000", TERM_NO);
	close ($self->{socket});

	$self->{server_summary};
    }

=head2 refdb_adduser

    Title   : refdb_adduser

    Usage   : $client->refdb_adduser($host, $database, $newuserpassword, $username);

Function: adds new users to the database

Parameter: $host: host specification from which the user is allowed to connect

Parameter: $database: name of the reference database

Parameter: $newuserpassword: password (required only for new users)

Parameter: $username: name of the user, as used to authenticate at the database engine

=cut

######################################################################
## refdb_adduser(): adds new users to the database
######################################################################
    sub refdb_adduser {
	my ($self, $host, $database, $newuserpasswd, $username) = @_;

	$self->_adduser($host, $database, $newuserpasswd, "t" , $username);
    }

=head2 refdb_deleteuser

    Title   : refdb_deleteuser

    Usage   : $client->refdb_deleteuser($host, $database, $username);

Function: deletes users from the database

Parameter: $host: host specification from which the user is allowed to connect

Parameter: $database: name of the reference database

Parameter: $username: name of the user, as used to authenticate at the database engine

=cut

######################################################################
## refdb_deleteuser(): deletes users from the database
######################################################################
    sub refdb_deleteuser {
	my ($self, $host, $database, $username) = @_;

	$self->_adduser($host, $database, undef, "f" , $username);
    }

=head2 refdb_addword

    Title   : refdb_addword

    Usage   : $client->refdb_addword($words);

 Function: adds reserved words to the main database

Parameter: $words: space-separated list of words

=cut

######################################################################
## refdb_addword(): adds/removes a reserved word
######################################################################
    sub refdb_addword {
	my ($self, $words) = @_;
	
	$self->_addword("f", $words);
    }

=head2 refdb_deleteword

    Title   : refdb_deleteword

    Usage   : $summary = $client->refdb_deleteword($words);

Function: removes reserved words from the main database

Parameter: $words: space-separated list of words

=cut

######################################################################
## refdb_deleteword(): removes reserved words
######################################################################
    sub refdb_deleteword {
	my ($self, $words) = @_;
	
	$self->_addword("t", $words);
    }


=head2 refdb_confserv

    Title   : refdb_confserv

    Usage   : $client->refdb_confserv($command);

Function: sends a configuration command to the server

Parameter: $command: the command proper, optionally followed by an argument

=cut

######################################################################
## refdb_confserv(): sends a configuration command to the server
######################################################################
    sub refdb_confserv {
	my ($self, $command) = @_;

	$self->{server_summary} = $self->_listvalue("confserv $command");

	close ($self->{socket});

	if ($command eq "stop" && $self->{server_summary} eq "708") {
	    $self->_init_dialog();
	    close ($self->{socket});
	}

	$self->{server_summary};
    }

=head2 refdb_createdb

    Title   : refdb_createdb

    Usage   : $client->refdb_createdb($dbname, $encoding);

Function: creates a new database

Parameter: $dbname: name of the reference database

Parameter: $encoding: character encoding

=cut

######################################################################
## refdb_createdb(): creates a new database
######################################################################
    sub refdb_createdb {
	my ($self, $dbname, $encoding) = @_;

	my $arg .= $dbname;

	if (length($encoding) > 0) {
	    $arg .= "-E $encoding";
	}

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("createdb $arg");
    }

=head2 refdb_deletedb

    Title   : refdb_deletedb

    Usage   : $client->refdb_deletedb($databasename);

Function: deletes a reference database

Parameter: $dbname: name of the database

=cut

######################################################################
## refdb_deletedb(): deletes reference databases
######################################################################
    sub refdb_deletedb {
	my ($self, $dbname) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("deletedb $dbname");
    }

=head2 refdb_deletestyle

    Title   : refdb_deletestyle

    Usage   : $client->refdb_deletestyle($stylename_regexp);

Function: deletes citation/bibliography styles

Parameter: $stylename_regexp: regular expression describing the names of the styles to be deleted

=cut

######################################################################
## refdb_deletestyle(): removes citation/bibliography styles 
######################################################################
    sub refdb_deletestyle {
	my ($self, $arg) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("deletestyle $arg");
    }

=head2 refdb_getstyle

    Title   : refdb_getstyle

    Usage   : $client->refdb_getstyle($stylename);

Function: retrieves a citation/bibliography style as a citestylex doc

Parameter: $stylename: name of the style

=cut

######################################################################
## refdb_getstyle(): retrieves a citation/bibliography style
######################################################################
    sub refdb_getstyle {
	my ($self, $stylename) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("getstyle $stylename");
    }

=head2 refdb_listdb

    Title   : refdb_listdb

    Usage   : $client->refdb_listdb($dbname_regexp);

Function: lists matching databases

Parameter: $dbname_regexp: regular expression describing the database names

=cut

######################################################################
## refdb_listdb(): lists matching databases
######################################################################
    sub refdb_listdb {
	my ($self, $arg) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("listdb $arg");
    }

=head2 refdb_listuser

    Title   : refdb_listuser

    Usage   : $client->refdb_listuser($dbname, $username_regexp);

Function: lists matching user names

Parameter: $username_regexp: regular expression describing the user names

=cut

######################################################################
## refdb_listuser(): lists matching databases
######################################################################
    sub refdb_listuser {
	my ($self, $dbname, $arg) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$arg = " -d $dbname " . $arg;

	$self->_listvalue("listuser $arg");
    }

=head2 refdb_listword

    Title   : refdb_listword

    Usage   : $client->refdb_listword($word_regexp);

Function: lists matching journal name words

Parameter: $wordname_regexp: regular expression describing the word names

=cut

######################################################################
## refdb_listword(): lists matching journal name words
######################################################################
    sub refdb_listword {
	my ($self, $arg) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("listword $arg");
    }

=head2 refdb_liststyle

    Title   : refdb_liststyle

    Usage   : $client->refdb_liststyle($stylename_regexp);

Function: lists matching citation/bibliography styles

Parameter: $stylename_regexp: regular expression describing the style names

=cut

######################################################################
## refdb_liststyle(): lists citation/bibliography styles
######################################################################
    sub refdb_liststyle {
	my ($self, $arg) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("liststyle $arg");
    }

=head2 refdb_viewstat

    Title   : refdb_viewstat

    Usage   : $client->refdb_viewstat();

Function: requests version/connection info from the server

=cut

######################################################################
## refdb_viewstat(): requests version/connection info from the server
######################################################################
    sub refdb_viewstat {
	my ($self) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("viewstat");
    }

=head2 refdb_scankw

    Title   : refdb_scankw

    Usage   : $client->refdb_scankw($dbname);

Function: runs a thorough keyword scan in the given database

Parameter: $dbname: name of the reference database

=cut

######################################################################
## refdb_scankw(): runs a keyword scan
######################################################################
    sub refdb_scankw {
	my ($self, $dbname) = @_;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("scankw -d $dbname");
    }

=head2 refdb_addref

    Title   : refdb_addref

    Usage   : $client->refdb_addref($owner, $refdata, $type, $encoding);

Function: adds references to the database

Parameter: $owner: name of the dataset owner, if different from current user

Parameter: $refdata: string containing the reference data

Parameter: $type: data type, must be one of 'ris' or 'risx'

Parameter: $encoding: character encoding of the input data (only for RIS data)

=cut

######################################################################
## refdb_addref(): adds references to the database
######################################################################
    sub refdb_addref {
	my ($self, $owner, $refdata, $type, $encoding) = @_;

	$self->_init_dialog();

	my $cmd = "addref -d $self->{db} ";

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	if (length($owner) > 0) {
	    $cmd .= "-U $owner ";
	}

	if (length($encoding) > 0) {
	    $cmd .= "-E $encoding ";
	}

	$cmd .= "-A $type";

	# return result
	$self->_addref($cmd, $refdata, $type);
    }

=head2 refdb_updateref

    Title   : refdb_updateref

    Usage   : $client->refdb_updateref($owner, $is_personal, $risdata, $type, $encoding);

Function: updates references in the database

Parameter: $owner: name of the dataset owner, if different from current user

Parameter: $is_personal: set to 't' if only the personal information shall be updated

Parameter: $refdata: string containing the reference data

Parameter: $type: data type, must be one of 'ris' or 'risx'

Parameter: $encoding: character encoding of the input data (only for RIS data)

=cut

######################################################################
## refdb_updateref(): updates references in the database
######################################################################
    sub refdb_updateref {
	my ($self, $owner, $is_personal, $risdata, $type, $encoding) = @_;

	my $cmd = "updateref -d $self->{db} ";

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}
	
	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	if (length($owner) > 0) {
	    $cmd .= "-U $owner ";
	}

	if (length($encoding) > 0) {
	    $cmd .= "-E $encoding ";
	}

	if ($is_personal eq "t") {
	    $cmd .= "-p ";
	}

	$cmd .= "-s $type";

	# return result
	$self->_addref($cmd, $risdata, $type);
    }

=head2 refdb_checkref

    Title   : refdb_checkref

    Usage   : $client->refdb_checkref($risdata, $type, $encoding, $outtype);

Function: checks references for duplicates in the database

Parameter: $refdata: string containing the reference data

Parameter: $type: data type, must be one of 'ris' or 'risx'

Parameter: $encoding: character encoding of the input data (only for RIS data)

  Parameter: $outtype: output type (scrn|xhtml)

=cut

######################################################################
## refdb_checkref(): checks references for duplicates in the database
######################################################################
    sub refdb_checkref {
	my ($self, $risdata, $type, $encoding, $outtype) = @_;

	my $cmd = "checkref -d $self->{db} ";

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	if (length($encoding) > 0) {
	    $cmd .= "-E $encoding ";
	}

	if (length($outtype) > 0) {
	    $cmd .= "-t $outtype ";
	}

	if (length($self->{css_url}) > 0) {
	    $cmd .= "-G $self->{css_url} ";
	}

	$cmd .= "-s $type";

	# return result
	$self->_addref($cmd, $risdata, $type);
    }

=head2 refdb_deleteref

    Title   : refdb_deleteref

    Usage   : $client->refdb_deleteref($idlist);

Function: deletes references from the database

Parameter: $idlist: string specifying the IDs of the references to be deleted

=cut

######################################################################
## refdb_deleteref(): deletes references from the database
######################################################################
    sub refdb_deleteref {
	my ($self, $idlist) = @_;

	my $cmd = "deleteref -d $self->{db} ";

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	my $arglength = length($idlist) + TERM_LEN;

	$cmd .= " $arglength";

	$self->_send_status("000", TERM_NO);
	$self->_iwrite("$cmd", TERM_YES);

	$self->_read_status();

	my $sl = new RefDB::Simplelist;

	$sl->{cmd} = $idlist;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->{server_data} = $self->_getsimplelist($sl);

	# return result summary
	$self->{server_summary} = $sl->{summary};
    }

=head2 refdb_addnote

    Title   : refdb_addnote

    Usage   : $client->refdb_addnote($owner, $xnotedata);

Function: adds notes to the database

Parameter: $owner: owner of the note, if different from the current user

Parameter: $xnotedata: XML data specifying the note

=cut

######################################################################
## refdb_addnote(): adds extended notes to the database
######################################################################
    sub refdb_addnote {
	my ($self, $owner, $xnotedata) = @_;

	$self->_init_dialog();

	my $cmd = "addnote -d $self->{db} ";

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	if (length($owner) > 0) {
	    $cmd .= "-U $owner ";
	}

	# return result as a list of lines
	$self->_addnote($cmd, $xnotedata);
    }

=head2 refdb_updatenote

    Title   : refdb_updatenote

    Usage   : $client->refdb_updatenote($owner, $xnotedata);

Function: updates references in the database

Parameter: $owner: owner of the note, if different from the current user

Parameter: $xnotedata: XML data specifying the note

=cut

######################################################################
## refdb_updatenote(): updates notes in the database
######################################################################
    sub refdb_updatenote {
	my ($self, $owner, $xnotedata, $type) = @_;

	my $cmd = "updatenote -d $self->{db} ";

	$self->_init_dialog();
	
	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	if (length($owner) > 0) {
	    $cmd .= "-U $owner ";
	}

	$cmd .= "-s $type";

	# return result as a list of lines
	$self->_addnote($cmd, $xnotedata);
    }

=head2 refdb_deletenote

    Title   : refdb_deletenote

    Usage   : $client->refdb_deletenote($idlist);

Function: deletes notes from the database

Parameter: $idlist: string specifying the ID values of the notes to be deleted
=cut

######################################################################
## refdb_deletenote(): deletes notes from the database
######################################################################
    sub refdb_deletenote {
	my ($self, $idlist) = @_;

	my $cmd = "deletenote -d $self->{db} ";

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	my $arglength = length($idlist) + TERM_LEN;

	$cmd .= " $arglength";

	$self->_send_status("000", TERM_NO);
	$self->_iwrite("$cmd", TERM_YES);

	$self->_read_status();

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	my $sl = new RefDB::Simplelist;

	$sl->{cmd} = $idlist;

	$self->{server_data} = $self->_getsimplelist($sl);

	# return result summary
	$self->{server_summary} = $sl->{summary};
    }

=head2 refdb_addlink

    Title   : refdb_addlink

    Usage   : $client->refdb_addlink($linkspec);

Function: links notes to database objects

Parameter: $linkspec: string specifying the link(s) to be created

=cut

######################################################################
## refdb_addlink(): links notes to database objects
######################################################################
    sub refdb_addlink {
	my ($self, $arg) = @_;

	$self->_addlink("f", $arg);
    }

=head2 refdb_deletelink

    Title   : refdb_deletelink

    Usage   : $client->refdb_deletelink($linkspec);

Function: unlinks notes from database objects

Parameter: $linkspec: string specifying the link(s) to be deleted

=cut

######################################################################
## refdb_deletelink(): unlinks notes from database objects
######################################################################
    sub refdb_deletelink {
	my ($self, $arg) = @_;

	$self->_addlink("t", $arg);
    }

=head2 refdb_getas

    Title   : refdb_getas

    Usage   : $client->refdb_getas($limit_string, $freq, $name_regexp);

 Function: retrieves matching series authors

  Parameter: $limit_string: specifies limit and offset
 
  Parameter: $freq: requests frequency information ("freq" | "relfreq")

  Parameter: $name_regexp: regular expression describing the names to be retrieved
    
=cut
    
######################################################################
## refdb_getas(): retrieves matching series authors
######################################################################
    sub refdb_getas {
	my ($self, $limit_string, $freq, $arg) = @_;
	my $cmd = "getas -d $self->{db} $arg";

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}

	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$self->_listvalue($cmd);
    }

=head2 refdb_getau

    Title   : refdb_getau

    Usage   : $client->refdb_getau($limit_string, $freq, $name_regexp);

Function: retrieves matching part authors

Parameter: $limit_string: specifies limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $name_regexp: regular expression describing the names to be retrieved

=cut

######################################################################
## refdb_getau(): retrieves matching part authors
######################################################################
    sub refdb_getau {
	my ($self, $limit_string, $freq, $arg) = @_;
	my $cmd = "getau -d $self->{db} $arg";

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}

	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$self->_listvalue($cmd);
    }


=head2 refdb_getax

    Title   : refdb_getax

    Usage   : $client->refdb_getax($limit_string, $freq, $name_regexp);

Function: retrieves matching authors/editors (all levels)

Parameter: $limit_string: specifies limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $name_regexp: regular expression describing the names to be retrieved

=cut

######################################################################
## refdb_getax(): retrieves matching authors/editors (all levels)
######################################################################
    sub refdb_getax {
	my ($self, $limit_string, $freq, $arg) = @_;
	my $cmd = "getax -d $self->{db} $arg";

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}

	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$self->_listvalue($cmd);
    }


=head2 refdb_geted

    Title   : refdb_geted

    Usage   : $client->refdb_geted($limit_string, $freq, $name_regexp);

Function: retrieves matching publication authors/editors

Parameter: $limit_string: specifies limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $name_regexp: regular expression describing the names to be retrieved

=cut

######################################################################
## refdb_geted(): retrieves matching publication authors/editors
######################################################################
    sub refdb_geted {
	my ($self, $limit_string, $freq, $arg) = @_;
	my $cmd = "geted -d $self->{db} $arg";

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}

	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$self->_listvalue($cmd);
    }


=head2 refdb_getkw

    Title   : refdb_getkw

    Usage   : $client->refdb_getkw($limit_string, $freq, $keyword_regexp);

Function: retrieves matching keywords

Parameter: $limit_string: specifies limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $keyword_regexp: regular expression describing the keywords to be retrieved

=cut

######################################################################
## refdb_getkw(): retrieves matching keywords
######################################################################
    sub refdb_getkw {
	my ($self, $limit_string, $freq, $arg) = @_;
	my $cmd = "getkw -d $self->{db} $arg";

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}

	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$self->_listvalue($cmd);
    }

=head2 refdb_getjf

    Title   : refdb_getjf

    Usage   : $client->refdb_getjf($is_all, $limit_string, $freq, $journal_regexp);

Function: retrieves matching periodicals (full names)

Parameter: $is_all: set to 't' if all synonymous journal names shall be returned

Parameter: $limit_string: specifies limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $name_regexp: regular expression describing the names to be retrieved

=cut

######################################################################
## refdb_getjf(): retrieves matching periodicals (full names)
######################################################################
    sub refdb_getjf {
	my ($self, $is_all, $limit_string, $freq, $arg) = @_;

	my $cmd = "getjf -d $self->{db} ";

	if ($is_all eq "t") {
	    $cmd .= "-a ";
	}
	
	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}
	
	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$cmd .= $arg;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue($cmd);
    }

=head2 refdb_getjo

    Title   : refdb_getjo

    Usage   : $client->refdb_getjo($is_all, $limit_string, $freq, $journal_regexp);

Function: retrieves matching periodical names (abbrev)

Parameter: $is_all: set to 't' if all synonymous journal names shall be returned

Parameter: $limit_string: select limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $name_regexp: regular expression describing the names to be retrieved

=cut

######################################################################
## refdb_getjo(): retrieves matching periodical names (abbrev)
######################################################################
    sub refdb_getjo {
	my ($self, $all, $limit_string, $freq, $arg) = @_;

	my $cmd = "getjo -d $self->{db} ";

	if (defined($all) && $all eq "t") {
	    $cmd .= "-a ";
	}
	
	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}
	
	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$cmd .= $arg;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue($cmd);
    }

=head2 refdb_getj1

    Title   : refdb_getj1

    Usage   : $client->refdb_getj1($is_all, $limit_string, $freq, $journal_regexp);

Function: retrieves matching periodical names (custom abbrev 1)

Parameter: $is_all: set to 't' if all synonymous journal names shall be returned
Parameter: $limit_string: select limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $name_regexp: regular expression describing the names to be retrieved

=cut

######################################################################
## refdb_getj1(): retrieves matching periodical names (custom abbrev1)
######################################################################
    sub refdb_getj1 {
	my ($self, $is_all, $limit_string, $freq, $arg) = @_;

	my $cmd = "getj1 -d $self->{db} ";

	if ($is_all eq "t") {
	    $cmd .= "-a ";
	}
	
	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}
	
	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$cmd .= $arg;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue($cmd);
    }

=head2 refdb_getj2

    Title   : refdb_getj2

    Usage   : $client->refdb_getj2($is_all, $limit_string, $freq, $journal_regexp);

Function: retrieves matching periodical names (custom abbrev 2)

Parameter: $is_all: set to 't' if all synonymous journal names shall be returned

Parameter: $limit_string: select limit and offset

Parameter: $freq: requests frequency information ("freq" | "relfreq")

Parameter: $name_regexp: regular expression describing the names to be retrieved

=cut

######################################################################
## refdb_getj2(): retrieves matching periodical names (custom abbrev 2)
######################################################################
    sub refdb_getj2 {
	my ($self, $is_all, $limit_string, $freq, $arg) = @_;

	my $cmd = "getj2 -d $self->{db} ";

	if ($is_all eq "t") {
	    $cmd .= "-a ";
	}
	
	if (length($limit_string) > 0) {
	    $cmd .= " -N $limit_string ";
	}
	
	if (length($freq) > 0) {
	    $cmd .= " -s $freq ";
	}

	$cmd .= $arg;

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue($cmd);
    }

=head2 refdb_getref

    Title   : refdb_getref

    Usage   : $client->refdb_getref($type, $format_string, $sort_string, $listname, $encoding, $limit_string, $frequency, $query_string);

Function: retrieves references

Parameter: $type: select output format

Parameter: $format_string: specify additional fields to be retrieved

Parameter: $sort_string: specify sorting key

Parameter: $listname: optional name of a personal reference list

Parameter: $encoding: the character encoding for the output data

Parameter: $limit_string: specifies limit and offset

Parameter: $frequency: if "t", include frequency information in output

Parameter: $query_string: the query that describes the datasets to be retrieved

=cut

######################################################################
## refdb_getref(): retrieves references
######################################################################
    sub refdb_getref {
	my ($self, $type, $format_string, $sort_string, $listname, $encoding, $limit_string, $namespace, $frequency, $arg) = @_;

	my $cmd = "getref -d $self->{db} ";

	if (length($type) > 0) {
	    $cmd .= "-t $type ";
	}
	
	if (length($format_string) > 0) {
	    $cmd .= "-s \"$format_string\" ";
	}
	
	if (length($sort_string) > 0) {
	    $cmd .= "-S $sort_string ";
	}
	
	if (length($self->{pdf_root}) > 0) {
	    $cmd .= "-R $self->{pdf_root} ";
	}
	
	if (length($self->{css_url}) > 0) {
	    $cmd .= "-G $self->{css_url} ";
	}
	
	if (length($encoding) > 0) {
	    $cmd .= "-E $encoding ";
	}

	if (length($listname) > 0) {
	    $cmd .= "-b $listname ";
	}
	
	if (length($limit_string) > 0) {
	    $cmd .= "-N $limit_string ";
	}
	
	if (length($namespace) > 0) {
	    $cmd .= "-n $namespace ";
	}
	
	if ($frequency eq "t") {
	    $cmd .= "-Q "
	}

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= " -u $self->{username}";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= " -w $self->{numberized_passwd}";
	}

	my $arglength = length($arg) + TERM_LEN;

	$cmd .= " $arglength";

	# send command to server
	$self->_send_status("000", TERM_NO);
	$self->_iwrite($cmd, TERM_YES);

	$self->_read_status();

	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}

	$self->_send_status("000", TERM_NO);

	$self->_iwrite($arg, TERM_YES);

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	my $inbuffer;

	my $done = 0;

	# loop until we have all datasets
	do {

	    $self->_read_status();

	    if ($self->{server_status} ne "404"
		&& $self->{server_status} ne "402") {
		return $self->get_status_msg();
	    }

	    if ($self->{server_status} eq "402") {
		$done++;
	    }

	    while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }

	    $self->_send_status("000", TERM_NO);
	} while (!$done);

	# retrieve command summary
	$self->_read_status();
	
	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}
	
	while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}
	$self->_send_status("000", TERM_NO);

	# print "sl summary went to:$sl->{summary}<<\n";


	close ($self->{socket});

	# return result summary
	$self->{server_summary};
    }



=head2 refdb_countref

    Title   : refdb_countref

    Usage   : $client->refdb_countref($listname, $limit_string, $query_string);

Function: counts references

Parameter: $listname: optional name of a personal reference list

Parameter: $limit_string: specifies limit and offset

Parameter: $query_string: the query that describes the datasets to be retrieved

=cut

######################################################################
## refdb_countref(): retrieves references
######################################################################
    sub refdb_countref {
	my ($self, $listname, $limit_string, $arg) = @_;

	my $cmd = "countref -d $self->{db} ";

	if (length($listname) > 0) {
	    $cmd .= "-b $listname ";
	}
	
	if (length($limit_string) > 0) {
	    $cmd .= "-N $limit_string ";
	}
	
	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= " -u $self->{username}";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= " -w $self->{numberized_passwd}";
	}

	my $arglength = length($arg) + TERM_LEN;

	$cmd .= " $arglength";

	# send command to server
	$self->_send_status("000", TERM_NO);
	$self->_iwrite($cmd, TERM_YES);

	$self->_read_status();

	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}

	$self->_send_status("000", TERM_NO);

	$self->_iwrite($arg, TERM_YES);

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	my $inbuffer;

	my $done = 0;

	$self->_read_status();

	if ($self->{server_status} ne "402") {
	    return $self->get_status_msg();
	}

	while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_data} .= $inbuffer;
	    if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}

	$self->_send_status("000", TERM_NO);

	# retrieve command summary
	$self->_read_status();
	
	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}
	
	while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}
	$self->_send_status("000", TERM_NO);

	# print "sl summary went to:$sl->{summary}<<\n";


	close ($self->{socket});

	# return result summary
	$self->{server_summary};
    }



=head2 refdb_pickref

    Title   : refdb_pickref

    Usage   : $client->refdb_pickref($idlist, $listname);

Function: adds references to a personal reference list

Parameters: $idlist: specifies the ID values of the references to be picked
            $listname: name of the personal reference list. If the string
                       is empty, use the default personal reference list

=cut

######################################################################
## refdb_pickref(): adds references to personal reference list
######################################################################
    sub refdb_pickref {
	my ($self, $idlist, $listname) = @_;

	$self->_pickref($idlist, "f", $listname);
    }

=head2 refdb_dumpref

    Title   : refdb_dumpref

    Usage   : $client->refdb_dumpref($idlist, $listname);

Function: removes references from a personal reference list

Parameters: $idlist: specifies the ID values of the references to be dumped
            $listname: name of the personal reference list. If the string
                       is empty, use the default personal reference list

=cut

######################################################################
## refdb_dumpref(): removes references from personal reference list
######################################################################
    sub refdb_dumpref {
	my ($self, $idlist, $listname) = @_;

	$self->_pickref($idlist, "t", $listname);
    }

=head2 refdb_getnote

    Title   : refdb_getnote

    Usage   : $client->refdb_getnote($type, $format_string, $sort_string, $encoding, $limit_string, $query_string);

Function: retrieves references

Parameter: $type: select output format

Parameter: $format_string: specify additional fields to be retrieved

Parameter: $sort_string: specify sorting key

Parameter: $encoding: the character encoding for the output data

Parameter: $limit_string: specifies limit and offset

Parameter: $query_string: the query that describes the datasets to be retrieved

=cut

######################################################################
## refdb_getnote(): retrieves notes
######################################################################
    sub refdb_getnote {
	my ($self, $type, $format_string, $sort_string, $encoding, $limit_string, $namespace, $arg) = @_;

	my $cmd = "getnote -d $self->{db} ";

	if (length($type) > 0) {
	    $cmd .= "-t $type ";
	}
	
	if (length($format_string) > 0) {
	    $cmd .= "-s \"$format_string\" ";
	}
	
	if (length($sort_string) > 0) {
	    $cmd .= "-S $sort_string ";
	}
	
	if (length($self->{pdf_root}) > 0) {
	    $cmd .= "-R $self->{pdf_root} ";
	}
	
	if (length($self->{css_url}) > 0) {
	    $cmd .= "-G $self->{css_url} ";
	}
	
	if (length($encoding) > 0) {
	    $cmd .= "-E $encoding ";
	}

	if (length($limit_string) > 0) {
	    $cmd .= "-N $limit_string ";
	}

	if (length($namespace) > 0) {
	    $cmd .= "-n $namespace ";
	}

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= " -u $self->{username}";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= " -w $self->{numberized_passwd}";
	}

	my $arglength = length($arg) + TERM_LEN;

	$cmd .= " $arglength";

	# send command to server
	$self->_send_status("000", TERM_NO);
	$self->_iwrite($cmd, TERM_YES);

	$self->_read_status();

	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}

	$self->_send_status("000", TERM_NO);

	$self->_iwrite($arg, TERM_YES);

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	my $inbuffer;

	my $done = 0;

	# loop until we have all datasets
	do {

	    $self->_read_status();

	    if ($self->{server_status} ne "404"
		&& $self->{server_status} ne "402") {
		return $self->get_status_msg();
	    }

	    if ($self->{server_status} eq "402") {
		$done++;
	    }

	    while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }

	    $self->_send_status("000", TERM_NO);
	} while (!$done);

	# retrieve command summary
	$self->_read_status();
	
	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}
	
	while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}
	$self->_send_status("000", TERM_NO);

	# print "sl summary went to:$sl->{summary}<<\n";


	close ($self->{socket});

	# return result summary
	$self->{server_summary};
    }


=head2 refdb_countnote

    Title   : refdb_countnote

    Usage   : $client->refdb_countnote($listname, $limit_string, $query_string);

Function: counts extended notes

Parameter: $listname: optional name of a personal reference list

Parameter: $limit_string: specifies limit and offset

Parameter: $query_string: the query that describes the datasets to be retrieved

=cut

######################################################################
## refdb_countnote(): retrieves references
######################################################################
    sub refdb_countnote {
	my ($self, $listname, $limit_string, $arg) = @_;

	my $cmd = "countnote -d $self->{db} ";

	if (length($listname) > 0) {
	    $cmd .= "-b $listname ";
	}
	
	if (length($limit_string) > 0) {
	    $cmd .= "-N $limit_string ";
	}
	
	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= " -u $self->{username}";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= " -w $self->{numberized_passwd}";
	}

	my $arglength = length($arg) + TERM_LEN;

	$cmd .= " $arglength";

	# send command to server
	$self->_send_status("000", TERM_NO);
	$self->_iwrite($cmd, TERM_YES);

	$self->_read_status();

	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}

	$self->_send_status("000", TERM_NO);

	$self->_iwrite($arg, TERM_YES);

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	my $inbuffer;

	my $done = 0;

	$self->_read_status();

	if ($self->{server_status} ne "402") {
	    return $self->get_status_msg();
	}

	while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_data} .= $inbuffer;
	    if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}

	$self->_send_status("000", TERM_NO);

	# retrieve command summary
	$self->_read_status();
	
	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}
	
	while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}
	$self->_send_status("000", TERM_NO);

	# print "sl summary went to:$sl->{summary}<<\n";


	close ($self->{socket});

	# return result summary
	$self->{server_summary};
    }



=head2 refdb_selectdb

    Title   : refdb_selectdb

    Usage   : $client->refdb_selectdb($dbname);

Function: selects an existing database as the current database

Parameter: $dbname: name of the reference database

=cut

######################################################################
## refdb_selectdb(): selects an existing database as the current database
######################################################################
    sub refdb_selectdb {
	my ($self, $dbname) = @_;

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("selectdb $dbname");

#    print "summary went to $summary<<\ndata went to $data<<\n";
	if (substr($self->get_data(), 0, 3) eq "240") {
	    $self->{db} = $dbname;
	}
#    print "self db went to $self->{db}\n";
	$self->{server_summary};
    }

=head2 refdb_whichdb

    Title   : refdb_whichdb

    Usage   : $client->refdb_whichdb();

Function: displays information about the current database

=cut

######################################################################
## refdb_whichdb(): displays information about the current database
######################################################################
    sub refdb_whichdb {
	my ($self) = @_;

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue("whichdb -d $self->{db}");
    }

=head2 refdb_updatejo

    Title   : refdb_updatejo

    Usage   : $client->refdb_updatejo($updaterequest);

Function: updates journal name synonyms

=cut

######################################################################
## refdb_updatejo(): links notes to database objects
######################################################################
    sub refdb_updatejo {
	my ($self, $arg) = @_;

	my $cmd;

	$cmd = "updatejo -d $self->{db} ";

	$cmd .= "\"" . $arg . "\"";

	# reset data
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$self->_listvalue($cmd);
    }

=head2 refdb_texbib

    Title   : refdb_texbib

    Usage   : $client->refdb_texbib($style, $cite_data);

Function: retrieves a bibliography in bibtex format based on citationlistx data

Parameter: $style: the name of the citation/bibliography style

Parameter: $cite_data: XML data describing the references

=cut

######################################################################
## refdb_texbib(): retrieves a bibtex bibliography
######################################################################
    sub refdb_texbib {
	my ($self, $style, $cite_data) = @_;

	my $cs_status;

	my $cmd = "gettexbib -d $self->{db} -s $style ";

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	my $cite_data_len = length($cite_data) + TERM_LEN;

	$cmd .= " $cite_data_len";

	$self->_send_status("000", TERM_NO);  # #1
	$self->_iwrite($cmd, TERM_YES);

	if (($cs_status = $self->_read_status()) != 0) { # #2
	    print $self->get_status_msg() . "\n";
	    return;
	}

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	# send id data to server
	$self->_send_status("000", TERM_NO); # #3
	$self->_iwrite($cite_data, TERM_YES);

	my $inbuffer;
	my $done = 0;

	# loop until we have all datasets
	do {

	    $self->_read_status(); # #4/#6

	    if ($self->{server_status} ne "404"
		&& $self->{server_status} ne "402") {
		return $self->get_status_msg();
	    }

	    if ($self->{server_status} eq "402") {
		$done++;
	    }

	    while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }

	    $self->_send_status("000", TERM_NO); # #7
	} while (!$done);

	# retrieve command summary
	$self->_read_status(); # #8
	
	if ($self->{server_status} ne "000") {
	    return $self->get_status_msg();
	}
	
	while (defined($inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}
	$self->_send_status("000", TERM_NO); # #9

	# print "sl summary went to:$sl->{summary}<<\n";


	close ($self->{socket});

	# return result summary
	$self->{server_summary};
    }

=head2 refdb_dbib

    Title   : refdb_dbib

    Usage   : $client->refdb_dbib($type, $style, $encoding, $cite_data);

Function: retrieves a cooked XML/SGML bibliography based on citationlistx data

Parameter: $type: type of the bibliography output

Parameter: $style: name of the citation/bibliography style

Parameter: $encoding: character encoding of the output data

Parameter: $cite_data: XML data specifying the references

=cut

######################################################################
## refdb_dbib(): retrieves a cooked XML/SGML bibliography
######################################################################
    sub refdb_dbib {
	my ($self, $type, $style, $encoding, $cite_data) = @_;
	
	my $cs_status;

	my $cmd = "getbib -d $self->{db} -t $type -s $style ";

	# reset result strings
	$self->{server_data} = "";
	$self->{stylespec} = "";

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	if (length($encoding) > 0) {
	    $cmd .= "-E $encoding ";
	}

	$self->_send_status("000", TERM_NO);
	$self->_iwrite($cmd, TERM_YES);

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$cs_status = $self->_read_status();

	if ($cs_status != 0 && $cs_status != 402) {
	    print $self->get_status_msg() . "\n";
	    return;
	}

	if ($cs_status == 402) {
	    # server will send stylespec
	    $self->{stylespec} = "";

	    # read stylesheet driver data

	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{stylespec} .= $inbuffer;
		if ($self->{stylespec} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }

	    $self->_send_status("000", TERM_NO);

	    $cs_status = $self->_read_status();

	    if ($cs_status != 0) {
		print $self->get_status_msg() . "\n";
		return;
	    }
	} # end if read stylespec


	# send XML data, phases 1 through 4
	my $result .= $self->_send_xml($cite_data);

	if ($self->{server_status} ne "403") {
	    return "999:0";
	}

	# Phase 5: signal server that we're done
	$self->_send_status("402", TERM_NO);

	$self->_read_status();

	if ($self->{server_status} eq "400") {
	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }
	    return "999:0";
	}
	elsif ($self->{server_status} ne "403") {
	    $self->{server_data} .= $self->get_status_msg();
	    return "999:0";
	}

	# request data
	$self->_send_status("000", TERM_NO);

	my $done_refs = 0;

	do {
	    # loop over all datasets
	    $self->_read_status();

	    if ($self->{server_status} eq "402") {
		$done_refs++;
	    }
	    elsif ($self->{server_status} ne "404") {
		$self->{server_data} .= $self->get_status_msg();
		return "999:0";
	    }

	    # loop until dataset is complete
	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }
	    $self->_send_status("000", TERM_NO);

	} while (!$done_refs);


	# read summary
	$cs_status = $self->_read_status();

	if ($cs_status != 0) {
	    print $self->get_status_msg() . "\n";
	    return;
	}

	while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}

	$self->_send_status("000", TERM_NO);

	close ($self->{socket});

	# return command summary
	$self->{server_summary};
    }

=head2 refdb_getrefx

    Title   : refdb_getrefx

    Usage   : $client->refdb_getrefx($type, $encoding, $cite_data);

Function: retrieves a raw XML/SGML bibliography based on citationlistx data

Parameter: $type: type of the bibliography output

Parameter: $encoding: character encoding of the output data

Parameter: $cite_data: XML data specifying the references

=cut

######################################################################
## refdb_getrefx(): retrieves a raw XML/SGML bibliography
######################################################################
    sub refdb_getrefx {
	my ($self, $type, $encoding, $cite_data) = @_;
	
	my $cs_status;

	my $cmd = "getrefx -d $self->{db} -t $type ";

	# reset result strings
	$self->{server_data} = "";
	$self->{stylespec} = "";

	$self->_init_dialog();

	if (length($self->{username}) > 0) {
	    $cmd .= "-u $self->{username} ";
	}

	if (length($self->{numberized_passwd}) > 0) {
	    $cmd .= "-w $self->{numberized_passwd} ";
	}

	if (length($encoding) > 0) {
	    $cmd .= "-E $encoding ";
	}

	$self->_send_status("000", TERM_NO);
	$self->_iwrite($cmd, TERM_YES);

	# reset result string
	$self->{server_data} = "";
	$self->{server_summary} = "";

	$cs_status = $self->_read_status();

	if ($cs_status != 0 && $cs_status != 402) {
	    print $self->get_status_msg() . "\n";
	    return;
	}

	# send XML data, phases 1 through 4
	my $result .= $self->_send_xml($cite_data);

	if ($self->{server_status} ne "403") {
	    return "999:0";
	}

	# Phase 5: signal server that we're done
	$self->_send_status("402", TERM_NO);

	$self->_read_status();

	if ($self->{server_status} eq "400") {
	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }
	    return "999:0";
	}
	elsif ($self->{server_status} ne "403") {
	    $self->{server_data} .= $self->get_status_msg();
	    return "999:0";
	}

	# request data
	$self->_send_status("000", TERM_NO);

	my $done_refs = 0;

	do {
	    # loop over all datasets
	    $self->_read_status();

	    if ($self->{server_status} eq "402") {
		$done_refs++;
	    }
	    elsif ($self->{server_status} ne "404") {
		$self->{server_data} .= $self->get_status_msg();
		return "999:0";
	    }

	    # loop until dataset is complete
	    while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
		$self->{server_data} .= $inbuffer;
		if ($self->{server_data} =~ s/$self->{CS_TERM}//) {
		    last;
		}
	    }
	    $self->_send_status("000", TERM_NO);

	} while (!$done_refs);


	# read summary
	$cs_status = $self->_read_status();

	if ($cs_status != 0) {
	    print $self->get_status_msg() . "\n";
	    return;
	}

	while (defined(my $inbuffer = $self->_tread(OUTBUF_LEN))) {
	    $self->{server_summary} .= $inbuffer;
	    if ($self->{server_summary} =~ s/$self->{CS_TERM}//) {
		last;
	    }
	}

	$self->_send_status("000", TERM_NO);

	close ($self->{socket});

	# return command summary
	$self->{server_summary};
    }


1; # make 'use' happy

__END__


