File Coverage

blib/lib/Trash/Park.pm
Criterion Covered Total %
statement 46 149 30.8
branch 1 50 2.0
condition 0 12 0.0
subroutine 14 27 51.8
pod 6 7 85.7
total 67 245 27.3


line stmt bran cond sub pod time code
1             ###########################################
2             package Trash::Park;
3             ###########################################
4 1     1   603228 use strict;
  1         2  
  1         30  
5 1     1   5 use warnings;
  1         1  
  1         33  
6              
7 1     1   5 use File::Path;
  1         7  
  1         48  
8 1     1   5 use File::Copy;
  1         2  
  1         49  
9 1     1   5 use File::Basename;
  1         1  
  1         65  
10 1     1   5 use Sysadm::Install qw(:all);
  1         2  
  1         6  
11 1     1   333 use File::Spec::Functions qw(rel2abs);
  1         1  
  1         49  
12 1     1   5 use Log::Log4perl qw(:easy);
  1         3  
  1         20  
13 1     1   641 use File::Find;
  1         2  
  1         97  
14 1     1   2420 use DBI;
  1         24637  
  1         82  
15              
16 1     1   12 use vars qw($VERSION);
  1         2  
  1         1589  
17              
18             $VERSION = "0.03";
19              
20             ###########################################
21             sub new {
22             ###########################################
23 1     1 1 1222 my($class, @options) = @_;
24              
25 1         8 my $self = {
26             trash_dir => "$ENV{HOME}/.trashpark",
27             # default expiration: 3 days
28             expire => 3600 * 24 * 3,
29             @options
30             };
31              
32 1 50       25 mkd "$self->{trash_dir}"
33             unless -d $self->{trash_dir};
34              
35 1         5 $self->{trash_idx_dir} =
36             "$self->{trash_dir}/index";
37              
38 1         3 bless $self, $class;
39              
40 1         10 DEBUG "Connecting to ",
41             $self->{trash_idx_dir};
42              
43 1         29 $self->{dbh} = DBI->connect(
44             "DBI:CSV:" .
45             "f_dir=$self->{trash_idx_dir}");
46              
47 0           $self->_db_init();
48              
49 0           return $self;
50             }
51              
52             ###########################################
53             sub clean {
54             ###########################################
55 0     0 1   my($self) = @_;
56              
57 0           return $self->expire(-1);
58             }
59              
60             ###########################################
61             sub expire {
62             ###########################################
63 0     0 1   my($self, $timespan) = @_;
64              
65 0           my $sql = qq[
66             DELETE FROM trash WHERE ? > move_time
67             ];
68              
69 0           my $exptime = time() - $timespan;
70              
71 0           DEBUG "$sql (exptime=$exptime)";
72              
73 0           cd $self->{trash_idx_dir};
74 0 0         $self->{dbh}->do($sql, {}, $exptime) or
75             LOGDIE "Delete failed ($sql)";
76 0           cdback;
77              
78 0           return 1;
79             }
80              
81             ###########################################
82             sub repo {
83             ###########################################
84 0     0 1   my($self) = @_;
85              
86 0           return "$self->{trash_dir}/repo";
87             }
88              
89             ###########################################
90             sub trash {
91             ###########################################
92 0     0 1   my($self, $item) = @_;
93              
94 0 0         if(-d $item) {
95             find(sub {
96 0 0   0     $self->trash_file($_) if -f;
97 0           }, $item);
98              
99             # Clean up symlinks, empty
100             # directories etc.
101 0           chmod 0755, $item;
102 0 0         if($self->{opts}->{i}) {
103 0           my $ans = ask "Recursively delete $item ([y]/n)?", "y";
104 0 0         if($ans !~ /y/i) {
105 0           INFO "Skipped";
106 0           return 1;
107             }
108             }
109 0           rmf $item;
110             } else {
111 0           $self->trash_file($item);
112             }
113             }
114              
115             ###########################################
116             sub trash_file {
117             ###########################################
118 0     0 0   my($self, $file) = @_;
119              
120 0 0         LOGDIE "File not found: $file ($!)" unless -f $file;
121              
122             # Make it absolute
123 0           $file = rel2abs($file);
124              
125 0           my $target =
126             "$self->{trash_dir}/repo$file";
127              
128 0           DEBUG "Moving $file to $target";
129              
130 0           my $target_dir = dirname($target);
131              
132 0 0         mkd($target_dir) unless -d $target_dir;
133              
134 0 0         if(-e $target) {
135 0           my $overwrite = ask "$target " .
136             "already exists. Overwrite? " .
137             "[y]/n", "y";
138              
139 0 0         if($overwrite !~ /y/i) {
140 0           WARN "Not deleting $file";
141 0           return;
142             }
143             }
144              
145 0           my ($dev,$ino,$mode,$nlink,$uid,$gid,
146             $rdev,$size,$atime,$mtime,$ctime,
147             $blksize,$blocks) = stat($file);
148              
149 0 0         LOGDIE "Cannot stat $file"
150             unless defined $dev;
151              
152 0 0         if($self->{opts}->{i}) {
153 0           my $ans = ask "Move $file to $target ([y]/n)?", "y";
154 0 0         if($ans !~ /y/i) {
155 0           INFO "Skipped";
156 0           return 1;
157             }
158             }
159              
160 0 0         $self->_move_with_force($file, $target) or
161             LOGDIE "Moving $file to ",
162             "$target failed ($!)";
163              
164 0           my $move_time = time();
165              
166 0           my $sql = qq[
167             INSERT INTO trash
168             (path, move_time, uid, mode)
169             VALUES (?, $move_time, $uid, $mode)
170             ];
171              
172 0           DEBUG "$sql (file=$file)";
173              
174 0           cd $self->{trash_idx_dir};
175 0 0         $self->{dbh}->do($sql, {}, $file) or
176             LOGDIE "Insert failed ($sql)";
177 0           cdback;
178             }
179              
180             ###########################################
181             sub history {
182             ###########################################
183 0     0 1   my($self, $newer_than) = @_;
184              
185 0           my @history = ();
186              
187 0           my $cond = "";
188              
189 0 0         if(defined $newer_than) {
190 0           $cond = "WHERE move_time < $newer_than";
191             }
192              
193 0           my $sql = qq{SELECT * from trash $cond};
194              
195 0           DEBUG "$sql";
196              
197 0 0         my $sth = $self->{dbh}->prepare($sql) or
198             LOGDIE $self->{dbh}->errstr();
199              
200 0           cd $self->{trash_idx_dir};
201 0           $sth->execute();
202 0           cdback;
203              
204 0           while(my $row =
205             $sth->fetchrow_arrayref()) {
206 0           my($file, $move_time,
207             $uid, $mode) = @$row;
208 0           DEBUG "Found $file, $move_time, $uid, $mode";
209              
210 0           push @history, Trash::Park::Element->new(
211             file => $file,
212             move_time => $move_time,
213             uid => $uid,
214             mode => $mode),
215             }
216              
217 0           return \@history;
218             }
219              
220             ###########################################
221             sub _move_with_force {
222             ###########################################
223 0     0     my($self, $file, $target) = @_;
224              
225 0           my $old_perms;
226 0           my $dir = dirname($file);
227              
228 0 0         if($self->_movable($file, 1)) {
229             # Move works fine if we don't have
230             # write permission on the file, but
231             # actually own the file. However, if
232             # the file is in a non- writable
233             # directory which we own, we need to
234             # change its permissions to +w first.
235              
236 0 0         if(! -w $dir) {
237 0           DEBUG "Changing $dir 's ",
238             "permissions to 0755 ",
239             "temporarily";
240 0           $old_perms = (stat($dir))[2];
241             # We try, but no big deal if it
242             # doesn't work, 'move' will catch
243             # it.
244 0           chmod 0755, $dir;
245             }
246             }
247              
248 0 0         move($file, $target) or
249             LOGDIE "Cannot move $file to ",
250             "$target ($!)";
251              
252 0 0         return 1 unless $old_perms;
253              
254 0           DEBUG "Changing $dir 's ",
255             "permissions back to ",
256             sprintf("%03o", $old_perms);
257              
258 0 0         chmod $old_perms, $dir if $old_perms;
259             }
260              
261             ###########################################
262             sub _movable {
263             ###########################################
264 0     0     my($self, $file, $force) = @_;
265              
266 0           my $dir = dirname($file);
267 0           my $d_own = (stat($dir))[4] == $>;
268 0           my $f_own = (stat($file))[4] == $>;
269 0           my $f_wr = -w $file;
270 0           my $d_wr = -w $dir;
271              
272 0 0 0       return 1 if ($f_wr or $f_own) and
      0        
      0        
273             ($d_wr or $d_own);
274              
275 0           return;
276             }
277              
278             ###########################################
279             sub _db_init {
280             ###########################################
281 0     0     my($self) = @_;
282              
283 0 0         if(! -d $self->{trash_idx_dir}) {
284 0           mkd($self->{trash_idx_dir});
285 0           cd $self->{trash_idx_dir};
286 0           DEBUG "Creating db table trash ",
287             "in $self->{trash_idx_dir}";
288              
289 0 0         $self->{dbh}->do(q{
290             CREATE TABLE trash (
291             path char(256),
292             move_time int,
293             uid int,
294             mode int,
295             )}) or die $self->{dbh}->errstr();
296              
297 0           cdback;
298             }
299             }
300              
301             ###########################################
302             package Trash::Park::Element;
303             ###########################################
304 1     1   962 use Stat::lsMode;
  1         747  
  1         50  
305 1     1   6 use base qw(Class::Accessor);
  1         2  
  1         860  
306              
307             Trash::Park::Element->mk_accessors(qw(file move_time uid mode));
308              
309             ###########################################
310             sub new {
311             ###########################################
312 0     0     my($class, @options) = @_;
313              
314             # mode, move_time, user, file coming in
315 0           my $self = { @options };
316              
317 0           bless $self, $class;
318             }
319              
320             ###########################################
321             sub as_string {
322             ###########################################
323 0     0     my($self) = @_;
324              
325 0   0       return sprintf "%s %s %10s %s",
326             scalar format_mode($self->{mode} & 07777),
327             nice_time($self->{move_time}),
328             getpwuid($self->{uid}) || $self->{uid},
329             $self->{file};
330             }
331              
332             ###########################################
333             sub nice_time {
334             ###########################################
335 0     0     my($time) = @_;
336              
337 0           my ($sec,$min,$hour,$mday,$mon,$year,
338             $wday,$yday,$isdst) = localtime($time);
339              
340 0           return sprintf
341             "%d-%02d-%02d %02d:%02d:%02d",
342             $year + 1900, $mon+1, $mday, $hour,
343             $min, $sec;
344             }
345              
346              
347             1;
348              
349             __END__