package PathsTree; use strict; use dir_node qw(:all); use Cwd qw(cwd abs_path getcwd); use File::Basename qw(fileparse); use File::Spec::Functions qw(file_name_is_absolute splitdir catfile catdir); sub new { my ($class) = @_; my $self = { _root_node => new DirNode('/',"dir") }; bless $self, $class; return $self; } sub display_tree { my ($self) = @_; return $self->{"_root_node"}->display_tree("-"); } sub get_links { my ($self) = @_; return $self->{"_root_node"}->get_links(); } sub get_files { my ($self) = @_; return $self->{"_root_node"}->get_files(); } sub get_dirs { my ($self) = @_; return $self->{"_root_node"}->get_dirs(); } sub get_node_by_path { my ($self,$path) = @_; my @subpaths = splitdir($path); my $prev_node = $self->{_root_node}; for my $index (0..$#subpaths) { next if (@subpaths[$index] eq ""); my $current_node_name = @subpaths[$index]; my $current_child = $prev_node->get_child_node_by_name(@subpaths[$index]); unless ($current_child) { return undef; } $prev_node = $current_child; } return $prev_node; } sub add_path { my ($self,$path) = @_; my @subpaths = splitdir($path); my $prev_node = $self->{_root_node}; for my $index (0..$#subpaths) { next if (@subpaths[$index] eq ""); # Ignore empty strings my $current_path = catdir(@subpaths[0..$index]); my $current_node_name = @subpaths[$index]; if (-l $current_path) { my @resloved_links = resolve_symlink($current_path); foreach my $link (@resloved_links) { if ($link eq $current_path) { next; } $self->add_path($link); } my $current_link_target = readlink($current_path); my $child_node = $self->get_node_by_path($current_path); if ($child_node) { $prev_node = $child_node; } else { $child_node = $prev_node->add_child_link_node($current_node_name,$current_link_target,"regular_link"); $prev_node = $child_node; } $prev_node = $self->get_node_by_path($current_link_target); } elsif (-f $current_path) { my $child_node = $self->get_node_by_path($current_path); if ($child_node) { $prev_node = $child_node; } else { $child_node = $prev_node->add_child_node($current_node_name,"file"); $prev_node = $child_node; } } elsif (-d $current_path) { my $child_node = $self->get_node_by_path($current_path); if ($child_node) { $prev_node = $child_node; } else { $child_node = $prev_node->add_child_node($current_node_name,"dir"); $prev_node = $child_node; } } else { log("Ignoring path with unknown type: $current_path"); } } } # TODO: Move to a separated file sub resolve_symlink { my ($file) = @_; unless (file_name_is_absolute($file)) { log("Failed to resolve link $file"); return; } my @files; my $origwd = getcwd; my $rv = eval { my $f = $file; while (1) { my $dir; ($f,$dir) = fileparse($f); last unless (-d $dir); chdir $dir or die "chdir $dir: $!"; push @files, catfile(getcwd,$f); last unless (-l $f); defined( $f = readlink $f ) or die "readlink $f (cwd=".getcwd."): $!"; } 1 }; my $err = $@ || 'unknown error'; chdir $origwd or (log("Failed to chdir $origwd: $!") && return); die $err unless ($rv); return @files ? @files : ($file); } 1;