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