partially implement tagvals
[id3fs.git] / lib / ID3FS / DB.pm
index 15855b2..07a5f86 100644 (file)
@@ -191,14 +191,15 @@ sub tags
     my @ids=();
 
     my $main_sql_start=("SELECT t2.name\n" .
-                       "\tFROM (SELECT files_id FROM tags t1\n" .
-                       "\t\tINNER JOIN files_x_tags ON t1.id=files_x_tags.tags_id\n" .
-                       "\t\tWHERE t1.id in\n\t\t\t(");
-    my $main_sql_mid=(")\n\t\t) AS subselect\n" .
-                     "\tINNER JOIN files_x_tags ON subselect.files_id=files_x_tags.files_id\n" .
-                     "\tINNER JOIN tags t2 ON files_x_tags.tags_id=t2.id\n" .
-                     "\tWHERE t2.id NOT IN (");
-    my $main_sql_end=")\n\tGROUP BY t2.name;";
+                       "\tFROM (\n" .
+                       $self->tags_subselect(@constraints) .
+                       ") AS subselect\n" .
+                     "INNER JOIN files_x_tags ON subselect.files_id=files_x_tags.files_id\n" .
+                     "INNER JOIN tags t2 ON files_x_tags.tags_id=t2.id\n" .
+                     "WHERE t2.id NOT IN (");
+    my $main_sql_end=")\nGROUP BY t2.name;";
+    # FIXME: generalise more?
+
     while(my $constraint=shift @constraints)
     {
        my $cid=$constraint->{id};
@@ -207,7 +208,6 @@ sub tags
     @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
     my $tagstr=join(", ", @ids);
     my $sql = ($main_sql_start . $tagstr .
-              $main_sql_mid   . $tagstr .
               $main_sql_end);
     print "SQL: $sql\n";
     my $result=$self->cmd_rows($sql);
@@ -238,23 +238,12 @@ sub artists
        return(map { $_->[0]; } @$tags);
     }
     my @ids=();
-    my $main_sql_start=("SELECT artists.name\n" .
-                       "\tFROM (SELECT files_id FROM tags\n" .
-                       "\t\tINNER JOIN files_x_tags ON tags.id=files_x_tags.tags_id\n" .
-                       "\t\tWHERE tags.id in\n\t\t\t(");
-    my $main_sql_end=(")\n\t\t) AS subselect\n" .
-                     "\tINNER JOIN files ON subselect.files_id=files.id\n" .
-                     "\tINNER JOIN artists ON files.artists_id=artists.id\n" .
-                     "\n\tGROUP BY artists.name;");
-    while(my $constraint=shift @constraints)
-    {
-       my $cid=$constraint->{id};
-       push(@ids, $cid);
-    }
-    @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
-    my $tagstr=join(", ", @ids);
-    my $sql = ($main_sql_start . $tagstr .
-              $main_sql_end);
+    my $sql=("SELECT artists.name FROM (\n" .
+            $self->tags_subselect(@constraints) .
+            ") AS subselect\n" .
+            "INNER JOIN files ON subselect.files_id=files.id\n" .
+            "INNER JOIN artists ON files.artists_id=artists.id\n" .
+            "GROUP BY artists.name;");
     print "SQL: $sql\n";
     my $result=$self->cmd_rows($sql);
     my @tagnames=map { $_->[0]; } @$result;
@@ -269,25 +258,15 @@ sub albums
     # FIXME: rework PathElements
     if(ref($constraints[$#constraints]) eq "ID3FS::PathElement::Artist")
     {
-       return $self->artist_albums($constraints[$#constraints]->{id});
-    }
-    my $main_sql_start=("SELECT albums.name\n" .
-                       "\tFROM (SELECT files_id FROM tags\n" .
-                       "\t\tINNER JOIN files_x_tags ON tags.id=files_x_tags.tags_id\n" .
-                       "\t\tWHERE tags.id in\n\t\t\t(");
-    my $main_sql_end=(")\n\t\t) AS subselect\n" .
-                     "\tINNER JOIN files ON subselect.files_id=files.id\n" .
-                     "\tINNER JOIN albums ON files.albums_id=albums.id\n" .
-                     "\n\tGROUP BY albums.name;");
-    while(my $constraint=shift @constraints)
-    {
-       my $cid=$constraint->{id};
-       push(@ids, $cid);
+       return $self->artist_albums($constraints[$#constraints]->{id}, @constraints);
     }
-    @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
-    my $str=join(", ", @ids);
-    my $sql = ($main_sql_start . $str .
-              $main_sql_end);
+    my $sql=("SELECT albums.name\n" .
+            "\tFROM (\n" .
+            $self->tags_subselect(@constraints) .
+            "\t) AS subselect\n" .
+            "INNER JOIN files ON subselect.files_id=files.id\n" .
+            "INNER JOIN albums ON files.albums_id=albums.id\n" .
+            "GROUP BY albums.name;");
     my $result=$self->cmd_rows($sql);
     my @names=map { $_->[0]; } @$result;
     print "ALBUMS: ", join(', ', @names), "\n";
@@ -296,8 +275,11 @@ sub albums
 
 sub artist_albums
 {
-    my($self, $artist_id)=@_;
-    my $sql=("SELECT albums.name FROM files\n\t" .
+    my($self, $artist_id, @constraints)=@_;
+    my $sql=("SELECT albums.name FROM (\n" .
+            $self->tags_subselect(@constraints) .
+            "\t) AS subselect\n" .
+            "INNER JOIN files ON subselect.files_id=files.id\n" .
             "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.name <> ''\n\t" .
@@ -311,8 +293,11 @@ sub artist_albums
 
 sub artist_tracks
 {
-    my($self, $artist_id)=@_;
-    my $sql=("SELECT files.name FROM files\n\t" .
+    my($self, $artist_id, @constraints)=@_;
+    my $sql=("SELECT files.name FROM (\n" .
+            $self->tags_subselect(@constraints) .
+            "\t) AS subselect\n" .
+            "INNER JOIN files ON subselect.files_id=files.id\n" .
             "INNER JOIN artists ON artists.id=files.artists_id\n\t" .
             "INNER JOIN albums  ON albums.id=files.albums_id\n\t" .
             "WHERE artists.id=? AND albums.name=''\n\t" .
@@ -345,7 +330,7 @@ sub tracks
     # FIXME: rework PathElements
     if(ref($constraints[$#constraints]) eq "ID3FS::PathElement::Artist")
     {
-       return $self->artist_tracks($constraints[$#constraints]->{id});
+       return $self->artist_tracks($constraints[$#constraints]->{id}, @constraints);
     }
     elsif(ref($constraints[$#constraints]) eq "ID3FS::PathElement::Album")
     {
@@ -359,23 +344,12 @@ sub tracks
        return $self->album_tracks($artist_id, $constraints[$#constraints]->{id});
     }
 
-    my $main_sql_start=("SELECT files.name\n" .
-                       "\tFROM (SELECT files_id FROM tags\n" .
-                       "\t\tINNER JOIN files_x_tags ON tags.id=files_x_tags.tags_id\n" .
-                       "\t\tWHERE tags.id in\n\t\t\t(");
-    my $main_sql_end=(")\n\t\t) AS subselect\n" .
-                     "\tINNER JOIN files ON files.id=subselect.files_id" .
-                     "\tGROUP BY files.name;");
-    my @ids;
-    while(my $constraint=shift @constraints)
-    {
-       my $cid=$constraint->{id};
-       push(@ids, $cid);
-    }
-    @ids = map( { "\"$_\""; } grep { defined; } @ids) unless($self->{postgres});
-    my $str=join(", ", @ids);
-    my $sql = ($main_sql_start . $str .
-              $main_sql_end);
+    my $sql=("SELECT files.name\n" .
+            "\tFROM (\n" .
+            $self->tags_subselect(@constraints) .
+            "\t) AS subselect\n" .
+            "INNER JOIN files ON files.id=subselect.files_id\n" .
+            "GROUP BY files.name;");
     print "SQL: $sql\n";
     my $result=$self->cmd_rows($sql);
     my @names=map { $_->[0]; } @$result;
@@ -385,7 +359,7 @@ sub tracks
 
 sub filename
 {
-    my($self, @constraints)=@_;
+    my($self, $mountpoint, @constraints)=@_;
     if(ref($constraints[$#constraints]) eq "ID3FS::PathElement::File")
     {
        my $id=$constraints[$#constraints]->{id};
@@ -395,11 +369,103 @@ sub filename
                 "GROUP BY paths.name, files.name");
        print "FILENAME SQL: $sql\n";
        my ($path, $name)=$self->cmd_onerow($sql, $id);
-       return($self->{absbase} . "/$path/$name");
+       my $id3fs_path=join('/', map { $_->{name}; }  @constraints);
+       return($self->relativise($path, $name, $mountpoint, $id3fs_path));
     }
     die("DB::filename: unhandled case\n"); #FIXME
 }
 
+sub tags_subselect
+{
+    my($self,@constraints)=@_;
+    use Data::Dumper;
+    my @tags=();
+    my @tags_vals=();
+    my $lasttag='';
+#    print "CONSTRAINTS: \n", Dumper \@constraints;
+    for my $constraint (@constraints)
+    {
+#      print ref($constraint),"\n";
+       if(ref($constraint) eq "ID3FS::PathElement::Tag")
+       {
+           if($self->tag_has_values($constraint->{id}))
+           {
+#              print "HASVALUES\n";
+               $lasttag=$constraint->{id} if defined($constraint->{id});
+           }
+           else
+           {
+#              print "NOVALUES\n";
+               push(@tags, $constraint->{id}) if(defined($constraint->{id}));
+           }
+       }
+       elsif(ref($constraint) eq "ID3FS::PathElement::Tagval")
+       {
+#          print "TAGVAL\n";
+           push(@tags_vals, [$lasttag, $constraint->{id}]) if defined($constraint->{id});
+       }
+    }
+    unless($self->{postgres})
+    {
+       @tags=map{ "\"$_\""; } @tags;
+       @tags_vals=map( { [ map({ "\"$_\""; } @$_ ) ] } @tags_vals);
+    }
+#    print "TAGS\n", Dumper \@tags;
+#    print "\nVALS\n", Dumper(\@tags_vals), "\n";
+    my $sql=("\tSELECT files_id FROM tags t1\n" .
+            "\tINNER JOIN files_x_tags ON t1.id=files_x_tags.tags_id\n" .
+            "\tINNER JOIN tags_x_tagvals ON t1.id=tags_x_tagvals.tags_id\n");
+    my @clauses=();
+    if(@tags)
+    {
+       push(@clauses, "(t1.id IN ( " . join(', ', @tags) ." ) )");
+    }
+    for my $pair (@tags_vals)
+    {
+       my($tag, $val)=@$pair;
+       push(@clauses, "( t1.id=$tag AND tags_x_tagvals.tagvals_id=$val )");
+    }
+    if(@clauses)
+    {
+       $sql .= "\tWHERE\n\t\t" . join("\n\t\tOR ", @clauses) . "\n";
+    }
+    return $sql;
+}
+
+
+sub relativise
+{
+    my($self, $path, $name, $mountpoint, $id3fs_path)=@_;
+    $id3fs_path=~s/(.*)\/.*/$1/;
+    my $rpath="$self->{absbase}/$path";
+    my $vpath="$mountpoint/$id3fs_path";
+    my @path=split(/\//,$rpath);
+    my @rel=split(/\//,$vpath);
+    #absolute paths have empty first element due to leading /
+    shift(@path) if($path[0] eq "");
+    shift(@rel)  if($rel[0]  eq "");
+    if($path[0] ne $rel[0])
+    {
+       #no path in common, return absolute
+       print "FAIL: NO PATHS IN COMMON\n";
+       return $name;
+    }
+    # f: /home/foo/bar/baz.mp3
+    # r: /home/ianb/music/albums
+    while(@path && @rel && ($path[0] eq $rel[0]))
+    {
+       shift(@path);
+       shift(@rel);
+       print "POP ";
+    }
+    print "\n";
+    my $upcount=scalar(@rel);
+    my $result="../" x $upcount;
+    $result .= join("/",@path);
+    $result .= "/$name";
+    return $result;
+}
+
 sub bare_tags
 {
     my($self)=@_;