package Site::Version; ###### 
### 	$Id: Version.pm,v 1.3 2002/08/21 15:42:08 steve Exp $
###
###	This class performs version control operations.  Eventually it may
###	be extended (subclassed) to allow alternative VC techniques; for now
###	it only handles CVS.

use XML::Node;
use Exporter();
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);

BEGIN {
    $VERSION=1.00;
    @ISA = qw(Exporter XML::Node);
    @EXPORT = qw(
		 );
}


#############################################################################
###
### Constructors:
###

### Version->new($documentRoot, $dirpath, $username, $userid)
#	Create a new version-control object for the current directory.
#	At this point we assume that the user has been authenticated
#	and has write privileges for files in the directory; it's up to
#	the caller not to call destructive operations if the user only
#	has read access.
sub new {
    my ($class, $root, $dirpath, $uname, $userid) = @_;

    my $self = {'root'=>$root, 'dirpath'=>$dirpath, 
		'userid'=>$userid, 'username'=>$uname};
    $self = bless $self, $class;

    return $self;
}

#############################################################################
###
### Access functions:
###

sub dirPath {
    my $self = shift;
    return $self->{'dirpath'};
}

sub root {
    my $self = shift;
    return $self->{'root'};
}

sub userID {
    my $self = shift;
    return $self->{'userid'};
}

sub userName {
    my $self = shift;
    return $self->{'username'};
}

sub fullPath {
    my $self = shift;
    return $self->root() . $self->dirPath();
}



#############################################################################
###
### Notes on User Names/ID's:
###

## There are two main ways we can handle users for version control:
##
##   Generic users: 
##
##	We use a fixed set of generic names for _classes_ of users, e.g., 
##	{ owner, friend, fan, subscriber, member, visitor, ...} 
##	The MemberName of the actual user is in the commit message. 
##
##   Named users:
##
##	Each user has a Unix-style account name, and we use these names
##	as their usernames for version control as well as for everything
##	else.  Given sufficient privileges, some users could have _real_ 
##	accounts (maybe in a chroot jail) with, for example, a .ssh 
##	subdirectory.

## === Actually, we can't easily use either!  CVS expects each user to have
## === their own working directory, and the username is built in to the
## === Root.  It *might* be possible to use -d to change this, but that
## === requires experimentation.

## It would be nice if we didn't have to force a choice between these two,
## and instead could leave it up to the site administrator.  In the case of
## PenguinSong, for example, we might want named users for PenguinSong.org
## developers, and generic users for PenguinSong.net members -- unless they
## happened to be developers!

## Basically, we'll leave the details up to the User class, and let it
## hand us a userid, CVS password or, for real users, home directory,
## and a human-readable name -- possibly with other information -- for
## the commit message.


#############################################################################
##
## Notes on Operations:
##

## Initially, we can do without things like retrieving old versions and
## just stick to the basics:
##
##  o registration (cvs add)
##  o commit
##  o remove
##  o get metadata: description, latest message, user, version#, status


#############################################################################
###
### Basic Operations
###


### $v->add($fn, $dscr, $flags)
#	$fn is, as usual, the path of a file relative to the fullPath.
#	Binary files should have $flags = '-kb'
sub add {
    my ($self, $fn, $dscr, $flags) = @_;
    my $dir = $self->fullPath();
    $message = "[" . $self->userName() . "] " . $dscr;

    # === pass user, password to pserver ===
    my $result = `cd $dir; cvs add $flags -m '$message' $fn`;
    return $result;
}


### $v->commit($fn, $message)
#	As with all Version operations, $filepath is the full, filesystem
#	path to the file to be operated on.
sub commit {
    my ($self, $fn, $message, $flags) = @_;
    my $dir = $self->fullPath();
    $message = "[" . $self->userName() . "] " . $message;

    # === pass user, password to pserver ===
    my $result = `cd $dir;cvs commit $flags -m '$message' $fn`;
    return $result;
}


### $v->remove($fn)
#	$fn is, as usual, the path of a file relative to the fullPath.
sub remove {
    my ($self, $fn, $flags) = @_;
    my $dir = $self->fullPath();

    # === pass user, password to pserver ===
    my $result = `cd $dir; rm $fn && cvs remove $fn`;
    return $result;
}

### $v->info($fn, $flags) -> {...}
#	$fn is, as usual, the path of a file relative to the fullPath.
#	This operation requires only read access to the directory and file.
#
#	 "cvs log -N -r $fn" gets the latest log comment
#	-N suppresses tags, which can get bulky.  
#	-r is latest revision on the main branch
sub info {
    my ($self, $fn, $flags) = @_;
    my $dir = $self->fullPath();

    # === pass user, password to pserver ===
    my $result = `cd $dir; cvs log -N -r $fn`;
    # At this point, $result is a list of label: value headers (note that
    # the label may contain spaces), a row of dashes, revision and date
    # headers, followed immediately by the log comment.  
    # There's a blank line at the front, and a row of "="'s at the end.

    my $info = {};
    my ($n, $v, $msg);
    foreach (split(/\n/, $result)) {
	if (/^([a-zA-Z ]+):\s*([.*])$/) {
	    $n = $1;
	    $v = $2;
	    $info->{$n} = $v;
	} elsif (/^\s+([.*])$/) {
	    $info->{$n} .= "\n$1" if $n;
	} elsif (/^(revision)\s*([.*])$/i) {
	    $n = $1;
	    $v = $2;
	    $info->{$n} = $v;
	    last;
	} elsif (/^\[-=]*$/) {
	    # ignore blank lines or lines of equal signs or dashes
	    # === could switch modes on --- to pick up rev, date, msg.
    } else {
	$msg .= $_;
    }
    $info->{'message'} = $msg;
    # === parse user out of $msg ===

    return $info;
}

### $v->log($fn, $flags)
#	$fn is, as usual, the path of a file relative to the fullPath.
#	Obtain and return, unmodified, the CVS log information for $fn.
#	This operation requires only read access to the directory and file.
sub log {
    my ($self, $fn, $dscr, $flags) = @_;
    my $dir = $self->fullPath();

    # === pass user, password to pserver ===
    my $result = `cd $dir; cvs log $flags $fn`;
    return $result;
}


### $v->update($flags)
#	Perform a CVS update operation in the current directory.
#
#	This will rarely, if ever, be necessary in interactive directories;
#	it _is_ needed for updating a _non_-interactive subtree from a remote
#	CVS repository.
#
sub update {
    my ($self, $flags) = @_;
    my $dir = $self->fullPath();

    # === pass user, password to pserver ===
    my $result = `cd $dir; cvs update $flags`;
    return $result;
}

#############################################################################
1;
