use strict;
use warnings;
use DBI;
-use ID3FS::File;
+use ID3FS::AudioFile;
use Cwd;
our $SCHEMA_VERSION=1;
bless($self,$class);
$self->{me}=shift;
- $self->{dbpath}=shift;
+ $self->{verbose}=shift;
+ my $init=shift;
+ my $dbpath=shift;
$self->{base}=shift;
- $self->{fallbackdir}=shift;
+ my $fallbackdir=shift;
- if(!defined($self->{base}) &&
- defined($self->{fallbackdir}) &&
- -d $self->{fallbackdir})
- {
- $self->{base}=$self->{fallbackdir};
- }
- $self->{dbpath}="$self->{base}/$dbfile" unless(defined($self->{dbpath}));
+ $dbpath=$self->find_db($init, $dbpath, $fallbackdir);
+ return undef unless($dbpath);
$self->{absbase}=Cwd::abs_path($self->{base});
- my $connectstr="dbi:SQLite:dbname=$self->{dbpath}";
+ my $connectstr="dbi:SQLite:dbname=$dbpath";
my ($user, $pass)=("", "");
if($self->{postgres})
{
$user="ianb";
$pass="foo";
}
- my $exists=-f $self->{dbpath};
+ my $exists=-f $dbpath;
$self->{dbh}=DBI->connect($connectstr, $user, $pass,
{ AutoCommit=>1 } );
unless(defined($self->{dbh}))
{
$self->create();
}
-
+ $self->enable_foreign_keys();
return $self;
}
+sub find_db
+{
+ my($self, $init, $dbpath, $fallbackdir)=@_;
+ my $file=undef;
+ my $base=undef;
+ if(defined($dbpath))
+ {
+ $file=$dbpath;
+ }
+ if(defined ($self->{base}))
+ {
+ $file="$self->{base}/$dbfile" unless defined($file);
+ $base=$self->{base};
+ }
+ elsif(defined($fallbackdir) && -d $fallbackdir)
+ {
+ my $path=Cwd::abs_path($fallbackdir);
+ do
+ {
+ $file="$path/$dbfile";
+ $base=$path;
+ $path=~s/(.*)\/.*/$1/;
+ }
+ while(! -f $file && length($path) && -d $path);
+ if(! -f $file)
+ {
+ $file="$fallbackdir/$dbfile";
+ $base=$fallbackdir;
+ }
+ }
+ if(!-f $file && !$init)
+ {
+ print "$self->{me}: db not found at $file\n";
+ return undef;
+ }
+ $self->{base}=$base;
+ return $file;
+}
+
sub create
{
my($self,$name)=@_;
}
}
+sub enable_foreign_keys
+{
+ my $self=shift;
+ $self->cmd("PRAGMA foreign_keys = ON");
+}
+
sub last_update
{
my($self, $newval)=@_;
my $main_sql_end=")\n\tGROUP BY t2.name;";
while(my $constraint=shift @constraints)
{
- print "CONSTRAINT: $constraint->{name}\n";
my $cid=$constraint->{id};
push(@ids, $cid);
}
- @ids = map( { "\"$_\""; } @ids) unless($self->{postgres});
+ @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
my $tagstr=join(", ", @ids);
my $sql = ($main_sql_start . $tagstr .
$main_sql_mid . $tagstr .
sub tag_values
{
- my($self, $tag)=@_;
- my $sql=("SELECT DISTINCT tagvals.name FROM tags\n" .
- "INNER JOIN tags_x_tagvals ON tags.id=tags_x_tagvals.tags_id\n" .
- "INNER JOIN tagvals ON tagvals.id=tags_x_tagvals.tagvals_id\n" .
- "WHERE tags.name=?");
- my $tags=$self->cmd_rows($sql, $tag);
- return(map { $_->[0]; } @$tags);
+ my($self, $tagid)=@_;
+ my $sql=("SELECT DISTINCT tagvals.name FROM tagvals\n" .
+ "INNER JOIN tags_x_tagvals ON tagvals.id=tags_x_tagvals.tagvals_id\n" .
+ "WHERE tags_x_tagvals.tags_id=?");
+ my $tags=$self->cmd_rows($sql, $tagid);
+ my @tags=map { $_->[0]; } @$tags;
+ @tags=map { length($_) ? $_ : "NOVALUE"; } @tags;
+ return @tags;
}
sub artists
"\n\tGROUP BY artists.name;");
while(my $constraint=shift @constraints)
{
- print "CONSTRAINT: $constraint->{name}\n";
my $cid=$constraint->{id};
push(@ids, $cid);
}
- @ids = map( { "\"$_\""; } @ids) unless($self->{postgres});
+ @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
my $tagstr=join(", ", @ids);
my $sql = ($main_sql_start . $tagstr .
$main_sql_end);
"\n\tGROUP BY albums.name;");
while(my $constraint=shift @constraints)
{
- print "CONSTRAINT: $constraint->{name}\n";
my $cid=$constraint->{id};
push(@ids, $cid);
}
- @ids = map( { "\"$_\""; } @ids) unless($self->{postgres});
+ @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
my $str=join(", ", @ids);
my $sql = ($main_sql_start . $str .
$main_sql_end);
- print "SQL: $sql\n";
my $result=$self->cmd_rows($sql);
my @names=map { $_->[0]; } @$result;
print "ALBUMS: ", join(', ', @names), "\n";
print "ARTIST_TRACKS SQL: $sql\n";
my $result=$self->cmd_rows($sql, $artist_id);
my @names=map { $_->[0]; } @$result;
- @names = map { s/.*\///; $_; } @names;
print "ARTISTTRACKS: ", join(', ', @names), "\n";
return(@names);
}
sub album_tracks
{
- my($self, $album_id)=@_;
+ my($self, $artist_id, $album_id)=@_;
my $sql=("SELECT files.name FROM files\n\t" .
- "INNER JOIN albums ON albums.id=files.albums_id\n\t" .
- "WHERE albums.id=?\n\t" . #AND albums.name <> ''\n\t" .
+ "INNER JOIN albums ON albums.id=files.albums_id\n\t" .
+ "INNER JOIN artists ON artists.id=files.artists_id\n\t" .
+ "WHERE artists.id=? AND albums.id=?\n\t" .
"GROUP BY files.name\n");
- print "ALBUM_TRACKS SQL($album_id): $sql\n";
- my $result=$self->cmd_rows($sql, $album_id);
+ print "ALBUM_TRACKS SQL($artist_id, $album_id): $sql\n";
+ my $result=$self->cmd_rows($sql, $artist_id, $album_id);
my @names=map { $_->[0]; } @$result;
- @names = map { s/.*\///; $_;} @names;
print "TRACKS: ", join(', ', @names), "\n";
return(@names);
}
}
elsif(ref($constraints[$#constraints]) eq "ID3FS::PathElement::Album")
{
- return $self->album_tracks($constraints[$#constraints]->{id});
+ my $artist_id=0;
+ my $artist=$constraints[($#constraints)-1];
+ if(defined($artist) && (ref($artist) eq "ID3FS::PathElement::Artist"))
+ {
+ # should always happen
+ $artist_id=$artist->{id};
+ }
+ return $self->album_tracks($artist_id, $constraints[$#constraints]->{id});
}
my $main_sql_start=("SELECT files.name\n" .
my @ids;
while(my $constraint=shift @constraints)
{
- print "CONSTRAINT: $constraint->{name}\n";
my $cid=$constraint->{id};
push(@ids, $cid);
}
- @ids = map( { "\"$_\""; } @ids) unless($self->{postgres});
+ @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
my $str=join(", ", @ids);
my $sql = ($main_sql_start . $str .
$main_sql_end);
print "SQL: $sql\n";
my $result=$self->cmd_rows($sql);
my @names=map { $_->[0]; } @$result;
- @names = map { s/.*\///; $_; } @names;
print "TRACKS: ", join(', ', @names), "\n";
return(@names);
}
die("DB::filename: unhandled case\n"); #FIXME
}
+sub bare_tags
+{
+ my($self)=@_;
+ my $sql=("SELECT tags.name FROM tags\n" .
+ "LEFT JOIN tags_x_tagvals ON tags.id=tags_x_tagvals.tags_id\n" .
+ "WHERE tags_x_tagvals.tags_id IS NULL\n" .
+ "GROUP BY tags.name\n");
+ my $result=$self->cmd_rows($sql);
+ my @names=map { $_->[0]; } @$result;
+ return (@names);
+}
+
+sub tags_with_values
+{
+ my($self)=@_;
+ my $sql=("SELECT tags.name, tagvals.name FROM tags\n" .
+ "INNER JOIN tags_x_tagvals ON tags.id=tags_x_tagvals.tags_id\n" .
+ "INNER JOIN tagvals ON tagvals.id=tags_x_tagvals.tagvals_id\n" .
+ "GROUP BY tags.name, tagvals.name\n");
+ my $result=$self->cmd_rows($sql);
+ my $tags={};
+ for my $pair (@$result)
+ {
+ push(@{$tags->{$pair->[0]}}, $pair->[1]);
+ }
+ return $tags;
+}
+
sub id
{
my($self, $type, $val)=@_;
- print "ID: $type $val\n";
my $sql="SELECT id FROM $type WHERE name=?";
my ($id)=$self->cmd_onerow($sql, $val);
return($id);
{
($pathpart, $filepart) = ($relpath =~ /(.*)\/(.*)/);
}
- my $file=ID3FS::File->new($path);
+ my $file=ID3FS::AudioFile->new($path);
return unless(defined($file));
my $artist=$file->artist();
my $album=$file->album();
my $v1genre=$file->v1genre();
my $year=$file->year();
- my $audiotype=$file->album();
+ my $audiotype=$file->audiotype();
my $tags=$file->tags();
my $haspic=$file->haspic();
$artist=undef unless($self->ok($artist));
+ print "$self->{me}: $path: no artist tag defined\n" unless(defined($artist));
my $artist_id=$self->add_to_table("artists", $artist);
my $path_id=$self->add_to_table("paths", $pathpart);
$album=undef unless($self->ok($album));
+ if($self->{verbose} && !defined($album))
+ {
+ print "$self->{me}: $path: no album tag defined\n";
+ }
+
my $albums_id=$self->add_to_table("albums", $album);
my $file_id=$self->add_to_table("files", $filepart,
{ "artists_id" => $artist_id,
return $id;
}
+sub tag_has_values
+{
+ my($self, $id)=@_;
+ my $sql=("SELECT COUNT(*) FROM tags\n\t" .
+ "INNER JOIN tags_x_tagvals ON tags.id=tags_x_tagvals.tags_id\n\t" .
+ "INNER JOIN tagvals ON tagvals.id=tags_x_tagvals.tagvals_id\n\t" .
+ "WHERE tags.id=?\n");
+ my ($rows)=$self->cmd_onerow($sql, $id);
+ return $rows;
+}
+
+sub files_in
+{
+ my ($self, $dir)=@_;
+ $dir=~s/^$self->{base}\/?//;
+# print "Munged dir: $dir\n";
+ my $sql=("SELECT files.name FROM files\n" .
+ "INNER JOIN paths ON files.paths_id=paths.id\n" .
+ "WHERE paths.name=?\n");
+ my $files=$self->cmd_rows($sql, $dir);
+ return(map { $_->[0]; } @$files);
+}
+
+sub prune_directories
+{
+ my($self)=@_;
+ my $sql=("SELECT name, id FROM paths ORDER BY name\n");
+ my $pathsref=$self->cmd_rows($sql);
+ my @ids=();
+ for my $pathpair (@$pathsref)
+ {
+ my($path, $id)=@$pathpair;
+ my $fullpath="$self->{absbase}/$path";
+# print "PRUNING PATH $fullpath: ";
+ unless(-d $fullpath)
+ {
+ push(@ids, $id)
+ }
+ }
+ $self->prune_paths(@ids);
+ return scalar(@ids);
+}
+
+sub prune_paths
+{
+ my($self, @ids)=@_;
+ return unless(@ids);
+ my $sql=("DELETE FROM files WHERE paths_id IN (\n\t" .
+ join(', ', map { "\"$_\""; } @ids). "\n\t)");
+ print "SQL: \n", $sql, "\n";
+ $self->cmd($sql);
+}
+
+sub remove_unused
+{
+ my($self)=@_;
+ my $sql=<<'EOT';
+ DELETE FROM artists WHERE id IN (
+ SELECT artists.id FROM artists
+ LEFT JOIN files ON files.artists_id=artists.id
+ WHERE files.id IS NULL);
+
+ DELETE FROM albums WHERE id IN (
+ SELECT albums.id FROM albums
+ LEFT JOIN files ON files.albums_id=albums.id
+ WHERE files.id IS NULL);
+
+ DELETE FROM paths WHERE id IN (
+ SELECT paths.id FROM paths
+ LEFT JOIN files ON files.paths_id=paths.id
+ WHERE files.id IS NULL);
+
+ DELETE FROM files_x_tags WHERE files_id IN (
+ SELECT files_x_tags.files_id FROM files_x_tags
+ LEFT JOIN files ON files.id=files_x_tags.files_id
+ WHERE files.id IS NULL);
+
+ DELETE FROM tags WHERE id IN (
+ SELECT tags.id FROM tags
+ LEFT JOIN files_x_tags ON files_x_tags.tags_id=tags.id
+ WHERE files_x_tags.files_id IS NULL);
+
+ DELETE FROM tags_x_tagvals WHERE tags_id IN (
+ SELECT tags_x_tagvals.tags_id FROM tags_x_tagvals
+ LEFT JOIN tags ON tags.id=tags_x_tagvals.tags_id
+ WHERE tags.id IS NULL);
+
+ DELETE FROM tagvals WHERE id IN (
+ SELECT tagvals.id FROM tagvals
+ LEFT JOIN tags_x_tagvals ON tags_x_tagvals.tagvals_id=tagvals.id
+ WHERE tags_x_tagvals.tagvals_id IS NULL);
+EOT
+ print "SQL: $sql\n";
+ my @sql=split(/\n\n/, $sql);
+ $self->cmd($_) for (@sql);
+}
+
sub relation_exists
{
my ($self, $relname, $fields)=@_;
last_update
);
-CREATE TABLE files (
- id INTEGER PRIMARY KEY,
- artists_id,
- albums_id,
- paths_id,
- name text
-);
-
CREATE TABLE paths (
id INTEGER PRIMARY KEY,
name text
name text
);
+CREATE TABLE files (
+ id INTEGER PRIMARY KEY,
+ name text,
+ artists_id,
+ albums_id,
+ paths_id,
+ FOREIGN KEY(artists_id) REFERENCES artists(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY(albums_id) REFERENCES albums(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY(paths_id) REFERENCES paths(id) ON DELETE CASCADE ON UPDATE CASCADE
+);
+
CREATE TABLE tags (
id INTEGER PRIMARY KEY,
name text
CREATE TABLE files_x_tags (
files_id INTEGER,
- tags_id INTEGER
+ tags_id INTEGER,
+ FOREIGN KEY(files_id) REFERENCES files(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY(tags_id) REFERENCES tags(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE tags_x_tagvals (
tags_id INTEGER,
- tagvals_id INTEGER
+ tagvals_id INTEGER,
+ FOREIGN KEY(tags_id) REFERENCES tags(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY(tagvals_id) REFERENCES tagvals(id) ON DELETE CASCADE ON UPDATE CASCADE
);
+