#! /usr/bin/perl # This file is maintained at http://git.mdcc.cx/draai # oggsymlinks - maintain a symlink forest # Copyright (C) 2008 Wessel Dankers # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use strict; use warnings; use utf8; use Carp; my $dot = ord '.'; my $slash = ord '/'; my $verbose = $ENV{'DR_VERBOSE'}; my $debug = $ENV{'DR_DEBUG'}; my $dryrun = $ENV{'DR_DRYRUN'}; my $target = shift @ARGV; my %target; opendir TARGET, $target or croak "Can't open $target: $!"; while(my $artist = readdir TARGET) { next if ord($artist) == $dot; my $dir = "$target/$artist"; my %albums; unless(opendir ARTIST, $dir) { my $err = $!; unless(-d $dir) { carp "$dir is not a directory, skipping"; undef $target{$artist}; next; } croak "Can't open $dir: $err"; } while(my $album = readdir ARTIST) { next if ord($album) == $dot; my $link = "$dir/$album"; my $dest = readlink $link; $albums{$album} = $dest; unless(defined $dest) { my $err = $!; if(!-l $link) { carp "$link is not a symlink, skipping"; } else { carp "Can't read symlink $link: $err"; } next; } } $target{$artist} = \%albums; } closedir TARGET; sub path { my ($dir, $path) = @_; return $path if ord($path) == $slash; return "$dir/$path"; } my %source; foreach my $source (@ARGV) { # strip multiple/trailing slashes $source =~ s|//+|/|g; $source =~ s|(.)/$|$1|; my $psource = path($target, $source); opendir SOURCE, $psource or croak "Can't open $psource: $!"; while(my $artist = readdir SOURCE) { next if ord($artist) == $dot; my $dir = "$source/$artist"; my $pdir = "$psource/$artist"; my $dude; $source{$artist} = {} unless exists $source{$artist}; $dude = $source{$artist}; my %albums; unless(opendir ARTIST, $pdir) { my $err = $!; unless(-d $pdir) { carp "$pdir is not a directory, skipping"; next; } croak "Can't open $pdir: $!"; } while(my $album = readdir ARTIST) { print "Inspecting $source, $artist, $album\n" if $debug; next if ord($album) == $dot; $dude->{$album} = $source; } } closedir SOURCE; } # remove redundant/bad symlinks and directories while(my ($artist, $tartist) = each(%target)) { next unless defined $tartist; my $sartist = $source{$artist}; while(my ($album, $link) = each(%$tartist)) { next unless defined $link; my $to = "$target/$artist/$album"; if(defined $sartist) { my $src = $sartist->{$album}; next if defined $src and $link eq path('..', "$src/$artist/$album"); } if($verbose) { print "Would run: " if $dryrun; print "unlink $to\n"; } unless($dryrun) { unlink $to or croak "Can't create remove $to: $!"; } delete $tartist->{$album}; } next if defined $sartist; next if keys %$tartist; my $dir = path($target, $artist); if($verbose) { print "Would run: " if $dryrun; print "rmdir $dir\n"; } unless($dryrun) { rmdir $dir or warn "Can't rmdir $dir: $!"; } } # create missing directories and symlinks while(my ($artist, $sartist) = each(%source)) { my $tartist; if(exists $target{$artist}) { $tartist = $target{$artist}; next unless defined $tartist; } else { my $dir = path($target, $artist); if($verbose) { print "Would run: " if $dryrun; print "mkdir $dir\n"; } unless($dryrun) { mkdir $dir or croak "Can't mkdir $dir: $!"; } $tartist = {}; } while(my ($album, $src) = each(%$sartist)) { print "Checking for missing symlinks for $src, $artist, $album\n" if $debug; next if exists $tartist->{$album}; my $from = path('..', "$src/$artist/$album"); my $to = "$target/$artist/$album"; if($verbose) { print "Would run: " if $dryrun; print "symlink $from, $to\n"; } unless($dryrun) { symlink $from, $to or croak "Can't create symlink to $to at $from: $!"; } } }