fix relativising symlinks
[id3fs.git] / lib / ID3FS / Path.pm
index 4947256..0326af6 100644 (file)
@@ -1,3 +1,19 @@
+# id3fs - a FUSE-based filesystem for browsing audio metadata
+# Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 package ID3FS::Path;
 
 use strict;
@@ -10,9 +26,11 @@ our ($STATE_INVALID, $STATE_ROOT, $STATE_TAG, $STATE_TAGVAL,
 
 our %priorities=( "OR" => 0, "AND" => 1, "NOT" => 2 );
 
-our $PATH_ALLTRACKS="TRACKS";
-our $PATH_NOARTIST="NOARTIST";
-our $PATH_NOALBUM="NOALBUM";
+our $PATH_ALLTRACKS= "TRACKS";
+our $PATH_NOARTIST = "NOARTIST";
+our $PATH_NOALBUM  = "NOALBUM";
+
+our $ENABLE_FILTER = 1;
 
 sub new
 {
@@ -77,7 +95,7 @@ sub dirents
 #    print "DIRENTS: FILE: $self->{path}\n";
     if($state==$STATE_ALL)
     {
-       @dents=($PATH_ALLTRACKS, $PATH_NOARTIST, $self->artists());
+       @dents=($self->filter($PATH_ALLTRACKS, $PATH_NOARTIST), $self->artists());
     }
     elsif($state==$STATE_TAG || $state==$STATE_TAGVAL)
     {
@@ -92,8 +110,7 @@ sub dirents
            {
                @dents=qw(AND OR);
            }
-           push(@dents, $self->filter($PATH_ALLTRACKS, $PATH_NOARTIST));
-           push(@dents, $self->artists());
+           push(@dents, $self->filter($PATH_ALLTRACKS, $PATH_NOARTIST), $self->artists());
        }
     }
     elsif($state==$STATE_BOOLEAN)
@@ -112,7 +129,7 @@ sub dirents
     }
     elsif($state==$STATE_ALBUMS)
     {
-       @dents=($self->filter($PATH_ALLTRACKS, $PATH_NOALBUM), $self->albums());
+       @dents=$self->filter($PATH_ALLTRACKS, $PATH_NOALBUM, $self->albums());
     }
     elsif($state==$STATE_TRACKLIST)
     {
@@ -136,7 +153,6 @@ sub parse
     my @parts=@{$self->{components}};
     my($tag, $tagval);
     $self->{elements}=[];
-    $self->{bare_not}=0;
     $self->{in_all}=0;
     my $root_not=0;
     my $tags_seen=0;
@@ -336,11 +352,6 @@ sub parse
        }
     }
 
-    if($root_not && ($tags_seen < 2))
-    {
-       $self->{bare_not}=1;
-    }
-
     # remove trailing boolean
     my @elements=@{$self->{elements}};
     while(@elements && $self->is("boolean", $elements[$#elements]))
@@ -352,13 +363,8 @@ sub parse
     $self->{tagtree}=$self->elements_to_tree(\@elements);
     if($self->{tagtree})
     {
-       my ($conditions, @joins)=$self->{tagtree}->to_sql();
+#      my ($conditions, @joins)=$self->{tagtree}->to_sql();
 #      print "CONDITIONS(", scalar(@joins), "): ", $conditions, "\n";
-#      print "TREE: ",  $self->{tagtree}->print(), "\n";
-#      print("SQL CONDITION(", scalar(@{$self->{joins}}), "): ",
-#            $self->{sqlconditions}, "\n");
-#      use Data::Dumper;
-#      print Dumper $self->{tagtree};
     }
 }
 
@@ -511,45 +517,23 @@ sub tags
 #    print "THASVALS: $hasvals\n";
 #    print "TPARENT: ", (defined($parent)? $parent : "NO"), "\n";
     my @ids=();
-    my $sql=("SELECT tags.name FROM (\n" .
-            $self->tags_subselect() .
-            ") AS subselect\n" .
-            "INNER JOIN files_x_tags ON subselect.files_id=files_x_tags.files_id\n" .
-            "INNER JOIN tags ON files_x_tags.tags_id=tags.id\n");
-    my @allused=$self->used_tags();
-    my @used=grep { ref($_) ne "ARRAY"; } @allused;
-    my @used_with_vals=grep { ref($_) eq "ARRAY"; } @allused;
-#    print "tags(): USED: ", join(", ", @used), "\n";
-#    print "tags(): USED_WITH_VALS: ", join(", ", map { "[".$_->[0]. ", ".$_->[1]."]";} @used_with_vals), "\n";
-    my @orclauses=();
-    my @andclauses=();
-    my $id=$self->trailing_tag_id();
-    if($hasvals)
+    my $sql="SELECT tags.name FROM ";
+    if($self->in_or())
     {
-#      print "HAS_VALUES\n";
-       my @values=map { "'".$_->[1]."'"; } grep { $_->[0] == $id; } @used_with_vals;
-       my $clause="(tags.parents_id='$id'";
-       if(@values)
-       {
-           $clause .= " AND tags.id NOT IN (" . join(', ', @values) . ")";
-       }
-       $clause .= ")";
-       push(@andclauses, $clause);
+       $sql .= "files_x_tags\n";
     }
     else
     {
-#      print "HASNT VALUES\n";;
-       if(@used)
-       {
-           push(@andclauses, "(NOT (tags.parents_id='' AND tags.id IN (" . join(', ', @used) . ")))");
-       }
-       for my $pair (@used_with_vals)
-       {
-           push(@andclauses, "(NOT (tags.parents_id='" . $pair->[0] . "' AND tags.id='" . $pair->[1] . "'))");
-       }
+       $sql .= ("(\n" .
+                $self->tags_subselect() .
+                ") AS subselect\n" .
+                "INNER JOIN files_x_tags ON subselect.files_id=files_x_tags.files_id\n");
     }
+    $sql .= "INNER JOIN tags ON files_x_tags.tags_id=tags.id\n";
+    my @andclauses=();
+    my $id=$self->trailing_tag_id();
 
-    my $parentclause= "(tags.parents_id='";
+    my $parentclause= "tags.parents_id='";
     if($hasvals)
     {
        $parentclause .= $id;
@@ -558,17 +542,19 @@ sub tags
     {
        $parentclause .= $parent;
     }
-    $parentclause .= "')";
+    $parentclause .= "'";
     push(@andclauses, $parentclause);
 
-    if(@orclauses)
+    my @used=$self->used_tags();
+    if(@used)
     {
-       push(@andclauses, '( ' . join(' OR ', @orclauses) . ' )');
+       push(@andclauses, "tags.id NOT IN (" . join(', ', @used) . ")");
     }
     if(@andclauses)
     {
        $sql .= "WHERE " . join(' AND ', @andclauses) . "\n";
     }
+
     $sql .= "GROUP BY tags.name;";
     print "SQL(TAGS): $sql\n" if($self->{verbose});
     my @tagnames=$self->{db}->cmd_firstcol($sql);
@@ -702,7 +688,7 @@ sub filename
        print "FILENAME SQL: $sql\n" if($self->{verbose});
        my ($path, $name)=$self->{db}->cmd_onerow($sql, $id);
        my $id3fs_path=join('/', map { $_->{name}; }  @{$self->{elements}});
-       return($self->{db}->relativise($path, $name, $mountpoint));
+       return($self->{db}->relativise($path, $name, $mountpoint, $self->{path}));
     }
     # should never happen
     return "ERROR";
@@ -712,20 +698,15 @@ sub tags_subselect
 {
     my($self)=@_;
     my $hasvals=$self->expecting_values();
-    # we need to specially handle a bare /NOT/tag with no other clauses,
-    # using a simple WHERE id !='tagid' instead of a LEFT JOIN
-    if($self->{bare_not})
-    {
-       return $self->bare_not_subselect();
-    }
     if($self->{in_all})
     {
+       # FIXME: is this used?
+       die "IN_ALL CALLED!!\n";
        return "\tSELECT id FROM files AS files_id\n";
     }
     my $tree=$self->{tagtree};
+    # FIXME
     print "UNDEF!!\n" unless($self->{tagtree});
-    use Data::Dumper;
-    print Dumper $tree;
     my $parent=$self->trailing_tag_parent();
 
 #    print "ELEMENTS: ", join('/', map { $_->{name}; } @{$self->{elements}}), "\n";
@@ -738,7 +719,7 @@ sub tags_subselect
     }
     my ($sqlclause, @joins)=(undef, ());
     ($sqlclause, @joins) = $tree->to_sql($hasvals) if($tree);
-#    print "SQL(" . scalar(@joins) .": $sqlclause\n";
+#    print "SQL(" . scalar(@joins) ."): $sqlclause\n";
     my $sql="\tSELECT fxt1.files_id FROM tags t1";
     my @crosses=();
     my @inners=();
@@ -767,27 +748,6 @@ sub tags_subselect
     return $sql;
 }
 
-sub bare_not_subselect
-{
-    my($self)=@_;
-    my @tags=grep { $self->is("tag", $_); } @{$self->{elements}};
-    my $sql=("\tSELECT f1.id AS files_id FROM files f1 WHERE f1.id NOT IN (\n" .
-            "\t\tSELECT fxt1.files_id FROM tags t1\n" .
-            "\t\tINNER JOIN files_x_tags fxt1 ON t1.id=fxt1.tags_id\n" .
-            "\t\tWHERE ");
-    if(scalar(@tags) > 1)
-    {
-       $sql .= ("(t1.parents_id='" . $tags[0]->id() . "' AND t1.id='" .
-                $tags[1]->id() . "')");
-    }
-    else
-    {
-       $sql .= ("(t1.parents_id='' AND t1.id='" . $tags[0]->id() . "')");
-    }
-    $sql .= "\n\t\tGROUP BY fxt1.files_id\n\t)\n";
-    return($sql);
-}
-
 sub sql_start
 {
     my($self, $tables)=@_;
@@ -806,7 +766,7 @@ sub sql_start
     return $sql;
 }
 
-
+# FIXME: remove
 sub constraints_tag_list
 {
     my($self, @constraints)=@_;
@@ -848,18 +808,19 @@ sub constraints_tag_list
 sub filter
 {
     my($self, @dirs)=@_;
+    return(@dirs) unless($ENABLE_FILTER);
     my $base=$self->{path};
     my @outdirs=();
     for my $dir (@dirs)
     {
-       print "\nFILTER (",$self->state(), "): $base / $dir\n";
+#      print "\nFILTER (",$self->state(), "): $base / $dir\n";
        if($self->empty("$base/$dir"))
        {
-           print "empty: $base / $dir\n";
+#          print "empty: $base / $dir\n";
        }
        else
        {
-           print "non-empty, accepting: $base / $dir\n";
+#          print "non-empty, accepting: $base / $dir\n";
            push(@outdirs, $dir);
        }
     }
@@ -877,4 +838,20 @@ sub empty
     return 1;
 }
 
+# if path is .../OR/ or .../OR/NOT
+sub in_or
+{
+    my($self)=@_;
+    my $tail=$self->tail();
+    return 0 unless($tail);
+    return 0 unless($tail->type() eq "boolean");
+    return 1 if($tail->name() eq "OR");
+    return 0 unless($tail->name() eq "NOT");
+    my $parent=$self->tail_parent();
+    return 0 unless($parent);
+    return 0 unless($parent->type() eq "boolean");
+    return 1 if($parent->name() eq "OR");
+    return 0;
+}
+
 1;