This commit is contained in:
GRiker 2013-01-24 06:27:05 -07:00
commit d2735cc80e
9 changed files with 267 additions and 90 deletions

View File

@ -11,6 +11,7 @@ import os, traceback
from collections import defaultdict from collections import defaultdict
from functools import wraps, partial from functools import wraps, partial
from calibre.db.categories import get_categories
from calibre.db.locking import create_locks, RecordLock from calibre.db.locking import create_locks, RecordLock
from calibre.db.fields import create_field from calibre.db.fields import create_field
from calibre.db.search import Search from calibre.db.search import Search
@ -293,13 +294,13 @@ class Cache(object):
Return all the books associated with the item identified by Return all the books associated with the item identified by
``item_id``, where the item belongs to the field ``name``. ``item_id``, where the item belongs to the field ``name``.
Returned value is a tuple of book ids, or the empty tuple if the item Returned value is a set of book ids, or the empty set if the item
or the field does not exist. or the field does not exist.
''' '''
try: try:
return self.fields[name].books_for(item_id) return self.fields[name].books_for(item_id)
except (KeyError, IndexError): except (KeyError, IndexError):
return () return set()
@read_api @read_api
def all_book_ids(self, type=frozenset): def all_book_ids(self, type=frozenset):
@ -415,9 +416,7 @@ class Cache(object):
all_book_ids = frozenset(self._all_book_ids() if ids_to_sort is None all_book_ids = frozenset(self._all_book_ids() if ids_to_sort is None
else ids_to_sort) else ids_to_sort)
get_metadata = partial(self._get_metadata, get_user_categories=False) get_metadata = partial(self._get_metadata, get_user_categories=False)
def get_lang(book_id): lang_map = self.fields['languages'].book_value_map
ans = self._field_for('languages', book_id)
return ans[0] if ans else None
fm = {'title':'sort', 'authors':'author_sort'} fm = {'title':'sort', 'authors':'author_sort'}
@ -426,10 +425,10 @@ class Cache(object):
idx = field + '_index' idx = field + '_index'
is_series = idx in self.fields is_series = idx in self.fields
ans = self.fields[fm.get(field, field)].sort_keys_for_books( ans = self.fields[fm.get(field, field)].sort_keys_for_books(
get_metadata, get_lang, all_book_ids,) get_metadata, lang_map, all_book_ids,)
if is_series: if is_series:
idx_ans = self.fields[idx].sort_keys_for_books( idx_ans = self.fields[idx].sort_keys_for_books(
get_metadata, get_lang, all_book_ids) get_metadata, lang_map, all_book_ids)
ans = {k:(v, idx_ans[k]) for k, v in ans.iteritems()} ans = {k:(v, idx_ans[k]) for k, v in ans.iteritems()}
return ans return ans
@ -447,6 +446,11 @@ class Cache(object):
return self._search_api(self, query, restriction, return self._search_api(self, query, restriction,
virtual_fields=virtual_fields) virtual_fields=virtual_fields)
@read_api
def get_categories(self, sort='name', book_ids=None, icon_map=None):
return get_categories(self, sort=sort, book_ids=book_ids,
icon_map=icon_map)
# }}} # }}}
class SortKey(object): class SortKey(object):

View File

@ -0,0 +1,118 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from functools import partial
from operator import attrgetter
from calibre.library.field_metadata import TagsIcons
from calibre.utils.config_base import tweaks
from calibre.utils.icu import sort_key
CATEGORY_SORTS = { 'name', 'popularity', 'rating' }
class Tag(object):
def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
tooltip=None, icon=None, category=None, id_set=None,
is_editable=True, is_searchable=True, use_sort_as_name=False):
self.name = self.original_name = name
self.id = id
self.count = count
self.state = state
self.is_hierarchical = ''
self.is_editable = is_editable
self.is_searchable = is_searchable
self.id_set = id_set if id_set is not None else set([])
self.avg_rating = avg/2.0 if avg is not None else 0
self.sort = sort
self.use_sort_as_name = use_sort_as_name
if self.avg_rating > 0:
if tooltip:
tooltip = tooltip + ': '
tooltip = _('%(tt)sAverage rating is %(rating)3.1f')%dict(
tt=tooltip, rating=self.avg_rating)
self.tooltip = tooltip
self.icon = icon
self.category = category
def __unicode__(self):
return u'%s:%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state,
self.category, self.tooltip)
def __str__(self):
return unicode(self).encode('utf-8')
def __repr__(self):
return str(self)
def find_categories(field_metadata):
for category, cat in field_metadata.iteritems():
if (cat['is_category'] and cat['kind'] not in {'user', 'search'} and
category != 'news'):
yield (category, cat['is_multiple'].get('cache_to_list', None), False)
elif (cat['datatype'] == 'composite' and
cat['display'].get('make_category', False)):
yield (category, cat['is_multiple'].get('cache_to_list', None), True)
def create_tag_class(category, fm, icon_map):
cat = fm[category]
icon = None
tooltip = None if category in {'formats', 'identifiers'} else ('(' + category + ')')
label = fm.key_to_label(category)
if icon_map:
if not fm.is_custom_field(category):
if category in icon_map:
icon = icon_map[label]
else:
icon = icon_map['custom:']
icon_map[category] = icon
is_editable = category not in {'news', 'rating', 'languages', 'formats',
'identifiers'}
if (tweaks['categories_use_field_for_author_name'] == 'author_sort' and
(category == 'authors' or
(cat['display'].get('is_names', False) and
cat['is_custom'] and cat['is_multiple'] and
cat['datatype'] == 'text'))):
use_sort_as_name = True
else:
use_sort_as_name = False
return partial(Tag, use_sort_as_name=use_sort_as_name, icon=icon,
tooltip=tooltip, is_editable=is_editable,
category=category)
def get_categories(dbcache, sort='name', book_ids=None, icon_map=None):
if icon_map is not None and type(icon_map) != TagsIcons:
raise TypeError('icon_map passed to get_categories must be of type TagIcons')
if sort not in CATEGORY_SORTS:
raise ValueError('sort ' + sort + ' not a valid value')
fm = dbcache.field_metadata
book_rating_map = dbcache.fields['rating'].book_value_map
lang_map = dbcache.fields['languages'].book_value_map
categories = {}
book_ids = frozenset(book_ids) if book_ids else book_ids
for category, is_multiple, is_composite in find_categories(fm):
tag_class = create_tag_class(category, fm, icon_map)
cats = dbcache.fields[category].get_categories(
tag_class, book_rating_map, lang_map, book_ids)
if sort == 'popularity':
key=attrgetter('count')
elif sort == 'rating':
key=attrgetter('avg_rating')
else:
key=lambda x:sort_key(x.sort or x.name)
cats.sort(key=key)
categories[category] = cats
return categories

View File

@ -9,7 +9,7 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from threading import Lock from threading import Lock
from collections import defaultdict from collections import defaultdict, Counter
from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY
from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata import title_sort
@ -24,22 +24,26 @@ class Field(object):
def __init__(self, name, table): def __init__(self, name, table):
self.name, self.table = name, table self.name, self.table = name, table
self.has_text_data = self.metadata['datatype'] in ('text', 'comments',
'series', 'enumeration')
self.table_type = self.table.table_type
dt = self.metadata['datatype'] dt = self.metadata['datatype']
self.has_text_data = dt in {'text', 'comments', 'series', 'enumeration'}
self.table_type = self.table.table_type
self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x)
self._default_sort_key = '' self._default_sort_key = ''
if self.metadata['datatype'] in ('int', 'float', 'rating'): if dt in { 'int', 'float', 'rating' }:
self._default_sort_key = 0 self._default_sort_key = 0
elif self.metadata['datatype'] == 'bool': elif dt == 'bool':
self._default_sort_key = None self._default_sort_key = None
elif self.metadata['datatype'] == 'datetime': elif dt == 'datetime':
self._default_sort_key = UNDEFINED_DATE self._default_sort_key = UNDEFINED_DATE
if self.name == 'languages': if self.name == 'languages':
self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x))
self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == self.is_multiple = (bool(self.metadata['is_multiple']) or self.name ==
'formats') 'formats')
self.category_formatter = type(u'')
if dt == 'rating':
self.category_formatter = lambda x:'\u2605'*int(x/2)
elif name == 'languages':
self.category_formatter = calibre_langcode_to_name
@property @property
def metadata(self): def metadata(self):
@ -63,7 +67,7 @@ class Field(object):
def books_for(self, item_id): def books_for(self, item_id):
''' '''
Return the ids of all books associated with the item identified by Return the ids of all books associated with the item identified by
item_id as a tuple. An empty tuple is returned if no books are found. item_id as a set. An empty set is returned if no books are found.
''' '''
raise NotImplementedError() raise NotImplementedError()
@ -77,7 +81,7 @@ class Field(object):
''' '''
return iter(()) return iter(())
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
''' '''
Return a mapping of book_id -> sort_key. The sort key is suitable for Return a mapping of book_id -> sort_key. The sort key is suitable for
use in sorting the list of all books by this field, via the python cmp use in sorting the list of all books by this field, via the python cmp
@ -94,6 +98,27 @@ class Field(object):
''' '''
raise NotImplementedError() raise NotImplementedError()
def get_categories(self, tag_class, book_rating_map, lang_map, book_ids=None):
ans = []
if not self.is_many:
return ans
special_sort = hasattr(self, 'category_sort_value')
for item_id, item_book_ids in self.table.col_book_map.iteritems():
if book_ids is not None:
item_book_ids = item_book_ids.intersection(book_ids)
if item_book_ids:
ratings = tuple(r for r in (book_rating_map.get(book_id, 0) for
book_id in item_book_ids) if r > 0)
avg = sum(ratings)/len(ratings) if ratings else 0
name = self.category_formatter(self.table.id_map[item_id])
sval = (self.category_sort_value(item_id, item_book_ids, lang_map)
if special_sort else name)
c = tag_class(name, id=item_id, sort=sval, avg=avg,
id_set=item_book_ids, count=len(item_book_ids))
ans.append(c)
return ans
class OneToOneField(Field): class OneToOneField(Field):
def for_book(self, book_id, default_value=None): def for_book(self, book_id, default_value=None):
@ -103,12 +128,12 @@ class OneToOneField(Field):
return (book_id,) return (book_id,)
def books_for(self, item_id): def books_for(self, item_id):
return (item_id,) return {item_id}
def __iter__(self): def __iter__(self):
return self.table.book_col_map.iterkeys() return self.table.book_col_map.iterkeys()
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
return {id_ : self._sort_key(self.table.book_col_map.get(id_, return {id_ : self._sort_key(self.table.book_col_map.get(id_,
self._default_sort_key)) for id_ in all_book_ids} self._default_sort_key)) for id_ in all_book_ids}
@ -150,7 +175,7 @@ class CompositeField(OneToOneField):
ans = mi.get('#'+self.metadata['label']) ans = mi.get('#'+self.metadata['label'])
return ans return ans
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
return {id_ : sort_key(self.get_value_with_cache(id_, get_metadata)) for id_ in return {id_ : sort_key(self.get_value_with_cache(id_, get_metadata)) for id_ in
all_book_ids} all_book_ids}
@ -193,7 +218,7 @@ class OnDeviceField(OneToOneField):
def __iter__(self): def __iter__(self):
return iter(()) return iter(())
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
return {id_ : self.for_book(id_) for id_ in return {id_ : self.for_book(id_) for id_ in
all_book_ids} all_book_ids}
@ -223,12 +248,12 @@ class ManyToOneField(Field):
return (id_,) return (id_,)
def books_for(self, item_id): def books_for(self, item_id):
return self.table.col_book_map.get(item_id, ()) return self.table.col_book_map.get(item_id, set())
def __iter__(self): def __iter__(self):
return self.table.id_map.iterkeys() return self.table.id_map.iterkeys()
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
ans = {id_ : self.table.book_col_map.get(id_, None) ans = {id_ : self.table.book_col_map.get(id_, None)
for id_ in all_book_ids} for id_ in all_book_ids}
sk_map = {cid : (self._default_sort_key if cid is None else sk_map = {cid : (self._default_sort_key if cid is None else
@ -238,11 +263,17 @@ class ManyToOneField(Field):
def iter_searchable_values(self, get_metadata, candidates, default_value=None): def iter_searchable_values(self, get_metadata, candidates, default_value=None):
cbm = self.table.col_book_map cbm = self.table.col_book_map
empty = set()
for item_id, val in self.table.id_map.iteritems(): for item_id, val in self.table.id_map.iteritems():
book_ids = set(cbm.get(item_id, ())).intersection(candidates) book_ids = cbm.get(item_id, empty).intersection(candidates)
if book_ids: if book_ids:
yield val, book_ids yield val, book_ids
@property
def book_value_map(self):
return {book_id:self.table.id_map[item_id] for book_id, item_id in
self.table.book_col_map.iteritems()}
class ManyToManyField(Field): class ManyToManyField(Field):
is_many = True is_many = True
@ -263,12 +294,12 @@ class ManyToManyField(Field):
return self.table.book_col_map.get(book_id, ()) return self.table.book_col_map.get(book_id, ())
def books_for(self, item_id): def books_for(self, item_id):
return self.table.col_book_map.get(item_id, ()) return self.table.col_book_map.get(item_id, set())
def __iter__(self): def __iter__(self):
return self.table.id_map.iterkeys() return self.table.id_map.iterkeys()
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
ans = {id_ : self.table.book_col_map.get(id_, ()) ans = {id_ : self.table.book_col_map.get(id_, ())
for id_ in all_book_ids} for id_ in all_book_ids}
all_cids = set() all_cids = set()
@ -282,8 +313,9 @@ class ManyToManyField(Field):
def iter_searchable_values(self, get_metadata, candidates, default_value=None): def iter_searchable_values(self, get_metadata, candidates, default_value=None):
cbm = self.table.col_book_map cbm = self.table.col_book_map
empty = set()
for item_id, val in self.table.id_map.iteritems(): for item_id, val in self.table.id_map.iteritems():
book_ids = set(cbm.get(item_id, ())).intersection(candidates) book_ids = cbm.get(item_id, empty).intersection(candidates)
if book_ids: if book_ids:
yield val, book_ids yield val, book_ids
@ -295,6 +327,11 @@ class ManyToManyField(Field):
for count, book_ids in val_map.iteritems(): for count, book_ids in val_map.iteritems():
yield count, book_ids yield count, book_ids
@property
def book_value_map(self):
return {book_id:tuple(self.table.id_map[item_id] for item_id in item_ids)
for book_id, item_ids in self.table.book_col_map.iteritems()}
class IdentifiersField(ManyToManyField): class IdentifiersField(ManyToManyField):
def for_book(self, book_id, default_value=None): def for_book(self, book_id, default_value=None):
@ -303,7 +340,7 @@ class IdentifiersField(ManyToManyField):
ids = default_value ids = default_value
return ids return ids
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
'Sort by identifier keys' 'Sort by identifier keys'
ans = {id_ : self.table.book_col_map.get(id_, ()) ans = {id_ : self.table.book_col_map.get(id_, ())
for id_ in all_book_ids} for id_ in all_book_ids}
@ -318,6 +355,17 @@ class IdentifiersField(ManyToManyField):
if val: if val:
yield val, {book_id} yield val, {book_id}
def get_categories(self, tag_class, book_rating_map, lang_map, book_ids=None):
ans = []
for id_key, item_book_ids in self.table.col_book_map.iteritems():
if book_ids is not None:
item_book_ids = item_book_ids.intersection(book_ids)
if item_book_ids:
c = tag_class(id_key, id_set=item_book_ids, count=len(item_book_ids))
ans.append(c)
return ans
class AuthorsField(ManyToManyField): class AuthorsField(ManyToManyField):
def author_data(self, author_id): def author_data(self, author_id):
@ -327,6 +375,9 @@ class AuthorsField(ManyToManyField):
'link' : self.table.alink_map[author_id], 'link' : self.table.alink_map[author_id],
} }
def category_sort_value(self, item_id, book_ids, lang_map):
return self.table.asort_map[item_id]
class FormatsField(ManyToManyField): class FormatsField(ManyToManyField):
def for_book(self, book_id, default_value=None): def for_book(self, book_id, default_value=None):
@ -346,21 +397,50 @@ class FormatsField(ManyToManyField):
for val, book_ids in val_map.iteritems(): for val, book_ids in val_map.iteritems():
yield val, book_ids yield val, book_ids
def get_categories(self, tag_class, book_rating_map, lang_map, book_ids=None):
ans = []
for fmt, item_book_ids in self.table.col_book_map.iteritems():
if book_ids is not None:
item_book_ids = item_book_ids.intersection(book_ids)
if item_book_ids:
c = tag_class(fmt, id_set=item_book_ids, count=len(item_book_ids))
ans.append(c)
return ans
class SeriesField(ManyToOneField): class SeriesField(ManyToOneField):
def sort_key_for_series(self, book_id, get_lang, series_sort_order): def sort_key_for_series(self, book_id, lang_map, series_sort_order):
sid = self.table.book_col_map.get(book_id, None) sid = self.table.book_col_map.get(book_id, None)
if sid is None: if sid is None:
return self._default_sort_key return self._default_sort_key
lang = lang_map.get(book_id, None) or None
if lang:
lang = lang[0]
return self._sort_key(title_sort(self.table.id_map[sid], return self._sort_key(title_sort(self.table.id_map[sid],
order=series_sort_order, order=series_sort_order, lang=lang))
lang=get_lang(book_id)))
def sort_keys_for_books(self, get_metadata, get_lang, all_book_ids): def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids):
sso = tweaks['title_series_sorting'] sso = tweaks['title_series_sorting']
return {book_id:self.sort_key_for_series(book_id, get_lang, sso) for book_id return {book_id:self.sort_key_for_series(book_id, lang_map, sso) for book_id
in all_book_ids} in all_book_ids}
def category_sort_value(self, item_id, book_ids, lang_map):
lang = None
tss = tweaks['title_series_sorting']
if tss != 'strictly_alphabetic':
c = Counter()
for book_id in book_ids:
l = lang_map.get(book_id, None)
if l:
c[l[0]] += 1
if c:
lang = c.most_common(1)[0][0]
val = self.table.id_map[item_id]
return title_sort(val, order=tss, lang=lang)
def create_field(name, table): def create_field(name, table):
cls = { cls = {
ONE_ONE : OneToOneField, ONE_ONE : OneToOneField,

View File

@ -132,13 +132,10 @@ class ManyToOneTable(Table):
'SELECT book, {0} FROM {1}'.format( 'SELECT book, {0} FROM {1}'.format(
self.metadata['link_column'], self.link_table)): self.metadata['link_column'], self.link_table)):
if row[1] not in self.col_book_map: if row[1] not in self.col_book_map:
self.col_book_map[row[1]] = [] self.col_book_map[row[1]] = set()
self.col_book_map[row[1]].append(row[0]) self.col_book_map[row[1]].add(row[0])
self.book_col_map[row[0]] = row[1] self.book_col_map[row[0]] = row[1]
for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(self.col_book_map[key])
class ManyToManyTable(ManyToOneTable): class ManyToManyTable(ManyToOneTable):
''' '''
@ -154,15 +151,12 @@ class ManyToManyTable(ManyToOneTable):
for row in db.conn.execute( for row in db.conn.execute(
self.selectq.format(self.metadata['link_column'], self.link_table)): self.selectq.format(self.metadata['link_column'], self.link_table)):
if row[1] not in self.col_book_map: if row[1] not in self.col_book_map:
self.col_book_map[row[1]] = [] self.col_book_map[row[1]] = set()
self.col_book_map[row[1]].append(row[0]) self.col_book_map[row[1]].add(row[0])
if row[0] not in self.book_col_map: if row[0] not in self.book_col_map:
self.book_col_map[row[0]] = [] self.book_col_map[row[0]] = []
self.book_col_map[row[0]].append(row[1]) self.book_col_map[row[0]].append(row[1])
for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(self.col_book_map[key])
for key in tuple(self.book_col_map.iterkeys()): for key in tuple(self.book_col_map.iterkeys()):
self.book_col_map[key] = tuple(self.book_col_map[key]) self.book_col_map[key] = tuple(self.book_col_map[key])
@ -191,8 +185,8 @@ class FormatsTable(ManyToManyTable):
if row[1] is not None: if row[1] is not None:
fmt = row[1].upper() fmt = row[1].upper()
if fmt not in self.col_book_map: if fmt not in self.col_book_map:
self.col_book_map[fmt] = [] self.col_book_map[fmt] = set()
self.col_book_map[fmt].append(row[0]) self.col_book_map[fmt].add(row[0])
if row[0] not in self.book_col_map: if row[0] not in self.book_col_map:
self.book_col_map[row[0]] = [] self.book_col_map[row[0]] = []
self.book_col_map[row[0]].append(fmt) self.book_col_map[row[0]].append(fmt)
@ -200,9 +194,6 @@ class FormatsTable(ManyToManyTable):
self.fname_map[row[0]] = {} self.fname_map[row[0]] = {}
self.fname_map[row[0]][fmt] = row[2] self.fname_map[row[0]][fmt] = row[2]
for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(self.col_book_map[key])
for key in tuple(self.book_col_map.iterkeys()): for key in tuple(self.book_col_map.iterkeys()):
self.book_col_map[key] = tuple(sorted(self.book_col_map[key])) self.book_col_map[key] = tuple(sorted(self.book_col_map[key]))
@ -215,15 +206,12 @@ class IdentifiersTable(ManyToManyTable):
for row in db.conn.execute('SELECT book, type, val FROM identifiers'): for row in db.conn.execute('SELECT book, type, val FROM identifiers'):
if row[1] is not None and row[2] is not None: if row[1] is not None and row[2] is not None:
if row[1] not in self.col_book_map: if row[1] not in self.col_book_map:
self.col_book_map[row[1]] = [] self.col_book_map[row[1]] = set()
self.col_book_map[row[1]].append(row[0]) self.col_book_map[row[1]].add(row[0])
if row[0] not in self.book_col_map: if row[0] not in self.book_col_map:
self.book_col_map[row[0]] = {} self.book_col_map[row[0]] = {}
self.book_col_map[row[0]][row[1]] = row[2] self.book_col_map[row[0]][row[1]] = row[2]
for key in tuple(self.col_book_map.iterkeys()):
self.col_book_map[key] = tuple(self.col_book_map[key])
class LanguagesTable(ManyToManyTable): class LanguagesTable(ManyToManyTable):
def read_id_maps(self, db): def read_id_maps(self, db):

View File

@ -42,6 +42,7 @@ class BaseTest(unittest.TestCase):
if attr == 'format_metadata': continue # TODO: Not implemented yet if attr == 'format_metadata': continue # TODO: Not implemented yet
attr1, attr2 = getattr(mi1, attr), getattr(mi2, attr) attr1, attr2 = getattr(mi1, attr), getattr(mi2, attr)
if attr == 'formats': if attr == 'formats':
continue # TODO: Not implemented yet
attr1, attr2 = map(lambda x:tuple(x) if x else (), (attr1, attr2)) attr1, attr2 = map(lambda x:tuple(x) if x else (), (attr1, attr2))
self.assertEqual(attr1, attr2, self.assertEqual(attr1, attr2,
'%s not the same: %r != %r'%(attr, attr1, attr2)) '%s not the same: %r != %r'%(attr, attr1, attr2))

View File

@ -241,6 +241,18 @@ class ReadingTest(BaseTest):
# }}} # }}}
def test_get_categories(self): # {{{
'Check that get_categories() returns the same data for both backends'
from calibre.library.database2 import LibraryDatabase2
old = LibraryDatabase2(self.library_path)
old_categories = old.get_categories()
cache = self.init_cache(self.library_path)
import pprint
pprint.pprint(old_categories)
pprint.pprint(cache.get_categories())
# }}}
def tests(): def tests():
return unittest.TestLoader().loadTestsFromTestCase(ReadingTest) return unittest.TestLoader().loadTestsFromTestCase(ReadingTest)

View File

@ -141,7 +141,7 @@ class ANDROID(USBMS):
# LG # LG
0x1004 : { 0x1004 : {
0x61c5 : [0x100, 0x226, 0x227, 0x9999], 0x61c5 : [0x100, 0x226, 0x227, 0x229, 0x9999],
0x61cc : [0x226, 0x227, 0x9999, 0x100], 0x61cc : [0x226, 0x227, 0x9999, 0x100],
0x61ce : [0x226, 0x227, 0x9999, 0x100], 0x61ce : [0x226, 0x227, 0x9999, 0x100],
0x618e : [0x226, 0x227, 0x9999, 0x100], 0x618e : [0x226, 0x227, 0x9999, 0x100],
@ -235,7 +235,7 @@ class ANDROID(USBMS):
'ADVANCED', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID', 'ADVANCED', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID',
'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E', 'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E',
'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS', 'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS',
'ICS'] 'ICS', 'E400']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
@ -246,7 +246,7 @@ class ANDROID(USBMS):
'FILE-CD_GADGET', 'GT-I9001_CARD', 'USB_2.0', 'XT875', 'FILE-CD_GADGET', 'GT-I9001_CARD', 'USB_2.0', 'XT875',
'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727', 'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727',
'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E', 'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E',
'NOVO7', 'ADVANCED', 'TABLET_PC', 'F'] 'NOVO7', 'ADVANCED', 'TABLET_PC', 'F', 'E400_SD_CARD']
OSX_MAIN_MEM = 'Android Device Main Memory' OSX_MAIN_MEM = 'Android Device Main Memory'

View File

@ -311,6 +311,14 @@ class ITUNES(DriverBase):
update_msg = None update_msg = None
update_needed = False update_needed = False
@property
def cache_dir(self):
return os.path.join(cache_dir(), 'itunes')
@property
def archive_path(self):
return os.path.join(self.cache_dir, "thumbs.zip")
# Public methods # Public methods
def add_books_to_metadata(self, locations, metadata, booklists): def add_books_to_metadata(self, locations, metadata, booklists):
''' '''

View File

@ -44,47 +44,13 @@ from calibre.utils.recycle_bin import delete_file, delete_tree
from calibre.utils.formatter_functions import load_user_template_functions from calibre.utils.formatter_functions import load_user_template_functions
from calibre.db.errors import NoSuchFormat from calibre.db.errors import NoSuchFormat
from calibre.db.lazy import FormatMetadata, FormatsList from calibre.db.lazy import FormatMetadata, FormatsList
from calibre.db.categories import Tag
from calibre.utils.localization import (canonicalize_lang, from calibre.utils.localization import (canonicalize_lang,
calibre_langcode_to_name) calibre_langcode_to_name)
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
SPOOL_SIZE = 30*1024*1024 SPOOL_SIZE = 30*1024*1024
class Tag(object):
def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
tooltip=None, icon=None, category=None, id_set=None,
is_editable = True, is_searchable=True, use_sort_as_name=False):
self.name = self.original_name = name
self.id = id
self.count = count
self.state = state
self.is_hierarchical = ''
self.is_editable = is_editable
self.is_searchable = is_searchable
self.id_set = id_set if id_set is not None else set([])
self.avg_rating = avg/2.0 if avg is not None else 0
self.sort = sort
self.use_sort_as_name = use_sort_as_name
if self.avg_rating > 0:
if tooltip:
tooltip = tooltip + ': '
tooltip = _('%(tt)sAverage rating is %(rating)3.1f')%dict(
tt=tooltip, rating=self.avg_rating)
self.tooltip = tooltip
self.icon = icon
self.category = category
def __unicode__(self):
return u'%s:%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state,
self.category, self.tooltip)
def __str__(self):
return unicode(self).encode('utf-8')
def __repr__(self):
return str(self)
class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
''' '''
An ebook metadata database that stores references to ebook files on disk. An ebook metadata database that stores references to ebook files on disk.