remove "use lib"
[id3fs.git] / bin / id3fs-index
index 62a631e..631bd63 100755 (executable)
@@ -1,8 +1,21 @@
 #!/usr/bin/perl -w
-# Ian Beckwith <ianb@erislabs.net>
 #
+# id3fs - a FUSE-based filesystem for browsing audio metadata
+# Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
+#
+# 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 <http://www.gnu.org/licenses/>.
 
-use lib '/home/ianb/projects/id3fs/id3fs/lib';
 use strict;
 use Getopt::Long qw(Configure);
 use ID3FS::DB;
@@ -14,31 +27,69 @@ my $verbose=0;
 my $help=0;
 my $basedir=undef;
 my $dbpath=undef;
+my $list=0;
+my $init=0;
+my @extensions=qw(mp3 flac ogg);
+my $files_pruned;
 
-my @extensions=qw(mp3); # ogg flac); # FIXME
 Configure(qw(bundling no_ignore_case));
 my $optret=GetOptions(
     "verbose|v"      => \$verbose,
-    "quiet|q"        => sub { $verbose=0; },
     "help|h"         => \$help,
-    "basedir|d=s"    => \$basedir,
-    "database|db=s"  => \$dbpath,
+    "dir|d=s"        => \$basedir,
+    "database|f=s"   => \$dbpath,
     "extensions|e=s" => sub { @extensions=split(/\s+|\s*,\s*/, $_[1]); },
+    "list|l"         => \$list,
     );
 
+if($list && !@ARGV)
+{
+    push(@ARGV, ".");
+}
 usage() if(!@ARGV || !$optret || $help);
+$init=1 unless($list);
 
-if(@ARGV > 1 && !defined($basedir))
+unless(defined($basedir) && defined($dbpath))
 {
-    die("$me: --basedir must be specified if multiple paths are supplied\n");
+    $basedir=ID3FS::DB::find_db($me, $init, @ARGV);
+    exit unless($basedir);
+    my $absbase=Cwd::abs_path($basedir);
+    for my $dir (@ARGV)
+    {
+       if(Cwd::abs_path($dir) !~ /^\Q$absbase\E/)
+       {
+           die("$me: $dir: must be under basedir $absbase - use --basedir to specify\n");
+       }
+    }
 }
+my $db=ID3FS::DB->new($me, $verbose, $init, $basedir, $dbpath);
+exit unless($db);
 
-my $db=ID3FS::DB->new($me, $dbpath, $basedir, $ARGV[0]);
-$db->last_update(time());
-
-while(my $path=shift)
+if($list)
 {
-    File::Find::find( {wanted => \&wanted, follow => 1, no_chdir => 1}, $path);
+    list_tags($db);
+}
+else
+{
+    $db->last_update(time());
+    my $base=$db->base_dir();
+    my $abs_base=Cwd::abs_path($base);
+    while(my $path=shift)
+    {
+       if(Cwd::abs_path($path) !~ /^$abs_base/)
+       {
+           print "$me: $path is outside $base, skipping\n";
+       }
+       File::Find::find( {wanted => \&wanted, follow => 1, no_chdir => 1}, $path);
+    }
+    my $directories_pruned=$db->prune_directories();
+    if($files_pruned || $directories_pruned)
+    {
+       print "$me: pruning removed files from db\n" if $verbose;
+       $db->remove_unused();
+    }
+    print "$me: analyzing db\n" if $verbose;
+    $db->analyze();
 }
 
 sub wanted
@@ -48,6 +99,7 @@ sub wanted
     if(-d)
     {
        print("$_\n") if $verbose;
+       prune($_);
     }
     elsif(-f && scalar(grep({ $ext eq lc($_);} @extensions)))
     {
@@ -56,39 +108,112 @@ sub wanted
     }
 }
 
+sub prune
+{
+    my $dir=shift;
+    $dir=Cwd::abs_path($dir);
+    return unless(opendir(DIR, $dir));
+    my $base=Cwd::abs_path($db->base_dir());
+    $dir=~s/^$base\/?//;
+    my @oldfiles=$db->files_in($dir);
+    my @newfiles=grep { !/^\.\.?$/; } readdir(DIR);
+    closedir(DIR);
+    @oldfiles=sort @oldfiles;
+    @newfiles=sort @newfiles;
+    my %hash;
+    # hash slice!
+    @hash{@newfiles}=();
+    for my $file (@oldfiles)
+    {
+       unless(exists($hash{$file}))
+       {
+           $files_pruned=1;
+           $db->unindex($dir, $file);
+       }
+    }
+}
+
+sub list_tags
+{
+    my($db)=@_;
+    my @baretags=$db->bare_tags();
+    my $valtags=$db->tags_with_values();
+    if(@baretags)
+    {
+       print "BARE TAGS\n";
+       print join(', ', sort @baretags), "\n\n";
+    }
+    if(keys(%$valtags))
+    {
+       print "TAGS WITH VALUES\n";
+       for my $key (sort keys %$valtags)
+       {
+           print "$key: ", join(', ', sort(@{$valtags->{$key}})), "\n";
+       }
+    }
+}
+
 sub usage
 {
-    die("Usage: $me [-v] [-q] [-h] [--] file...\n".
-       " -v\tVerbose\n".
-       " -q\tQuiet (default)\n".
-       " -h\tThis help\n".
-       " --\tEnd of options\n");
+    die("Usage: $me [-lvh] [-d basedir] [-f dbpath] [-e mp3,ogg,flac] [--] DIR...\n".
+       " -d|--dir=PATH              Base directory of source files (default: ARGV[0])\n".
+       " -f|--database=FILE         Path to database file (default: basedir/.id3fs)\n".
+       " -e|--extensions=EXT1,EXT2  File extensions to index (default: mp3, ogg, flac)\n".
+       " -l|list                    List tags in use\n" .
+       " -v|--verbose               Verbose\n".
+       " -h|--help                  This help\n".
+       " --                         End of options\n");
 }
 
 __END__
 
-
 =head1 NAME
 
-program - description
+id3fs-index - Add files to id3fs index
 
 =head1 SYNOPSIS
 
-B<> [I<-v>] [I<-q>] [I<-h>] [I<file>...]
+B<id3fs-index> [B<-lvh>] S<[B<-d >I<basedir>]> S<[B<-f >I<dbpath>]> S<[B<-e >I<mp3,ogg,flac>]> [B<-->] [I<DIR>...]
 
 =head1 DESCRIPTION
 
+Extracts id3 tags from mp3 files (and comment tags from ogg and flac
+files) and adds them to a sqlite database, ready for mounting
+with L<id3fsd(8)>.
+
 =head1 OPTIONS
 
 =over 4
 
-=item B<-v>
+=item B<-l> | B<--list>
 
-Enable verbose operation.
+List tags in use in specified database.
+
+=item S<B<-d >I<PATH>> | S<B<--dir=>I<PATH>>
+
+Specify base directory of source files. All files will be indexed
+relative to this point.
 
-=item B<-q>
+If not specified, defaults to the first non-option argument on the
+command line. Note that to avoid ambiguities, if more than one
+directory is specified on the command line, the base directory must
+be specified explicitly.
 
-Quiet (no output). This is the default.
+All files indexed must be under the base directory.
+
+=item S<B<-f >I<FILE>> | S<B<--database=>I<FILE>>
+
+Database file to use. If not specified, defaults to
+a hidden file called B<".id3fs"> under the base directory.
+
+=item S<B<-e >I<EXT1,EXT2>> | S<B<--extensions=>I<EXT1,EXT2>>
+
+File extensions to consider when indexing.
+Defaults to B<.mp3>, B<.ogg> and B<.flac>.
+
+=item B<-v>
+
+Enable verbose operation.
 
 =item B<-h>
 
@@ -100,35 +225,56 @@ End of options.
 
 =back
 
-=head1 FILES
+=head1 EXAMPLES
+
+Index all files in the current directory:
+
+    id3fs-index .
+
+Index current directory, printing each subdirectory as it recurses
+into it:
+
+    id3fs-index -v .
 
-=head1 ENVIRONMENT
+Just index some sub-directories:
 
-=head1 DIAGNOSTICS
+    id3fs-index -d . dir1 dir2
+
+Store the database in a custom location:
+
+    id3fs-index -f ~/.id3fs/index.sqlite .
+
+Only index .mp3 and .flac files:
+
+    id3fs-index -e mp3,flac .
 
 =head1 BUGS
 
-None known. Please report any found to ianb@erislabs.net
+Please report any found to ianb@erislabs.net
 
 =head1 SEE ALSO
 
+L<id3fsd(8)>, L<MP3::Tag>, L<Audio::Flac::Header>, L<Ogg::Vorbis::Header>
+
 =head1 AUTHOR
 
 Ian Beckwith <ianb@erislabs.net>
 
+Many thanks to Aubrey Stark-Toller for help wrangling SQL.
+
 =head1 AVAILABILITY
 
 The latest version can be found at:
 
-B<http://erislabs.net/ianb/projects/id3fs/>
+L<http://erislabs.net/ianb/projects/id3fs/>
 
 =head1 COPYRIGHT
 
-Copyright 2010 Ian Beckwith <ianb@erislabs.net>
+Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
 
 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
+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,