mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Adding from ISBN: Add an option to check if there are existing books with the specified ISBNs already in the library. Fixes #1919103 [Enhancement Request: "Add from ISBN" duplicate check](https://bugs.launchpad.net/calibre/+bug/1919103)
This commit is contained in:
parent
038163ac97
commit
4cd2abd3c1
@ -10,6 +10,7 @@ __docformat__ = 'restructuredtext en'
|
||||
Provides abstraction for metadata reading.writing from a variety of ebook formats.
|
||||
"""
|
||||
import os, sys, re
|
||||
from contextlib import suppress
|
||||
|
||||
from calibre import relpath, guess_type, prints, force_unicode
|
||||
from calibre.utils.config_base import tweaks
|
||||
@ -374,46 +375,59 @@ def MetaInformation(title, authors=(_('Unknown'),)):
|
||||
return Metadata(title, authors, other=mi)
|
||||
|
||||
|
||||
def check_digit_for_isbn10(isbn):
|
||||
check = sum((i+1)*int(isbn[i]) for i in range(9)) % 11
|
||||
return 'X' if check == 10 else str(check)
|
||||
|
||||
|
||||
def check_digit_for_isbn13(isbn):
|
||||
check = 10 - sum((1 if i%2 ==0 else 3)*int(isbn[i]) for i in range(12)) % 10
|
||||
if check == 10:
|
||||
check = 0
|
||||
return str(check)
|
||||
|
||||
|
||||
def check_isbn10(isbn):
|
||||
try:
|
||||
digits = tuple(map(int, isbn[:9]))
|
||||
products = [(i+1)*digits[i] for i in range(9)]
|
||||
check = sum(products)%11
|
||||
if (check == 10 and isbn[9] == 'X') or check == int(isbn[9]):
|
||||
return isbn
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
with suppress(Exception):
|
||||
return check_digit_for_isbn10(isbn) == isbn[9]
|
||||
return False
|
||||
|
||||
|
||||
def check_isbn13(isbn):
|
||||
try:
|
||||
digits = tuple(map(int, isbn[:12]))
|
||||
products = [(1 if i%2 ==0 else 3)*digits[i] for i in range(12)]
|
||||
check = 10 - (sum(products)%10)
|
||||
if check == 10:
|
||||
check = 0
|
||||
if unicode_type(check) == isbn[12]:
|
||||
return isbn
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
with suppress(Exception):
|
||||
return check_digit_for_isbn13(isbn) == isbn[12]
|
||||
return False
|
||||
|
||||
|
||||
def check_isbn(isbn):
|
||||
if not isbn:
|
||||
return None
|
||||
isbn = re.sub(r'[^0-9X]', '', isbn.upper())
|
||||
il = len(isbn)
|
||||
if il not in (10, 13):
|
||||
return None
|
||||
all_same = re.match(r'(\d)\1{9,12}$', isbn)
|
||||
if all_same is not None:
|
||||
return None
|
||||
if len(isbn) == 10:
|
||||
return check_isbn10(isbn)
|
||||
if len(isbn) == 13:
|
||||
return check_isbn13(isbn)
|
||||
if il == 10:
|
||||
return isbn if check_isbn10(isbn) else None
|
||||
if il == 13:
|
||||
return isbn if check_isbn13(isbn) else None
|
||||
return None
|
||||
|
||||
|
||||
def normalize_isbn(isbn):
|
||||
if not isbn:
|
||||
return isbn
|
||||
ans = check_isbn(isbn)
|
||||
if ans is None:
|
||||
return isbn
|
||||
if len(ans) == 10:
|
||||
ans = '978' + ans[:9]
|
||||
ans += check_digit_for_isbn13(ans)
|
||||
return ans
|
||||
|
||||
|
||||
def check_issn(issn):
|
||||
if not issn:
|
||||
return None
|
||||
|
@ -14,7 +14,7 @@ from qt.core import QApplication, QDialog, QPixmap, QTimer
|
||||
from calibre import as_unicode, guess_type
|
||||
from calibre.constants import iswindows
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.metadata import MetaInformation, normalize_isbn
|
||||
from calibre.gui2 import (
|
||||
choose_dir, choose_files, error_dialog, gprefs, info_dialog, question_dialog,
|
||||
warning_dialog
|
||||
@ -363,8 +363,35 @@ class AddAction(InterfaceAction):
|
||||
for path in temp_files:
|
||||
os.remove(path)
|
||||
|
||||
def add_isbns(self, books, add_tags=[]):
|
||||
self.isbn_books = list(books)
|
||||
def check_for_existing_isbns(self, books):
|
||||
db = self.gui.current_db.new_api
|
||||
book_id_identifiers = db.all_field_for('identifiers', db.all_book_ids(tuple))
|
||||
existing_isbns = {normalize_isbn(ids.get('isbn', '')): book_id for book_id, ids in book_id_identifiers.items()}
|
||||
existing_isbns.pop('', None)
|
||||
ok = []
|
||||
duplicates = []
|
||||
for book in books:
|
||||
q = normalize_isbn(book['isbn'])
|
||||
if q and q in existing_isbns:
|
||||
duplicates.append((book, existing_isbns[q]))
|
||||
else:
|
||||
ok.append(book)
|
||||
if duplicates:
|
||||
det_msg = '\n'.join(f'{book["isbn"]}: {db.field_for("title", book_id)}' for book, book_id in duplicates)
|
||||
if question_dialog(self.gui, _('Duplicates found'), _(
|
||||
'Books with some of the specified ISBNs already exist in the calibre library.'
|
||||
' Click "Show details" for the full list. Do you want to add them anyway?'), det_msg=det_msg
|
||||
):
|
||||
ok += [x[0] for x in duplicates]
|
||||
return ok
|
||||
|
||||
def add_isbns(self, books, add_tags=[], check_for_existing=False):
|
||||
books = list(books)
|
||||
if check_for_existing:
|
||||
books = self.check_for_existing_isbns(books)
|
||||
if not books:
|
||||
return
|
||||
self.isbn_books = books
|
||||
self.add_by_isbn_ids = set()
|
||||
self.isbn_add_tags = add_tags
|
||||
QTimer.singleShot(10, self.do_one_isbn_add)
|
||||
@ -490,7 +517,7 @@ class AddAction(InterfaceAction):
|
||||
from calibre.gui2.dialogs.add_from_isbn import AddFromISBN
|
||||
d = AddFromISBN(self.gui)
|
||||
if d.exec_() == QDialog.DialogCode.Accepted and d.books:
|
||||
self.add_isbns(d.books, add_tags=d.set_tags)
|
||||
self.add_isbns(d.books, add_tags=d.set_tags, check_for_existing=d.check_for_existing)
|
||||
|
||||
def add_books(self, *args):
|
||||
'''
|
||||
|
@ -7,16 +7,15 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
from qt.core import (
|
||||
QDialog, QApplication, QIcon, QVBoxLayout, QHBoxLayout, QDialogButtonBox,
|
||||
QPlainTextEdit, QPushButton, QLabel, QLineEdit, Qt
|
||||
QApplication, QCheckBox, QDialog, QDialogButtonBox, QHBoxLayout, QIcon, QLabel,
|
||||
QLineEdit, QPlainTextEdit, QPushButton, Qt, QVBoxLayout
|
||||
)
|
||||
|
||||
from calibre.ebooks.metadata import check_isbn
|
||||
from calibre.constants import iswindows
|
||||
from calibre.gui2 import gprefs, question_dialog, error_dialog
|
||||
from polyglot.builtins import unicode_type, filter
|
||||
from calibre.ebooks.metadata import check_isbn
|
||||
from calibre.gui2 import error_dialog, gprefs, question_dialog
|
||||
from polyglot.builtins import filter, unicode_type
|
||||
|
||||
|
||||
class AddFromISBN(QDialog):
|
||||
@ -57,8 +56,8 @@ class AddFromISBN(QDialog):
|
||||
" create entries for books based on the ISBN and download metadata and covers for them.</p>\n"
|
||||
"<p>Any invalid ISBNs in the list will be ignored.</p>\n"
|
||||
"<p>You can also specify a file that will be added with each ISBN. To do this enter the full"
|
||||
" path to the file after a <code>>></code>. For example:</p>\n"
|
||||
"<p><code>9788842915232 >> %s</code></p>"), self)
|
||||
" path to the file after a <code>>></code>. For example:</p>\n"
|
||||
"<p><code>9788842915232 >> %s</code></p>"), self)
|
||||
l.addWidget(la), la.setWordWrap(True)
|
||||
l.addSpacing(20)
|
||||
self.la2 = la = QLabel(_("&Tags to set on created book entries:"), self)
|
||||
@ -67,6 +66,10 @@ class AddFromISBN(QDialog):
|
||||
le.setText(', '.join(gprefs.get('add from ISBN tags', [])))
|
||||
la.setBuddy(le)
|
||||
l.addWidget(le)
|
||||
self._check_for_existing = ce = QCheckBox(_('Check for books with the same ISBN already in library'), self)
|
||||
ce.setChecked(gprefs.get('add from ISBN dup check', False))
|
||||
l.addWidget(ce)
|
||||
|
||||
l.addStretch(10)
|
||||
|
||||
def paste(self, *args):
|
||||
@ -78,10 +81,15 @@ class AddFromISBN(QDialog):
|
||||
new = old + '\n' + txt
|
||||
self.isbn_box.setPlainText(new)
|
||||
|
||||
@property
|
||||
def check_for_existing(self):
|
||||
return self._check_for_existing.isChecked()
|
||||
|
||||
def accept(self, *args):
|
||||
tags = unicode_type(self.add_tags.text()).strip().split(',')
|
||||
tags = list(filter(None, [x.strip() for x in tags]))
|
||||
gprefs['add from ISBN tags'] = tags
|
||||
gprefs['add from ISBN dup check'] = self.check_for_existing
|
||||
self.set_tags = tags
|
||||
bad = set()
|
||||
for line in unicode_type(self.isbn_box.toPlainText()).strip().splitlines():
|
||||
|
Loading…
x
Reference in New Issue
Block a user