#!/usr/bin/perl -w
#
# 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 strict;
use Getopt::Long qw(Configure);
use ID3FS::DB;
use File::Find;
use vars qw($me);
$me=($0=~/(?:.*\/)?(.*)/)[0];

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;

Configure(qw(bundling no_ignore_case));
my $optret=GetOptions(
    "verbose|v"      => \$verbose,
    "help|h"         => \$help,
    "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);

unless(defined($basedir) && defined($dbpath))
{
    $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);

if($list)
{
    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
{
    my $ext='';
    if(/.*\.(.*)/) { $ext=lc($1); }
    if(-d)
    {
	print("$_\n") if $verbose;
	prune($_);
    }
    elsif(-f && scalar(grep({ $ext eq lc($_);} @extensions)))
    {
	s/^\.\///;
	$db->add($_);
    }
}

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 [-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

id3fs-index - Add files to id3fs index

=head1 SYNOPSIS

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<-l> | B<--list>

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.

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.

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>

Show a short help message.

=item B<-->

End of options.

=back

=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 .

Just index some sub-directories:

    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

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:

L<http://erislabs.net/ianb/projects/id3fs/>

=head1 COPYRIGHT

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/>.

=cut
