File Coverage

blib/lib/File/Append/TempFile.pm
Criterion Covered Total %
statement 78 100 78.0
branch 18 38 47.3
condition 3 9 33.3
subroutine 14 15 93.3
pod 9 9 100.0
total 122 171 71.3


line stmt bran cond sub pod time code
1             package File::Append::TempFile;
2              
3 2     2   27500 use v5.010;
  2         7  
4 2     2   10 use strict;
  2         3  
  2         40  
5 2     2   10 use warnings;
  2         11  
  2         75  
6              
7             =head1 NAME
8              
9             File::Append::TempFile - Perl extension for appending data to files
10              
11             =head1 SYNOPSIS
12              
13             use File::Append::TempFile;
14              
15             $f = new File::Append::TempFile();
16             $f->begin_work('/etc/hosts') or die "Appending: ".$f->err();
17             $f->add_line("127.0.0.2 localhvost\n");
18             $f->commit();
19              
20             $f->begin_work('/etc/hosts') or die "Appending: ".$f->err();
21             $f->add_line("...\n");
22             $f->rollback();
23              
24             =head1 DESCRIPTION
25              
26             The C module provides an OOP interface to appending
27             data to files using a temporary file, in order to ensure the atomicity of
28             the updates.
29              
30             An append session is initiated by invoking the C method and
31             passing it the name of the file. At this point, a temporary file is
32             created in the same directory as the original file and the original's
33             contents is copied to the temporary. More data is added to the temporary
34             file using the C method. When done appending, the C
35             method will atomically move the temporary file over the original.
36             If something goes wrong, the C method will remove the temporary
37             file without affecting the original in any way.
38              
39             =cut
40              
41 2     2   10 use File::Basename qw(basename dirname);
  2         4  
  2         176  
42 2     2   2324 use File::Temp;
  2         44978  
  2         2506  
43              
44             our @ISA = qw();
45              
46             our $VERSION = '0.07';
47              
48             our $debug = 0;
49              
50             my %tempfiles;
51              
52             =head1 METHODS
53              
54             The C class defines the following methods:
55              
56             =over 4
57              
58             =item new ()
59              
60             Create a new C object. No file processing is
61             done at this point.
62              
63             =cut
64              
65             sub new
66             {
67 1     1 1 771 my $proto = shift;
68 1   33     8 my $class = ref $proto || $proto;
69 1         2 my $self;
70              
71 1         6 $self = {
72             fname => undef,
73             f => undef,
74             err => undef,
75             debug => undef
76             };
77 1         3 bless $self, $class;
78 1         6 $tempfiles{$self} = $self;
79 1         3 return $self;
80             }
81              
82             =item err ( [MESSAGE] )
83              
84             Set or obtain an error message describing the last error that occurred
85             during the processing of the current C object.
86              
87             =cut
88              
89             sub err($ $)
90             {
91 0     0 1 0 my ($self, $err) = @_;
92              
93 0 0       0 $self->{err} = $err if @_ > 1;
94 0         0 return $self->{err};
95             }
96              
97             =item diag ([FLAG])
98              
99             Set or obtain the diagnostic output flag. If it is set, the methods
100             will display diagnostic information on the standard error stream.
101              
102             =cut
103              
104             sub diag($ $)
105             {
106 3     3 1 902 my ($self, $debug) = @_;
107              
108 3 100       10 $self->{debug} = $debug if @_ > 1;
109 3         9 return $self->{debug};
110             }
111              
112             =item begin_work (FILENAME)
113              
114             Creates a temporary file in the same directory as the specified one and
115             copies the original's contents over to the new file. Further data may
116             be added using the C method and then either stored as the
117             original with the C method, or discarded with the C
118             method.
119              
120             =cut
121              
122             sub begin_work($ $)
123             {
124 3     3 1 810 my ($self, $fname) = @_;
125 3         3 my ($orig, $f);
126 0         0 my @stat;
127              
128 3 50       8 if ($self->{f}) {
129 0 0       0 return undef unless $self->rollback();
130             }
131 3         7 $self->{fname} = $self->{f} = undef;
132              
133 3 50       86 if (!open $orig, '<', $fname) {
134 0         0 $self->err("Opening $fname: $!");
135 0         0 return undef;
136             }
137 3         24 @stat = stat $orig;
138 3         173 $f = File::Temp->new(basename($fname).'.XXXXXX',
139             DIR => dirname($fname));
140 3 50       1041 if (!defined $f) {
141 0         0 $self->err("Creating a temporary file for $fname: $!");
142 0         0 return undef;
143             }
144 3 50       8 return undef unless $self->do_copy($orig, $f);
145 3         20 close $orig;
146              
147 3         8 $self->{fname} = $fname;
148 3         5 $self->{f} = $f;
149 3         9 $self->{stat} = [ @stat ];
150 3         21 return 1;
151             }
152              
153             =item add_line (DATA)
154              
155             Append data to the temporary file. This does not affect the original in
156             any way until C is invoked.
157              
158             =cut
159              
160             sub add_line($ $)
161             {
162 4     4 1 1009 my ($self, $line) = @_;
163 4         7 my $f = $self->{f};
164              
165 4 50       10 if (!defined $f) {
166 0         0 $self->err("Cannot add_line() to an unopened tempfile");
167 0         0 return undef;
168             }
169 4         14 $self->debug("RDBG about to add a line to $f for $self->{fname}\n");
170 4 50       13 if (!(print $f $line)) {
171 0         0 $self->err("Could not add to the tempfile: $!");
172 0         0 return undef;
173             }
174 4         14 return 1;
175             }
176              
177             =item commit ()
178              
179             Replace the original file with the temporary copy, to which data may have
180             been added using C.
181              
182             B This method uninitializes the C object,
183             that is, removes B association between it and the original file and
184             even file name! The next method invoked on this C
185             object should be C.
186              
187             =cut
188              
189             sub commit($)
190             {
191 1     1 1 235 my ($self) = @_;
192 1         2 my $f = $self->{f};
193              
194 1 50 33     7 if (!defined $f || !defined $self->{fname}) {
195 0         0 $self->err("Cannot commit an unopened tempfile");
196 0         0 return undef;
197             }
198 1         3 $self->debug("RDBG about to commit $f to $self->{fname}\n");
199              
200             # Fix stuff up
201 1 50       5 if (defined $self->{stat}) {
202             # Mode
203 1 50       22 if (!chmod $self->{stat}->[2], $f) {
204 0         0 $self->err("Could not chmod $self->{stat}->[2] ".
205             "$f: $!");
206 0         0 return undef;
207             }
208             # Owner & group
209 1 50       20 if (!chown $self->{stat}->[4], $self->{stat}->[5], $f) {
210 0         0 $self->err("Could not chown $self->{stat}->[4], ".
211             "$self->{stat}->[5], $f: $!");
212 0         0 return undef;
213             }
214             }
215            
216 1 50       4 if (!rename $f, $self->{fname}) {
217 0         0 $self->err("Renaming $f to $self->{fname}: $!");
218 0         0 return undef;
219             }
220 1         91 $f->unlink_on_destroy(0);
221 1         38 close $f;
222 1         5 $self->debug("RDBG successfully committed $f to $self->{fname}\n");
223 1         3 $self->{fname} = $self->{f} = undef;
224 1         3 return 1;
225             }
226              
227             =item rollback ()
228              
229             Discard all the changes made to the temporary copy and remove it. This
230             does not affect the original file in any way.
231              
232             B This method uninitializes the C object,
233             that is, removes B association between it and the original file and
234             even file name! The next method invoked on this C
235             object should be C.
236              
237             =cut
238              
239             sub rollback($)
240             {
241 2     2 1 255 my ($self) = @_;
242              
243 2         9 $self->debug(ref($self)."->rollback() for $self->{fname}\n");
244 2 50       7 if (defined $self->{f}) {
245 2         4 my $f = $self->{f};
246 2         6 $self->debug("RDBG closing and removing $f\n");
247 2         7 $f->unlink_on_destroy(1);
248 2         99 close $f;
249 2         9 undef $self->{f};
250             }
251 2         336 undef $self->{fname};
252 2         6 $self->debug("RDBG rollback seems complete\n");
253 2         9 return 1;
254             }
255              
256             =back
257              
258             There are also several methods used internally by the
259             C routines:
260              
261             =over 4
262              
263             =item debug (MESSAGE)
264              
265             Display a diagnostic message to the standard error stream if the output
266             of diagnostic messages has been enabled.
267              
268             =cut
269              
270             sub debug($ $)
271             {
272 12     12 1 82 my ($self, $msg) = @_;
273              
274 12 50 33     52 if ($self->{debug} || $debug) {
275 0         0 print STDERR $msg;
276             }
277             }
278              
279             =item do_copy (ORIG TEMP)
280              
281             Actually perform the copying of the original file data into the temporary
282             file at C time. This allows derived classes to modify
283             the file structure if needed.
284              
285             The two parameters are the file handles for the original and the
286             temporary file.
287              
288             =cut
289              
290             sub do_copy($ $ $)
291             {
292 3     3 1 6 my ($self, $orig, $f) = @_;
293            
294 3         30 while (<$orig>) {
295 8         36 print $f $_;
296             }
297 3         11 return 1;
298             }
299              
300             END
301             {
302 1 50   1   270 print STDERR "RDBG File::Append::TempFile END block\n" if $debug;
303 1 50       4 print STDERR "RDBG ".keys(%tempfiles)."\n" if $debug;
304 1         4 foreach (keys %tempfiles) {
305 1 50       5 $tempfiles{$_}->rollback() if $tempfiles{$_}->{f};
306             }
307             }
308              
309             =back
310              
311             =head1 SEE ALSO
312              
313             The C website:
314              
315             http://devel.ringlet.net/sysutils/file-append-tempfile/
316              
317             =head1 BUGS
318              
319             =over 4
320              
321             =item * Note that the original file may have changed between C
322             and C - those changes B be lost!
323              
324             =back
325              
326             =head1 AUTHOR
327              
328             Peter Pentchev, Eroam@ringlet.netE
329              
330             =head1 COPYRIGHT AND LICENSE
331              
332             Copyright (C) 2006, 2015 Peter Pentchev.
333              
334             This library is free software; you can redistribute it and/or modify
335             it under the same terms as Perl itself, either Perl version 5.8.7 or,
336             at your option, any later version of Perl 5 you may have available.
337              
338             =cut
339              
340             1;