newdb: Automatically fix broken link tables

Fixes #1218783 [Beim Starten von Calibre bleibt das Startlogo bestehen und Pythons meldet einen Fehler.](https://bugs.launchpad.net/calibre/+bug/1218783)
This commit is contained in:
Kovid Goyal 2013-08-30 16:06:27 +05:30
parent e35a7bd6fb
commit 5b1ba794b1
3 changed files with 52 additions and 7 deletions

View File

@ -20,7 +20,7 @@ from calibre.db import SPOOL_SIZE, _get_next_series_num_for_list
from calibre.db.categories import get_categories
from calibre.db.locking import create_locks
from calibre.db.errors import NoSuchFormat
from calibre.db.fields import create_field, IDENTITY
from calibre.db.fields import create_field, IDENTITY, InvalidLinkTable
from calibre.db.search import Search
from calibre.db.tables import VirtualTable
from calibre.db.write import get_series_values, uniq
@ -866,10 +866,18 @@ class Cache(object):
def search(self, query, restriction='', virtual_fields=None, book_ids=None):
return self._search_api(self, query, restriction, virtual_fields=virtual_fields, book_ids=book_ids)
@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)
@api
def get_categories(self, sort='name', book_ids=None, icon_map=None, already_fixed=None):
try:
with self.read_lock:
return get_categories(self, sort=sort, book_ids=book_ids, icon_map=icon_map)
except InvalidLinkTable as err:
bad_field = err.field_name
if bad_field == already_fixed:
raise
with self.write_lock:
self.fields[bad_field].table.fix_link_table(self.backend)
return self.get_categories(sort=sort, book_ids=book_ids, icon_map=icon_map, already_fixed=bad_field)
@write_api
def update_last_modified(self, book_ids, now=None):

View File

@ -27,6 +27,12 @@ def bool_sort_key(bools_are_tristate):
IDENTITY = lambda x: x
class InvalidLinkTable(Exception):
def __init__(self, name):
Exception.__init__(self, name)
self.field_name = name
class Field(object):
is_many = False
@ -144,9 +150,15 @@ class Field(object):
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(id_map[item_id])
try:
name = self.category_formatter(id_map[item_id])
except KeyError:
# db has entries in the link table without entries in the
# id table, for example, see
# https://bugs.launchpad.net/bugs/1218783
raise InvalidLinkTable(self.name)
sval = (self.category_sort_value(item_id, item_book_ids, lang_map)
if special_sort else name)
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)

View File

@ -64,6 +64,9 @@ class Table(object):
def remove_books(self, book_ids, db):
return set()
def fix_link_table(self, db):
pass
class VirtualTable(Table):
'''
@ -201,6 +204,17 @@ class ManyToOneTable(Table):
cbm[item_id].add(book)
bcm[book] = item_id
def fix_link_table(self, db):
linked_item_ids = {item_id for item_id in self.book_col_map.itervalues()}
extra_item_ids = linked_item_ids - set(self.id_map)
if extra_item_ids:
for item_id in extra_item_ids:
book_ids = self.col_book_map.pop(item_id, ())
for book_id in book_ids:
self.book_col_map.pop(book_id, None)
db.conn.executemany('DELETE FROM {0} WHERE {1}=?'.format(
self.link_table, self.metadata['link_column']), tuple((x,) for x in extra_item_ids))
def remove_books(self, book_ids, db):
clean = set()
for book_id in book_ids:
@ -284,6 +298,17 @@ class ManyToManyTable(ManyToOneTable):
self.book_col_map = {k:tuple(v) for k, v in bcm.iteritems()}
def fix_link_table(self, db):
linked_item_ids = {item_id for item_ids in self.book_col_map.itervalues() for item_id in item_ids}
extra_item_ids = linked_item_ids - set(self.id_map)
if extra_item_ids:
for item_id in extra_item_ids:
book_ids = self.col_book_map.pop(item_id, ())
for book_id in book_ids:
self.book_col_map[book_id] = tuple(iid for iid in self.book_col_map.pop(book_id, ()) if iid not in extra_item_ids)
db.conn.executemany('DELETE FROM {0} WHERE {1}=?'.format(
self.link_table, self.metadata['link_column']), tuple((x,) for x in extra_item_ids))
def remove_books(self, book_ids, db):
clean = set()
for book_id in book_ids: