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