Initial implementation of remove_book(), needs testing

This commit is contained in:
Kovid Goyal 2013-07-06 12:43:02 +05:30
parent 20920b37b2
commit 0711e03bd4
3 changed files with 125 additions and 5 deletions

View File

@ -29,7 +29,7 @@ from calibre.utils.magick.draw import save_cover_data_to
from calibre.utils.recycle_bin import delete_tree, delete_file
from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable,
SizeTable, FormatsTable, AuthorsTable, IdentifiersTable, PathTable,
CompositeTable, LanguagesTable, UUIDTable)
CompositeTable, UUIDTable)
# }}}
'''
@ -711,7 +711,6 @@ class DB(object):
'authors':AuthorsTable,
'formats':FormatsTable,
'identifiers':IdentifiersTable,
'languages':LanguagesTable,
}.get(col, ManyToManyTable)
tables[col] = cls(col, self.field_metadata[col].copy())
@ -1165,5 +1164,16 @@ class DB(object):
with lopen(path, 'rb') as f:
return f.read()
def remove_books(self, path_map, permanent=False):
for book_id, path in path_map.iteritems():
if path:
path = os.path.join(self.library_path, path)
if os.path.exists(path):
self.rmtree(path, permanent=permanent)
parent = os.path.dirname(path)
if len(os.listdir(parent)) == 0:
self.rmtree(parent, permanent=permanent)
self.conn.executemany(
'DELETE FROM books WHERE id=?', [(x,) for x in path_map])
# }}}

View File

@ -1129,6 +1129,19 @@ class Cache(object):
self._add_format(book_id, fmt, stream_or_path, dbapi=dbapi)
return ids, duplicates
@write_api
def remove_books(self, book_ids, permanent=False):
path_map = {}
for book_id in book_ids:
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
path = None
path_map[book_id] = path
self.backend.remove_books(path_map, permanent=permanent)
for field in self.fields.itervalues():
field.table.remove_books(book_ids, self.backend)
# }}}
class SortKey(object): # {{{

View File

@ -20,6 +20,10 @@ _c_speedup = plugins['speedup'][0]
ONE_ONE, MANY_ONE, MANY_MANY = xrange(3)
class Null:
pass
null = Null()
def _c_convert_timestamp(val):
if not val:
return None
@ -55,6 +59,9 @@ class Table(object):
self.link_table = (link_table if link_table else
'books_%s_link'%self.metadata['table'])
def remove_books(self, book_ids, db):
return set()
class VirtualTable(Table):
'''
@ -83,6 +90,14 @@ class OneToOneTable(Table):
self.metadata['column'], self.metadata['table'])):
self.book_col_map[row[0]] = self.unserialize(row[1])
def remove_books(self, book_ids, db):
clean = set()
for book_id in book_ids:
val = self.book_col_map.pop(book_id, null)
if val is not null:
clean.add(val)
return clean
class PathTable(OneToOneTable):
def set_path(self, book_id, path, db):
@ -113,6 +128,15 @@ class UUIDTable(OneToOneTable):
self.uuid_to_id_map.pop(self.book_col_map.get(book_id, None), None) # discard old uuid
self.uuid_to_id_map[uuid] = book_id
def remove_books(self, book_ids, db):
clean = set()
for book_id in book_ids:
val = self.book_col_map.pop(book_id, null)
if val is not null:
self.uuid_to_id_map.pop(val, None)
clean.add(val)
return clean
class CompositeTable(OneToOneTable):
def read(self, db):
@ -124,6 +148,9 @@ class CompositeTable(OneToOneTable):
self.composite_sort = d.get('composite_sort', False)
self.use_decorations = d.get('use_decorations', False)
def remove_books(self, book_ids, db):
return set()
class ManyToOneTable(Table):
'''
@ -156,6 +183,27 @@ class ManyToOneTable(Table):
self.col_book_map[row[1]].add(row[0])
self.book_col_map[row[0]] = row[1]
def remove_books(self, book_ids, db):
clean = set()
for book_id in book_ids:
item_id = self.book_col_map.pop(book_id, None)
if item_id is not None:
try:
self.col_book_map[item_id].discard(book_id)
except KeyError:
if self.id_map.pop(item_id, null) is not null:
clean.add(item_id)
else:
if not self.col_book_map[item_id]:
del self.col_book_map[item_id]
if self.id_map.pop(item_id, null) is not null:
clean.add(item_id)
if clean:
db.conn.executemany(
'DELETE FROM {0} WHERE id=?'.format(self.metadata['table']),
[(x,) for x in clean])
return clean
class ManyToManyTable(ManyToOneTable):
'''
@ -166,6 +214,7 @@ class ManyToManyTable(ManyToOneTable):
table_type = MANY_MANY
selectq = 'SELECT book, {0} FROM {1} ORDER BY id'
do_clean_on_remove = True
def read_maps(self, db):
for row in db.conn.execute(
@ -180,6 +229,27 @@ class ManyToManyTable(ManyToOneTable):
for key in tuple(self.book_col_map.iterkeys()):
self.book_col_map[key] = tuple(self.book_col_map[key])
def remove_books(self, book_ids, db):
clean = set()
for book_id in book_ids:
item_ids = self.book_col_map.pop(book_id, ())
for item_id in item_ids:
try:
self.col_book_map[item_id].discard(book_id)
except KeyError:
if self.id_map.pop(item_id, null) is not null:
clean.add(item_id)
else:
if not self.col_book_map[item_id]:
del self.col_book_map[item_id]
if self.id_map.pop(item_id, null) is not null:
clean.add(item_id)
if clean and self.do_clean_on_remove:
db.conn.executemany(
'DELETE FROM {0} WHERE id=?'.format(self.metadata['table']),
[(x,) for x in clean])
return clean
class AuthorsTable(ManyToManyTable):
def read_id_maps(self, db):
@ -197,8 +267,17 @@ class AuthorsTable(ManyToManyTable):
db.conn.executemany('UPDATE authors SET sort=? WHERE id=?',
[(v, k) for k, v in aus_map.iteritems()])
def remove_books(self, book_ids, db):
clean = ManyToManyTable.remove_books(self, book_ids, db)
for item_id in clean:
self.alink_map.pop(item_id, None)
self.asort_map.pop(item_id, None)
return clean
class FormatsTable(ManyToManyTable):
do_clean_on_remove = False
def read_id_maps(self, db):
pass
@ -220,6 +299,13 @@ class FormatsTable(ManyToManyTable):
for key in tuple(self.book_col_map.iterkeys()):
self.book_col_map[key] = tuple(sorted(self.book_col_map[key]))
def remove_books(self, book_ids, db):
clean = ManyToManyTable.remove_books(self, book_ids, db)
for book_id in book_ids:
self.fname_map.pop(book_id, None)
self.size_map.pop(book_id, None)
return clean
def set_fname(self, book_id, fmt, fname, db):
self.fname_map[book_id][fmt] = fname
db.conn.execute('UPDATE data SET name=? WHERE book=? AND format=?',
@ -280,8 +366,19 @@ class IdentifiersTable(ManyToManyTable):
self.book_col_map[row[0]] = {}
self.book_col_map[row[0]][row[1]] = row[2]
class LanguagesTable(ManyToManyTable):
def remove_books(self, book_ids, db):
clean = set()
for book_id in book_ids:
item_map = self.book_col_map.pop(book_id, {})
for item_id in item_map:
try:
self.col_book_map[item_id].discard(book_id)
except KeyError:
clean.add(item_id)
else:
if not self.col_book_map[item_id]:
del self.col_book_map[item_id]
clean.add(item_id)
return clean
def read_id_maps(self, db):
ManyToManyTable.read_id_maps(self, db)