File Coverage

blib/lib/File/Prepend/Undoable.pm
Criterion Covered Total %
statement 52 53 98.1
branch 22 34 64.7
condition 10 14 71.4
subroutine 6 6 100.0
pod 1 1 100.0
total 91 108 84.2


line stmt bran cond sub pod time code
1             package File::Prepend::Undoable;
2              
3             our $DATE = '2017-07-10'; # DATE
4             our $VERSION = '0.05'; # VERSION
5              
6 1     1   37710 use 5.010001;
  1         6  
7 1     1   5 use strict;
  1         2  
  1         18  
8 1     1   5 use warnings;
  1         2  
  1         23  
9 1     1   2630 use Log::ger;
  1         73  
  1         5  
10              
11 1     1   1531 use File::Trash::Undoable;
  1         10135  
  1         449  
12              
13             our %SPEC;
14              
15             $SPEC{prepend} = {
16             v => 1.1,
17             summary => 'Prepend string to a file, with undo support',
18             description => <<'_',
19              
20             On do, will trash file, copy it to original location (with the same permission
21             and ownership as the original) and prepend the string to the beginning of file.
22             On undo will trash the new file and untrash the original.
23              
24             Some notes:
25              
26             * Chown will not be done if we are not running as root.
27              
28             * Symlink is currently not permitted.
29              
30             * Since transaction requires the function to be idempotent, in the `check_state`
31             phase the function will check if the string has been prepended. It will refuse
32             to prepend the same string twice.
33              
34             * Take care not to use string that are too large, as the string, being a
35             function parameter, is entered into the transaction table.
36              
37             Fixed state: file exists and string has been prepended to beginning of file.
38              
39             Fixable state: file exists and string has not been prepended to beginning of
40             file.
41              
42             Unfixable state: file does not exist or path is not a regular file (directory
43             and symlink included).
44              
45             _
46             args => {
47             path => {
48             summary => 'The file to prepend',
49             schema => 'str*',
50             req => 1,
51             pos => 0,
52             },
53             string => {
54             summary => 'The string to prepend to file',
55             req => 1,
56             pos => 1,
57             },
58             },
59             features => {
60             tx => {v=>2},
61             idempotent => 1,
62             },
63             };
64             sub prepend {
65 28     28 1 4746040 my %args = @_;
66              
67             # TMP, schema
68 28   50     211 my $tx_action = $args{-tx_action} // '';
69             my $taid = $args{-tx_action_id}
70 28 100       189 or return [400, "Please specify -tx_action_id"];
71 23         90 my $dry_run = $args{-dry_run};
72 23         131 my $path = $args{path};
73 23 50       193 defined($path) or return [400, "Please specify path"];
74 23 50       118 defined($args{string}) or return [400, "Please specify string"];
75 23         90 my $string = "$args{string}";
76              
77 23         382 my $is_sym = (-l $path);
78 23         292 my @st = stat($path);
79 23   100     299 my $exists = $is_sym || (-e _);
80 23         93 my $is_file = (-f _);
81 23         81 my $size = (-s _);
82              
83 23 100       139 if ($tx_action eq 'check_state') {
    50          
84 14 100       100 return [412, "File $path does not exist"] unless $exists;
85 13 100 100     292 return [412, "File $path is not a regular file"] if $is_sym||!$is_file;
86 11 50       57 return [500, "File $path can't be stat'd"] unless @st;
87              
88 11 50       59 if ($size >= length($string)) {
89 11         28 my $buf;
90 11 50       369 open my($fh),"<",$path or return [500, "Can't open file $path: $!"];
91 11         274 read $fh, $buf, length($string);
92 11 100 66     222 if (defined($buf) && $buf eq $string) {
93 2         87 return [304, "File $path already prepended with string, ".
94             "won't prepend twice"];
95             }
96             }
97 9 50       62 log_info("(DRY) Prepending string to file $path ...") if $dry_run;
98 9         469 return [200, "File $path needs to be prepended with a string", undef,
99             {undo_actions=>[
100             ['File::Trash::Undoable::untrash', # restore original
101             {path=>$path, suffix=>substr($taid,0,8)}],
102             ['File::Trash::Undoable::trash', # trash new file
103             {path=>$path, suffix=>substr($taid,0,8)."n"}],
104             ]}];
105             } elsif ($tx_action eq 'fix_state') {
106 9         110 log_info("Prepending string to file $path ...");
107 9         119 my $res = File::Trash::Undoable::trash(
108             -tx_action=>'fix_state', path=>$path, suffix=>substr($taid,0,8));
109 9 50 33     61651 return $res unless $res->[0] == 200 || $res->[0] == 304;
110 9 50       343 open my($oh), "<", $res->[2]
111             or return [500, "Can't open $res->[2] for reading: $!"];
112 9 50       538 open my($nh), ">", $path
113             or return [500, "Can't open $path for writing: $!"];
114 9         109 print $nh $string;
115 9         151 while (my $l = <$oh>) { print $nh $l }
  9         81  
116 9 50       282 close $nh or return [500, "Can't close: $!"];
117 9         269 chmod $st[2] & 07777, $path; # XXX ignore error?
118 9 50       73 unless ($>) { chown $st[4], $st[5], $path } # XXX ignore error?
  9         183  
119 9         588 return [200, "OK"];
120             }
121 0           [400, "Invalid -tx_action"];
122             }
123              
124             1;
125             # ABSTRACT: Prepend string to a file, with undo support
126              
127             __END__
128              
129             =pod
130              
131             =encoding UTF-8
132              
133             =head1 NAME
134              
135             File::Prepend::Undoable - Prepend string to a file, with undo support
136              
137             =head1 VERSION
138              
139             This document describes version 0.05 of File::Prepend::Undoable (from Perl distribution File-Prepend-Undoable), released on 2017-07-10.
140              
141             =head1 FUNCTIONS
142              
143              
144             =head2 prepend
145              
146             Usage:
147              
148             prepend(%args) -> [status, msg, result, meta]
149              
150             Prepend string to a file, with undo support.
151              
152             On do, will trash file, copy it to original location (with the same permission
153             and ownership as the original) and prepend the string to the beginning of file.
154             On undo will trash the new file and untrash the original.
155              
156             Some notes:
157              
158             =over
159              
160             =item * Chown will not be done if we are not running as root.
161              
162             =item * Symlink is currently not permitted.
163              
164             =item * Since transaction requires the function to be idempotent, in the C<check_state>
165             phase the function will check if the string has been prepended. It will refuse
166             to prepend the same string twice.
167              
168             =item * Take care not to use string that are too large, as the string, being a
169             function parameter, is entered into the transaction table.
170              
171             =back
172              
173             Fixed state: file exists and string has been prepended to beginning of file.
174              
175             Fixable state: file exists and string has not been prepended to beginning of
176             file.
177              
178             Unfixable state: file does not exist or path is not a regular file (directory
179             and symlink included).
180              
181             This function is not exported.
182              
183             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
184              
185              
186             Arguments ('*' denotes required arguments):
187              
188             =over 4
189              
190             =item * B<path>* => I<str>
191              
192             The file to prepend.
193              
194             =item * B<string>* => I<any>
195              
196             The string to prepend to file.
197              
198             =back
199              
200             Special arguments:
201              
202             =over 4
203              
204             =item * B<-tx_action> => I<str>
205              
206             For more information on transaction, see L<Rinci::Transaction>.
207              
208             =item * B<-tx_action_id> => I<str>
209              
210             For more information on transaction, see L<Rinci::Transaction>.
211              
212             =item * B<-tx_recovery> => I<str>
213              
214             For more information on transaction, see L<Rinci::Transaction>.
215              
216             =item * B<-tx_rollback> => I<str>
217              
218             For more information on transaction, see L<Rinci::Transaction>.
219              
220             =item * B<-tx_v> => I<str>
221              
222             For more information on transaction, see L<Rinci::Transaction>.
223              
224             =back
225              
226             Returns an enveloped result (an array).
227              
228             First element (status) is an integer containing HTTP status code
229             (200 means OK, 4xx caller error, 5xx function error). Second element
230             (msg) is a string containing error message, or 'OK' if status is
231             200. Third element (result) is optional, the actual result. Fourth
232             element (meta) is called result metadata and is optional, a hash
233             that contains extra information.
234              
235             Return value: (any)
236              
237             =head1 HOMEPAGE
238              
239             Please visit the project's homepage at L<https://metacpan.org/release/File-Prepend-Undoable>.
240              
241             =head1 SOURCE
242              
243             Source repository is at L<https://github.com/perlancar/perl-File-Prepend-Undoable>.
244              
245             =head1 BUGS
246              
247             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=File-Prepend-Undoable>
248              
249             When submitting a bug or request, please include a test-file or a
250             patch to an existing test-file that illustrates the bug or desired
251             feature.
252              
253             =head1 SEE ALSO
254              
255             L<Rinci::Transaction>
256              
257             =head1 AUTHOR
258              
259             perlancar <perlancar@cpan.org>
260              
261             =head1 COPYRIGHT AND LICENSE
262              
263             This software is copyright (c) 2017, 2015, 2014, 2012 by perlancar@cpan.org.
264              
265             This is free software; you can redistribute it and/or modify it under
266             the same terms as the Perl 5 programming language system itself.
267              
268             =cut