| File-Append-TempFile documentation | Contained in the File-Append-TempFile distribution. |
File::Append::TempFile - Perl extension for appending data to files
use File::Append::TempFile;
$f = new File::Append::TempFile();
$f->begin_work('/etc/hosts') or die "Appending: ".$f->err();
$f->add_line("127.0.0.2 localhvost\n");
$f->commit();
$f->begin_work('/etc/hosts') or die "Appending: ".$f->err();
$f->add_line("...\n");
$f->rollback();
The File::Append::TempFile module provides an OOP interface to appending
data to files using a temporary file, in order to ensure the atomicity of
the updates.
An append session is initiated by invoking the begin_work() method and
passing it the name of the file. At this point, a temporary file is
created in the same directory as the original file and the original's
contents is copied to the temporary. More data is added to the temporary
file using the add_line() method. When done appending, the commit()
method will atomically move the temporary file over the original.
If something goes wrong, the rollback() method will remove the temporary
file without affecting the original in any way.
The File::Append::TempFile class defines the following methods:
Create a new File::Append::TempFile object. No file processing is
done at this point.
Set or obtain an error message describing the last error that occurred
during the processing of the current File::Append::TempFile object.
Set or obtain the diagnostic output flag. If it is set, the methods will display diagnostic information on the standard error stream.
Creates a temporary file in the same directory as the specified one and
copies the original's contents over to the new file. Further data may
be added using the add_line() method and then either stored as the
original with the commit() method, or discarded with the rollback()
method.
Append data to the temporary file. This does not affect the original in
any way until commit() is invoked.
Replace the original file with the temporary copy, to which data may have
been added using add_line().
NOTE: This method uninitializes the File::Append::TempFile object,
that is, removes any association between it and the original file and
even file name! The next method invoked on this File::Append::TempFile
object should be begin_work().
Discard all the changes made to the temporary copy and remove it. This does not affect the original file in any way.
NOTE: This method uninitializes the File::Append::TempFile object,
that is, removes any association between it and the original file and
even file name! The next method invoked on this File::Append::TempFile
object should be begin_work().
There are also several methods used internally by the
File::Append::TempFile routines:
Display a diagnostic message to the standard error stream if the output of diagnostic messages has been enabled.
Actually perform the copying of the original file data into the temporary
file at begin_work() time. This allows derived classes to modify
the file structure if needed.
The two parameters are the file handles for the original and the temporary file.
The File::Append::TempFile website:
http://devel.ringlet.net/sysutils/file-append-tempfile/
begin_work()
and commit() - those changes will be lost!Peter Pentchev, <roam@ringlet.net>
Copyright (C) 2006 by Peter Pentchev.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.7 or, at your option, any later version of Perl 5 you may have available.
$Ringlet: TempFile.pm 1635 2007-12-27 14:06:01Z roam $
| File-Append-TempFile documentation | Contained in the File-Append-TempFile distribution. |
package File::Append::TempFile; use 5.006; use strict; use warnings;
use Fcntl; our @ISA = qw(); our $VERSION = '0.05'; our $debug = 0; my %tempfiles;
sub new { my $proto = shift; my $class = ref $proto || $proto; my $self; $self = { 'fname' => undef, 'tmpfname' => undef, 'f' => undef, 'err' => undef, 'debug' => undef }; bless $self, $class; $tempfiles{$self} = $self; return $self; }
sub err($ $) { my ($self, $err) = @_; $self->{'err'} = $err if @_ > 1; return $self->{'err'}; }
sub diag($ $) { my ($self, $debug) = @_; $self->{'debug'} = $debug if @_ > 1; return $self->{'debug'}; }
sub begin_work($ $) { my ($self, $fname) = @_; my ($tmpfname, $orig, $f); my @stat; if ($self->{'f'}) { return undef unless $self->rollback(); } $self->{'fname'} = $self->{'tmpfname'} = $self->{'f'} = undef; if (!open($orig, '<', $fname)) { $self->err("Opening $fname: $!"); return undef; } @stat = stat $orig; $tmpfname = sprintf '%s.%05d.%04d', $fname, $$, int rand 10000; if (!sysopen($f, $tmpfname, O_WRONLY | O_EXCL | O_CREAT, 0600)) { $self->err("Creating temporary file $tmpfname: $!"); return undef; } return undef unless $self->do_copy($orig, $f); close $orig; $self->{'fname'} = $fname; $self->{'tmpfname'} = $tmpfname; $self->{'f'} = $f; $self->{'stat'} = [ @stat ]; return 1; }
sub add_line($ $) { my ($self, $line) = @_; my $f = $self->{'f'}; if (!defined($f)) { $self->err("Cannot add_line() to an unopened tempfile"); return undef; } $self->debug("RDBG about to add a line to $self->{tmpfname} for $self->{fname}\n"); if (!(print $f $line)) { $self->err("Could not add to the tempfile: $!"); return undef; } return 1; }
sub commit($) { my ($self) = @_; if (!defined($self->{'f'}) || !defined($self->{'tmpfname'}) || !defined($self->{'fname'})) { $self->err("Cannot commit an unopened tempfile"); return undef; } $self->debug("RDBG about to commit $self->{tmpfname} to $self->{fname}\n"); # Fix stuff up if (defined($self->{'stat'})) { # Mode if (!chmod($self->{'stat'}->[2], $self->{'tmpfname'})) { $self->err("Could not chmod $self->{stat}->[2] ". $self->{'tmpfname'}.": $!"); return undef; } # Owner & group if (!chown($self->{'stat'}->[4], $self->{'stat'}->[5], $self->{'tmpfname'})) { $self->err("Could not chown $self->{stat}->[4], ". $self->{'stat'}->[5].", $self->{tmpfname}: $!"); return undef; } } if (!rename($self->{'tmpfname'}, $self->{'fname'})) { $self->err("Renaming $self->{tmpfname} to $self->{fname}: $!"); return undef; } close $self->{'f'}; $self->debug("RDBG successfully committed $self->{tmpfname} to $self->{fname}\n"); $self->{'fname'} = $self->{'tmpfname'} = $self->{'f'} = undef; return 1; }
sub rollback($) { my ($self) = @_; $self->debug(ref($self)."->rollback() for $self->{fname}\n"); if (defined($self->{'tmpfname'})) { $self->debug("RDBG removing $self->{tmpfname}\n"); if (!unlink($self->{'tmpfname'})) { $self->err("Removing $self->{tmpfname}: $!"); return undef; } undef $self->{'tmpname'}; } if (defined($self->{'f'})) { $self->debug("RDBG closing the file\n"); close $self->{'f'}; undef $self->{'f'}; } undef $self->{'fname'}; $self->debug("RDBG rollback seems complete\n"); return 1; }
sub debug($ $) { my ($self, $msg) = @_; if ($self->{'debug'} || $debug) { print STDERR $msg; } }
sub do_copy($ $ $) { my ($self, $orig, $f) = @_; while (<$orig>) { print $f $_; } return 1; } END { print STDERR "RDBG File::Append::TempFile END block\n" if $debug; print STDERR "RDBG ".keys(%tempfiles)."\n" if $debug; foreach (keys %tempfiles) { $tempfiles{$_}->rollback() if $tempfiles{$_}->{'tmpfname'}; } }
1;