~andrewsomething/exaile/karmic

« back to all changes in this revision

Viewing changes to xl/trackdb.py

  • Committer: Aren Olson
  • Date: 2009-09-12 00:36:59 UTC
  • Revision ID: reacocard@gmail.com-20090912003659-w373sg0n04uoa8op
remove useless files, add soem of the fixes from lp bug 420019

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008-2009 Adam Olsen 
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2, or (at your option)
6
 
# any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16
 
 
17
 
"""
18
 
TrackDB
19
 
 
20
 
:class:`TrackDB`:
21
 
    a track database. basis for playlist, collection
22
 
 
23
 
:class:`TrackSearcher`
24
 
    fast, advanced method for searching a dictionary of tracks
25
 
"""
26
 
 
27
 
from xl.nls import gettext as _
28
 
from xl import common, track, event, xdg
29
 
 
30
 
try:
31
 
    import cPickle as pickle
32
 
except:
33
 
    import pickle
34
 
 
35
 
import shelve
36
 
import traceback
37
 
 
38
 
from copy import deepcopy
39
 
import logging, random, time, os, time
40
 
logger = logging.getLogger(__name__)
41
 
 
42
 
#FIXME: make these user-customizable
43
 
SEARCH_ITEMS = ('artist', 'albumartist', 'album', 'title')
44
 
SORT_FALLBACK = ('tracknumber', 'discnumber', 'album')
45
 
 
46
 
def get_sort_tuple(fields, track):
47
 
    """
48
 
        Returns the sort tuple for a single track
49
 
 
50
 
        :param fields: the tag(s) to sort by
51
 
        :type fields: a single string or iterable of strings
52
 
        :param track: the track to sort
53
 
        :type track: :class:`xl.track.Track`
54
 
    """
55
 
    def lower(x):
56
 
        if type(x) == type(""):
57
 
            return x.lower()
58
 
        return x
59
 
    items = []
60
 
    if not type(fields) in (list, tuple):
61
 
        items = [lower(track.sort_param(field))]
62
 
    else:
63
 
        items = [lower(track.sort_param(field)) for field in fields]
64
 
 
65
 
    items.append(track)
66
 
    return tuple(items)
67
 
 
68
 
def sort_tracks(fields, tracks, reverse=False):
69
 
    """
70
 
        Sorts tracks by the field passed
71
 
 
72
 
        :param fields: field(s) to sort by 
73
 
        :type fields: string or list of strings
74
 
 
75
 
        :param tracks: tracks to sort 
76
 
        :type tracks: list of :class:`xl.track.Track`
77
 
 
78
 
        :param reverse: sort in reverse?
79
 
        :type reverse: bool
80
 
    """
81
 
    tracks = [get_sort_tuple(fields, t) for t in tracks]
82
 
    tracks.sort(reverse=reverse)
83
 
    return [t[-1] for t in tracks]
84
 
 
85
 
class TrackHolder(object):
86
 
    def __init__(self, track, key, **kwargs):
87
 
        self._track = track
88
 
        self._key = key
89
 
        self._attrs = kwargs
90
 
 
91
 
    def __getitem__(self, tag):
92
 
        return self._track[tag]
93
 
 
94
 
    def __setitem__(self, tag, values):
95
 
        self._track[tag] = values
96
 
 
97
 
 
98
 
class TrackDB(object):
99
 
    """
100
 
        Manages a track database. 
101
 
 
102
 
        Allows you to add, remove, retrieve, search, save and load
103
 
        Track objects.
104
 
 
105
 
        :param name:   The name of this TrackDB.
106
 
        :type name: string
107
 
        :param location:   Path to a file where this trackDB
108
 
                should be stored.
109
 
        :type location: string
110
 
        :param pickle_attrs:   A list of attributes to store in the
111
 
                pickled representation of this object. All
112
 
                attributes listed must be built-in types, with
113
 
                one exception: If the object contains the phrase
114
 
                'tracks' in its name it may be a list or dict
115
 
                or :class:`xl.track.Track` objects.
116
 
        :type pickle_attrs: list of strings
117
 
    """
118
 
    def __init__(self, name='', location="", pickle_attrs=[]):
119
 
        """
120
 
            Sets up the trackDB.
121
 
        """
122
 
        self.name = name
123
 
        self.location = location
124
 
        self._dirty = False
125
 
        self.tracks = {}
126
 
        self.pickle_attrs = pickle_attrs
127
 
        self.pickle_attrs += ['tracks', 'name', '_key']
128
 
        self._saving = False
129
 
        self._key = 0
130
 
        self._dbversion = 1
131
 
        self._deleted_keys = []
132
 
        if location:
133
 
            self.load_from_location()
134
 
            event.timeout_add(300000, self._timeout_save)
135
 
 
136
 
    def _timeout_save(self):
137
 
        self.save_to_location()
138
 
        return True
139
 
 
140
 
    def set_name(self, name):
141
 
        """
142
 
            Sets the name of this :class:`TrackDB`
143
 
 
144
 
            :param name:   The new name.
145
 
            :type name: string
146
 
        """
147
 
        self.name = name
148
 
        self._dirty = True
149
 
 
150
 
    def get_name(self):
151
 
        """
152
 
            Gets the name of this :class:`TrackDB`
153
 
 
154
 
            :return: The name.
155
 
            :rtype: string
156
 
        """
157
 
        return self.name
158
 
 
159
 
    def set_location(self, location):
160
 
        self.location = location
161
 
        self._dirty = True
162
 
 
163
 
    @common.synchronized
164
 
    def load_from_location(self, location=None):
165
 
        """
166
 
            Restores :class:`TrackDB` state from the pickled representation
167
 
            stored at the specified location.
168
 
 
169
 
            :param location: the location to load the data from
170
 
            :type location: string
171
 
        """
172
 
        if not location:
173
 
            location = self.location
174
 
        if not location:
175
 
            raise AttributeError(
176
 
                    _("You did not specify a location to load the db from"))
177
 
 
178
 
        try:
179
 
            pdata = shelve.open(self.location, flag='c', 
180
 
                    protocol=common.PICKLE_PROTOCOL)
181
 
            if pdata.has_key("_dbversion"):
182
 
                if pdata['_dbversion'] > self._dbversion:
183
 
                    raise common.VersionError, \
184
 
                            "DB was created on a newer Exaile version."
185
 
        except common.VersionError:
186
 
            raise
187
 
        except:
188
 
            logger.error("Failed to open music DB.")
189
 
            return
190
 
 
191
 
        for attr in self.pickle_attrs:
192
 
            try:
193
 
                if 'tracks' == attr:
194
 
                    data = {}
195
 
                    for k in (x for x in pdata.keys() \
196
 
                            if x.startswith("tracks-")):
197
 
                        p = pdata[k]
198
 
                        tr = track.Track(_unpickles=p[0])
199
 
                        data[tr.get_loc()] = TrackHolder(tr, p[1], **p[2])
200
 
                    setattr(self, attr, data)
201
 
                else:
202
 
                    setattr(self, attr, pdata[attr])
203
 
            except:
204
 
                pass #FIXME
205
 
 
206
 
        pdata.close()
207
 
 
208
 
        self._dirty = False 
209
 
 
210
 
    @common.synchronized
211
 
    def save_to_location(self, location=None):
212
 
        """
213
 
            Saves a pickled representation of this :class:`TrackDB` to the
214
 
            specified location.
215
 
            
216
 
            :param location: the location to save the data to
217
 
            :type location: string
218
 
        """
219
 
        logger.debug("Saving %(name)s DB to %(location)s." %
220
 
            {'name' : self.name, 'location' : location or self.location})
221
 
        if not self._dirty:
222
 
            for k, track in self.tracks.iteritems():
223
 
                if track._track._dirty: 
224
 
                    self._dirty = True
225
 
                    break
226
 
 
227
 
        if not self._dirty:
228
 
            return
229
 
 
230
 
        if not location:
231
 
            location = self.location
232
 
        if not location:
233
 
            raise AttributeError(_("You did not specify a location to save the db"))
234
 
 
235
 
        if self._saving: 
236
 
            return
237
 
        self._saving = True
238
 
 
239
 
        try:
240
 
            pdata = shelve.open(self.location, flag='c', 
241
 
                    protocol=common.PICKLE_PROTOCOL)
242
 
            if pdata.has_key("_dbversion"):
243
 
                if pdata['_dbversion'] > self._dbversion:
244
 
                    raise ValueError, "DB was created on a newer Exaile version."
245
 
        except:
246
 
            logger.error("Failed to open music DB for write.")
247
 
            return
248
 
 
249
 
        for attr in self.pickle_attrs:
250
 
            # bad hack to allow saving of lists/dicts of Tracks
251
 
            if 'tracks' == attr:
252
 
                for k, track in self.tracks.iteritems():
253
 
                    if track._track._dirty or "tracks-%s"%track._key not in pdata:
254
 
                        pdata["tracks-%s"%track._key] = (
255
 
                                track._track._pickles(),
256
 
                                track._key,
257
 
                                deepcopy(track._attrs))
258
 
            else:
259
 
                pdata[attr] = deepcopy(getattr(self, attr))
260
 
 
261
 
        pdata['_dbversion'] = self._dbversion
262
 
 
263
 
        for key in self._deleted_keys:
264
 
            if "tracks-%s"%key in pdata:
265
 
                del pdata["tracks-%s"%key]
266
 
 
267
 
        pdata.sync()
268
 
        pdata.close()
269
 
        
270
 
        for track in self.tracks.itervalues():
271
 
            if track._track._dirty: 
272
 
                track._dirty = False
273
 
 
274
 
        self._dirty = False
275
 
        self._saving = False
276
 
 
277
 
    def list_tag(self, tag, search_terms="", use_albumartist=False, 
278
 
                 ignore_the=False, sort=False, sort_by=[], reverse=False):
279
 
        """
280
 
            lists out all the values for a particular, tag, without duplicates
281
 
            
282
 
            can also optionally prefer albumartist's value over artist's, this
283
 
            is primarily useful for the collection panel
284
 
        """
285
 
        def the_cmp(x, y):
286
 
            if isinstance(x, basestring):
287
 
                x = x.lower()
288
 
                x = common.the_cutter(x)
289
 
            if isinstance(y, basestring):
290
 
                y = y.lower()
291
 
                y = common.the_cutter(y)
292
 
            return cmp(x, y)
293
 
 
294
 
        if sort_by == []:
295
 
            tset = set()
296
 
 
297
 
            for t in self.search(search_terms):
298
 
                try:
299
 
                    for i in t[tag]:
300
 
                        tset.add(i)
301
 
                except:
302
 
                    tset.add(t[tag])
303
 
 
304
 
            vals = list(tset)
305
 
            if ignore_the:
306
 
                cmp_type = the_cmp
307
 
            else:
308
 
                cmp_type = lambda x,y: cmp(x.lower(), y.lower())
309
 
            vals = sorted(vals, cmp=cmp_type)
310
 
        else:
311
 
            tracks = self.search(search_terms)
312
 
            tracks = sort_tracks(sort_by, tracks, reverse)
313
 
            count = 1
314
 
            while count < len(tracks):
315
 
                if tracks[count][tag] == tracks[count-1][tag]:
316
 
                    del tracks[count]
317
 
                count += 1
318
 
            vals = [u" / ".join(x[tag]) for x in tracks if x[tag]]
319
 
 
320
 
        return vals
321
 
 
322
 
    def get_track_by_loc(self, loc, raw=False):
323
 
        """
324
 
            returns the track having the given loc. if no such track exists,
325
 
            returns None
326
 
        """
327
 
        try:
328
 
            return self.tracks[loc]._track
329
 
        except KeyError:
330
 
            return None
331
 
 
332
 
    def get_tracks_by_locs(self, locs):
333
 
        """
334
 
            returns the track having the given loc. if no such track exists,
335
 
            returns None
336
 
        """
337
 
        return [self.get_track_by_loc(loc) for loc in locs]
338
 
 
339
 
    def get_track_attr(self, loc, attr):
340
 
        return self.get_track_by_loc(loc)[attr]
341
 
 
342
 
    def search(self, query, sort_fields=None, return_lim=-1, tracks=None, reverse=False):
343
 
        """
344
 
            Search the trackDB, optionally sorting by sort_field
345
 
 
346
 
            :param query:  the search
347
 
            :param sort_fields:  the field(s) to sort by.  Use RANDOM to sort
348
 
                randomly.
349
 
            :type sort_fields: A string or list of strings
350
 
            :param return_lim:  limit the number of tracks returned to a
351
 
                maximum
352
 
        """
353
 
        searcher = TrackSearcher()
354
 
        if not tracks:
355
 
            tracks = self.tracks
356
 
        elif type(tracks) == list:
357
 
            do_search = {}
358
 
            for track in tracks:
359
 
                do_search[track.get_loc()] = track
360
 
            tracks = do_search
361
 
        elif type(tracks) == dict:
362
 
            pass
363
 
        else:
364
 
            raise ValueError
365
 
 
366
 
        tracksres = searcher.search(query, tracks.copy())
367
 
        tracks = []
368
 
        for tr in tracksres.itervalues():
369
 
            if hasattr(tr, '_track'):
370
 
                #print "GOOD"
371
 
                tracks.append(tr._track)
372
 
            else:
373
 
                #print "BAD"
374
 
                tracks.append(tr)
375
 
 
376
 
        if sort_fields:
377
 
            if sort_fields == 'RANDOM':
378
 
                random.shuffle(tracks)
379
 
            else:
380
 
                tracks = sort_tracks(sort_fields, tracks, reverse)
381
 
        if return_lim > 0:
382
 
            tracks = tracks[:return_lim]
383
 
 
384
 
        return tracks
385
 
 
386
 
    def loc_is_member(self, loc):
387
 
        """
388
 
            Returns True if loc is a track in this collection, False
389
 
            if it is not
390
 
        """
391
 
        # check to see if it's in one of our libraries, this speeds things
392
 
        # up if it isn't
393
 
        lib = None
394
 
        if hasattr(self, 'libraries'):
395
 
            for k, v in self.libraries.iteritems():
396
 
                if loc.startswith('file://%s' % k):
397
 
                    lib = v
398
 
            if not lib:
399
 
                return False
400
 
 
401
 
        # check for the actual track
402
 
        if self.get_track_by_loc(loc):
403
 
            return True
404
 
        else:
405
 
            return False
406
 
 
407
 
    def get_count(self):
408
 
        """
409
 
            Returns the number of tracks stored in this database
410
 
        """
411
 
        count = len(self.tracks)
412
 
        return count
413
 
 
414
 
    def add(self, track):
415
 
        """
416
 
            Adds a track to the database of tracks
417
 
 
418
 
            :param track: The Track to add 
419
 
            :type track: :class:`xl.track.Track`
420
 
        """
421
 
        self.add_tracks([track])
422
 
 
423
 
    @common.synchronized
424
 
    def add_tracks(self, tracks):
425
 
        for tr in tracks:
426
 
            self.tracks[tr.get_loc()] = TrackHolder(tr, self._key)
427
 
            self._key += 1
428
 
            event.log_event("track_added", self, tr.get_loc())
429
 
        self._dirty = True 
430
 
 
431
 
    def remove(self, track):
432
 
        """
433
 
            Removes a track from the database
434
 
 
435
 
            :param track: the Track to remove 
436
 
            :type track: Track]   
437
 
        """
438
 
        self.remove_tracks([track])
439
 
    
440
 
    @common.synchronized            
441
 
    def remove_tracks(self, tracks):
442
 
        for tr in tracks:
443
 
            self._deleted_keys.append(self.tracks[tr.get_loc()]._key)
444
 
            del self.tracks[tr.get_loc()]
445
 
            event.log_event("track_removed", self, tr.get_loc())
446
 
        self._dirty = True
447
 
      
448
 
 
449
 
class TrackSearcher(object):
450
 
    """
451
 
        Search a TrackDB for matching tracks
452
 
    """ 
453
 
    def tokenize_query(self, search):
454
 
        """ 
455
 
            tokenizes a search query 
456
 
        """
457
 
        search = " " + search + " "
458
 
 
459
 
        search = search.replace(" OR ", " | ")
460
 
        search = search.replace(" NOT ", " ! ")
461
 
 
462
 
        newsearch = ""
463
 
        in_quotes = False
464
 
        n = 0
465
 
        while n < len(search):
466
 
            c = search[n]
467
 
            if c == "\\":
468
 
                n += 1
469
 
                try:
470
 
                    newsearch += search[n]
471
 
                except IndexError:
472
 
                    traceback.print_exc()
473
 
            elif in_quotes and c != "\"":
474
 
                newsearch += c
475
 
            elif c == "\"":
476
 
                in_quotes = in_quotes == False # toggle
477
 
                newsearch += c
478
 
            elif c in ["|", "!", "(", ")"]:
479
 
                newsearch += " " + c + " "
480
 
            elif c == " ":
481
 
                try:
482
 
                    if search[n+1] != " ":
483
 
                        if search[n+1] not in ["=", ">", "<"]:
484
 
                            newsearch += " "
485
 
                except IndexError:
486
 
                    pass
487
 
            else:
488
 
                newsearch += c
489
 
            n += 1
490
 
 
491
 
 
492
 
        # split the search into tokens to be parsed
493
 
        search = " " + newsearch.lower() + " "
494
 
        tokens = search.split(" ")
495
 
        tokens = [t for t in tokens if t != ""]
496
 
 
497
 
        # handle "" grouping
498
 
        etokens = []
499
 
        counter = 0
500
 
        while counter < len(tokens):
501
 
            if '"' in tokens[counter]:
502
 
                tk = tokens[counter]
503
 
                while tk.count('"') - tk.count('\\"') < 2:
504
 
                    try:
505
 
                        tk += " " + tokens[counter+1]
506
 
                        counter += 1
507
 
                    except IndexError: # someone didnt match their "s
508
 
                        break
509
 
                first = tk.index('"', 0)
510
 
                last = first
511
 
                while True:
512
 
                    try:
513
 
                        last = tk.index('"', last+1)
514
 
                    except ValueError:
515
 
                        break
516
 
                tk = tk[:first] + tk[first+1:last] + tk[last+1:]
517
 
                etokens.append(tk)
518
 
                counter += 1
519
 
            else:
520
 
                if tokens[counter].strip() is not "":
521
 
                    etokens.append(tokens[counter])
522
 
                counter += 1
523
 
        tokens = etokens
524
 
 
525
 
        # reduce tokens to a search tree and optimize it
526
 
        tokens = self.__red(tokens)
527
 
        tokens = self.__optimize_tokens(tokens)
528
 
 
529
 
        return tokens
530
 
 
531
 
    def __optimize_tokens(self, tokens):
532
 
        """ 
533
 
            optimizes token order for fast search 
534
 
 
535
 
            :param tokens: tokens to optimize 
536
 
            :type tokens: token list
537
 
        """
538
 
        # only optimizes the top level of tokens, the speed
539
 
        # gains from optimizing recursively are usually negligible
540
 
        l1 = []
541
 
        l2 = []
542
 
        l3 = []
543
 
 
544
 
        for token in tokens:
545
 
            # direct equality is the most reducing so put them first
546
 
            if type(token) == str and "=" in token:
547
 
                l1.append(token)
548
 
            # then other normal keywords
549
 
            elif type(token) == str and "=" not in token:
550
 
                l2.append(token)
551
 
            # then anything else like ! or ()
552
 
            else:
553
 
                l3.append(token)
554
 
 
555
 
        tokens = l1 + l2 + l3
556
 
        return tokens
557
 
 
558
 
    def __red(self, tokens):
559
 
        """ 
560
 
            reduce tokens to a parsable format 
561
 
 
562
 
            :param tokens: the list of tokens to reduce 
563
 
            :type tokens: list of string
564
 
        """
565
 
        # base case since we use recursion
566
 
        if tokens == []:
567
 
            return []
568
 
 
569
 
        # handle parentheses
570
 
        elif "(" in tokens:
571
 
            num_found = 0
572
 
            start = None
573
 
            end = None
574
 
            count = 0
575
 
            for t in tokens:
576
 
                if t == "(":
577
 
                    if start is None:
578
 
                        start = count
579
 
                    else:
580
 
                        num_found += 1
581
 
                elif t == ")":
582
 
                    if end is None and num_found == 0:
583
 
                        end = count
584
 
                    else:
585
 
                        num_found -= 1
586
 
                if start and end:
587
 
                    break
588
 
                count += 1
589
 
            before = tokens[:start]
590
 
            inside = self.__red(tokens[start+1:end])
591
 
            after = tokens[end+1:]
592
 
            tokens = before + [["(",inside]] + after
593
 
 
594
 
        # handle NOT
595
 
        elif "!" in tokens:
596
 
            start = tokens.index("!")
597
 
            end = start+2
598
 
            before = tokens[:start]
599
 
            inside = tokens[start+1:end]
600
 
            after = tokens[end:]
601
 
            tokens = before + [["!", inside]] + after
602
 
 
603
 
        # handle OR
604
 
        elif "|" in tokens:
605
 
            start = tokens.index("|")
606
 
            inside = [tokens[start-1], tokens[start+1]]
607
 
            before = tokens[:start-1]
608
 
            after = tokens[start+2:]
609
 
            tokens = before + [["|",inside]] + after
610
 
 
611
 
        # nothing special, so just return it
612
 
        else:
613
 
            return tokens
614
 
 
615
 
        return self.__red(tokens)
616
 
 
617
 
    def search(self, query, tracks, sort_order=None):
618
 
        """
619
 
            executes a search using the passed query and (optionally) 
620
 
            the passed tracks
621
 
 
622
 
            :param query: the query to search for
623
 
            :type query: string
624
 
            :param tracks: the dict of tracks to use 
625
 
            :type tracks: dict of :class:`xl.track.Track`
626
 
        """
627
 
        tokens = self.tokenize_query(query)
628
 
        tracks = self.__do_search(tokens, tracks)
629
 
        return tracks
630
 
 
631
 
    def __do_search(self, tokens, current_list):
632
 
        """ 
633
 
            search for tracks by using the parsed tokens 
634
 
 
635
 
            :param tokens: tokens to use when searching 
636
 
            :type tokens: token list
637
 
            :param current_list: dict of tracks to search 
638
 
            :type current_list: dict of Track
639
 
        """
640
 
        new_list = {}
641
 
        # if there's no more tokens, everything matches!
642
 
        try:
643
 
            token = tokens[0]
644
 
        except IndexError:
645
 
            return current_list
646
 
 
647
 
        # is it a special operator?
648
 
        if type(token) == list:
649
 
            if len(token) == 1:
650
 
                token = token[0]
651
 
            subtoken = token[0]
652
 
            # NOT
653
 
            if subtoken == "!":
654
 
                to_remove = self.__do_search(token[1], current_list)
655
 
                for l,track in current_list.iteritems():
656
 
                    if l not in to_remove:
657
 
                        new_list[l]=track
658
 
            # OR
659
 
            elif subtoken == "|":
660
 
                new_list.update(
661
 
                        self.__do_search([token[1][0]], current_list))
662
 
                new_list.update(
663
 
                        self.__do_search([token[1][1]], current_list))
664
 
            # ()
665
 
            elif subtoken == "(":
666
 
                new_list = self.__do_search(token[1], current_list)
667
 
            else:
668
 
                logger.warning("Bad search token")
669
 
                return current_list
670
 
 
671
 
        # normal token
672
 
        else:
673
 
            # exact match in tag
674
 
            if "==" in token:
675
 
                tag, content = token.split("==", 1)
676
 
                #if content[0] == "\"" and content[-1] == "\"":
677
 
                #    content = content[1:-1]
678
 
                #content = content.strip().strip('"')
679
 
                if content == "__null__":
680
 
                    content = None
681
 
                for l,tr in current_list.iteritems():
682
 
                    if content == tr[tag]:
683
 
                        new_list[l] = tr
684
 
                        continue
685
 
                    try:
686
 
                        for t in tr[tag]:
687
 
                            if str(t).lower() == content or t == content:
688
 
                                new_list[l]=tr
689
 
                                break
690
 
                    except:
691
 
                        pass
692
 
            # keyword in tag
693
 
            elif "=" in token:
694
 
                tag, content = token.split("=", 1)
695
 
                content = content.strip().strip('"')
696
 
                for l,tr in current_list.iteritems():
697
 
                    try:
698
 
                        for t in tr[tag]:
699
 
                            if content in str(t).lower():
700
 
                                new_list[l]=tr
701
 
                                break
702
 
                    except:
703
 
                        pass
704
 
            # greater than
705
 
            elif ">" in token:
706
 
                tag, content = token.split(">", 1)
707
 
                content = content.strip().strip('"')
708
 
                for l,tr in current_list.iteritems():
709
 
                    try:
710
 
                        if float(content) < float(tr[tag]):
711
 
                            new_list[l]=tr
712
 
                    except:
713
 
                        pass
714
 
            # less than
715
 
            elif "<" in token:
716
 
                tag, content = token.split("<", 1)
717
 
                content = content.strip().strip('"')
718
 
                for l,tr in current_list.iteritems():
719
 
                    try:
720
 
                        if float(content) > float(tr[tag]):
721
 
                            new_list[l]=tr
722
 
                    except:
723
 
                        pass
724
 
            # plain keyword
725
 
            else:
726
 
                content = token.strip().strip('"')
727
 
                for l,tr in current_list.iteritems():
728
 
                    for item in SEARCH_ITEMS:
729
 
                        try:
730
 
                            for t in tr[item]:
731
 
                                if content in t.lower():
732
 
                                    new_list[l]=tr
733
 
                                    break
734
 
                        except:
735
 
                            pass
736
 
 
737
 
        return self.__do_search(tokens[1:], new_list)
738
 
 
739
 
 
740
 
# vim: et sts=4 sw=4
741