diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
index 0b37fe2515..1539443bcf 100644
--- a/src/calibre/gui2/add.py
+++ b/src/calibre/gui2/add.py
@@ -13,9 +13,11 @@ from calibre.gui2 import question_dialog, error_dialog, info_dialog
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.metadata import MetaInformation
from calibre.constants import preferred_encoding, filesystem_encoding
+from calibre.library.database2 import LibraryDatabase2
+from calibre.utils.config import prefs
class DuplicatesAdder(QThread):
-
+ # Add duplicate books
def __init__(self, parent, db, duplicates, db_adder):
QThread.__init__(self, parent)
self.db, self.db_adder = db, db_adder
@@ -27,6 +29,7 @@ class DuplicatesAdder(QThread):
formats = [f for f in formats if not f.lower().endswith('.opf')]
id = self.db.create_book_entry(mi, cover=cover,
add_duplicates=True)
+ # here we add all the formats for dupe book record created above
self.db_adder.add_formats(id, formats)
self.db_adder.number_of_books_added += 1
self.emit(SIGNAL('added(PyQt_PyObject)'), count)
@@ -52,7 +55,7 @@ class RecursiveFind(QThread):
_('Searching in')+' '+dirpath[0])
self.books += list(self.db.find_books_in_directory(dirpath[0],
self.single_book_per_directory))
-
+
def run(self):
root = os.path.abspath(self.path)
try:
@@ -73,7 +76,7 @@ class RecursiveFind(QThread):
return
self.books = [formats for formats in self.books if formats]
-
+
if not self.canceled:
self.emit(SIGNAL('found(PyQt_PyObject)'), self.books)
@@ -125,6 +128,33 @@ class DBAdder(Thread):
fmts[-1] = fmt
return fmts
+ def fuzzy_title(self, title):
+ indefinites = ['the', 'a', 'an']
+ removals = [';', ':', ',', '\'', '[', ']', '(', ')', '{', '}', '<', '>']
+ replacements = ['.', '_', '-']
+ title = title.strip().lower()
+ for removal in removals:
+ title = title.replace(removal, '')
+ for replacement in replacements:
+ title = title.replace(replacement, ' ')
+ title_split = title.split()
+ for indefinite in indefinites:
+ if title_split[0] == indefinite:
+ title = ' '.join(title_split[1:])
+ return title
+
+ def find_identical_books(self, mi):
+ author_id = [unicode(self.db.conn.get('SELECT id FROM authors WHERE name = ?;', ([mi.author[0]]), all=False))]
+ identical_book_ids = []
+ book_ids = self.db.conn.get('SELECT book FROM books_authors_link WHERE author = ?;', (author_id), all=True)
+ for book_id in book_ids:
+ fbook_title = self.db.conn.get('SELECT title FROM books WHERE id = ?;', (book_id), all=False)
+ fbook_title = self.fuzzy_title(fbook_title)
+ mbook_title = self.fuzzy_title(mi.title)
+ if fbook_title == mbook_title:
+ identical_book_ids.append(book_id)
+ return identical_book_ids
+
def add(self, id, opf, cover, name):
formats = self.ids.pop(id)
if opf.endswith('.error'):
@@ -145,16 +175,30 @@ class DBAdder(Thread):
if self.db is not None:
if cover:
cover = open(cover, 'rb').read()
- id = self.db.create_book_entry(mi, cover=cover, add_duplicates=False)
- self.number_of_books_added += 1
- if id is None:
- self.duplicates.append((mi, cover, formats))
+ if prefs['add_formats_to_existing']:
+ identical_book_list = self.find_identical_books(mi)
+ if (identical_book_list): # books with same author and nearly same title exist in db
+ for identical_book in identical_book_list:
+ formats = [f for f in formats if not f.lower().endswith('.opf')]
+ self.add_formats(identical_book[0], formats)
+ else:
+ id = self.db.create_book_entry(mi, cover=cover, add_duplicates=True)
+ self.number_of_books_added += 1 # what does this do?
+ formats = [f for f in formats if not f.lower().endswith('.opf')]
+ self.add_formats(id, formats)
else:
- formats = [f for f in formats if not f.lower().endswith('.opf')]
- self.add_formats(id, formats)
+ if cover:
+ cover = open(cover, 'rb').read()
+ id = self.db.create_book_entry(mi, cover=cover, add_duplicates=False)
+ self.number_of_books_added += 1
+ if id is None:
+ self.duplicates.append((mi, cover, formats))
+ else:
+ formats = [f for f in formats if not f.lower().endswith('.opf')]
+ self.add_formats(id, formats)
else:
self.names.append(name)
- self.paths.append(formats[0])
+ self.path.append(formats[0])
self.infos.append(mi)
return mi.title
diff --git a/src/calibre/gui2/dialogs/config/add_save.py b/src/calibre/gui2/dialogs/config/add_save.py
index 3c1e30ff01..aff995d84f 100644
--- a/src/calibre/gui2/dialogs/config/add_save.py
+++ b/src/calibre/gui2/dialogs/config/add_save.py
@@ -44,6 +44,7 @@ class AddSave(QTabWidget, Ui_TabWidget):
self.filename_pattern = FilenamePattern(self)
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
self.opt_swap_author_names.setChecked(prefs['swap_author_names'])
+ self.opt_add_formats_to_existing.setChecked(prefs['add_formats_to_existing'])
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
self.save_template.initialize('save_to_disk', opts.template, help)
self.send_template.initialize('send_to_device', opts.send_template, help)
@@ -69,6 +70,7 @@ class AddSave(QTabWidget, Ui_TabWidget):
pattern = self.filename_pattern.commit()
prefs['filename_pattern'] = pattern
prefs['swap_author_names'] = bool(self.opt_swap_author_names.isChecked())
+ prefs['add_formats_to_existing'] = bool(self.opt_add_formats_to_existing.isChecked())
return True
diff --git a/src/calibre/gui2/dialogs/config/add_save.ui b/src/calibre/gui2/dialogs/config/add_save.ui
index fbf9ceaf2a..1e7450ba90 100644
--- a/src/calibre/gui2/dialogs/config/add_save.ui
+++ b/src/calibre/gui2/dialogs/config/add_save.ui
@@ -49,6 +49,16 @@
-
+
+
+ Use with caution, this option will overwrite old copies of book with new copies in same format. Title match ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc.
+
+
+ &Add new formats to existing record of same book. (Caution: overwrites previous same format with new - matches author exactly, fuzzy matches title.)
+
+
+
+ -
&Configure metadata from file name
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index 4bde124c40..316fc1de64 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -670,6 +670,8 @@ def _prefs():
help=_('The priority of worker processes'))
c.add_opt('swap_author_names', default=False,
help=_('Swap author first and last names when reading metadata'))
+ c.add_opt('add_formats_to_existing', default=False,
+ help=_('Add new formats to existing book records'))
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
return c