Changeset 30 in mp3-find for trunk/lib/MP3
- Timestamp:
- 05/21/06 17:32:39 (18 years ago)
- Location:
- trunk/lib/MP3
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/lib/MP3/Find.pm
r25 r30 9 9 use Carp; 10 10 11 $VERSION = '0.0 5';11 $VERSION = '0.06'; 12 12 13 13 @EXPORT = qw(find_mp3s); -
trunk/lib/MP3/Find/DB.pm
r29 r30 50 50 }; 51 51 52 # TODO: use DSNs instead of SQLite db names53 sub search {54 my $self = shift;55 my ($query, $dirs, $sort, $options) = @_;56 57 croak 'Need a database name to search (set "db_file" in the call to find_mp3s)' unless $$options{db_file};58 59 my $dbh = DBI->connect("dbi:SQLite:dbname=$$options{db_file}", '', '', {RaiseError => 1});60 61 # use the 'LIKE' operator to ignore case62 my $op = $$options{ignore_case} ? 'LIKE' : '=';63 64 # add the SQL '%' wildcard to match substrings65 unless ($$options{exact_match}) {66 for my $value (values %$query) {67 $value = [ map { "%$_%" } @$value ];68 }69 }70 71 my ($where, @bind) = $sql->where(72 { map { $_ => { $op => $query->{$_} } } keys %$query },73 ( @$sort ? [ map { uc } @$sort ] : () ),74 );75 76 my $select = "SELECT * FROM mp3 $where";77 78 my $sth = $dbh->prepare($select);79 $sth->execute(@bind);80 81 my @results;82 while (my $row = $sth->fetchrow_hashref) {83 push @results, $row;84 }85 86 return @results;87 }88 89 # TODO: convert to using DSNs instead of hardcoded SQLite connections90 # TODO: extended table for ID3v2 data91 sub create_db {92 my $self = shift;93 my $db_file = shift or croak "Need a name for the database I'm about to create";94 my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1});95 $dbh->do('CREATE TABLE mp3 (' . join(',', map { "$$_[0] $$_[1]" } @COLUMNS) . ')');96 }97 98 # this is update_db and update_files (from Matt Dietrich) rolled into one99 =head2 update100 101 my $count = $finder->update({102 dsn => 'dbi:SQLite:dbname=mp3.db',103 files => \@filenames,104 dirs => [qw(music downloads/mp3)],105 });106 107 Compares the files in the C<files> list plus any MP3s found by searching108 in C<dirs> to their records in the database pointed to by C<dsn>. If the109 files found have been updated since they have been recorded in the database110 (or if they are not in the database), they are updated (or added).111 112 =cut113 114 sub update {115 my $self = shift;116 my $args = shift;117 118 my $dsn = $args->{dsn} or croak "Need a DSN to connect to";119 my @dirs = $args->{dirs}120 ? ref $args->{dirs} eq 'ARRAY'121 ? @{ $args->{dirs} }122 : ($args->{dirs})123 : ();124 125 my @files = $args->{files}126 ? ref $args->{files} eq 'ARRAY'127 ? @{ $args->{files} }128 : ($args->{files})129 : ();130 131 my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK;132 133 my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1});134 my $mtime_sth = $dbh->prepare('SELECT mtime FROM mp3 WHERE FILENAME = ?');135 my $insert_sth = $dbh->prepare(136 'INSERT INTO mp3 (' .137 join(',', map { $$_[0] } @COLUMNS) .138 ') VALUES (' .139 join(',', map { '?' } @COLUMNS) .140 ')'141 );142 my $update_sth = $dbh->prepare(143 'UPDATE mp3 SET ' .144 join(',', map { "$$_[0] = ?" } @COLUMNS) .145 ' WHERE FILENAME = ?'146 );147 148 my $count = 0; # the number of records added or updated149 my @mp3s; # metadata for mp3s found150 151 # look for mp3s using the filesystem backend if we have dirs to search in152 if (@dirs) {153 require MP3::Find::Filesystem;154 my $finder = MP3::Find::Filesystem->new;155 unshift @mp3s, $finder->find_mp3s(dir => \@dirs, no_format => 1);156 }157 158 # get the metadata on specific files159 unshift @mp3s, map { get_mp3_metadata({ filename => $_ }) } @files;160 161 # check each file against its record in the database162 for my $mp3 (@mp3s) {163 # see if the file has been modified since it was first put into the db164 $mp3->{mtime} = (stat($mp3->{FILENAME}))[9];165 $mtime_sth->execute($mp3->{FILENAME});166 my $records = $mtime_sth->fetchall_arrayref;167 168 warn "Multiple records for $$mp3{FILENAME}\n" if @$records > 1;169 170 if (@$records == 0) {171 # we are adding a record172 $insert_sth->execute(map { $mp3->{$$_[0]} } @COLUMNS);173 $status_callback->(A => $$mp3{FILENAME});174 $count++;175 } elsif ($mp3->{mtime} > $$records[0][0]) {176 # the mp3 file is newer than its record177 $update_sth->execute((map { $mp3->{$$_[0]} } @COLUMNS), $mp3->{FILENAME});178 $status_callback->(U => $$mp3{FILENAME});179 $count++;180 }181 }182 183 # SQLite specific code:184 # as a workaround for the 'closing dbh with active staement handles warning185 # (see http://rt.cpan.org/Ticket/Display.html?id=9643#txn-120724)186 foreach ($mtime_sth, $insert_sth, $update_sth) {187 $_->{RaiseError} = 0; # don't die on error188 $_->{PrintError} = 0; # ...and don't even say anything189 $_->{Active} = 1;190 $_->finish;191 }192 193 return $count;194 }195 196 197 198 sub update_db {199 my $self = shift;200 my $db_file = shift or croak "Need the name of the database to update";201 my $dirs = shift;202 203 my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK;204 205 my @dirs = ref $dirs eq 'ARRAY' ? @$dirs : ($dirs);206 207 my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1});208 my $mtime_sth = $dbh->prepare('SELECT mtime FROM mp3 WHERE FILENAME = ?');209 my $insert_sth = $dbh->prepare(210 'INSERT INTO mp3 (' .211 join(',', map { $$_[0] } @COLUMNS) .212 ') VALUES (' .213 join(',', map { '?' } @COLUMNS) .214 ')'215 );216 my $update_sth = $dbh->prepare(217 'UPDATE mp3 SET ' .218 join(',', map { "$$_[0] = ?" } @COLUMNS) .219 ' WHERE FILENAME = ?'220 );221 222 # the number of records added or updated223 my $count = 0;224 225 # look for mp3s using the filesystem backend226 require MP3::Find::Filesystem;227 my $finder = MP3::Find::Filesystem->new;228 for my $mp3 ($finder->find_mp3s(dir => \@dirs, no_format => 1)) {229 # see if the file has been modified since it was first put into the db230 $mp3->{mtime} = (stat($mp3->{FILENAME}))[9];231 $mtime_sth->execute($mp3->{FILENAME});232 my $records = $mtime_sth->fetchall_arrayref;233 234 warn "Multiple records for $$mp3{FILENAME}\n" if @$records > 1;235 236 if (@$records == 0) {237 $insert_sth->execute(map { $mp3->{$$_[0]} } @COLUMNS);238 $status_callback->(A => $$mp3{FILENAME});239 $count++;240 } elsif ($mp3->{mtime} > $$records[0][0]) {241 # the mp3 file is newer than its record242 $update_sth->execute((map { $mp3->{$$_[0]} } @COLUMNS), $mp3->{FILENAME});243 $status_callback->(U => $$mp3{FILENAME});244 $count++;245 }246 }247 248 # as a workaround for the 'closing dbh with active staement handles warning249 # (see http://rt.cpan.org/Ticket/Display.html?id=9643#txn-120724)250 foreach ($mtime_sth, $insert_sth, $update_sth) {251 $_->{RaiseError} = 0; # don't die on error252 $_->{PrintError} = 0; # ...and don't even say anything253 $_->{Active} = 1;254 $_->finish;255 }256 257 return $count;258 }259 260 # TODO: use DSNs instead of SQLite db names261 sub sync_db {262 my $self = shift;263 my $db_file = shift or croak "Need the name of the databse to sync";264 265 my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK;266 267 my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1});268 my $select_sth = $dbh->prepare('SELECT FILENAME FROM mp3');269 my $delete_sth = $dbh->prepare('DELETE FROM mp3 WHERE FILENAME = ?');270 271 # the number of records removed272 my $count = 0;273 274 $select_sth->execute;275 while (my ($filename) = $select_sth->fetchrow_array) {276 unless (-e $filename) {277 $delete_sth->execute($filename);278 $status_callback->(D => $filename);279 $count++;280 }281 }282 283 return $count;284 }285 286 # TODO: use DSNs instead of SQLite db names (this might get funky)287 sub destroy_db {288 my $self = shift;289 my $db_file = shift or croak "Need the name of a database to destory";290 unlink $db_file;291 }292 293 # module return294 1;295 296 52 =head1 NAME 297 53 … … 408 164 Creates a SQLite database in the file named c<$db_filename>. 409 165 166 =cut 167 168 # TODO: convert to using DSNs instead of hardcoded SQLite connections 169 # TODO: extended table for ID3v2 data 170 sub create_db { 171 my $self = shift; 172 my $db_file = shift or croak "Need a name for the database I'm about to create"; 173 my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1}); 174 $dbh->do('CREATE TABLE mp3 (' . join(',', map { "$$_[0] $$_[1]" } @COLUMNS) . ')'); 175 } 176 177 =head2 update 178 179 my $count = $finder->update({ 180 dsn => 'dbi:SQLite:dbname=mp3.db', 181 files => \@filenames, 182 dirs => \@dirs, 183 }); 184 185 Compares the files in the C<files> list plus any MP3s found by searching 186 in C<dirs> to their records in the database pointed to by C<dsn>. If the 187 files found have been updated since they have been recorded in the database 188 (or if they are not in the database), they are updated (or added). 189 190 =cut 191 192 # this is update_db and update_files (from Matt Dietrich) rolled into one 193 sub update { 194 my $self = shift; 195 my $args = shift; 196 197 my $dsn = $args->{dsn} or croak "Need a DSN to connect to"; 198 my @dirs = $args->{dirs} 199 ? ref $args->{dirs} eq 'ARRAY' 200 ? @{ $args->{dirs} } 201 : ($args->{dirs}) 202 : (); 203 204 my @files = $args->{files} 205 ? ref $args->{files} eq 'ARRAY' 206 ? @{ $args->{files} } 207 : ($args->{files}) 208 : (); 209 210 my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK; 211 212 my $dbh = DBI->connect($dsn, '', '', {RaiseError => 1}); 213 my $mtime_sth = $dbh->prepare('SELECT mtime FROM mp3 WHERE FILENAME = ?'); 214 my $insert_sth = $dbh->prepare( 215 'INSERT INTO mp3 (' . 216 join(',', map { $$_[0] } @COLUMNS) . 217 ') VALUES (' . 218 join(',', map { '?' } @COLUMNS) . 219 ')' 220 ); 221 my $update_sth = $dbh->prepare( 222 'UPDATE mp3 SET ' . 223 join(',', map { "$$_[0] = ?" } @COLUMNS) . 224 ' WHERE FILENAME = ?' 225 ); 226 227 my $count = 0; # the number of records added or updated 228 my @mp3s; # metadata for mp3s found 229 230 # look for mp3s using the filesystem backend if we have dirs to search in 231 if (@dirs) { 232 require MP3::Find::Filesystem; 233 my $finder = MP3::Find::Filesystem->new; 234 unshift @mp3s, $finder->find_mp3s(dir => \@dirs, no_format => 1); 235 } 236 237 # get the metadata on specific files 238 unshift @mp3s, map { get_mp3_metadata({ filename => $_ }) } @files; 239 240 # check each file against its record in the database 241 for my $mp3 (@mp3s) { 242 # see if the file has been modified since it was first put into the db 243 $mp3->{mtime} = (stat($mp3->{FILENAME}))[9]; 244 $mtime_sth->execute($mp3->{FILENAME}); 245 my $records = $mtime_sth->fetchall_arrayref; 246 247 warn "Multiple records for $$mp3{FILENAME}\n" if @$records > 1; 248 249 if (@$records == 0) { 250 # we are adding a record 251 $insert_sth->execute(map { $mp3->{$$_[0]} } @COLUMNS); 252 $status_callback->(A => $$mp3{FILENAME}); 253 $count++; 254 } elsif ($mp3->{mtime} > $$records[0][0]) { 255 # the mp3 file is newer than its record 256 $update_sth->execute((map { $mp3->{$$_[0]} } @COLUMNS), $mp3->{FILENAME}); 257 $status_callback->(U => $$mp3{FILENAME}); 258 $count++; 259 } 260 } 261 262 # SQLite specific code: 263 # as a workaround for the 'closing dbh with active staement handles warning 264 # (see http://rt.cpan.org/Ticket/Display.html?id=9643#txn-120724) 265 foreach ($mtime_sth, $insert_sth, $update_sth) { 266 $_->{RaiseError} = 0; # don't die on error 267 $_->{PrintError} = 0; # ...and don't even say anything 268 $_->{Active} = 1; 269 $_->finish; 270 } 271 272 return $count; 273 } 274 410 275 =head2 update_db 411 276 … … 416 281 from those files to the database. If a file already has a record 417 282 in the database, then it will only be updated if it has been modified 418 sinc ethe last time C<update_db> was run. 283 since the last time C<update_db> was run. 284 285 =cut 286 287 sub update_db { 288 my $self = shift; 289 my $db_file = shift or croak "Need the name of the database to update"; 290 my $dirs = shift; 291 292 my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK; 293 294 my @dirs = ref $dirs eq 'ARRAY' ? @$dirs : ($dirs); 295 296 my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1}); 297 my $mtime_sth = $dbh->prepare('SELECT mtime FROM mp3 WHERE FILENAME = ?'); 298 my $insert_sth = $dbh->prepare( 299 'INSERT INTO mp3 (' . 300 join(',', map { $$_[0] } @COLUMNS) . 301 ') VALUES (' . 302 join(',', map { '?' } @COLUMNS) . 303 ')' 304 ); 305 my $update_sth = $dbh->prepare( 306 'UPDATE mp3 SET ' . 307 join(',', map { "$$_[0] = ?" } @COLUMNS) . 308 ' WHERE FILENAME = ?' 309 ); 310 311 # the number of records added or updated 312 my $count = 0; 313 314 # look for mp3s using the filesystem backend 315 require MP3::Find::Filesystem; 316 my $finder = MP3::Find::Filesystem->new; 317 for my $mp3 ($finder->find_mp3s(dir => \@dirs, no_format => 1)) { 318 # see if the file has been modified since it was first put into the db 319 $mp3->{mtime} = (stat($mp3->{FILENAME}))[9]; 320 $mtime_sth->execute($mp3->{FILENAME}); 321 my $records = $mtime_sth->fetchall_arrayref; 322 323 warn "Multiple records for $$mp3{FILENAME}\n" if @$records > 1; 324 325 if (@$records == 0) { 326 $insert_sth->execute(map { $mp3->{$$_[0]} } @COLUMNS); 327 $status_callback->(A => $$mp3{FILENAME}); 328 $count++; 329 } elsif ($mp3->{mtime} > $$records[0][0]) { 330 # the mp3 file is newer than its record 331 $update_sth->execute((map { $mp3->{$$_[0]} } @COLUMNS), $mp3->{FILENAME}); 332 $status_callback->(U => $$mp3{FILENAME}); 333 $count++; 334 } 335 } 336 337 # as a workaround for the 'closing dbh with active staement handles warning 338 # (see http://rt.cpan.org/Ticket/Display.html?id=9643#txn-120724) 339 foreach ($mtime_sth, $insert_sth, $update_sth) { 340 $_->{RaiseError} = 0; # don't die on error 341 $_->{PrintError} = 0; # ...and don't even say anything 342 $_->{Active} = 1; 343 $_->finish; 344 } 345 346 return $count; 347 } 419 348 420 349 =head2 sync_db … … 426 355 removed. 427 356 357 =cut 358 359 # TODO: use DSNs instead of SQLite db names 360 sub sync_db { 361 my $self = shift; 362 my $db_file = shift or croak "Need the name of the databse to sync"; 363 364 my $status_callback = $self->{status_callback} || $DEFAULT_STATUS_CALLBACK; 365 366 my $dbh = DBI->connect("dbi:SQLite:dbname=$db_file", '', '', {RaiseError => 1}); 367 my $select_sth = $dbh->prepare('SELECT FILENAME FROM mp3'); 368 my $delete_sth = $dbh->prepare('DELETE FROM mp3 WHERE FILENAME = ?'); 369 370 # the number of records removed 371 my $count = 0; 372 373 $select_sth->execute; 374 while (my ($filename) = $select_sth->fetchrow_array) { 375 unless (-e $filename) { 376 $delete_sth->execute($filename); 377 $status_callback->(D => $filename); 378 $count++; 379 } 380 } 381 382 return $count; 383 } 384 428 385 =head2 destroy_db 429 386 … … 431 388 432 389 Permanantly removes the database. 390 391 =cut 392 393 # TODO: use DSNs instead of SQLite db names (this might get funky) 394 sub destroy_db { 395 my $self = shift; 396 my $db_file = shift or croak "Need the name of a database to destory"; 397 unlink $db_file; 398 } 399 400 401 # TODO: use DSNs instead of SQLite db names 402 sub search { 403 my $self = shift; 404 my ($query, $dirs, $sort, $options) = @_; 405 406 croak 'Need a database name to search (set "db_file" in the call to find_mp3s)' unless $$options{db_file}; 407 408 my $dbh = DBI->connect("dbi:SQLite:dbname=$$options{db_file}", '', '', {RaiseError => 1}); 409 410 # use the 'LIKE' operator to ignore case 411 my $op = $$options{ignore_case} ? 'LIKE' : '='; 412 413 # add the SQL '%' wildcard to match substrings 414 unless ($$options{exact_match}) { 415 for my $value (values %$query) { 416 $value = [ map { "%$_%" } @$value ]; 417 } 418 } 419 420 my ($where, @bind) = $sql->where( 421 { map { $_ => { $op => $query->{$_} } } keys %$query }, 422 ( @$sort ? [ map { uc } @$sort ] : () ), 423 ); 424 425 my $select = "SELECT * FROM mp3 $where"; 426 427 my $sth = $dbh->prepare($select); 428 $sth->execute(@bind); 429 430 my @results; 431 while (my $row = $sth->fetchrow_hashref) { 432 push @results, $row; 433 } 434 435 return @results; 436 } 437 438 # module return 439 1; 433 440 434 441 =head1 TODO
Note: See TracChangeset
for help on using the changeset viewer.