pass path to db methods, not just elements
[id3fs.git] / lib / ID3FS / Path.pm
1 package ID3FS::Path;
2
3 use strict;
4 use warnings;
5 use ID3FS::PathElement::Artist;
6 use ID3FS::PathElement::Album;
7 use ID3FS::PathElement::Boolean;
8 use ID3FS::PathElement::File;
9 use ID3FS::PathElement::Tag;
10 use ID3FS::PathElement::Tagval;
11 use ID3FS::Path::Node;
12
13 our ($STATE_INVALID, $STATE_ROOT, $STATE_TAG, $STATE_TAGVAL,
14      $STATE_BOOLEAN, $STATE_ALBUMS, $STATE_TRACKLIST,
15      $STATE_FILE)=(0..7);
16
17 our %priorities=( "OR" => 0, "AND" => 1, "NOT" => 2 );
18
19 sub new
20 {
21     my $proto=shift;
22     my $class=ref($proto) || $proto;
23     my $self={};
24     bless($self,$class);
25
26     $self->{db}=shift;
27     $self->{path}=shift;
28     $self->parse();
29 #    print "STATE: ", $self->state(), "\n";
30     return $self;
31 }
32
33 sub isdir
34 {
35     my($self)=@_;
36     if(($self->state() == $STATE_FILE) ||
37        ($self->state() == $STATE_INVALID))
38     {
39         return 0;
40     }
41     return 1;
42 }
43
44 sub isfile
45 {
46     my($self)=@_;
47     return 1 if($self->state() == $STATE_FILE);
48     return 0;
49 }
50
51 sub isvalid
52 {
53     my($self)=@_;
54     return($self->state() != $STATE_INVALID);
55 }
56
57 sub dest
58 {
59     my($self, $mountpoint)=@_;
60     if($self->state() == $STATE_FILE)
61     {
62         return $self->{db}->filename($mountpoint, $self);
63     }
64     return "ERROR"; #should never happen?
65 }
66
67 sub dirents
68 {
69     my($self)=@_;
70     my @dents=();
71     my $state=$self->state();
72 #    print "DIRENTS: STATE: $state\n";
73 #    print "DIRENTS: FILE: $self->{path}\n";
74     if($state==$STATE_TAG || $state==$STATE_TAGVAL)
75     {
76         my $tag=$self->{elements}->[$#{$self->{elements}}];
77         if($state==$STATE_TAG &&
78            defined($tag) &&
79            ref($tag) eq "ID3FS::PathElement::Tag" &&
80            $self->{db}->tag_has_values($tag->{id}))
81         {
82             @dents=$self->{db}->tags($self);
83         }
84         else
85         {
86             @dents=(qw(AND OR TRACKS NOARTIST),
87                     $self->{db}->artists($self));
88         }
89     }
90     elsif($state==$STATE_BOOLEAN)
91     {
92         my $parent=$self->{elements}->[$#{$self->{elements}}];
93         unless(defined($parent) &&
94                ref($parent) eq "ID3FS::PathElement::Boolean" &&
95                $parent->{name} eq "NOT")
96         {
97             push(@dents, "NOT");
98         }
99         push(@dents, $self->{db}->tags($self));
100     }
101     elsif($state==$STATE_ROOT)
102     {
103         @dents=(qw(ALL NOT), $self->{db}->tags($self));
104     }
105     elsif($state==$STATE_ALBUMS)
106     {
107         @dents=(qw(TRACKS NOALBUM),$self->{db}->albums($self));
108     }
109     elsif($state==$STATE_TRACKLIST)
110     {
111         @dents=$self->{db}->tracks($self);
112     }
113     else
114     {
115         print "DIRENTS: UNHANDLED STATE: $state\n";
116     }
117     return(@dents);
118 }
119
120 sub parse
121 {
122     my($self)=@_;
123     @{$self->{components}}=split(/\//, $self->{path});
124     shift @{$self->{components}}; # drop empty field before leading /
125 #    print "PATH: $self->{path}\n";
126     $self->state($STATE_ROOT);
127     return if($self->{path} eq "/");
128     my @parts=@{$self->{components}};
129     my($tag, $tagval);
130     $self->{elements}=[];
131     while(my $name=shift @parts)
132     {
133 #       print "NAME: $name\n";
134         my $state=$self->state();
135         if($state==$STATE_INVALID)
136         {
137 #           print "SM: INVALID: $name\n";
138             return;
139         }
140         elsif($state==$STATE_ROOT)
141         {
142 #           print "SM: ROOT: $name\n";
143             if($name eq "ALL")
144             {
145                 $self->state($STATE_TAG);
146             }
147             elsif($name eq "NOT")
148             {
149                 push(@{$self->{elements}}, ID3FS::PathElement::Boolean->new($self->{db}, $name));
150                 $self->state($STATE_BOOLEAN);
151             }
152             else
153             {
154                 $tag=ID3FS::PathElement::Tag->new($self->{db}, $name);
155                 if($tag)
156                 {
157                     push(@{$self->{elements}}, $tag);
158                     $self->state($STATE_TAG);
159                 }
160                 else
161                 {
162                     $self->state($STATE_INVALID);
163                 }
164             }
165         }
166         elsif($state==$STATE_TAG || $state==$STATE_TAGVAL)
167         {
168 #           print "SM: TAG/TAGVAL($state): $name\n";
169             my $tag=$self->{elements}->[$#{$self->{elements}}];
170             if($state==$STATE_TAG &&
171                defined($tag) &&
172                ref($tag) eq "ID3FS::PathElement::Tag" &&
173                $self->{db}->tag_has_values($tag->{id}))
174             {
175                 my $tagval=ID3FS::PathElement::Tag->new($self->{db}, $name, $tag->{id});
176                 if(defined($tagval))
177                 {
178                     $self->state($STATE_TAGVAL);
179                     # stay in tag state
180                     push(@{$self->{elements}}, $tagval);
181                 }
182                 else
183                 {
184                     $self->state($STATE_INVALID);
185                 }
186             }
187             elsif($name eq "TRACKS")
188             {
189                 $self->state($STATE_TRACKLIST);
190             }
191             elsif($name eq "NOARTIST")
192             {
193                 $self->state($STATE_TRACKLIST);
194             }
195             elsif($name eq "AND")
196             {
197                 $self->state($STATE_BOOLEAN);
198                 push(@{$self->{elements}}, ID3FS::PathElement::Boolean->new($self->{db}, $name));
199             }
200             elsif($name eq "OR")
201             {
202                 $self->state($STATE_BOOLEAN);
203                 push(@{$self->{elements}}, ID3FS::PathElement::Boolean->new($self->{db}, $name));
204             }
205             else
206             {
207                 my $artist=ID3FS::PathElement::Artist->new($self->{db}, $name);
208                 if($artist)
209                 {
210                     push(@{$self->{elements}}, $artist);
211                     $self->state($STATE_ALBUMS);
212                 }
213                 else
214                 {
215                     $self->state($STATE_INVALID);
216                 }
217             }
218         }
219         elsif($state==$STATE_BOOLEAN)
220         {
221 #           print "SM: BOOLEAN: $name\n";
222             my $parent=$self->{elements}->[$#{$self->{elements}}];
223             my $allownot=1;
224             if(defined($parent) &&
225                ref($parent) eq "ID3FS::PathElement::Boolean" &&
226                $parent->{name} eq "NOT")
227             {
228                 $allownot=0;
229             }
230             if($allownot && $name eq "NOT")
231             {
232                 $self->state($STATE_BOOLEAN);
233                 push(@{$self->{elements}}, ID3FS::PathElement::Boolean->new($self->{db}, $name));
234             }
235             else
236             {
237                 my $tag=ID3FS::PathElement::Tag->new($self->{db}, $name);
238                 if($tag)
239                 {
240                     push(@{$self->{elements}}, $tag);
241                     $self->state($STATE_TAG);
242                 }
243                 else
244                 {
245                     $self->state($STATE_INVALID);
246                 }
247             }
248         }
249         elsif($state==$STATE_ALBUMS)
250         {
251 #           print "SM: ALBUM: $name\n";
252             if($name eq "TRACKS")
253             {
254                 $self->state($STATE_TRACKLIST);
255             }
256             elsif($name eq "NOALBUM")
257             {
258                 $self->state($STATE_TRACKLIST);
259             }
260             else
261             {
262                 my $album=ID3FS::PathElement::Album->new($self->{db}, $name);
263                 if($album)
264                 {
265                     push(@{$self->{elements}}, $album);
266                     $self->state($STATE_TRACKLIST);
267                 }
268                 else
269                 {
270                     $self->state($STATE_INVALID);
271                 }
272             }
273         }
274         elsif($state==$STATE_TRACKLIST)
275         {
276 #           print "SM: TRACKLIST: $name\n";
277             my $track=ID3FS::PathElement::File->new($self->{db}, $name);
278             if($track)
279             {
280                 push(@{$self->{elements}}, $track);
281                 $self->state($STATE_FILE);
282             }
283             else
284             {
285                 $self->state($STATE_INVALID);
286             }
287         }
288         elsif($state==$STATE_FILE)
289         {
290 #           print "SM: FILE: $name\n";
291             # Can't have anything after a filename
292             $self->state($STATE_INVALID);
293         }
294         else
295         {
296             print "SM: ERROR: UNKNOWN STATE: $self->{state}\n";
297             $self->state($STATE_INVALID);
298         }
299     }
300     # sort elements by precedence
301     @{$self->{elements}}=$self->sort_elements(@{$self->{elements}});
302     my $thing=$self->elements_to_tree([ @{$self->{elements}} ]);
303     $self->{tagtree}=$self->elements_to_tree([ @{$self->{elements}} ]);
304     if($self->{tagtree})
305     {
306         ($self->{sqlconditions},
307          $self->{andsneeded}) = $self->{tagtree}->to_sql();
308         print("SQL CONDITION(", $self->{andsneeded}, "): ",
309               $self->{sqlconditions}, "\n");
310 #       use Data::Dumper;
311 #       print Dumper $self->{tagtree};
312     }
313 }
314
315 sub state
316 {
317     my($self, $newstate)=@_;
318     $self->{state}=$newstate if(defined($newstate));
319     return $self->{state};
320 }
321
322 sub elements_to_tree
323 {
324     my($self, $elements)=@_;
325     return undef unless(@$elements);
326     my ($left, $right, $op)=(undef, undef, undef);
327     my $thing=pop @$elements;
328     if(ref($thing) eq "ID3FS::PathElement::Boolean")
329     {
330         my $op=$thing;
331         $right=$self->elements_to_tree($elements);
332         if($op->{name} ne "NOT")
333         {
334             $left=$self->elements_to_tree($elements);
335         }
336         return ID3FS::Path::Node->new($left, $op, $right);
337     }
338     else
339     {
340         return ID3FS::Path::Node->new($thing);
341     }
342 }
343
344 # Dijkstra's shunting-yard algorithm
345 sub sort_elements
346 {
347     my ($self, @input)=@_;
348     my @opstack=();
349     my @output=();
350 #    print "INPUT: ", join(', ', map { $_->{name}; } @input), "\n";
351     while(my $thing = shift @input)
352     {
353         if(ref($thing) eq "ID3FS::PathElement::Tag")
354         {
355 #           print "Pushing $thing->{name} to output\n";
356             push(@output, $thing);
357 #           print "OPSTACK: ", join(', ', map { $_->{name}; } @opstack), "\n";
358 #           print "OUTPUT: ", join(', ', map { $_->{name}; } @output), "\n";
359         }
360         elsif(ref($thing) eq "ID3FS::PathElement::Boolean")
361         {
362 #           print "BOOL: $thing->{name}\n";
363             # bool
364 #           print "thing: $thing->{name}: $priorities{$thing->{name}} ";
365             if(@opstack)
366             {
367 #               print("topop: ", $opstack[$#opstack]->{name},
368 #                     ": ", $priorities{$opstack[$#opstack]->{name}}, "\n");
369             }
370             while(@opstack &&
371                   ($priorities{$thing->{name}} <= $priorities{$opstack[$#opstack]->{name}}))
372             {
373 #               print "Pushing ", $opstack[$#opstack]->{name}, " from opstack to output\n";
374                 push(@output, pop(@opstack));
375 #               print "OPSTACK: ", join(', ', map { $_->{name}; } @opstack), "\n";
376 #               print "OUTPUT: ", join(', ', map { $_->{name}; } @output), "\n";
377             }
378 #           print "Pushing $thing->{name} to opstack\n";
379             push(@opstack, $thing);
380 #           print "OPSTACK: ", join(', ', map { $_->{name}; } @opstack), "\n";
381 #           print "OUTPUT: ", join(', ', map { $_->{name}; } @output), "\n";
382         }
383     }
384     while(@opstack)
385     {
386         push(@output, pop(@opstack));
387     }
388 #    print "STACK: ", join(', ', map { $_->{name}; } @output), "\n";
389     return @output;
390 }
391
392 1;