sort out DB new interface; search in parent dirs for .id3fs
[id3fs.git] / bin / id3fs-index
1 #!/usr/bin/perl -w
2 # Ian Beckwith <ianb@erislabs.net>
3 #
4
5 use lib '/home/ianb/projects/id3fs/id3fs/lib'; # FIXME: remove
6 use strict;
7 use Getopt::Long qw(Configure);
8 use ID3FS::DB;
9 use File::Find;
10 use vars qw($me);
11 $me=($0=~/(?:.*\/)?(.*)/)[0];
12
13 my $verbose=0;
14 my $help=0;
15 my $basedir=undef;
16 my $dbpath=undef;
17 my $list=0;
18 my $init=0;
19 my @extensions=qw(mp3 flac ogg);
20 my $files_pruned;
21
22 Configure(qw(bundling no_ignore_case));
23 my $optret=GetOptions(
24     "verbose|v"      => \$verbose,
25     "quiet|q"        => sub { $verbose=0; },
26     "help|h"         => \$help,
27     "dir|d=s"        => \$basedir,
28     "database|f=s"   => \$dbpath,
29     "extensions|e=s" => sub { @extensions=split(/\s+|\s*,\s*/, $_[1]); },
30     "list|l"         => \$list,
31     );
32
33 usage() if(!@ARGV || !$optret || $help);
34 $init=1 unless($list);
35
36 if(@ARGV > 1 && !defined($basedir))
37 {
38     die("$me: --basedir must be specified if multiple paths are supplied\n");
39 }
40
41 my $db=ID3FS::DB->new($me, $verbose, $init, $dbpath, $basedir, $ARGV[0]);
42 exit unless($db);
43
44 if($list)
45 {
46     list_tags($db);
47 }
48 else
49 {
50     $db->last_update(time());
51
52     my $directories_pruned=$db->prune_directories();
53     while(my $path=shift)
54     {
55         File::Find::find( {wanted => \&wanted, follow => 1, no_chdir => 1}, $path);
56     }
57     if($files_pruned || $directories_pruned)
58     {
59         print "Removing data from pruned files\n" if $verbose;
60         $db->remove_unused();
61     }
62 }
63
64 sub wanted
65 {
66     my $ext='';
67     if(/.*\.(.*)/) { $ext=lc($1); }
68     if(-d)
69     {
70         print("$_\n") if $verbose;
71         prune($_);
72     }
73     elsif(-f && scalar(grep({ $ext eq lc($_);} @extensions)))
74     {
75         s/^\.\///;
76         $db->add($_);
77     }
78 }
79
80
81 sub prune
82 {
83     my $dir=shift;
84     return unless(opendir(DIR, $dir));
85     print "Pruning $dir\n";
86     my @oldfiles=$db->files_in($dir);
87     my @newfiles=grep { !/^\.\.?$/; } readdir(DIR);
88     closedir(DIR);
89     @oldfiles=sort @oldfiles;
90     @newfiles=sort @newfiles;
91     my %hash;
92     @hash{@newfiles}=();
93     for my $file (@oldfiles)
94     {
95         unless(exists($hash{$file}))
96         {
97             # FIXME: add path, rebasify
98             $files_pruned=1;
99             $db->unindex($file);
100         }
101     }
102 }
103
104 sub list_tags
105 {
106     my($db)=@_;
107     my @baretags=$db->bare_tags();
108     my $valtags=$db->tags_with_values();
109     if(@baretags)
110     {
111         print "BARE TAGS\n";
112         print join(', ', sort @baretags), "\n\n";
113     }
114     if(keys(%$valtags))
115     {
116         print "TAGS WITH VALUES\n";
117         for my $key (sort keys %$valtags)
118         {
119             print "$key: ", join(', ', sort(@{$valtags->{$key}})), "\n";
120         }
121     }
122 }
123
124 sub usage
125 {
126     die("Usage: $me [-vqh] [-d basedir] [-f dbpath] [-e mp3,ogg,flac] [--] DIR...\n".
127         " -v|--verbose\t\t\tVerbose\n".
128         " -q|--quiet\t\t\tQuiet (default)\n".
129         " -d|--dir=PATH\t\t\tBase directory of source files (default: ARGV[0])\n".
130         " -f|--database=FILE\t\tPath to database file (default: basedir/.id3fs)\n".
131         " -e|--extensions=EXT1,EXT2\tFile extensions to index (default: mp3, ogg, flac)\n".
132         " -h|--help\t\t\tThis help\n".
133         " --\t\t\t\tEnd of options\n");
134 }
135
136 __END__
137
138 =head1 NAME
139
140 id3fs-index - Add files to id3fs index
141
142 =head1 SYNOPSIS
143
144 B<id3fs-index> [B<-vqh>] S<[B<-d >I<basedir>]> S<[B<-f >I<dbpath>]> S<[B<-e >I<mp3,ogg,flac>]> [B<-->] [I<DIR>...]
145
146 =head1 DESCRIPTION
147
148 Extracts id3 tags from mp3 files (and comment tags from ogg and flac
149 files) and adds them to a sqlite database, ready for mounting
150 with L<id3fsd(8)>.
151
152 =head1 OPTIONS
153
154 =over 4
155
156 =item B<-v>
157
158 Enable verbose operation.
159
160 =item B<-q>
161
162 Quiet (no output). This is the default.
163
164 =item S<B<-d >I<PATH>> | S<B<--dir=>I<PATH>>
165
166 Specify base directory of source files. All files will be indexed
167 relative to this point.
168
169 If not specified, defaults to the first non-option argument on the
170 command line. Note that to avoid ambiguities, if more than one
171 directory is specified on the command line, the base directory must
172 be specified explicitly.
173
174 All files indexed must be under the base directory.
175
176 =item S<B<-f >I<FILE>> | S<B<--database=>I<FILE>>
177
178 Database file to use. If not specified, defaults to
179 a hidden file called B<".id3fs"> under the base directory.
180
181 =item S<B<-e >I<EXT1,EXT2>> | S<B<--extensions=>I<EXT1,EXT2>>
182
183 File extensions to consider when indexing.
184 Defaults to B<.mp3>, B<.ogg> and B<.flac>.
185
186 =item B<-h>
187
188 Show a short help message.
189
190 =item B<-->
191
192 End of options.
193
194 =back
195
196 =head1 EXAMPLES
197
198 Index all files in the current directory:
199
200     id3fs-index .
201
202 Index current directory, printing each subdirectory as it recurses
203 into it:
204
205     id3fs-index -v .
206
207 Just index some sub-directories:
208
209     id3fs-index -d . dir1 dir2
210
211 Store the database in a custom location:
212
213     id3fs-index -f ~/.id3fs/index.sqlite .
214
215 Only index .mp3 and .flac files:
216
217     id3fs-index -e mp3,flac .
218
219 =head1 BUGS
220
221 Please report any found to ianb@erislabs.net
222
223 =head1 SEE ALSO
224
225 L<id3fsd(8)>
226
227 =head1 AUTHOR
228
229 Ian Beckwith <ianb@erislabs.net>
230
231 =head1 AVAILABILITY
232
233 The latest version can be found at:
234
235 B<http://erislabs.net/ianb/projects/id3fs/>
236
237 =head1 COPYRIGHT
238
239 Copyright 2010 Ian Beckwith <ianb@erislabs.net>
240
241 This program is free software: you can redistribute it and/or modify
242 it under the terms of the GNU General Public License as published by
243 the Free Software Foundation; either version 3 of the License, or
244 (at your option) any later version.
245
246 This program is distributed in the hope that it will be useful,
247 but WITHOUT ANY WARRANTY; without even the implied warranty of
248 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
249 GNU General Public License for more details.
250
251 You should have received a copy of the GNU General Public License
252 along with this program.  If not, see <http://www.gnu.org/licenses/>.
253
254 =cut