~ubuntu-branches/ubuntu/oneiric/calibre/oneiric

« back to all changes in this revision

Viewing changes to src/calibre/library/field_metadata.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2010-06-21 10:18:08 UTC
  • mfrom: (1.3.12 upstream)
  • Revision ID: james.westby@ubuntu.com-20100621101808-aue828f532tmo4zt
Tags: 0.7.2+dfsg-1
* New major upstream version. See http://calibre-ebook.com/new-in/seven for
  details.
* Refresh patches to apply cleanly.
* debian/control: Bump python-cssutils to >= 0.9.7~ to ensure the existence
  of the CSSRuleList.rulesOfType attribute. This makes epub conversion work
  again. (Closes: #584756)
* Add debian/local/calibre-mount-helper: Simple and safe replacement for
  upstream's calibre-mount-helper, using udisks --mount and eject.
  (Closes: #584915, LP: #561958)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''
 
2
Created on 25 May 2010
 
3
 
 
4
@author: charles
 
5
'''
 
6
 
 
7
from calibre.utils.ordered_dict import OrderedDict
 
8
 
 
9
class TagsIcons(dict):
 
10
    '''
 
11
    If the client wants icons to be in the tag structure, this class must be
 
12
    instantiated and filled in with real icons. If this class is instantiated
 
13
    and passed to get_categories, All items must be given a value not None
 
14
    '''
 
15
 
 
16
    category_icons = ['authors', 'series', 'formats', 'publisher', 'rating',
 
17
                      'news',    'tags',   ':custom', ':user',     'search',]
 
18
    def __init__(self, icon_dict):
 
19
        for a in self.category_icons:
 
20
            if a not in icon_dict:
 
21
                raise ValueError('Missing category icon [%s]'%a)
 
22
            self[a] = icon_dict[a]
 
23
 
 
24
class FieldMetadata(dict):
 
25
    '''
 
26
    key: the key to the dictionary is:
 
27
    - for standard fields, the metadata field name.
 
28
    - for custom fields, the metadata field name prefixed by '#'
 
29
    This is done to create two 'namespaces' so the names don't clash
 
30
 
 
31
    label: the actual column label. No prefixing.
 
32
 
 
33
    datatype: the type of the information in the field. Valid values are float,
 
34
    int, rating, bool, comments, datetime, text.
 
35
    is_multiple: valid for the text datatype. If None, the field is to be
 
36
    treated as a single term. If not None, it contains a string, and the field
 
37
    is assumed to contain a list of terms separated by that string
 
38
 
 
39
    kind == standard: is a db field.
 
40
    kind == category: standard tag category that isn't a field. see news.
 
41
    kind == user: user-defined tag category.
 
42
    kind == search: saved-searches category.
 
43
 
 
44
    is_category: is a tag browser category. If true, then:
 
45
       table: name of the db table used to construct item list
 
46
       column: name of the column in the normalized table to join on
 
47
       link_column: name of the column in the connection table to join on
 
48
       If these are None, then the category constructor must know how
 
49
       to build the item list (e.g., formats).
 
50
       The order below is the order that the categories will
 
51
       appear in the tags pane.
 
52
 
 
53
    name: the text that is to be used when displaying the field. Column headings
 
54
    in the GUI, etc.
 
55
 
 
56
    search_terms: the terms that can be used to identify the field when
 
57
    searching. They can be thought of as aliases for metadata keys, but are only
 
58
    valid when passed to search().
 
59
 
 
60
    is_custom: the field has been added by the user.
 
61
 
 
62
    rec_index: the index of the field in the db metadata record.
 
63
 
 
64
    '''
 
65
    _field_metadata = [
 
66
            ('authors',   {'table':'authors',
 
67
                           'column':'name',
 
68
                           'link_column':'author',
 
69
                           'datatype':'text',
 
70
                           'is_multiple':',',
 
71
                           'kind':'field',
 
72
                           'name':_('Authors'),
 
73
                           'search_terms':['authors', 'author'],
 
74
                           'is_custom':False,
 
75
                           'is_category':True}),
 
76
            ('series',    {'table':'series',
 
77
                           'column':'name',
 
78
                           'link_column':'series',
 
79
                           'datatype':'text',
 
80
                           'is_multiple':None,
 
81
                           'kind':'field',
 
82
                           'name':_('Series'),
 
83
                           'search_terms':['series'],
 
84
                           'is_custom':False,
 
85
                           'is_category':True}),
 
86
            ('formats',   {'table':None,
 
87
                           'column':None,
 
88
                           'datatype':'text',
 
89
                           'is_multiple':',',
 
90
                           'kind':'field',
 
91
                           'name':_('Formats'),
 
92
                           'search_terms':['formats', 'format'],
 
93
                           'is_custom':False,
 
94
                           'is_category':True}),
 
95
            ('publisher', {'table':'publishers',
 
96
                           'column':'name',
 
97
                           'link_column':'publisher',
 
98
                           'datatype':'text',
 
99
                           'is_multiple':None,
 
100
                           'kind':'field',
 
101
                           'name':_('Publishers'),
 
102
                           'search_terms':['publisher'],
 
103
                           'is_custom':False,
 
104
                           'is_category':True}),
 
105
            ('rating',    {'table':'ratings',
 
106
                           'column':'rating',
 
107
                           'link_column':'rating',
 
108
                           'datatype':'rating',
 
109
                           'is_multiple':None,
 
110
                           'kind':'field',
 
111
                           'name':_('Ratings'),
 
112
                           'search_terms':['rating'],
 
113
                           'is_custom':False,
 
114
                           'is_category':True}),
 
115
            ('news',      {'table':'news',
 
116
                           'column':'name',
 
117
                           'datatype':None,
 
118
                           'is_multiple':None,
 
119
                           'kind':'category',
 
120
                           'name':_('News'),
 
121
                           'search_terms':[],
 
122
                           'is_custom':False,
 
123
                           'is_category':True}),
 
124
            ('tags',      {'table':'tags',
 
125
                           'column':'name',
 
126
                           'link_column': 'tag',
 
127
                           'datatype':'text',
 
128
                           'is_multiple':',',
 
129
                           'kind':'field',
 
130
                           'name':_('Tags'),
 
131
                           'search_terms':['tags', 'tag'],
 
132
                           'is_custom':False,
 
133
                           'is_category':True}),
 
134
            ('author_sort',{'table':None,
 
135
                            'column':None,
 
136
                            'datatype':'text',
 
137
                           'is_multiple':None,
 
138
                           'kind':'field',
 
139
                           'name':None,
 
140
                           'search_terms':[],
 
141
                           'is_custom':False,
 
142
                           'is_category':False}),
 
143
            ('comments',  {'table':None,
 
144
                           'column':None,
 
145
                           'datatype':'text',
 
146
                           'is_multiple':None,
 
147
                           'kind':'field',
 
148
                           'name':None,
 
149
                           'search_terms':['comments', 'comment'],
 
150
                           'is_custom':False, 'is_category':False}),
 
151
            ('cover',     {'table':None,
 
152
                           'column':None,
 
153
                           'datatype':None,
 
154
                           'is_multiple':None,
 
155
                           'kind':'field',
 
156
                           'name':None,
 
157
                           'search_terms':['cover'],
 
158
                           'is_custom':False,
 
159
                           'is_category':False}),
 
160
            ('flags',     {'table':None,
 
161
                           'column':None,
 
162
                           'datatype':'text',
 
163
                           'is_multiple':None,
 
164
                           'kind':'field',
 
165
                           'name':None,
 
166
                           'search_terms':[],
 
167
                           'is_custom':False,
 
168
                           'is_category':False}),
 
169
            ('id',        {'table':None,
 
170
                           'column':None,
 
171
                           'datatype':'int',
 
172
                           'is_multiple':None,
 
173
                           'kind':'field',
 
174
                           'name':None,
 
175
                           'search_terms':[],
 
176
                           'is_custom':False,
 
177
                           'is_category':False}),
 
178
            ('isbn',      {'table':None,
 
179
                           'column':None,
 
180
                           'datatype':'text',
 
181
                           'is_multiple':None,
 
182
                           'kind':'field',
 
183
                           'name':None,
 
184
                           'search_terms':['isbn'],
 
185
                           'is_custom':False,
 
186
                           'is_category':False}),
 
187
            ('lccn',      {'table':None,
 
188
                           'column':None,
 
189
                           'datatype':'text',
 
190
                           'is_multiple':None,
 
191
                           'kind':'field',
 
192
                           'name':None,
 
193
                           'search_terms':[],
 
194
                           'is_custom':False,
 
195
                           'is_category':False}),
 
196
            ('ondevice',  {'table':None,
 
197
                           'column':None,
 
198
                           'datatype':'text',
 
199
                           'is_multiple':None,
 
200
                           'kind':'field',
 
201
                           'name':None,
 
202
                           'search_terms':['ondevice'],
 
203
                           'is_custom':False,
 
204
                           'is_category':False}),
 
205
            ('path',      {'table':None,
 
206
                           'column':None,
 
207
                           'datatype':'text',
 
208
                           'is_multiple':None,
 
209
                           'kind':'field',
 
210
                           'name':None,
 
211
                           'search_terms':[],
 
212
                           'is_custom':False,
 
213
                           'is_category':False}),
 
214
            ('pubdate',   {'table':None,
 
215
                           'column':None,
 
216
                           'datatype':'datetime',
 
217
                           'is_multiple':None,
 
218
                           'kind':'field',
 
219
                           'name':None,
 
220
                           'search_terms':['pubdate'],
 
221
                           'is_custom':False,
 
222
                           'is_category':False}),
 
223
            ('series_index',{'table':None,
 
224
                             'column':None,
 
225
                             'datatype':'float',
 
226
                             'is_multiple':None,
 
227
                             'kind':'field',
 
228
                             'name':None,
 
229
                             'search_terms':[],
 
230
                             'is_custom':False,
 
231
                             'is_category':False}),
 
232
            ('sort',      {'table':None,
 
233
                           'column':None,
 
234
                           'datatype':'text',
 
235
                           'is_multiple':None,
 
236
                           'kind':'field',
 
237
                           'name':None,
 
238
                           'search_terms':[],
 
239
                           'is_custom':False,
 
240
                           'is_category':False}),
 
241
            ('size',      {'table':None,
 
242
                           'column':None,
 
243
                           'datatype':'float',
 
244
                           'is_multiple':None,
 
245
                           'kind':'field',
 
246
                           'name':None,
 
247
                           'search_terms':[],
 
248
                           'is_custom':False,
 
249
                           'is_category':False}),
 
250
            ('timestamp', {'table':None,
 
251
                           'column':None,
 
252
                           'datatype':'datetime',
 
253
                           'is_multiple':None,
 
254
                           'kind':'field',
 
255
                           'name':None,
 
256
                           'search_terms':['date'],
 
257
                           'is_custom':False,
 
258
                           'is_category':False}),
 
259
            ('title',     {'table':None,
 
260
                           'column':None,
 
261
                           'datatype':'text',
 
262
                           'is_multiple':None,
 
263
                           'kind':'field',
 
264
                           'name':None,
 
265
                           'search_terms':['title'],
 
266
                           'is_custom':False,
 
267
                           'is_category':False}),
 
268
            ('uuid',      {'table':None,
 
269
                           'column':None,
 
270
                           'datatype':'text',
 
271
                           'is_multiple':None,
 
272
                           'kind':'field',
 
273
                           'name':None,
 
274
                           'search_terms':[],
 
275
                           'is_custom':False,
 
276
                           'is_category':False}),
 
277
            ]
 
278
 
 
279
    # search labels that are not db columns
 
280
    search_items = [    'all',
 
281
#                        'date',
 
282
                        'search',
 
283
                    ]
 
284
 
 
285
    def __init__(self):
 
286
        self._tb_cats = OrderedDict()
 
287
        self._search_term_map = {}
 
288
        self.custom_label_to_key_map = {}
 
289
        for k,v in self._field_metadata:
 
290
            self._tb_cats[k] = v
 
291
            self._tb_cats[k]['label'] = k
 
292
            self._tb_cats[k]['display'] = {}
 
293
            self._tb_cats[k]['is_editable'] = True
 
294
            self._add_search_terms_to_map(k, self._tb_cats[k]['search_terms'])
 
295
        self.custom_field_prefix = '#'
 
296
        self.get = self._tb_cats.get
 
297
 
 
298
    def __getitem__(self, key):
 
299
        return self._tb_cats[key]
 
300
 
 
301
    def __setitem__(self, key, val):
 
302
        raise AttributeError('Assigning to this object is forbidden')
 
303
 
 
304
    def __delitem__(self, key):
 
305
        del self._tb_cats[key]
 
306
 
 
307
    def __iter__(self):
 
308
        for key in self._tb_cats:
 
309
            yield key
 
310
 
 
311
    def __contains__(self, key):
 
312
        return self.has_key(key)
 
313
 
 
314
    def has_key(self, key):
 
315
        return key in self._tb_cats
 
316
 
 
317
    def keys(self):
 
318
        return self._tb_cats.keys()
 
319
 
 
320
    def iterkeys(self):
 
321
        for key in self._tb_cats:
 
322
            yield key
 
323
 
 
324
    def itervalues(self):
 
325
        return self._tb_cats.itervalues()
 
326
 
 
327
    def values(self):
 
328
        return self._tb_cats.values()
 
329
 
 
330
    def iteritems(self):
 
331
        for key in self._tb_cats:
 
332
            yield (key, self._tb_cats[key])
 
333
 
 
334
    def items(self):
 
335
        return list(self.iteritems())
 
336
 
 
337
    def is_custom_field(self, key):
 
338
        return key.startswith(self.custom_field_prefix)
 
339
 
 
340
    def key_to_label(self, key):
 
341
        if 'label' not in self._tb_cats[key]:
 
342
            return key
 
343
        return self._tb_cats[key]['label']
 
344
 
 
345
    def label_to_key(self, label, prefer_custom=False):
 
346
        if prefer_custom:
 
347
            if label in self.custom_label_to_key_map:
 
348
                return self.custom_label_to_key_map[label]
 
349
        if 'label' in self._tb_cats:
 
350
            return label
 
351
        if not prefer_custom:
 
352
            if label in self.custom_label_to_key_map:
 
353
                return self.custom_label_to_key_map[label]
 
354
        raise ValueError('Unknown key [%s]'%(label))
 
355
 
 
356
    def get_custom_fields(self):
 
357
        return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']]
 
358
 
 
359
    def get_custom_field_metadata(self):
 
360
        l = {}
 
361
        for k in self._tb_cats:
 
362
            if self._tb_cats[k]['is_custom']:
 
363
                l[k] = self._tb_cats[k]
 
364
        return l
 
365
 
 
366
    def add_custom_field(self, label, table, column, datatype, colnum, name,
 
367
                               display, is_editable, is_multiple, is_category):
 
368
        key = self.custom_field_prefix + label
 
369
        if key in self._tb_cats:
 
370
            raise ValueError('Duplicate custom field [%s]'%(label))
 
371
        self._tb_cats[key] = {'table':table,       'column':column,
 
372
                             'datatype':datatype,  'is_multiple':is_multiple,
 
373
                             'kind':'field',       'name':name,
 
374
                             'search_terms':[key], 'label':label,
 
375
                             'colnum':colnum,      'display':display,
 
376
                             'is_custom':True,     'is_category':is_category,
 
377
                             'link_column':'value',
 
378
                             'is_editable': is_editable,}
 
379
        self._add_search_terms_to_map(key, [key])
 
380
        self.custom_label_to_key_map[label] = key
 
381
 
 
382
    def remove_custom_fields(self):
 
383
        for key in self.get_custom_fields():
 
384
            del self._tb_cats[key]
 
385
 
 
386
    def remove_dynamic_categories(self):
 
387
        for key in list(self._tb_cats.keys()):
 
388
            val = self._tb_cats[key]
 
389
            if val['is_category'] and val['kind'] in ('user', 'search'):
 
390
                del self._tb_cats[key]
 
391
 
 
392
 
 
393
    def add_user_category(self, label, name):
 
394
        if label in self._tb_cats:
 
395
            raise ValueError('Duplicate user field [%s]'%(label))
 
396
        self._tb_cats[label] = {'table':None,        'column':None,
 
397
                                'datatype':None,     'is_multiple':None,
 
398
                                'kind':'user',       'name':name,
 
399
                                'search_terms':[],    'is_custom':False,
 
400
                                'is_category':True}
 
401
 
 
402
    def add_search_category(self, label, name):
 
403
        if label in self._tb_cats:
 
404
            raise ValueError('Duplicate user field [%s]'%(label))
 
405
        self._tb_cats[label] = {'table':None,        'column':None,
 
406
                                'datatype':None,     'is_multiple':None,
 
407
                                'kind':'search',     'name':name,
 
408
                                'search_terms':[],    'is_custom':False,
 
409
                                'is_category':True}
 
410
 
 
411
    def set_field_record_index(self, label, index, prefer_custom=False):
 
412
        if prefer_custom:
 
413
            key = self.custom_field_prefix+label
 
414
            if key not in self._tb_cats:
 
415
                key = label
 
416
        else:
 
417
            if label in self._tb_cats:
 
418
                key = label
 
419
            else:
 
420
                key = self.custom_field_prefix+label
 
421
        self._tb_cats[key]['rec_index'] = index  # let the exception fly ...
 
422
 
 
423
 
 
424
#    DEFAULT_LOCATIONS = frozenset([
 
425
#        'all',
 
426
#        'author',       # compatibility
 
427
#        'authors',
 
428
#        'comment',      # compatibility
 
429
#        'comments',
 
430
#        'cover',
 
431
#        'date',
 
432
#        'format',       # compatibility
 
433
#        'formats',
 
434
#        'isbn',
 
435
#        'ondevice',
 
436
#        'pubdate',
 
437
#        'publisher',
 
438
#        'search',
 
439
#        'series',
 
440
#        'rating',
 
441
#        'tag',          # compatibility
 
442
#        'tags',
 
443
#        'title',
 
444
#                 ])
 
445
 
 
446
    def get_search_terms(self):
 
447
        s_keys = []
 
448
        for v in self._tb_cats.itervalues():
 
449
            map((lambda x:s_keys.append(x)), v['search_terms'])
 
450
        for v in self.search_items:
 
451
            s_keys.append(v)
 
452
#        if set(s_keys) != self.DEFAULT_LOCATIONS:
 
453
#            print 'search labels and default_locations do not match:'
 
454
#            print set(s_keys) ^ self.DEFAULT_LOCATIONS
 
455
        return s_keys
 
456
 
 
457
    def _add_search_terms_to_map(self, key, terms):
 
458
        if terms is not None:
 
459
            for t in terms:
 
460
                self._search_term_map[t] = key
 
461
 
 
462
    def search_term_to_key(self, term):
 
463
        if term in self._search_term_map:
 
464
            return  self._search_term_map[term]
 
465
        return term