Index: trunk/lib/MP3/Find/DB.pm
===================================================================
--- trunk/lib/MP3/Find/DB.pm	(revision 28)
+++ trunk/lib/MP3/Find/DB.pm	(revision 29)
@@ -9,4 +9,6 @@
 use DBI;
 use SQL::Abstract;
+
+use MP3::Find::Util qw(get_mp3_metadata);
 
 my $sql = SQL::Abstract->new;
@@ -48,4 +50,5 @@
 };
 
+# TODO: use DSNs instead of SQLite db names
 sub search {
     my $self = shift;
@@ -84,4 +87,6 @@
 }
 
+# TODO: convert to using DSNs instead of hardcoded SQLite connections
+# TODO: extended table for ID3v2 data
 sub create_db {
     my $self = shift;
@@ -90,4 +95,104 @@
     $dbh->do('CREATE TABLE mp3 (' . join(',', map { "$$_[0] $$_[1]" } @COLUMNS) . ')');
 }
+
+# this is update_db and update_files (from Matt Dietrich) rolled into one
+=head2 update
+
+    my $count = $finder->update({
+	dsn   => 'dbi:SQLite:dbname=mp3.db',
+	files => \@filenames,
+	dirs  => [qw(music downloads/mp3)],
+    });
+
+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).
+
+=cut
+
+sub update {
+    my $self = shift;
+    my $args = shift;
+
+    my $dsn   = $args->{dsn} or croak "Need a DSN to connect to";
+    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 $dbh = DBI->connect($dsn, '', '', {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 = ?'
+    );
+    
+    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 specific code:
+    # 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 ($mtime_sth, $insert_sth, $update_sth) {
+        $_->{RaiseError} = 0;  # don't die on error
+        $_->{PrintError} = 0;  # ...and don't even say anything
+        $_->{Active} = 1;
+        $_->finish;
+    }
+    
+    return $count;
+}
+
+
 
 sub update_db {
@@ -129,5 +234,4 @@
         warn "Multiple records for $$mp3{FILENAME}\n" if @$records > 1;
         
-        #TODO: maybe print status updates somewhere else?
         if (@$records == 0) {
             $insert_sth->execute(map { $mp3->{$$_[0]} } @COLUMNS);
@@ -154,4 +258,5 @@
 }
 
+# TODO: use DSNs instead of SQLite db names
 sub sync_db {
     my $self = shift;
@@ -179,4 +284,5 @@
 }
 
+# TODO: use DSNs instead of SQLite db names (this might get funky)
 sub destroy_db {
     my $self = shift;
@@ -342,4 +448,9 @@
 Peter Eichman <peichman@cpan.org>
 
+=head1 THANKS
+
+Thanks to Matt Dietrich for suggesting having an option to just 
+update specific files instead of doing a (longer) full search.
+
 =head1 COPYRIGHT AND LICENSE
 
Index: trunk/lib/MP3/Find/Filesystem.pm
===================================================================
--- trunk/lib/MP3/Find/Filesystem.pm	(revision 28)
+++ trunk/lib/MP3/Find/Filesystem.pm	(revision 29)
@@ -9,4 +9,6 @@
 use MP3::Info;
 use Scalar::Util qw(looks_like_number);
+
+use MP3::Find::Util qw(get_mp3_metadata);
 
 eval {
@@ -97,31 +99,9 @@
         return if $filename =~ $$options{exclude_path};
     }
-    
-    my $mp3 = {
-        FILENAME => $filename,
-        %{ get_mp3tag($filename)  || {} },
-        %{ get_mp3info($filename) || {} },
-    };
-    
-    if ($CAN_USE_ID3V2 and $$options{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;
-		    }
-		}
-	    }
-	}
-    }
+
+    my $mp3 = get_mp3_metadata({
+	filename  => $filename,
+	use_id3v2 => $options->{use_id3v2},
+    });
 
     for my $field (keys(%{ $query })) {
Index: trunk/lib/MP3/Find/Util.pm
===================================================================
--- trunk/lib/MP3/Find/Util.pm	(revision 28)
+++ trunk/lib/MP3/Find/Util.pm	(revision 29)
@@ -7,5 +7,11 @@
 use vars qw(@EXPORT_OK);
 
-@EXPORT_OK = qw(build_query);
+@EXPORT_OK = qw(build_query get_mp3_metadata);
+
+use Carp;
+use MP3::Info;
+
+eval { require MP3::Tag };
+my $CAN_USE_ID3V2 = $@ ? 0 : 1;
 
 sub build_query {
@@ -38,4 +44,39 @@
 }
 
+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;
