###
###  Copyright 2000-2007 University of Illinois Board of Trustees
###  All rights reserved. 
###
###  PSGConf::Action::File - base class for psgconf file-generator action types
###
###  Campus Information Technologies and Educational Services
###  University of Illinois at Urbana-Champaign
###


package PSGConf::Action::File;

use strict;

use File::Basename;
use File::Compare;
use File::Copy;
use File::Path;
use File::stat;
use POSIX;

use Text::Diff ();		# Use () so we do not get diff() redefinition errors

use PSGConf::Action::ChMod;
use PSGConf::Action::ChOwn;
use PSGConf::Action::ChGrp;

use PSGConf::Util;

our @ISA = qw(PSGConf::Action::ChMod
			PSGConf::Action::ChOwn
			PSGConf::Action::ChGrp);


###############################################################################
###  constructor
###############################################################################

sub new
{
	my ($class, %opts) = @_;
	my ($self) = \%opts;

	$self->{backup} = 1
		if (!exists($self->{backup}));

	### Call the ChMod action as well.
	$self = PSGConf::Action::ChMod::new($class, %$self);

	### Call the ChOwn action as well.
	$self = PSGConf::Action::ChOwn::new($class, %$self);

	### Call the ChGrp action as well.
	return PSGConf::Action::ChGrp::new($class, %$self);
}


###############################################################################
###  utility function to check whether file changed
###############################################################################

sub _file_changed
{
	my ($self, $psgconf) = @_;

	### check for changes
	$self->{changed} = 1
		if (! -e $self->{name}
			|| compare($self->{name}, $self->{tmpfile}) != 0);

	### check for permission differences
	$self->PSGConf::Action::ChMod::check($psgconf);

	### check for ownership differences
	$self->PSGConf::Action::ChOwn::check($psgconf);

	### check for group differences
	$self->PSGConf::Action::ChGrp::check($psgconf);

	return $self->{changed};
}


###############################################################################
###  check method
###############################################################################

sub check
{
	my ($self, $psgconf) = @_;

	### Intentionally left blank.
}


###############################################################################
###  diff method
###############################################################################

sub diff
{
	my ($self, $psgconf) = @_;
	my ($mytgt);

	if (! -e $self->{name} || compare($self->{name}, $self->{tmpfile}) != 0) {
		print "###############################################################################\n";
		print "### DIFF FOR $self->{name}\n";
		if (! -e $self->{name})
		{
			print "###          (original file does not exist)\n";
			$mytgt = '/dev/null';
		}
		else
		{
			$mytgt = $self->{name};
		}
		print "###############################################################################\n";

		###
		### Make sure we only run Text::Diff::diff on known text files.
		###
		if ( -e  $self->{tmpfile} ) {
			if ( -T $mytgt && -T $self->{tmpfile} ) {
				print Text::Diff::diff $mytgt, $self->{tmpfile};
			} else {
	    			printf "+ Binary file(s) %s, %s are different\n",
					$mytgt, $self->{tmpfile};
			}
		}

		print "\n";
	}

	### check for permission differences
	$self->PSGConf::Action::ChMod::diff($psgconf);

	### check for ownership differences
	$self->PSGConf::Action::ChOwn::diff($psgconf);

	### check for group differences
	$self->PSGConf::Action::ChGrp::diff($psgconf);
}


###############################################################################
###  do() method
###############################################################################

sub do
{
	my ($self, $psgconf) = @_;

	### install new file if contents changed
	if (-e $self->{tmpfile})
	{
		### save or remove original file
		if (-e $self->{name}
		    || -l $self->{name})
		{
			###
			### for regular files, don't delete the original,
			### since it might have multiple hard links
			###

			if (!$self->{backup})
			{
				if (! -f $self->{name})
				{
					rmtree($self->{name});
				}
			}
			elsif (! $psgconf->save_file($self->{name}, $self->{backup}))
			{
				return -1;
			}
		}

		### copy new file into place
		mkpath(dirname($self->{name}));
		if (!copy($self->{tmpfile}, $self->{name}))
		{
			warn "\t!!! copy('$self->{tmpfile}', "
				     . "'$self->{name}'): $!\n";
			return -1;
		}
	}

	### check for permission differences
	$self->PSGConf::Action::ChMod::do($psgconf);

	### check for ownership differences
	$self->PSGConf::Action::ChOwn::do($psgconf);

	### check for group differences
	$self->PSGConf::Action::ChGrp::do($psgconf);

	### remove tmpfile if applicable
	if (-e $self->{tmpfile}
	    && $psgconf->{rm_tmpfiles}
	    && !unlink($self->{tmpfile}))
	{
		warn "\t!!! unlink('$self->{tmpfile}'): $!\n";
	}

	return 1;
}


###############################################################################
###  documentation
###############################################################################

1;

__END__

=head1 NAME

PSGConf::Action::File - base class for psgconf file-generator action types

=head1 SYNOPSIS

  use PSGConf::Action::File;

  $psgconf->register_actions(
		PSGConf::Action::File->new(
			'name'		=> '/path/to/file',
			...
		),
		...
	);

=head1 DESCRIPTION

The B<PSGConf::Action::File> module provides a base class for B<PSGConf>
action classes that manipulate a file.

The B<PSGConf::Action::File> class is not intended to be used to directly
instantiate action objects, but it does support a number of methods for
use in subclasses.

The B<PSGConf::Action::File> class is derived from the
B<PSGConf::Action::ChMod>, B<PSGConf::Action::ChOwn> and
B<PSGConf::Action::ChGrp> classes, but it defines/overrides
the following methods:

=over 4

=item _file_changed($psgconf)

Utility method for use in subclasses' check() method.  Checks to see
whether the existing file differs from the tmpfile then calls
the C<check> methods in C<ChMod>, C<ChOwn> and C<ChGrp>.  Sets the
object's I<changed>attribute and returns the same values as the check()
method.

=item check()

Returns 0.  (This method should be replaced by the subclass.)

=item diff()

Display the differences between the tmpfile and the existing file.
Also displays mode and ownership changes.

=item do()

Moves the tmpfile into place, optionally backing up the original file.
Also sets mode and ownership.

=back

In addition to the attributes supported by the B<PSGConf::Action>
class, the B<PSGConf::Action::File> class supports the following
attributes:

=over 4

=item I<mode>

The octal mode of the file.  Defaults to 0644.  Set to -1 to disable
check.

=item I<uid>

The numeric user id of the file.  Defaults to 0.  Set to -1 to disable
check.

=item I<gid>

The numeric group id of the file.  Defaults to 0.  Set to -1 to disable
check.

=item I<backup>

A boolean flag to indicate whether a backup copy of the existing file
will be saved.  If enabled, existing files will be saved under their
original name but with the prefix ".<backupext>".  Default is to
backup.

=back

=head1 BUGS

The class should support multiple instantiations for the same file,
where subsequent instantiations would use the tmpfile from the previous
instantiation instead of the actual original file on the system.

=head1 SEE ALSO

L<perl>

L<PSGConf>

L<PSGConf::Action::ChGrp>

L<PSGConf::Action::ChMod>

L<PSGConf::Action::ChOwn>

L<PSGConf::Util>

=cut



syntax highlighted by Code2HTML, v. 0.9.1