partial support for tag expression as binary tree
[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->{elements}});
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->{elements}});
83         }
84         else
85         {
86             @dents=(qw(AND OR TRACKS NOARTIST),
87                     $self->{db}->artists(@{$self->{elements}}));
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->{elements}}));
100     }
101     elsif($state==$STATE_ROOT)
102     {
103         @dents=(qw(ALL NOT), $self->{db}->tags(@{$self->{elements}}));
104     }
105     elsif($state==$STATE_ALBUMS)
106     {
107         @dents=(qw(TRACKS NOALBUM),$self->{db}->albums(@{$self->{elements}}));
108     }
109     elsif($state==$STATE_TRACKLIST)
110     {
111         @dents=$self->{db}->tracks(@{$self->{elements}});
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     $self->{tagtree}=$self->elements_to_tree(@{$self->{elements}});
301 }
302
303 sub state
304 {
305     my($self, $newstate)=@_;
306     $self->{state}=$newstate if(defined($newstate));
307     return $self->{state};
308 }
309
310 sub elements_to_tree
311 {
312     my($self, @elements)=@_;
313     my $op=undef;
314     my $top=undef;
315     my $node=undef;
316     my $lastop=undef;
317     use Data::Dumper;
318     while(my $element=shift @elements)
319     {
320         my $tag;
321         if(ref($element) eq "ID3FS::PathElement::Boolean")
322         {
323             $lastop=$op;
324             $op=$element->{name};
325 #           print "BOOL: $op\n";
326         }
327         if(ref($element) eq "ID3FS::PathElement::Tag")
328         {
329             $tag=$element->{name};
330             while(@elements && ref($elements[0]) eq "ID3FS::PathElement::Tag")
331             {
332                 $tag .= "/" . (shift @elements)->{name};
333             }
334 #           print "TAG: $tag\n";
335             my $node=ID3FS::Path::Node->new($tag);
336             if(!$top)
337             {
338                 $top=$node;
339             }
340             elsif($op)
341             {
342                 my $nextop=undef;
343                 if(!defined($lastop) || ($priorities{$lastop} >= $priorities{$op}))
344                 {
345                     $top=ID3FS::Path::Node->new($node, $op, $top);
346                 }
347                 else
348                 {
349                     $top=ID3FS::Path::Node->new($top, $op, $node);
350                 }
351             }
352             else
353             {
354                 die ("FAIL - SHOULD NOT HAPPEN\n");
355             }
356         }
357     }
358     print($top->print(), "\n") if $top;
359 }
360
361 1;