diff options
| -rwxr-xr-x | videomv.pl | 212 | 
1 files changed, 148 insertions, 64 deletions
| @@ -7,7 +7,7 @@  # See http://sam.zoy.org/wtfpl/COPYING for more details. -$VERSION = "0.1, 10 August 2011"; +$VERSION = "0.2, 10 August 2011";  use Getopt::Long qw/:config posix_default no_ignore_case gnu_compat                              bundling auto_version auto_help/; @@ -23,8 +23,12 @@ use strict;  ################################################################################  # Configuration -my $symlinks  = catdir($HOME,'video','MOVIES');    # Symlinks folder -my $directors = catdir($HOME,'video','DIRECTORS'); # Directors folder + +my $symlinks  = catdir('/','tmp','video','MOVIES');    # Symlinks folder +my $directors = catdir('', 'tmp','video','DIRECTORS'); # Directors folder + +#my $symlinks  = catdir($HOME,'video','MOVIES');    # Symlinks folder +#my $directors = catdir($HOME,'video','DIRECTORS'); # Directors folder  my $driver    = "mysql";  my $database  = "videodb"; @@ -42,7 +46,7 @@ videomv.pl - Move your videos to or within your collection.  =head1 SYNOPSIS -B<videomv.pl> [B<--db>] [B<-q>] +B<videomv.pl> [B<--lookup-db>] [B<--ignore-db>] [B<-q>]  I<path/to/oldfile> [{I<MOVIES>,I<DIRECTORS/director/>}I</>[I<newfile>]]  =head1 DESCRIPTION @@ -69,7 +73,6 @@ not verify these conventions!         `- ... -  Depending on whether I<path/to/> is I<MOVIES/>,  I<DIRECTORS/*/>, or something  else, you will get one of the six behaviors below. For the sake of @@ -79,7 +82,7 @@ paths that contain symlinks are fine as well.  =over 4 -=item B<videomv.pl> [B<--db>] I<MOVIES/oldfile> I<MOVIES/newfile> +=item B<videomv.pl> [B<--lookup-db>] I<MOVIES/oldfile> I<MOVIES/newfile>  I<oldfile> is expected to be an existing symlink in I<MOVIES/>,  which targets to I<../DIRECTORS/director/oldfile>. If @@ -100,8 +103,8 @@ will be created, and  =back -If the B<--db> flag is set, the new I<director> will be found on the -database instead of by parsing the path of the old target.  +If the B<--lookup-db> flag is set, the new I<director> will be found on the +database instead of by parsing the path of the old target.  =item B<videomv.pl> I<MOVIES/oldfile> I<DIRECTORS/newdirector/>[I<newfile>] @@ -129,7 +132,7 @@ will be created, and  =back -=item B<videomv.pl> [B<--db>] I<DIRECTORS/director/oldfile> I<MOVIES/newfile> +=item B<videomv.pl> [B<--lookup-db>] I<DIRECTORS/director/oldfile> I<MOVIES/newfile>  A symlink I<MOVIES/oldfile> -> I<../DIRECTORS/director/oldfile> is  expected to exist. @@ -150,14 +153,14 @@ will be created, and  =back -If the B<--db> flag is set, the new I<director> will be found on the -database instead of by parsing the path of the old target.  +If the B<--lookup-db> flag is set, the new I<director> will be found on the +database instead of by parsing the path of the old target.  =item B<videomv.pl> I<DIRECTORS/olddirector/oldfile> I<DIRECTOR/newdirector/>[I<newfile>]  A symlink I<MOVIES/oldfile> -> I<../DIRECTORS/olddirector/oldfile> is -expected to exist.   +expected to exist.  If I<olddirector/oldfile>=I<newdirector/newfile>  (or if I<olddirector>=I<newdirector> and I<newfile> was not given),  an error will be raised. @@ -178,7 +181,7 @@ will be created, and  =back -=item B<videomv.pl> B<--db> I<path/to/oldfile> [I<MOVIES>[I</newfile>]] +=item B<videomv.pl> B<--lookup-db> I<path/to/oldfile> [I<MOVIES>[I</newfile>]]  Where I<path/to/> is neither of I<MOVIES/> nor I<DIRECTORS/*/>,  and I<oldfile> is a regular file. @@ -199,7 +202,7 @@ was not given) will be created, and  =back -Note that the B<--db> flag has to be set for this to work; if not, an error +Note that the B<--lookup-db> flag has to be set for this to work; if not, an error  will be raised. @@ -225,27 +228,37 @@ argument was not given) will be created,  =back +The actions above will be performed in the given order. If some action +fails, B<videomv.pl> will try to revert the successful ones (in reverse +order).  =head1 OPTIONS  =over 8 -=item B<--db> +=item B<--lookup-db> -If the second argument is in I<MOVIES/*/> and this flag is set, B<videomv.pl>  +If the second argument is in I<MOVIES/*/> and this flag is set, B<videomv.pl>  will search the I<director> in the data base. An error will be raied if  no entry is (or multiple entries are) found. Look above for details. +=item B<--ignore-db> + +Do not connect to the database, (thus do not update it with the new +filename). This flag is incompatible with B<--lookup-db>. +  =item B<-q>, B<--quiet>  By default, B<videomv.pl> prints each action it performs, with their -return status. This flag supresses this behavior. +return status. This flag supresses this behavior. Use at your own risk.  =back  =head1 EXIT STATUS -The exit status is 0 if all the actions went through, and 1 otherwise. +The exit status is 0 if all the actions went through, 1 if some error +happened, and 2 if some action fails and the successful ones have been +smoothly reverted.  =head1 REQUIREMENTS @@ -261,16 +274,17 @@ Copyright 2011 Guilhem Moulin. See the source for copying conditions.  #TODO: overwrite existing files? -#TODO: revert in case of error  #TODO: explore depth>1 in DIRECTORS/ -my $db_flag; +my $lookupdb_flag; +my $ignoredb_flag;  # Get options -GetOptions( "db"      => \$db_flag, -            "q|quiet" => sub { open LOG, '>', '/dev/null' -                               or die "Cannot open `/dev/null': $!" }, -            "man"     => sub { pod2usage(-exitstatus => 0, -verbose => 2) } +GetOptions( "lookup-db"=> \$lookupdb_flag, +            "ignore-db"=> \$ignoredb_flag, +            "q|quiet"  => sub { open LOG, '>', '/dev/null' +                                or die "Cannot open `/dev/null': $!" }, +            "man"      => sub { pod2usage(-exitstatus => 0, -verbose => 2) }            )      or  pod2usage(2);  pod2usage(2)  if  ($#ARGV < 0 or $#ARGV > 1); @@ -279,12 +293,15 @@ pod2usage(2)  if  ($#ARGV < 0 or $#ARGV > 1);  my ($old_path,$new_path) = @ARGV;  $new_path = $symlinks unless defined $new_path; -die "Error: `" .$old_path. "' does not exist\n" +die "Error: incompatible options.\n" if +    (defined $lookupdb_flag) and (defined $ignoredb_flag); + +die "Error: `" .$old_path. "' does not exist.\n"      unless (-l $old_path or -f $old_path);  $new_path = catfile ($new_path, basename($old_path))      if -d realpath($new_path); -die "Error: `" .$new_path. "' exists\n" +die "Error: `" .$new_path. "' exists.\n"      if (-l $new_path or -f $new_path);  my $old_filename = basename($old_path); @@ -301,25 +318,28 @@ my @real_directors = splitdir($real_directors);  # Connect to database -my $dsn = "DBI:$driver:database=$database;host=$hostname;port=$port"; -my $dbh = DBI->connect($dsn, $user, $password) -              or die "Can't connect do database"; -$dbh->do( "set names utf8" ) or die; - - -# Lookup for the file in the videodb database -my $RES = -    $dbh->selectall_hashref ( "SELECT id,director,title FROM $videodata -                               WHERE filename = ?", -                              'id', undef, $old_filename -    ) +my ($dsn, $dbh, $RES, $nRES, $id); +unless (defined $ignoredb_flag) { +    $dsn = "DBI:$driver:database=$database;host=$hostname;port=$port"; +    $dbh = DBI->connect($dsn, $user, $password) +               or die "Can't connect do database"; +    $dbh->do( "set names utf8" ) or die; + + +    # Lookup for the file in the videodb database +    $RES = +        $dbh->selectall_hashref ( "SELECT id,director,title FROM $videodata +                                   WHERE filename = ?", +                                  'id', undef, $old_filename +        )          or die "Can't select: " .$dbh->errstr. "\n"; -my $nRES = scalar (keys %$RES); +    $nRES = scalar (keys %$RES); -# The ID of the first movie found in the database, if any -my $id = each %$RES; +    # The ID of the first movie found in the database, if any +    $id = each %$RES; +} -my $r; +my $r = 0;  my ($old_director, $new_director);  my ($old_symlink, $new_symlink, $new_target, $old_target); @@ -327,20 +347,20 @@ if (&is_symlink($real_new_path)) {      # The destination is under $symlinks -    if (defined $db_flag) { +    if (defined $lookupdb_flag) {          # Find the new director on the database          # Ensure there is exactly one entry in the db -        die "Error: No entry found in the database while the flag `--db' was set.\n" +        die "Error: No entry found in the database while the flag `--lookup-db' was set.\n"              if $nRES == 0; -        die "Error: Multiples entries found in the database while the flag `--db' was set.\n" +        die "Error: Multiples entries found in the database while the flag `--lookup-db' was set.\n"              if $nRES > 1;          $new_director = $RES->{$id}->{director};          $new_director =~ s/ ?: ?/ -/;          $new_director =~ tr@/@_@;          $new_director =~ s/[[:cntrl:]]//; -         +          # Create directory if it doesn't exist          my $new_dirname = catfile ($directors, $new_director);          unless (-d $new_dirname) { @@ -398,7 +418,7 @@ if (&is_symlink($real_new_path)) {          $old_target = $old_path;          die "Error: `" .$old_path. "' is expected to be a regular file.\n"              unless -f $old_path; -        die "Error: Dunno where to put this file. Try the `--db' flag.\n" +        die "Error: Dunno where to put this file. Try the `--lookup-db' flag.\n"              unless defined $new_director;          $new_target = catfile ($directors, $new_director, $new_filename); @@ -448,33 +468,39 @@ else {  } +my @actions;    # Successful actions  if (dirname (realpath $old_target) eq dirname (realpath $new_target)) { -    unless ($old_filename eq $new_filename) { -        print LOG "Renaming target...      "; -        &ack (\$r, rename $old_target, $new_target); -    } +    &perform ("Renaming target...      ", rename $old_target, $new_target) +        unless ($old_filename eq $new_filename);  }  else { -    print LOG "Moving target...        "; -    &ack (\$r, move $old_target, $new_target); +    &perform ("Moving target...        ", move $old_target, $new_target);  } + +my $old_tar;  if (defined $old_symlink) { -    print LOG "Deleting old symlink... "; -    &ack (\$r, unlink $old_symlink); +    $old_tar = readlink $old_symlink; +    &perform ("Deleting old symlink... ", unlink $old_symlink);  } -print LOG "Creating new symlink... "; -&ack (\$r, symlink catfile(updir(),'DIRECTORS',$new_director,$new_filename), -                   $new_symlink); -unless ($old_filename eq $new_filename) { +&perform ("Creating new symlink... ", +          symlink catfile(updir(),'DIRECTORS',$new_director,$new_filename), +                  $new_symlink); + + +unless (defined $ignoredb_flag || $old_filename eq $new_filename) {      print LOG "Updating database...    ";      if ($nRES == 0) { +        $r = 2;          print LOG "failed!: no entry found.\n"; +        &revert(@actions);      }      elsif ($nRES > 1) { +        $r = 2;          print LOG "failed!: multiple entries found.\n"; +        &revert(@actions);      }      else {          my $rv = $dbh->do ( "UPDATE $videodata SET filename = ? WHERE id = ?", @@ -484,10 +510,12 @@ unless ($old_filename eq $new_filename) {      }  } +  # Disconnect -$dbh->disconnect(); +$dbh->disconnect() unless defined $ignoredb_flag; + +exit $r; -return $r;  ################################################################################ @@ -499,12 +527,68 @@ sub ack {      my ($r, $test) = @_;      if ($test) {          print LOG "OK\n"; +        return 0;      } else {          print LOG "failed!: $!\n"; -        $$r = 1; +        $$r = 2 if defined $r; +        return 1; +    } +} + +# Perform the given action; revert successful changes in case of failing +sub perform { +    return if $r; + +    my ($action,$test) = @_; + +    print LOG $action; +    if (&ack (\$r, $test)) { +        &revert(@actions); +    } +    else { +        push @actions, $action;      }  } +# Revert successful changes +sub revert { +    my @actions = reverse @_; + +    print LOG "\n"; +    print STDERR "Failed!: reverting successful changes...\n"; + +    while ($#actions >= 0) { +        my $rev; +        if ($actions[0] =~ /^Renaming target/) { +            $rev = rename $new_target, $old_target; +        } +        elsif ($actions[0] =~ /^Moving target/) { +            $rev = move $new_target, $old_target; +        } +        elsif ($actions[0] =~ /^Moving target/) { +            $rev = move $new_target, $old_target; +        } +        elsif ($actions[0] =~ /^Deleting( old symlink.*)/) { +            $actions[0] = 'Creating'. $1; +            $rev = symlink $old_tar, $old_symlink; +        } +        elsif ($actions[0] =~ /^Creating( new symlink.*)/) { +            $actions[0] = 'Deleting'. $1; +            $rev = unlink $new_symlink; +        } +        else { +            print STDERR "Error: unknown action `" .$actions[0]. "'!\n"; +            $actions[0] = undef; +            $r = 1; +        } + +        if (defined $actions[0]) { +            print LOG $actions[0]; +            &ack (\$r, $rev); +        } +        shift @actions; +    } +}  # Check wether a path starts with $movies  sub is_symlink { @@ -515,7 +599,7 @@ sub is_symlink {          shift @real_symlinks;          shift @path;      }; -      +      if ($#real_symlinks<0 && $#path==0) {          return 1; # That's a "symlink"      } else { @@ -533,7 +617,7 @@ sub is_director {          shift @real_directors;          shift @path;      }; -     +      if ($#real_directors<0 && $#path>=0) {          ${$_[0]} = $path[0] if defined $_[0];          return 1; # That's a "director" | 
