# $Id: File.pm,v 1.2 2004/11/20 17:40:17 gwolf Exp $
######################################
# Comas - Conference Management System
######################################
# Copyright 2003 CONSOL
# Congreso Nacional de Software Libre (http://www.consol.org.mx/)
#   Gunnar Wolf <gwolf@gwolf.cx>
#   Manuel Rabade <mig@mig-29.net>
#
# 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
######################################

######################################
# Module: Comas::File
# Manages the proposals' files
######################################
# Depends on:
#
# Comas::Common - Common functions for various Comas modules
# DBD::Pg - PostgreSQL database server driver for DBI
package Comas::File;

use strict;
use warnings;
use Carp;
use Comas::Common qw(valid_hash);
# DBD::Pg must be used here, as we are using some constants from it later on
use DBD::Pg;

=head1 NAME

Comas::File - Manages the proposals' files

=head1 SYNOPSIS

=head2 OBJECT CONSTRUCTOR

    $file = Comas::File->new(-db=>$db,
                             -id=>$number);

or

    $file = Comas::Proposal->new(-db=>$db,
                                 -proposal_id=>$prop_id,
                                 -filename=>$filename,
                                 -data=>$data, 
                                 -descr=>$descr);

The constructor must receive a Comas::DB object in the C<-db> attribute.

The presence of the C<-id> attribute in the constructor implies that no other
descriptor attribute will be supplied - if C<new> is called with both kinds of
attributes, it will fail.

The attributes accepted by the constructor are (check Comas' database
documentation for further information) C<-id>, C<-proposal_id>, C<-filename>
and C<-data>.

If C<new> is called with C<-id>, it will retrieve and work with an existing
proposal, identified by the supplied ID. If the data are supplied, a new 
file will be created and associated to the proposal with the specified data.

=head2 LOOKUP FUNCTIONS

    @ids = Comas::File->search(-db=>$db,
                               -proposal_id=>$id);

Will return in C<@ids> the IDs of the files registered for a given proposal.
Of course, if no files are found for said proposal or if the proposal is not
valid, an empty list will be returned. All the attributes valid for the 
constructor are valid.

=head2 DELETING FILES

    $ok = $file->delete;

This will (completely and irreversibly) remove the current file. In order to
reflect this fact, after calling this method B<the object will be destroyed> 
as well. Never use a file object that has been deleted, it will bomb on you.
You should, however, check the result of this call - It is possible that the
file does not get erased (i.e., if trying to delete a file for an accepted
proposal). If the file cannot be deleted, the object will (of course) remain
existing and still point to it.

=head2 ATTRIBUTE ACCESSORS AND MUTATORS

In order to access the proposal's attributes, you can use:

    $id = $file->get_id;
    $filename = $file->get_filename;
    $descr = $file->get_descr;
    $proposal_id = $file->get_proposal_id;
    $data = $file->get_data;
    $filesize = $file->get_filesize;

And if you want to modify the current value for any of the attributes:

    $ok = $file->set_filename($filename);
    $ok = $file->set_descr($descr);
    $ok = $file->set_data($data);

=head1 REQUIRES

L<Comas::DB|Comas::DB>

L<Comas::Common|Comas::Common>

=head1 SEE ALSO

Comas' database documentation, http://wiki.consol.org.mx/comas_wiki/

=head1 AUTHOR

Gunnar Wolf, gwolf@gwolf.cx

Manuel Rabade, mig@mig-29.net

Comas has been developed for CONSOL, Congreso Nacional de Software Libre,
http://www.consol.org.mx/

=head1 COPYRIGHT

Copyright 2003 Gunnar Wolf and Manuel Rabade

This library is free software, you can redistribute it and/or modify it
under the terms of the GPL version 2 or later.

=cut

#################################################################
# Class Metods

#################################################################
# Object constructor
sub new {
    my ($class, $file);
    $class = shift;
    $file = { valid_hash(@_) };
    return undef unless (scalar keys %$file);

    if (!defined($file->{-db}) or !ref($file->{-db})) {
	carp "Invocation error - mandatory '-db' field not specified or wrong";
	return undef;
    }

    if (defined $file->{-id}) {
	my ($sth, $r);
	if (scalar keys %$file > 2) {
	    carp "Invocation error - '-id' given, no other attributes accepted";
	    return undef; 
	}

	# Get the material's ID, filename and description (the data will only
	# be brought when it is requested, in order not to spend needless RAM)
	unless ($sth = $file->{-db}->prepare('SELECT  proposal_id, filename,
        descr FROM prop_material WHERE id = ?')) {
	    carp "Could not query for the file's data: ";
	    return undef;
	}
	if ($sth->execute($file->{-id}) eq '0E0') {
	    carp 'Could not prepare file query';
	    return undef;
	}

	# Get the retrieved data in the right format
	$r = $sth->fetchrow_hashref;
	map {$file->{"-$_"} = $r->{$_}} keys %$r;
    } else {
	my ($sth, $id);
	# We did not get an ID, so we need all of the other arguments in order
	# to create a new file.
	for my $req qw(-proposal_id -filename -data -descr) {
	    if (!$file->{$req}) {
		carp "Required argument '$req' missing";
		return undef;
	    }
	}
	# We were given a proposal's ID, as well as the filename, description 
	# and data - This can only mean we have to create a new prop_material
	# entry.
	# We use here $sth->bind_param so that the 'data' (bytea) field does
	# not need excaping. For the two other fields, we don't need to specify
	# their data type.
	$file->{-db}->begin_work;
	unless ($sth = $file->{-db}->prepare('INSERT INTO prop_material 
                (proposal_id, filename, descr, data) VALUES (?, ?, ?, ?)') and
		$sth->bind_param(1, $file->{-proposal_id}) and
		$sth->bind_param(2, $file->{-filename}) and
		$sth->bind_param(3, $file->{-descr}) and
		$sth->bind_param(4, $file->{-data},
				 { pg_type => DBD::Pg::PG_BYTEA }) and
		$sth->execute and
		$sth = $file->{-db}->prepare("SELECT currval(
                'prop_material_id_seq');") and $sth->execute and
		($id) = $sth->fetchrow_array) {
	    $file->{-db}->rollback;
	    carp 'Error inserting the requested material';
	    return undef;
	}
	$file->{-db}->commit;

	# Create a new File with this ID
	$file = Comas::File->new(-db => $file->{-db}, -id => $id);
    }

    bless ($file, $class);
    return $file;
}

######################################################################
# Lookup
sub search {
    my (%args, $sth, @list);
    # We are not instantiating, this is a class method - just shift and drop.
    shift;
    %args = valid_hash(@_);
    unless ($args{-db} and $args{-proposal_id} and scalar(keys %args) == 2) {
	carp 'Invocation error - Expecting -db and -proposal_id';
	return undef;
    }

    unless ($sth = $args{-db}->prepare('SELECT id FROM prop_material WHERE 
            proposal_id = ? ORDER BY id') and
	    $sth->execute($args{-proposal_id})) {
	carp 'Could not query for material regarding specified proposal';
	return undef;
    }
    @list = map {$_->[0]} (@{$sth->fetchall_arrayref});

    return @list;
}

######################################################################
# Deleting files
sub delete {
    my ($file, $sth);
    $file = shift;
    unless ($sth = $file->{-db}->prepare('DELETE FROM prop_material WHERE
            id = ?') and $sth->execute($file->{-id})) {
	######## Check for the DB's error status, may found the cause for
	######## the failure
	carp 'Could not prepare/execute the material removal query';
	return undef;
    }

    # The file has been deleted, destroy the object.
    %$file = ();
    return 1;
}

######################################################################
# Attribute accessors and mutators
sub get_id { my $file = shift; return $file->{-id};}
sub get_filename { my $file = shift; return $file->{-filename};}
sub get_descr { my $file = shift; return $file->{-descr};}
sub get_proposal_id { my $file = shift; return $file->{-proposal_id};}
sub get_data {
    my ($file, $sth, $data);
    $file = shift;

    unless ($sth = $file->{-db}->prepare('SELECT data FROM prop_material 
            WHERE id = ?') and $sth->execute($file->{-id})) {
	carp 'Could not retreive the requested material';
	return undef;
    }
    ($data) = $sth->fetchrow_array;

    return $data;
}

sub get_filesize {
    my ($file, $sth, $size);
    $file = shift;

    unless ($sth = $file->{-db}->prepare('SELECT length(data) FROM 
            prop_material WHERE id = ?') and $sth->execute($file->{-id})) {
	carp "Could not retreive the requested material's size";
	return undef;
    }
    ($size) = $sth->fetchrow_array;

    return $size;
}

sub set_filename { 
    my ($file, $new_name, $sth);
    $file = shift;
    $new_name = shift;

    unless ($sth = $file->{-db}->prepare('UPDATE prop_material SET 
            filename = ? WHERE id = ?') and
	    $sth->execute($new_name, $file->get_id)) {
	carp 'Error modifying the requested material';
	return undef;
    }
    $file->{-filename} = $new_name;

    return 1;
}

sub set_descr { 
    my ($file, $new_descr, $sth);
    $file = shift;
    $new_descr = shift;

    unless ($sth = $file->{-db}->prepare('UPDATE prop_material SET 
            descr = ? WHERE id = ?') and
	    $sth->execute($new_descr, $file->get_id)) {
	carp 'Error modifying the requested material';
	return undef;
    }
    $file->{-descr} = $new_descr;

    return 1;
}

sub set_data {
    my ($file, $new_data, $sth);
    $file = shift;
    $new_data = shift;
    
    unless ($sth = $file->{-db}->prepare('UPDATE prop_material SET 
            data = ? WHERE id = ?') and
	    $sth->execute($new_data, $file->get_id)) {
	carp 'Error modifying the requested material';
	return undef;
    }
    $file->{-data} = $new_data;
    
    return 1;
}

1;

# $Log: File.pm,v $
# Revision 1.2  2004/11/20 17:40:17  gwolf
# Bugfix - DBD::Pg must be used before compiling Comas::File in order to provide the PG_BYTEA constant
#
# Revision 1.1  2004/11/12 05:44:36  gwolf
# Added the functionality to be able to handle more than one file per proposal and store it in the DB, not in separate files.
#
