File Coverage

blib/lib/File/Trash/Undoable.pm
Criterion Covered Total %
statement 69 96 71.8
branch 27 54 50.0
condition 5 13 38.4
subroutine 8 11 72.7
pod 5 5 100.0
total 114 179 63.6


line stmt bran cond sub pod time code
1             package File::Trash::Undoable;
2              
3             our $DATE = '2017-07-11'; # DATE
4             our $VERSION = '0.22'; # VERSION
5              
6 1     1   1398 use 5.010001;
  1         6  
7 1     1   5 use strict;
  1         2  
  1         16  
8 1     1   4 use warnings;
  1         2  
  1         24  
9 1     1   5 use Log::ger;
  1         2  
  1         6  
10              
11 1     1   1489 use File::MoreUtil qw(l_abs_path);
  1         380  
  1         51  
12 1     1   382 use File::Trash::FreeDesktop;
  1         3212  
  1         863  
13              
14             our %SPEC;
15              
16             $SPEC{':package'} = {
17             v => 1.1,
18             summary => 'Trash files, with undo/redo capability',
19             };
20              
21             my $trash = File::Trash::FreeDesktop->new;
22              
23             $SPEC{trash} = {
24             v => 1.1,
25             name => 'trash',
26             summary => 'Trash a file',
27             args => {
28             path => {
29             schema => 'str*',
30             req => 1,
31             },
32             suffix => {
33             schema => 'str',
34             },
35             },
36             description => <<'_',
37              
38             Fixed state: path does not exist.
39              
40             Fixable state: path exists.
41              
42             _
43             features => {
44             tx => {v=>2},
45             idempotent => 1,
46             },
47             };
48             sub trash {
49 66     66 1 1389306 my %args = @_;
50              
51             # TMP, SCHEMA
52 66   50     425 my $tx_action = $args{-tx_action} // "";
53 66         236 my $dry_run = $args{-dry_run};
54 66         178 my $path = $args{path};
55 66 50       232 defined($path) or return [400, "Please specify path"];
56 66         173 my $suffix = $args{suffix};
57              
58 66         1134 my @st = lstat($path);
59 66   66     601 my $exists = (-l _) || (-e _);
60              
61 66         186 my (@do, @undo);
62              
63 66 100       218 if (defined $suffix) {
64 46 100       236 if ($tx_action eq 'check_state') {
    50          
65 24 100       104 if ($exists) {
66 22         152 unshift @undo, [untrash => {path=>$path, suffix=>$suffix}];
67             }
68 24 100       99 if (@undo) {
69 22 50       83 log_info("(DRY) Trashing $path ...") if $dry_run;
70 22         848 return [200, "File/dir $path should be trashed",
71             undef, {undo_actions=>\@undo}];
72             } else {
73 2         57 return [304, "File/dir $path already does not exist"];
74             }
75             } elsif ($tx_action eq 'fix_state') {
76 22         193 log_info("Trashing $path ...");
77 22         113 my $tfile;
78 22         57 eval { $tfile = $trash->trash({suffix=>$suffix}, $path) };
  22         192  
79 22 50       47804 return $@ ? [500, "trash() failed: $@"] : [200, "OK", $tfile];
80             }
81 0         0 return [400, "Invalid -tx_action"];
82             } else {
83             my $taid = $args{-tx_action_id}
84 20 100       105 or return [412, "Please specify -tx_action_id"];
85 17         72 $suffix = substr($taid, 0, 8);
86 17 100       73 if ($exists) {
87 14         96 push @do , [trash => {path=>$path, suffix=>$suffix}];
88 14         77 unshift @undo, [untrash => {path=>$path, suffix=>$suffix}];
89             }
90 17 100       70 if (@undo) {
91 14 50       48 log_info("(DRY) Trashing $path (suffix $suffix) ...") if $dry_run;
92 14         473 return [200, "", undef, {do_actions=>\@do, undo_actions=>\@undo}];
93             } else {
94 3         72 return [304, "File/dir $path already does not exist"];
95             }
96             }
97             }
98              
99             $SPEC{untrash} = {
100             v => 1.1,
101             summary => 'Untrash a file',
102             description => <<'_',
103              
104             Fixed state: path exists.
105              
106             Fixable state: Path does not exist (and exists in trash, and if suffix is
107             specified, has the same suffix).
108              
109             _
110             args => {
111             path => {
112             schema => 'str*',
113             req => 1,
114             },
115             suffix => {
116             schema => 'str',
117             },
118             },
119             features => {
120             tx => {v=>2},
121             idempotent => 1,
122             },
123             };
124             sub untrash {
125 32     32 1 414705 my %args = @_;
126              
127             # TMP, SCHEMA
128 32   50     184 my $tx_action = $args{-tx_action} // "";
129 32         97 my $dry_run = $args{-dry_run};
130 32         111 my $path0 = $args{path};
131 32 50       129 defined($path0) or return [400, "Please specify path"];
132 32         123 my $suffix = $args{suffix};
133              
134 32         161 my $apath = l_abs_path($path0);
135 32         1308 my @st = lstat($apath);
136 32   33     233 my $exists = (-l _) || (-e _);
137              
138 32 100       170 if ($tx_action eq 'check_state') {
    50          
139              
140 16         38 my @undo;
141 16 50       53 return [304, "Path $path0 already exists"] if $exists;
142              
143 16         223 my @res = $trash->list_contents({
144             search_path=>$apath, suffix=>$suffix});
145 16 50       79379 return [412, "File/dir $path0 does not exist in trash"] unless @res;
146 16         112 unshift @undo, [trash => {path => $apath, suffix=>$suffix}];
147 16 50       74 log_info("(DRY) Untrashing $path0 ...") if $dry_run;
148 16         588 return [200, "File/dir $path0 should be untrashed",
149             undef, {undo_actions=>\@undo}];
150              
151             } elsif ($tx_action eq 'fix_state') {
152 16         138 log_info("Untrashing $path0 ...");
153 16         93 eval { $trash->recover({suffix=>$suffix}, $apath) };
  16         141  
154 16 50       68205 return $@ ? [500, "untrash() failed: $@"] : [200, "OK"];
155             }
156 0           [400, "Invalid -tx_action"];
157             }
158              
159             $SPEC{trash_files} = {
160             v => 1.1,
161             summary => 'Trash files (with undo support)',
162             args => {
163             files => {
164             summary => 'Files/dirs to delete',
165             description => <<'_',
166              
167             Files must exist.
168              
169             _
170             schema => ['array*' => {of=>'str*'}],
171             req => 1,
172             pos => 0,
173             greedy => 1,
174             },
175             },
176             features => {
177             tx => {v=>2},
178             idempotent => 1,
179             },
180             };
181             sub trash_files {
182 0     0 1   my %args = @_;
183              
184             # TMP, SCHEMA
185 0           my $dry_run = $args{-dry_run};
186 0           my $ff = $args{files};
187 0 0         $ff or return [400, "Please specify files"];
188 0 0         ref($ff) eq 'ARRAY' or return [400, "Files must be array"];
189 0 0         @$ff > 0 or return [400, "Please specify at least 1 file"];
190              
191 0           my (@do, @undo);
192 0           for (@$ff) {
193 0 0         my @st = lstat($_) or return [400, "Can't stat $_: $!"];
194 0 0 0       (-l _) || (-e _) or return [400, "File does not exist: $_"];
195 0           my $orig = $_;
196 0           $_ = l_abs_path($_);
197 0 0         $_ or return [400, "Can't convert to absolute path: $orig"];
198 0 0         log_info("(DRY) Trashing %s ...", $orig) if $dry_run;
199 0           push @do , [trash => {path=>$_}];
200 0           unshift @undo, [untrash => {path=>$_, mtime=>$st[9]}];
201             }
202              
203 0           return [200, "", undef, {do_actions=>\@do, undo_actions=>\@undo}];
204             }
205              
206             $SPEC{list_trash_contents} = {
207             v => 1.1,
208             summary => 'List contents of trash directory',
209             };
210             sub list_trash_contents {
211 0     0 1   my %args = @_;
212 0           [200, "OK", [$trash->list_contents]];
213             }
214              
215             $SPEC{empty_trash} = {
216             v => 1.1,
217             summary => 'Empty trash',
218             };
219             sub empty_trash {
220 0     0 1   my %args = @_;
221 0           my $cmd = $args{-cmdline};
222              
223 0           $trash->empty;
224 0 0         if ($cmd) {
225 0           $cmd->run_clear_history;
226             } else {
227 0           [200, "OK"];
228             }
229             }
230              
231             1;
232             # ABSTRACT: Trash files, with undo/redo capability
233              
234             __END__
235              
236             =pod
237              
238             =encoding UTF-8
239              
240             =head1 NAME
241              
242             File::Trash::Undoable - Trash files, with undo/redo capability
243              
244             =head1 VERSION
245              
246             This document describes version 0.22 of File::Trash::Undoable (from Perl distribution File-Trash-Undoable), released on 2017-07-11.
247              
248             =head1 SYNOPSIS
249              
250             # use the trash-u script
251              
252             =head1 DESCRIPTION
253              
254             This module provides routines to trash files, with undo/redo support. Actual
255             trashing/untrashing is provided by L<File::Trash::FreeDesktop>.
256              
257             Screenshots:
258              
259             =head1 FUNCTIONS
260              
261              
262             =head2 empty_trash
263              
264             Usage:
265              
266             empty_trash() -> [status, msg, result, meta]
267              
268             Empty trash.
269              
270             This function is not exported.
271              
272             No arguments.
273              
274             Returns an enveloped result (an array).
275              
276             First element (status) is an integer containing HTTP status code
277             (200 means OK, 4xx caller error, 5xx function error). Second element
278             (msg) is a string containing error message, or 'OK' if status is
279             200. Third element (result) is optional, the actual result. Fourth
280             element (meta) is called result metadata and is optional, a hash
281             that contains extra information.
282              
283             Return value: (any)
284              
285              
286             =head2 list_trash_contents
287              
288             Usage:
289              
290             list_trash_contents() -> [status, msg, result, meta]
291              
292             List contents of trash directory.
293              
294             This function is not exported.
295              
296             No arguments.
297              
298             Returns an enveloped result (an array).
299              
300             First element (status) is an integer containing HTTP status code
301             (200 means OK, 4xx caller error, 5xx function error). Second element
302             (msg) is a string containing error message, or 'OK' if status is
303             200. Third element (result) is optional, the actual result. Fourth
304             element (meta) is called result metadata and is optional, a hash
305             that contains extra information.
306              
307             Return value: (any)
308              
309              
310             =head2 trash
311              
312             Usage:
313              
314             trash(%args) -> [status, msg, result, meta]
315              
316             Trash a file.
317              
318             Fixed state: path does not exist.
319              
320             Fixable state: path exists.
321              
322             This function is not exported.
323              
324             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
325              
326              
327             Arguments ('*' denotes required arguments):
328              
329             =over 4
330              
331             =item * B<path>* => I<str>
332              
333             =item * B<suffix> => I<str>
334              
335             =back
336              
337             Special arguments:
338              
339             =over 4
340              
341             =item * B<-tx_action> => I<str>
342              
343             For more information on transaction, see L<Rinci::Transaction>.
344              
345             =item * B<-tx_action_id> => I<str>
346              
347             For more information on transaction, see L<Rinci::Transaction>.
348              
349             =item * B<-tx_recovery> => I<str>
350              
351             For more information on transaction, see L<Rinci::Transaction>.
352              
353             =item * B<-tx_rollback> => I<str>
354              
355             For more information on transaction, see L<Rinci::Transaction>.
356              
357             =item * B<-tx_v> => I<str>
358              
359             For more information on transaction, see L<Rinci::Transaction>.
360              
361             =back
362              
363             Returns an enveloped result (an array).
364              
365             First element (status) is an integer containing HTTP status code
366             (200 means OK, 4xx caller error, 5xx function error). Second element
367             (msg) is a string containing error message, or 'OK' if status is
368             200. Third element (result) is optional, the actual result. Fourth
369             element (meta) is called result metadata and is optional, a hash
370             that contains extra information.
371              
372             Return value: (any)
373              
374              
375             =head2 trash_files
376              
377             Usage:
378              
379             trash_files(%args) -> [status, msg, result, meta]
380              
381             Trash files (with undo support).
382              
383             This function is not exported.
384              
385             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
386              
387              
388             Arguments ('*' denotes required arguments):
389              
390             =over 4
391              
392             =item * B<files>* => I<array[str]>
393              
394             Files/dirs to delete.
395              
396             Files must exist.
397              
398             =back
399              
400             Special arguments:
401              
402             =over 4
403              
404             =item * B<-tx_action> => I<str>
405              
406             For more information on transaction, see L<Rinci::Transaction>.
407              
408             =item * B<-tx_action_id> => I<str>
409              
410             For more information on transaction, see L<Rinci::Transaction>.
411              
412             =item * B<-tx_recovery> => I<str>
413              
414             For more information on transaction, see L<Rinci::Transaction>.
415              
416             =item * B<-tx_rollback> => I<str>
417              
418             For more information on transaction, see L<Rinci::Transaction>.
419              
420             =item * B<-tx_v> => I<str>
421              
422             For more information on transaction, see L<Rinci::Transaction>.
423              
424             =back
425              
426             Returns an enveloped result (an array).
427              
428             First element (status) is an integer containing HTTP status code
429             (200 means OK, 4xx caller error, 5xx function error). Second element
430             (msg) is a string containing error message, or 'OK' if status is
431             200. Third element (result) is optional, the actual result. Fourth
432             element (meta) is called result metadata and is optional, a hash
433             that contains extra information.
434              
435             Return value: (any)
436              
437              
438             =head2 untrash
439              
440             Usage:
441              
442             untrash(%args) -> [status, msg, result, meta]
443              
444             Untrash a file.
445              
446             Fixed state: path exists.
447              
448             Fixable state: Path does not exist (and exists in trash, and if suffix is
449             specified, has the same suffix).
450              
451             This function is not exported.
452              
453             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
454              
455              
456             Arguments ('*' denotes required arguments):
457              
458             =over 4
459              
460             =item * B<path>* => I<str>
461              
462             =item * B<suffix> => I<str>
463              
464             =back
465              
466             Special arguments:
467              
468             =over 4
469              
470             =item * B<-tx_action> => I<str>
471              
472             For more information on transaction, see L<Rinci::Transaction>.
473              
474             =item * B<-tx_action_id> => I<str>
475              
476             For more information on transaction, see L<Rinci::Transaction>.
477              
478             =item * B<-tx_recovery> => I<str>
479              
480             For more information on transaction, see L<Rinci::Transaction>.
481              
482             =item * B<-tx_rollback> => I<str>
483              
484             For more information on transaction, see L<Rinci::Transaction>.
485              
486             =item * B<-tx_v> => I<str>
487              
488             For more information on transaction, see L<Rinci::Transaction>.
489              
490             =back
491              
492             Returns an enveloped result (an array).
493              
494             First element (status) is an integer containing HTTP status code
495             (200 means OK, 4xx caller error, 5xx function error). Second element
496             (msg) is a string containing error message, or 'OK' if status is
497             200. Third element (result) is optional, the actual result. Fourth
498             element (meta) is called result metadata and is optional, a hash
499             that contains extra information.
500              
501             Return value: (any)
502              
503             =for HTML <p><img src="http://blogs.perl.org/users/perlancar/screenshot-trashu.jpg" /><br />
504              
505             =head1 HOMEPAGE
506              
507             Please visit the project's homepage at L<https://metacpan.org/release/File-Trash-Undoable>.
508              
509             =head1 SOURCE
510              
511             Source repository is at L<https://github.com/perlancar/perl-File-Trash-Undoable>.
512              
513             =head1 BUGS
514              
515             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=File-Trash-Undoable>
516              
517             When submitting a bug or request, please include a test-file or a
518             patch to an existing test-file that illustrates the bug or desired
519             feature.
520              
521             =head1 SEE ALSO
522              
523             =over 4
524              
525             =item * B<gvfs-trash>
526              
527             A command-line utility, part of the GNOME project.
528              
529             =item * B<trash-cli>, https://github.com/andreafrancia/trash-cli
530              
531             A Python-based command-line application. Also follows freedesktop.org trash
532             specification.
533              
534             =item * B<rmv>, http://code.google.com/p/rmv/
535              
536             A bash script. Features undo ("rollback"). At the time of this writing, does not
537             support per-filesystem trash (everything goes into home trash).
538              
539             =back
540              
541             =head1 AUTHOR
542              
543             perlancar <perlancar@cpan.org>
544              
545             =head1 COPYRIGHT AND LICENSE
546              
547             This software is copyright (c) 2017, 2016, 2015, 2014, 2013, 2012 by perlancar@cpan.org.
548              
549             This is free software; you can redistribute it and/or modify it under
550             the same terms as the Perl 5 programming language system itself.
551              
552             =cut