File Coverage

blib/lib/Dir/List.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Dir::List;
2              
3             # Module contains perldoc as well as inline code documentation.
4             # The best is you read the perldoc (perldoc Dir::List), look
5             # at the doc/example*.pl and if you still don't know what to
6             # do, read the source. :-)
7              
8 1     1   26345 use 5.008;
  1         4  
  1         40  
9 1     1   6 use strict;
  1         1  
  1         36  
10 1     1   5 use warnings;
  1         18  
  1         31  
11              
12             # This modules should be listed in Makefile.PL as well.
13 1     1   660 use Cache::File;
  0            
  0            
14             use Filesys::DiskUsage qw/du/;
15             use Clone qw/clone/;
16             use File::Type;
17             use Date::Format;
18             use FreezeThaw qw/safeFreeze thaw/;
19              
20             require Exporter;
21              
22             our @ISA = qw(Exporter);
23              
24             our %EXPORT_TAGS = ( 'all' => [ qw(
25            
26             ) ] );
27              
28             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
29              
30             our @EXPORT = qw(
31            
32             );
33              
34             # Set our version (comes from cvs).
35             (our $VERSION) = '$Revision: 1.4 $' =~ /([\d.]+)/;
36              
37             # Base constructor
38             sub new {
39             my $class = shift;
40             my $args = shift;
41             my $self = {};
42              
43             if($args) {
44             die "The argument supplied is not a reference; Use ->new({ exclude = [ ^iso ^pub ] }) for example." unless ref $args;
45             }
46             my @default_exclude = qw//;
47             $self->{exclude} = $args->{exclude} || \@default_exclude;
48             $self->{use_cache} = $args->{use_cache} || 0;
49             $self->{check_diskusage} = $args->{check_diskusage} || 1;
50             $self->{show_directory_owner} = $args->{show_directory_owner} || 1;
51             $self->{show_file_owner} = $args->{show_file_owner} || 1;
52             $self->{show_directory_group} = $args->{show_directory_group} || 1;
53             $self->{show_file_group} = $args->{show_file_group} || 1;
54             $self->{datetimeformat} = $args->{datetimeformat} || "%Y-%m-%d %H:%M:%S";
55             $self->{new_is_max_sec} = $args->{new_is_max_sec} || 86400 * 5;
56              
57             if($self->{use_cache}) {
58             $self->{__cache} = Cache::File->new(
59             cache_root => $args->{cache_root} || '/tmp/Dir_List',
60             default_expires => $args->{cache_expires} || '5 minutes',
61             );
62             }
63              
64             bless($self, $class);
65             return $self;
66             }
67              
68             sub dirinfo($) {
69             my $self = shift;
70             my $dir = shift;
71             delete $self->{list};
72              
73             my $ft = new File::Type;
74              
75             # Add a slash if not yet there...
76             $dir .= '/' unless $dir =~ /\/$/;
77              
78             # Check if caching is enabled and cache has been defined
79             if($self->{use_cache}) {
80             if($self->{__cache}) {
81             # Check if we cached the list allready.
82             if(my $dirinfo = $self->{__cache}->get($dir)) {
83             # We allways use safeFreeze to store complex structures
84             # to the cache, so thaw it first.
85             $dirinfo = \thaw($dirinfo);
86             # On ref is enough.
87             $dirinfo = $$dirinfo;
88             # Set the cached flag, so developer knows, it comes from the cache.
89             $dirinfo->{cache_info}->{cached} = 1;
90              
91             # Hmm. That's it, nothin' more to do. return the list.
92             # I love cachin'...
93             return $dirinfo;
94             }
95             }
96             }
97              
98             # Open the directory
99             if(opendir(DIR, $dir)) {
100              
101             # Read the files
102             my @files = readdir(DIR);
103              
104             # Loop through the filelist.
105             foreach(sort @files) {
106             my $excluded = 0;
107             # Run through the exclude regexes initialized at new()
108             foreach my $exclude_regex (@{$self->{exclude}}) {
109             if($_ =~ /$exclude_regex/) {
110             # If it matches...
111             $excluded = 1;
112             }
113             }
114             # Skip it.
115             next if $excluded;
116              
117             # Also ignore the current directory '.' and the parent directory;
118             next if $_ eq '.';
119             if($_ eq '..') {
120             $self->{has_parent} = 1;
121             next;
122             } else {
123             $self->{has_parent} = 0;
124             }
125              
126             # At this point we have excluded (hidden) all files that we don't want to show and
127             # we skipped the current directory...
128              
129              
130             # Check if the "file" is a directory
131             if(-d "$dir$_") {
132             my($retval, $size);
133              
134             # Bad hack to check if the directory is accessible
135             open(TST, "pushd $dir$_ >/dev/null 2>&1; RETVAL=\$?; echo \$RETVAL|");
136             $retval = ;
137             close(TST);
138              
139             # Retval 1 means, there was some error; Normally a "permission denied"
140             if($retval == 1) {
141             # Set size to unknown, as we cannot gather the diskusage.
142             $self->{list}->{dirs}->{$_}->{size} = "Unknown";
143             # Set the inaccessible flag
144             $self->{list}->{dirs}->{$_}->{inaccessible} = 1;
145             } else {
146             # Calculate the diskusage using File::DiskUsage du function, which is simliar to the unix command 'du'
147             $self->{list}->{dirs}->{$_}->{size} = du({ 'human-readable' => 1 }, "$dir$_") || "Unknown";
148             # Set the inaccessible flag to 0, as this directory is not inaccessible
149             $self->{list}->{dirs}->{$_}->{inaccessible} = 0;
150             }
151             # Get the uid/gid fomr the directory
152             $self->{list}->{dirs}->{$_}->{uid} = $self->getuid("$dir$_");
153             $self->{list}->{dirs}->{$_}->{gid} = $self->getgid("$dir$_");
154              
155             # Gather user/group if the developer want's us to
156             if($self->{show_directory_owner}) {
157             $self->{list}->{dirs}->{$_}->{userinfo} = $self->getuserinfo($self->{list}->{dirs}->{$_}->{uid});
158             }
159             if($self->{show_directory_group}) {
160             $self->{list}->{dirs}->{$_}->{groupinfo} = $self->getgroupinfo($self->{list}->{dirs}->{$_}->{gid});
161             }
162              
163             # Gather/set the last_modified
164             $self->{list}->{dirs}->{$_}->{last_modified} = $self->last_modified("$dir$_");
165              
166             # Check if this is a new directory; Based on the new_is_max_sec.
167             $self->{list}->{dirs}->{$_}->{new} = $self->is_new("$dir$_");
168             } else {
169             # Gather the size of the file... Yes, stat would tell us the size as well, but du has build
170             # in human-readable support. :-)
171             $self->{list}->{files}->{$_}->{size} = du({ 'human-readable' => 1 }, "$dir$_") || "Unknown";
172              
173             # Gather uid/gid
174             $self->{list}->{files}->{$_}->{uid} = $self->getuid("$dir$_");
175             $self->{list}->{files}->{$_}->{gid} = $self->getgid("$dir$_");
176              
177             # Gather user/group if the developer want's us to
178             if($self->{show_file_owner}) {
179             $self->{list}->{files}->{$_}->{userinfo} = $self->getuserinfo($self->{list}->{files}->{$_}->{uid});
180             }
181             if($self->{show_file_group}) {
182             $self->{list}->{files}->{$_}->{groupinfo} = $self->getgroupinfo($self->{list}->{files}->{$_}->{gid});
183             }
184              
185             # Check the mime_type
186             $self->{list}->{files}->{$_}->{system_mime_type} = $ft->mime_type("$dir$_");
187             # Check the internal type (FileLister specific)
188             $self->{list}->{files}->{$_}->{internal_type} = $self->internaltype($_);
189             # Gather/set the last_modified
190             $self->{list}->{files}->{$_}->{last_modified} = $self->last_modified("$dir$_");
191             # Check if this is a new file; Based on the new_is_max_sec.
192             $self->{list}->{files}->{$_}->{new} = $self->is_new("$dir$_");
193             }
194             }
195              
196             # Check if caching is enabled and the cache has been defined
197             if($self->{use_cache}) {
198             if($self->{__cache}) {
199             # Add some information to the cache (times)
200             my @lt = localtime(time);
201             $self->{list}->{cache_info}->{time_string} = strftime($self->{datetimeformat}, @lt);
202             $self->{list}->{cache_info}->{time_epoch} = time;
203             # Save it to the cache
204             $self->{__cache}->set($dir, safeFreeze($self->{list}));
205             }
206             }
207             # We don't need to give the caching info to the developer, if it's
208             # not the cached version...
209             delete $self->{list}->{cache_info};
210              
211             # Return the list...
212             return $self->{list};
213             } else {
214             return undef;
215             }
216             }
217              
218             # Helper function to clear the cache (not used internal, developer's may use this)
219             sub clearcache {
220             my $self = shift;
221             if($self->{__cache}) {
222             $self->{__cache}->clear();
223             }
224             }
225              
226             # Helper function to remove an entry from the cache (not used internal, developer's may use this)
227             sub remove_from_cache($) {
228             my $self = shift;
229             my $arg = shift;
230              
231             if($self->{__cache}) {
232             $self->{__cache}->remove($arg);
233             }
234             }
235              
236             # Helper function to retrieve the uid from a file/directory
237             sub getuid($) {
238             my $self = shift;
239             my $arg = shift;
240             # UID is number four in stat
241             return (stat($arg))[4];
242             }
243              
244             # Helper function to retrieve the userinformation for a uid
245             sub getuserinfo($) {
246             my $self = shift;
247             my $arg = shift;
248             # If it's allready cached (within' this process/instance), don't ask the system again
249             unless(defined $self->{uid_cache}->{$arg}) {
250             my($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell,$expire) = getpwuid($arg);
251              
252             # Save the information to our current instance (caching).
253             $self->{uid_cache}->{$arg} = {
254             name => $name,
255             passwd => $passwd,
256             uid => $uid,
257             gid => $gid,
258             quota => $quota,
259             comment => $comment,
260             gcos => $gcos,
261             dir => $dir,
262             shell => $shell,
263             expire => $expire,
264             };
265             }
266             # We need to clone it, else we would get a reference to the existing hash
267             return clone($self->{uid_cache}->{$arg});
268             }
269              
270             # Helper function to retrieve the gid from a file/directory
271             sub getgid($) {
272             my $self = shift;
273             my $arg = shift;
274             return (stat($arg))[5];
275             }
276              
277             # Helper function to retrieve the groupinformation for a gid
278             sub getgroupinfo($) {
279             my $self = shift;
280             my $arg = shift;
281             # If it's allready cached (within' this process/instance), don't ask the system again
282             unless(defined $self->{gid_cache}->{$arg}) {
283             my($name,$passwd,$gid,$members) = getgrgid($arg);
284             $self->{gid_cache}->{$arg} = {
285             gid => $gid,
286             name => $name,
287             passwd => $passwd,
288             members => $members,
289             };
290             }
291             # We need to clone it, else we would get a reference to the existing hash
292             return clone($self->{gid_cache}->{$arg});
293             }
294              
295             # We have an internal list of filetypes.
296             sub internaltype($) {
297             my $self = shift;
298             my $arg = shift;
299             # Make an array containing hashes, that holds
300             # our types.
301             # This must be an array! Else it could be that .gz
302             # would override the .tar.gz regex...
303             my @types = (
304             { regex => "\.zip", type => 'zip' },
305             { regex => "\.rar", type => 'rar' },
306             { regex => "\.tgz", type => 'tgz' },
307             { regex => "\.tar.gz", type => 'tgz' },
308             { regex => "\.gz", type => 'gz' },
309             { regex => "\.tar", type => 'tar' },
310             { regex => "\.rpm", type => 'rpm' },
311             { regex => "\.pdf", type => 'pdf' },
312             { regex => "\.patch", type => 'patch' },
313             { regex => "\.patch.gz", type => 'patch' },
314             { regex => "\.sh", type => 'sh' },
315             { regex => "\.pl", type => 'pl' },
316             { regex => "\.text", type => 'txt' },
317             { regex => "\.txt", type => 'txt' },
318             { regex => "\.tex", type => 'tex' },
319             { regex => "\.iso", type => 'iso' },
320             );
321             # Loop through the types
322             foreach (@types) {
323             # If it matches, return it, we don't need to loop any longer
324             return $_->{type} if $arg =~ /$_->{regex}$/;
325             }
326             # We can get here, if no type matched... => return undef.
327             return undef;
328             }
329              
330             # Helper function, that returns a nice formated datetime.
331             sub last_modified($) {
332             my $self = shift;
333             my $arg = shift;
334             my @lt = localtime(((stat($arg)))[9]);
335             return strftime($self->{datetimeformat}, @lt);
336             }
337              
338             # Helper function, that returns 0/1; Based on new_is_max_sec and the difference
339             # between current datetime and files last_modfication datetime.
340             sub is_new($) {
341             my $self = shift;
342             my $arg = shift;
343              
344             my $filetime = (((stat($arg)))[9]);
345             return 1 if time - $filetime < $self->{new_is_max_sec};
346             return 0;
347             }
348              
349             1;
350             __END__