Index: trunk/lib/MP3/Find.pm
===================================================================
--- trunk/lib/MP3/Find.pm	(revision 43)
+++ trunk/lib/MP3/Find.pm	(revision 1)
@@ -9,5 +9,5 @@
 use Carp;
 
-$VERSION = '0.07';
+$VERSION = '0.01';
 
 @EXPORT = qw(find_mp3s);
@@ -37,6 +37,5 @@
 =head1 SYNOPSIS
 
-    # select with backend you want
-    use MP3::Find qw(Filesystem);
+    use MP3Find;
     
     print "$_\n" foreach find_mp3s(
@@ -47,5 +46,5 @@
         },
         ignore_case => 1,
-        exact_match => 1,
+        match_words => 1,
         sort => [qw(year album tracknum)],
         printf => '%2n. %a - %t (%b: %y)',
@@ -60,27 +59,10 @@
 or the actual Perl data structure representing the results.
 
-There are currently two backends to this module: L<MP3::Find::Filesystem>
-and L<MP3::Find::DB>. You choose which one you want by passing its
-name as the argument to you C<use> statement; B<MP3::Find> will look for
-a B<MP3::Find::$BACKEND> module. If no backend name is given, it will
-default to using L<MP3::Find::Filesystem>.
-
-B<Note:> I'm still working out some kinks in the DB backend, so it
-is currently not as stable as the Filesystem backend.
-
-B<Note the second>: This whole project is still in the alpha stage, so
-I can make no guarentees that there won't be significant interface changes
-in the next few versions or so. Also, comments about what about the API
-rocks (or sucks!) are appreciated.
-
 =head1 REQUIRES
 
-L<File::Find>, L<MP3::Info>, and L<Scalar::Util> are needed for
-the filesystem backend (L<MP3::Find::Filesystem>). In addition,
-if L<MP3::Tag> is available, you can search by explicit ID3v2
-tag frames.
+L<File::Find>, L<MP3::Info>, L<Scalar::Util>
 
-L<DBI>, L<DBD::SQLite>, and L<SQL::Abstract> are needed for the
-database backend (L<MP3::Find::DB>).
+L<DBI> and L<DBD::SQLite> are needed if you want to have a
+database backend.
 
 =head1 EXPORTS
@@ -96,6 +78,6 @@
 =item C<dir>
 
-Arrayref or scalar; tell C<find_mp3s> where to start the search.
-Directories in the arrayref are searched sequentially.
+Where to start the search. This can either be a single string or
+an arrayref. Defaults to your home directory.
 
 =item C<query>
@@ -105,16 +87,13 @@
 or lower case; C<find_mp3s> will convert them into upper case for 
 you. Value may either be strings, which are converted into regular
-exporessions, or may be C<qr/.../> regular expressions already.
+exporessions, or may be C<qr[...]> regular expressions already.
 
 =item C<ignore_case>
 
-Boolean, default false; set to a true value to ignore case when
-matching search strings to the ID3 tag values.
+Ignore case when matching search strings to the ID3 tag values.
 
 =item C<exact_match>
 
-Boolean, default false; set to a true value to add an implicit
-C<^> and C<$> around each query string. Does nothing if the query
-term is already a regular expression.
+Adds an implicit C<^> and C<$> around each query string.
 
 =item C<sort>
@@ -122,7 +101,5 @@
 What field or fields to sort the results by. Can either be a single
 scalar field name to sort by, or an arrayref of field names. Again,
-acceptable field names are anything that L<MP3::Info> knows about;
-field names will be converted to upper case as with the C<query>
-option.
+acceptable field names are anything that L<MP3::Info> knows about.
 
 =item C<printf>
@@ -147,9 +124,8 @@
 =item C<no_format>
 
-Boolean, default false; set to a true value to have C<find_mp3s> to
-return an array of hashrefs instead of an array of (formatted) strings.
-Each hashref consists of the key-value pairs from C<MP3::Info::get_mp3_tag>
-and C<MP3::Info::get_mp3_info>, plus the key C<FILENAME> (with the obvious 
-value ;-)
+Causes C<find_mp3s> to return an array of hashrefs instead of an array
+of (formatted) strings. Each hashref consists of the key-value pairs
+from C<MP3::Info::get_mp3_tag> and C<MP3::Info::get_mp3_info>, plus
+the key C<FILENAME> (with the obvious value ;-)
 
     @results = (
@@ -168,29 +144,19 @@
 =back
 
-=head1 BUGS
-
-There are probably some in there; let me know if you find any (patches
-welcome).
-
 =head1 TODO
 
-Better tests, using some actual sample mp3 files.
+More of a structured query would be nice; currently everything
+is and-ed together, and it would be nice to be able to put query
+keys together with a mixture of and and or.
 
-Other backends (a caching filesystem backend, perhaps?)
+Searching a big directory is slo-o-ow! Investigate some sort of 
+caching of results?
+
+The current sorting function is also probably quite inefficient.
 
 =head1 SEE ALSO
 
-L<MP3::Find::Filesystem>, L<MP3::Find::DB>
-
-L<mp3find> is the command line frontend to this module (it
-currently only uses the filesystem backend).
-
-L<mp3db> is a (currently rather barebones) command line 
-frontend for creating and updating a SQLite database for 
-use with L<MP3::Find::DB>.
-
 See L<MP3::Info> for more information about the fields you can
-search and sort on. See L<http://id3.org/> for information about
-ID3v2 tags.
+search and sort on.
 
 L<File::Find::Rule::MP3Info> is another way to search for MP3
Index: trunk/lib/MP3/Find/Base.pm
===================================================================
--- trunk/lib/MP3/Find/Base.pm	(revision 43)
+++ trunk/lib/MP3/Find/Base.pm	(revision 1)
@@ -4,5 +4,8 @@
 use warnings;
 
+use vars qw($VERSION);
 use Carp;
+
+$VERSION = '0.01';
 
 my %format_codes = (
@@ -18,6 +21,5 @@
     my $invocant = shift;
     my $class = ref $invocant || $invocant;
-    my %options = @_;
-    my $self = \%options;
+    my $self = {};
     bless $self, $class;
 }
@@ -38,12 +40,10 @@
         ();
     
+    
     foreach (keys %QUERY) {
-        if (defined $QUERY{$_}) {
-            # package everything uniformly, so subclasses don't need to unpack it
-            $QUERY{$_} = [ $QUERY{$_} ] unless ref $QUERY{$_} eq 'ARRAY';
-        } else {
-            # so we don't have spurious warnings when trying to match against undef        
-            delete $QUERY{$_};
-        }
+        # so we don't have spurious warnings when trying to match against undef
+        delete $QUERY{$_} unless defined $QUERY{$_};
+        # package everything unioformly, so subclasses don't need to unpack
+        $QUERY{$_} = [ $QUERY{$_} ] unless ref $QUERY{$_} eq 'ARRAY';
     }
     
@@ -90,107 +90,134 @@
 =head1 NAME
 
-MP3::Find::Base - Base class for MP3::Find backends
+MP3::Find - Search and sort MP3 files based on their ID3 tags
 
 =head1 SYNOPSIS
 
-    package MyFinder;
-    use base 'MP3::Find::Base';
-    
-    sub search {
-        my $self = shift;
-        my ($query, $dirs, $sort, $options) = @_;
-        
-        # do something to find and sort the mp3s...
-        my @results = do_something(...);
-        
-        return @results;
-    }
-    
-    package main;
-    my $finder = MyFinder->new;
-    
-    # see MP3::Find for details about %options
-    print "$_\n" foreach $finder->find_mp3s(\%options);        
+    use MP3Find;
+    
+    print "$_\n" foreach find_mp3s(
+        dir => '/home/peter/cds',
+        query => {
+            artist => 'ilyaimy',
+            title => 'deep in the am',
+        },
+        ignore_case => 1,
+        match_words => 1,
+        sort => [qw(year album tracknum)],
+        printf => '%2n. %a - %t (%b: %y)',
+    );
 
 =head1 DESCRIPTION
 
-This is the base class for the classes that actually do the
-searching and sorting for L<MP3::Find>.
-
-=head1 METHODS
-
-=head2 new
-
-Really simple constructor. If you pass it a hash of options, it
-will hang on to them for you.
-
-=head2 search
-
-This is the one you should override in your subclass. If you
-don't, the base class C<search> method will croak.
-
-The C<search> method is called by the C<find_mp3s> method with
-the following arguments: the finder object, a hashref of query
-parameters, an arrayref of directories to search, and a hashref
-of miscellaneous options.
-
-The search method should return a list of hashrefs representing
-the results of the search. Each hashref should have the following
-keys (all except C<FILENAME> are derived from the keys returned
-by the C<get_mp3tag> and C<get_mp3Info> functions from L<MP3::Info>):
-
-    FILENAME
-    
-    TITLE
-    ARTIST
-    ALBUM
-    YEAR
-    COMMENT
-    GENRE
-    TRACKNUM
-    
-    VERSION         -- MPEG audio version (1, 2, 2.5)
-    LAYER           -- MPEG layer description (1, 2, 3)
-    STEREO          -- boolean for audio is in stereo
-    
-    VBR             -- boolean for variable bitrate
-    BITRATE         -- bitrate in kbps (average for VBR files)
-    FREQUENCY       -- frequency in kHz
-    SIZE            -- bytes in audio stream
-    OFFSET          -- bytes offset that stream begins
-    
-    SECS            -- total seconds
-    MM              -- minutes
-    SS              -- leftover seconds
-    MS              -- leftover milliseconds
-    TIME            -- time in MM:SS
-    
-    COPYRIGHT       -- boolean for audio is copyrighted
-    PADDING         -- boolean for MP3 frames are padded
-    MODE            -- channel mode (0 = stereo, 1 = joint stereo,
-                    -- 2 = dual channel, 3 = single channel)
-    FRAMES          -- approximate number of frames
-    FRAME_LENGTH    -- approximate length of a frame
-    VBR_SCALE       -- VBR scale from VBR header
-
+This module allows you to search for MP3 files by their ID3 tags.
+You can ask for the results to be sorted by one or more of those
+tags, and return either the list of filenames (the deault), a
+C<printf>-style formatted string for each file using its ID3 tags,
+or the actual Perl data structure representing the results.
+
+=head1 REQUIRES
+
+L<File::Find>, L<MP3::Info>, L<Scalar::Util>
+
+L<DBI> and L<DBD::SQLite> are needed if you want to have a
+database backend.
+
+=head1 EXPORTS
 
 =head2 find_mp3s
 
-The method that should be called by the program doing the searching.
-
-See L<MP3::Find> for an explanation of the options that can be passed
-to C<find_mp3s>.
+    my @results = find_mp3s(%options);
+
+Takes the following options:
+
+=over
+
+=item C<dir>
+
+Where to start the search. This can either be a single string or
+an arrayref. Defaults to your home directory.
+
+=item C<query>
+
+Hashref of search parameters. Recognized fields are anything that
+L<MP3::Info> knows about. Field names can be given in either upper
+or lower case; C<find_mp3s> will convert them into upper case for 
+you. Value may either be strings, which are converted into regular
+exporessions, or may be C<qr[...]> regular expressions already.
+
+=item C<ignore_case>
+
+Ignore case when matching search strings to the ID3 tag values.
+
+=item C<exact_match>
+
+Adds an implicit C<^> and C<$> around each query string.
+
+=item C<sort>
+
+What field or fields to sort the results by. Can either be a single
+scalar field name to sort by, or an arrayref of field names. Again,
+acceptable field names are anything that L<MP3::Info> knows about.
+
+=item C<printf>
+
+By default, C<find_mp3s> just returns the list of filenames. The 
+C<printf> option allows you to provide a formatting string to apply
+to the data for each file. The style is roughly similar to Perl's
+C<printf> format strings. The following formatting codes are 
+recognized:
+
+    %a - artist
+    %t - title
+    %b - album
+    %n - track number
+    %y - year
+    %g - genre
+    %% - literal '%'
+
+Numeric modifers may be used in the same manner as with C<%s> in
+Perl's C<printf>.
+
+=item C<no_format>
+
+Causes C<find_mp3s> to return an array of hashrefs instead of an array
+of (formatted) strings. Each hashref consists of the key-value pairs
+from C<MP3::Info::get_mp3_tag> and C<MP3::Info::get_mp3_info>, plus
+the key C<FILENAME> (with the obvious value ;-)
+
+    @results = (
+        {
+            FILENAME => ...,
+            TITLE    => ...,
+            ARTIST   => ...,
+            ...
+            SECS     => ...,
+            BITRATE  => ...,
+            ...
+        },
+        ...
+    );
+
+=back
 
 =head1 TODO
 
-More format codes? Possibly look into using L<String::Format>
+More of a structured query would be nice; currently everything
+is and-ed together, and it would be nice to be able to put query
+keys together with a mixture of and and or.
+
+Searching a big directory is slo-o-ow! Investigate some sort of 
+caching of results?
+
+The current sorting function is also probably quite inefficient.
 
 =head1 SEE ALSO
-
-L<MP3::Find>, L<MP3::Find::Filesystem>, L<MP3::Find::DB>
 
 See L<MP3::Info> for more information about the fields you can
 search and sort on.
 
+L<File::Find::Rule::MP3Info> is another way to search for MP3
+files based on their ID3 tags.
+
 =head1 AUTHOR
 
Index: trunk/lib/MP3/Find/DB.pm
===================================================================
--- trunk/lib/MP3/Find/DB.pm	(revision 43)
+++ trunk/lib/MP3/Find/DB.pm	(revision 1)
@@ -5,545 +5,9 @@
 
 use base qw(MP3::Find::Base);
-use Carp;
 
 use DBI;
 use SQL::Abstract;
 
-use MP3::Find::Util qw(get_mp3_metadata);
-
 my $sql = SQL::Abstract->new;
-
-my @COLUMNS = (
-    [ mtime        => 'INTEGER' ],  # filesystem mtime, so we can do incremental updates
-    [ FILENAME     => 'TEXT' ], 
-    [ TITLE        => 'TEXT' ], 
-    [ ARTIST       => 'TEXT' ], 
-    [ ALBUM        => 'TEXT' ],
-    [ YEAR         => 'INTEGER' ], 
-    [ COMMENT      => 'TEXT' ], 
-    [ GENRE        => 'TEXT' ], 
-    [ TRACKNUM     => 'INTEGER' ], 
-    [ VERSION      => 'NUMERIC' ],
-    [ LAYER        => 'INTEGER' ], 
-    [ STEREO       => 'TEXT' ],
-    [ VBR          => 'TEXT' ],
-    [ BITRATE      => 'INTEGER' ], 
-    [ FREQUENCY    => 'INTEGER' ], 
-    [ SIZE         => 'INTEGER' ], 
-    [ OFFSET       => 'INTEGER' ], 
-    [ SECS         => 'INTEGER' ], 
-    [ MM           => 'INTEGER' ],
-    [ SS           => 'INTEGER' ],
-    [ MS           => 'INTEGER' ], 
-    [ TIME         => 'TEXT' ],
-    [ COPYRIGHT    => 'TEXT' ], 
-    [ PADDING      => 'INTEGER' ], 
-    [ MODE         => 'INTEGER' ],
-    [ FRAMES       => 'INTEGER' ], 
-    [ FRAME_LENGTH => 'INTEGER' ], 
-    [ VBR_SCALE    => 'INTEGER' ],
-);
-
-my $DEFAULT_STATUS_CALLBACK = sub {
-    my ($action_code, $filename) = @_;
-    print STDERR "$action_code $filename\n";
-};
-
-=head1 NAME
-
-MP3::Find::DB - SQLite database backend to MP3::Find
-
-=head1 SYNOPSIS
-
-    use MP3::Find::DB;
-    my $finder = MP3::Find::DB->new;
-    
-    my @mp3s = $finder->find_mp3s(
-        dir => '/home/peter/music',
-        query => {
-            artist => 'ilyaimy',
-            album  => 'myxomatosis',
-        },
-        ignore_case => 1,
-        db_file => 'mp3.db',
-    );
-    
-    # you can do things besides just searching the database
-    
-    # create another database
-    $finder->create({ db_file => 'my_mp3s.db' });
-    
-    # update the database by searching the filesystem
-    $finder->update({
-	db_file => 'my_mp3s.db',
-	dirs => ['/home/peter/mp3', '/home/peter/cds'],
-    });
-
-    # or just update specific mp3s
-    $finder->update({
-	db_file => 'my_mp3s.db',
-	files => \@filenames,
-    });
-    
-    # and then blow it away
-    $finder->destroy_db('my_mp3s.db');
-
-=head1 REQUIRES
-
-L<DBI>, L<DBD::SQLite>, L<SQL::Abstract>
-
-=head1 DESCRIPTION
-
-This is the database backend for L<MP3::Find>. The easiest way to
-use it is with a SQLite database, but you can also pass in your own
-DSN or database handle.
-
-The database you use should have at least one table named C<mp3> with 
-the following schema:
-
-    CREATE TABLE mp3 (
-        mtime         INTEGER,
-        FILENAME      TEXT, 
-        TITLE         TEXT, 
-        ARTIST        TEXT, 
-        ALBUM         TEXT,
-        YEAR          INTEGER, 
-        COMMENT       TEXT, 
-        GENRE         TEXT, 
-        TRACKNUM      INTEGER, 
-        VERSION       NUMERIC,
-        LAYER         INTEGER, 
-        STEREO        TEXT,
-        VBR           TEXT,
-        BITRATE       INTEGER, 
-        FREQUENCY     INTEGER, 
-        SIZE          INTEGER, 
-        OFFSET        INTEGER, 
-        SECS          INTEGER, 
-        MM            INTEGER,
-        SS            INTEGER,
-        MS            INTEGER, 
-        TIME          TEXT,
-        COPYRIGHT     TEXT, 
-        PADDING       INTEGER, 
-        MODE          INTEGER,
-        FRAMES        INTEGER, 
-        FRAME_LENGTH  INTEGER, 
-        VBR_SCALE     INTEGER
-    );
-
-B<Note:> I'm still working out some kinks in here, so this backend
-is currently not as stable as the Filesystem backend. Expect API
-fluctuations for now.
-
-B<Deprecated Methods:> C<create_db>, C<update_db>, C<sync_db>, and
-C<destroy_db> have been deprecated in this release, and will be 
-removed in a future release. Please switch to the new methods C<create>,
-C<update>, C<sync>, and C<destory>.
-
-=head2 Special Options
-
-When using this backend, provide one of the following additional options
-to the C<search> method:
-
-=over
-
-=item C<dsn>, C<username>, C<password>
-
-A custom DSN and (optional) username and password. This gets passed
-to the C<connect> method of L<DBI>.
-
-=item C<dbh>
-
-An already created L<DBI> database handle object.
-
-=item C<db_file>
-
-The name of the SQLite database file to use.
-
-=back
-
-=cut
-
-# get a database handle from named arguments
-sub _get_dbh {
-    my $args = shift;
-
-    # we got an explicit $dbh object
-    return $args->{dbh} if defined $args->{dbh};
-
-    # or a custom DSN
-    if (defined $args->{dsn}) {
-    	my $dbh = DBI->connect(
-	    $args->{dsn}, 
-	    $args->{username}, 
-	    $args->{password}, 
-	    { RaiseError => 1 },
-	);
-	return $dbh;
-    }
-    
-    # default to a SQLite database
-    if (defined $args->{db_file}) {
-	my $dbh = DBI->connect(
-	    "dbi:SQLite:dbname=$$args{db_file}",
-	    '',
-	    '',
-	    { RaiseError => 1 },
-	);
-	return $dbh;
-    }
-
-    return;
-}
-
-sub _sqlite_workaround {
-    # as a workaround for the 'closing dbh with active staement handles warning
-    # (see http://rt.cpan.org/Ticket/Display.html?id=9643#txn-120724)
-    foreach (@_) {
-        $_->{RaiseError} = 0;  # don't die on error
-        $_->{PrintError} = 0;  # ...and don't even say anything
-        $_->{Active} = 1;
-        $_->finish;
-    }
-}
- 
-=head1 METHODS
-
-=head2 new
-
-    my $finder = MP3::Find::DB->new(
-        status_callback => \&callback,
-    );
-
-The C<status_callback> gets called each time an entry in the
-database is added, updated, or deleted by the C<update> and
-C<sync> methods. The arguments passed to the callback are
-a status code (A, U, or D) and the filename for that entry.
-The default callback just prints these to C<STDERR>:
-
-    sub default_callback {
-        my ($status_code, $filename) = @_;
-        print STDERR "$status_code $filename\n";
-    }
-
-To suppress any output, set C<status_callback> to an empty sub:
-
-    status_callback => sub {}
-
-=head2 create
-
-    $finder->create({
-	dsn => 'dbi:SQLite:dbname=mp3.db',
-	dbh => $dbh,
-	db_file => 'mp3.db',
-    });
-
-Creates a new table for storing mp3 info in the database. You can provide
-either a DSN (plus username and password, if needed), an already created
-database handle, or just the name of an SQLite database file.
-
-=cut
-
-sub create {
-    my $self = shift;
-    my $args = shift;
-
-    my $dbh = _get_dbh($args) or croak "Please provide a DBI database handle, DSN, or SQLite database filename";
-    
-    my $create = 'CREATE TABLE mp3 (' . join(',', map { "$$_[0] $$_[1]" } @COLUMNS) . ')';
-    $dbh->do($create);
-}
-
-=head2 create_db (DEPRECATED)
-
-    $finder->create_db($db_filename);
-
-Creates a SQLite database in the file named C<$db_filename>.
-
-=cut
-
-# TODO: extended table for ID3v2 data
-sub create_db {
-    my $self = shift;
-    my $db_file = shift or croak "Need a name for the database I'm about to create";
-    my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1});
-    my $create = 'CREATE TABLE mp3 (' . join(',', map { "$$_[0] $$_[1]" } @COLUMNS) . ')';
-    $dbh->do($create);
-}
-
-=head2 update
-
-    my $count = $finder->update({
-	dsn   => 'dbi:SQLite:dbname=mp3.db',
-	files => \@filenames,
-	dirs  => \@dirs,
-    });
-
-Compares the files in the C<files> list plus any MP3s found by searching
-in C<dirs> to their records in the database pointed to by C<dsn>. If the
-files found have been updated since they have been recorded in the database
-(or if they are not in the database), they are updated (or added).
-
-Instead of a C<dsn>, you can also provide either an already created
-database handle as C<dbh> or the filename of an SQLite database as C<db_file>.
-
-=cut
-
-# this is update_db and update_files (from Matt Dietrich) rolled into one
-sub update {
-    my $self = shift;
-    my $args = shift;
-
-    my $dbh = _get_dbh($args) or croak "Please provide a DBI database handle, DSN, or SQLite database filename";
-
-    my @dirs  = $args->{dirs}
-		    ? ref $args->{dirs} eq 'ARRAY'
-			? @{ $args->{dirs} }
-			: ($args->{dirs})
-		    : ();
-
-    my @files  = $args->{files}
-		    ? ref $args->{files} eq 'ARRAY' 
-			? @{ $args->{files} }
-			: ($args->{files})
-		    : ();
-    
-    my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK;
-
-    my $mtime_sth = $dbh->prepare('SELECT mtime FROM mp3 WHERE FILENAME = ?');
-    my $insert_sth = $dbh->prepare(
-        'INSERT INTO mp3 (' . 
-            join(',', map { $$_[0] } @COLUMNS) .
-        ') VALUES (' .
-            join(',', map { '?' } @COLUMNS) .
-        ')'
-    );
-    my $update_sth = $dbh->prepare(
-        'UPDATE mp3 SET ' . 
-            join(',', map { "$$_[0] = ?" } @COLUMNS) . 
-        ' WHERE FILENAME = ?'
-    );
-    
-    my $count = 0;  # the number of records added or updated
-    my @mp3s;       # metadata for mp3s found
-
-    # look for mp3s using the filesystem backend if we have dirs to search in
-    if (@dirs) {
-	require MP3::Find::Filesystem;
-	my $finder = MP3::Find::Filesystem->new;
-	unshift @mp3s, $finder->find_mp3s(dir => \@dirs, no_format => 1);
-    }
-
-    # get the metadata on specific files
-    unshift @mp3s, map { get_mp3_metadata({ filename => $_ }) } @files;
-
-    # check each file against its record in the database
-    for my $mp3 (@mp3s) {	
-        # see if the file has been modified since it was first put into the db
-        $mp3->{mtime} = (stat($mp3->{FILENAME}))[9];
-        $mtime_sth->execute($mp3->{FILENAME});
-        my $records = $mtime_sth->fetchall_arrayref;
-        
-        warn "Multiple records for $$mp3{FILENAME}\n" if @$records > 1;
-        
-        if (@$records == 0) {
-	    # we are adding a record
-            $insert_sth->execute(map { $mp3->{$$_[0]} } @COLUMNS);
-            $status_callback->(A => $$mp3{FILENAME});
-            $count++;
-        } elsif ($mp3->{mtime} > $$records[0][0]) {
-            # the mp3 file is newer than its record
-            $update_sth->execute((map { $mp3->{$$_[0]} } @COLUMNS), $mp3->{FILENAME});
-            $status_callback->(U => $$mp3{FILENAME});
-            $count++;
-        }
-    }
-    
-    # SQLite buggy driver
-    _sqlite_workaround($mtime_sth, $insert_sth, $update_sth);
-     
-    return $count;
-}
-
-=head2 update_db (DEPRECATED)
-
-    my $count = $finder->update_db($db_filename, \@dirs);
-
-Searches for all mp3 files in the directories named by C<@dirs>
-using L<MP3::Find::Filesystem>, and adds or updates the ID3 info
-from those files to the database. If a file already has a record
-in the database, then it will only be updated if it has been modified
-since the last time C<update_db> was run.
-
-=cut
-
-sub update_db {
-    my $self = shift;
-    my $db_file = shift or croak "Need the name of the database to update";
-    my $dirs = shift;
-    
-    my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK;
-    
-    my @dirs = ref $dirs eq 'ARRAY' ? @$dirs : ($dirs);
-    
-    my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1});
-    my $mtime_sth = $dbh->prepare('SELECT mtime FROM mp3 WHERE FILENAME = ?');
-    my $insert_sth = $dbh->prepare(
-        'INSERT INTO mp3 (' . 
-            join(',', map { $$_[0] } @COLUMNS) .
-        ') VALUES (' .
-            join(',', map { '?' } @COLUMNS) .
-        ')'
-    );
-    my $update_sth = $dbh->prepare(
-        'UPDATE mp3 SET ' . 
-            join(',', map { "$$_[0] = ?" } @COLUMNS) . 
-        ' WHERE FILENAME = ?'
-    );
-    
-    # the number of records added or updated
-    my $count = 0;
-    
-    # look for mp3s using the filesystem backend
-    require MP3::Find::Filesystem;
-    my $finder = MP3::Find::Filesystem->new;
-    for my $mp3 ($finder->find_mp3s(dir => \@dirs, no_format => 1)) {
-        # see if the file has been modified since it was first put into the db
-        $mp3->{mtime} = (stat($mp3->{FILENAME}))[9];
-        $mtime_sth->execute($mp3->{FILENAME});
-        my $records = $mtime_sth->fetchall_arrayref;
-        
-        warn "Multiple records for $$mp3{FILENAME}\n" if @$records > 1;
-        
-        if (@$records == 0) {
-            $insert_sth->execute(map { $mp3->{$$_[0]} } @COLUMNS);
-            $status_callback->(A => $$mp3{FILENAME});
-            $count++;
-        } elsif ($mp3->{mtime} > $$records[0][0]) {
-            # the mp3 file is newer than its record
-            $update_sth->execute((map { $mp3->{$$_[0]} } @COLUMNS), $mp3->{FILENAME});
-            $status_callback->(U => $$mp3{FILENAME});
-            $count++;
-        }
-    }
-    
-    # SQLite buggy driver
-    _sqlite_workaround($mtime_sth, $insert_sth, $update_sth);
-    
-    return $count;
-}
-
-=head2 sync
-
-    my $count = $finder->sync({ dsn => $DSN });
-
-Removes entries from the database that refer to files that no longer
-exist in the filesystem. Returns the count of how many records were
-removed.
-
-=cut
-
-sub sync {
-    my $self = shift;
-    my $args = shift;
-
-    my $dbh = _get_dbh($args) or croak "Please provide a DBI database handle, DSN, or SQLite database filename";
-    
-    my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK;
-
-    my $select_sth = $dbh->prepare('SELECT FILENAME FROM mp3');
-    my $delete_sth = $dbh->prepare('DELETE FROM mp3 WHERE FILENAME = ?');
-    
-    # the number of records removed
-    my $count = 0;
-    
-    $select_sth->execute;
-    while (my ($filename) = $select_sth->fetchrow_array) {
-        unless (-e $filename) {
-            $delete_sth->execute($filename);
-            $status_callback->(D => $filename);
-            $count++;
-        }
-    }
-    
-    # SQLite buggy driver
-    _sqlite_workaround($select_sth, $delete_sth);
-    
-    return $count;    
-}
-
-=head2 sync_db (DEPRECATED)
-
-    my $count = $finder->sync_db($db_filename);
-
-Removes entries from the database that refer to files that no longer
-exist in the filesystem. Returns the count of how many records were
-removed.
-
-=cut
-
-sub sync_db {
-    my $self = shift;
-    my $db_file = shift or croak "Need the name of the databse to sync";
-
-    my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK;
-
-    my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1});
-    my $select_sth = $dbh->prepare('SELECT FILENAME FROM mp3');
-    my $delete_sth = $dbh->prepare('DELETE FROM mp3 WHERE FILENAME = ?');
-    
-    # the number of records removed
-    my $count = 0;
-    
-    $select_sth->execute;
-    while (my ($filename) = $select_sth->fetchrow_array) {
-        unless (-e $filename) {
-            $delete_sth->execute($filename);
-            $status_callback->(D => $filename);
-            $count++;
-        }
-    }
-    
-    return $count;    
-}
-
-=head2 destroy
-
-    $finder->destroy({ db_file => $filename });
-
-Permanantly removes the database. Unlike the other utility methods,
-this one can only act on SQLite C<db_file> filenames, and not DSNs
-or database handles.
-
-=cut
-
-sub destroy {
-    my $self = shift;
-    my $args = shift;
-
-    # XXX: this method only knows how to deal with SQLite files;
-    # is there a way to DROP a database given a $dbh?
-
-    my $db_file = $args->{db_file} or croak "Need a db_file argument";
-
-    # actually delete the thing
-    unlink $db_file;
-}
-
-=head2 destroy_db (DEPRECATED)
-
-    $finder->destroy_db($db_filename);
-
-Permanantly removes the database.
-
-=cut
-
-sub destroy_db {
-    my $self = shift;
-    my $db_file = shift or croak "Need the name of a database to destroy";
-    unlink $db_file;
-}
-
 
 sub search {
@@ -551,7 +15,5 @@
     my ($query, $dirs, $sort, $options) = @_;
     
-    croak 'Need a database name to search (set "db_file" in the call to find_mp3s)' unless $$options{db_file};
-    
-    my $dbh = _get_dbh($options);
+    my $dbh = DBI->connect("dbi:SQLite:dbname=$$options{db_file}", '', '');
     
     # use the 'LIKE' operator to ignore case
@@ -585,31 +47,2 @@
 # module return
 1;
-
-=head1 TODO
-
-Store/search ID3v2 tags
-
-Driver classes to handle database dependent tasks?
-
-=head1 SEE ALSO
-
-L<MP3::Find>, L<MP3::Find::Filesystem>, L<mp3db>
-
-=head1 AUTHOR
-
-Peter Eichman <peichman@cpan.org>
-
-=head1 THANKS
-
-Thanks to Matt Dietrich <perl@rainboxx.de> for suggesting having an 
-option to just update specific files instead of doing a (longer) full
-search.
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (c) 2006 by Peter Eichman. All rights reserved.
-
-This program is free software; you can redistribute it and/or
-modify it under the same terms as Perl itself.
-
-=cut
Index: trunk/lib/MP3/Find/Filesystem.pm
===================================================================
--- trunk/lib/MP3/Find/Filesystem.pm	(revision 43)
+++ trunk/lib/MP3/Find/Filesystem.pm	(revision 1)
@@ -9,17 +9,4 @@
 use MP3::Info;
 use Scalar::Util qw(looks_like_number);
-
-use MP3::Find::Util qw(get_mp3_metadata);
-
-eval {
-    require Sort::Key;
-    Sort::Key->import(qw(multikeysorter));
-    require Sort::Key::Natural;
-};
-my $USE_SORT_KEY = $@ ? 0 : 1;
-
-
-eval { require MP3::Tag };
-my $CAN_USE_ID3V2 = $@ ? 0 : 1;
 
 use_winamp_genres();
@@ -43,48 +30,22 @@
     }
     
-    if ($$options{exclude_path}) {
-        my $ref = ref $$options{exclude_path};
-        if ($ref eq 'ARRAY') {
-            $$options{exclude_path} = '(' . join('|', @{ $$options{exclude_path} }) . ')';
-        }
-        unless ($ref eq 'Regexp') {
-            $$options{exclude_path} = qr[$$options{exclude_path}];
-        }
-    }
-    
-    if ($$options{use_id3v2} and not $CAN_USE_ID3V2) {
-	# they want to use ID3v2, but don't have MP3::Tag
-	warn "MP3::Tag is required to search ID3v2 tags\n";
-    }
-	
     # run the actual find
     my @results;
-    find(sub { match_mp3($File::Find::name, $query, \@results, $options) }, $_) foreach @$dirs;
+    find(sub { match_mp3($File::Find::name, $query, \@results) }, $_) foreach @$dirs;
     
     # sort the results
     if (@$sort) {
-	if ($USE_SORT_KEY) {
-	    # use Sort::Key to do a (hopefully!) faster sort
-	    #TODO: profile this; at first glance, it doesn't actually seem to be any faster
-	    #warn "Using Sort::Key";
-	    my $sorter = multikeysorter(
-		sub { my $info = $_; map { $info->{uc $_} } @$sort },
-		map { 'natural' } @$sort
-	    );
-	    @results = $sorter->(@results);
-	} else {
-	    @results = sort {
-		my $compare;
-		foreach (map { uc } @$sort) {
-		    # use Scalar::Util to do the right sort of comparison
-		    $compare = (looks_like_number($a->{$_}) && looks_like_number($b->{$_})) ?
-			$a->{$_} <=> $b->{$_} :
-			$a->{$_} cmp $b->{$_};
-		    # we found a field they differ on
-		    last if $compare;
-		}
-		return $compare;
-	    } @results;
-	}
+        @results = sort {
+            my $compare;
+            foreach (map { uc } @$sort) {
+                # use Scalar::Util to do the right sort of comparison
+                $compare = (looks_like_number($a->{$_}) && looks_like_number($b->{$_})) ?
+                    $a->{$_} <=> $b->{$_} :
+                    $a->{$_} cmp $b->{$_};
+                # we found a field they differ on
+                last if $compare;
+            }
+            return $compare;
+        } @results;
     }
     
@@ -93,16 +54,12 @@
 
 sub match_mp3 {
-    my ($filename, $query, $results, $options) = @_;
+    my ($filename, $query, $results) = @_;
     
     return unless $filename =~ m{[^/]\.mp3$};
-    if ($$options{exclude_path}) {
-        return if $filename =~ $$options{exclude_path};
-    }
-
-    my $mp3 = get_mp3_metadata({
-	filename  => $filename,
-	use_id3v2 => $options->{use_id3v2},
-    });
-
+    my $mp3 = {
+        FILENAME => $filename,
+        %{ get_mp3tag($filename)  || {} },
+        %{ get_mp3info($filename) || {} },
+    };
     for my $field (keys(%{ $query })) {
         my $value = $mp3->{uc($field)};
@@ -116,73 +73,2 @@
 # module return
 1;
-
-=head1 NAME
-
-MP3::Find::Filesystem - File::Find-based backend to MP3::Find
-
-=head1 SYNOPSIS
-
-    use MP3::Find::Filesystem;
-    my $finder = MP3::Find::Filesystem->new;
-    
-    my @mp3s = $finder->find_mp3s(
-        dir => '/home/peter/music',
-        query => {
-            artist => 'ilyaimy',
-            album  => 'myxomatosis',
-        },
-        ignore_case => 1,
-    );
-
-=head1 REQUIRES
-
-L<File::Find>, L<MP3::Info>, L<Scalar::Util>
-
-L<MP3::Tag> is also needed if you want to search using ID3v2 tags.
-
-=head1 DESCRIPTION
-
-This module implements the C<search> method from L<MP3::Find::Base>
-using a L<File::Find> based search of the local filesystem.
-
-=head2 Special Options
-
-=over
-
-=item C<exclude_path>
-
-Scalar or arrayref; any file whose name matches any of these paths
-will be skipped.
-
-=item C<use_id3v2>
-
-Boolean, defaults to false. If set to true, MP3::Find::Filesystem will
-use L<MP3::Tag> to get the ID3v2 tag for each file. You can then search
-for files by their ID3v2 data, using the four-character frame names. 
-This isn't very useful if you are just search by artist or title, but if,
-for example, you have made use of the C<TOPE> ("Orignal Performer") frame,
-you could search for all the cover songs in your collection:
-
-    $finder->find_mp3s(query => { tope => '.' });
-
-As with the basic query keys, ID3v2 query keys are converted to uppercase
-internally.
-
-=back
-
-=head1 SEE ALSO
-
-L<MP3::Find>, L<MP3::Find::DB>
-
-=head1 AUTHOR
-
-Peter Eichman <peichman@cpan.org>
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (c) 2006 by Peter Eichman. All rights reserved.
-
-This program is free software; you can redistribute it and/or
-modify it under the same terms as Perl itself.
-
-=cut
Index: trunk/lib/MP3/Find/Util.pm
===================================================================
--- trunk/lib/MP3/Find/Util.pm	(revision 43)
+++ trunk/lib/MP3/Find/Util.pm	(revision 1)
@@ -7,11 +7,5 @@
 use vars qw(@EXPORT_OK);
 
-@EXPORT_OK = qw(build_query get_mp3_metadata);
-
-use Carp;
-use MP3::Info;
-
-eval { require MP3::Tag };
-my $CAN_USE_ID3V2 = $@ ? 0 : 1;
+@EXPORT_OK = qw(build_query);
 
 sub build_query {
@@ -44,39 +38,4 @@
 }
 
-sub get_mp3_metadata {
-    my $args = shift;
-
-    my $filename = $args->{filename} or croak "get_mp3_metadata needs a 'filename' argument";
-    
-    my $mp3 = {
-        FILENAME => $filename,
-        %{ get_mp3tag($filename)  || {} },
-        %{ get_mp3info($filename) || {} },
-    };
-    
-    if ($CAN_USE_ID3V2 and $args->{use_id3v2}) {
-	# add ID3v2 tag info, if present
-	my $mp3_tags = MP3::Tag->new($filename);
-	unless (defined $mp3_tags) {
-	    warn "Can't get MP3::Tag object for $filename\n";
-	} else {
-	    $mp3_tags->get_tags;
-	    if (my $id3v2 = $mp3_tags->{ID3v2}) {
-		for my $frame_id (keys %{ $id3v2->get_frame_ids }) {
-		    my ($info) = $id3v2->get_frame($frame_id);
-		    if (ref $info eq 'HASH') {
-			# use the "Text" value as the value for this frame, if present
-			$mp3->{$frame_id} = $info->{Text} if exists $info->{Text};
-		    } else {
-			$mp3->{$frame_id} = $info;
-		    }
-		}
-	    }
-	}
-    }
-
-    return $mp3;
-}
-
 # module return
 1;
