Migrated Add Books action

This commit is contained in:
Kovid Goyal 2010-08-12 11:09:47 -06:00
parent 5d96982933
commit 82dc900a11
6 changed files with 98 additions and 68 deletions

View File

@ -5,8 +5,12 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QToolButton, QAction, QIcon
from calibre.customize import InterfaceActionBase from calibre.customize import InterfaceActionBase
from calibre.gui2 import Dispatcher
class InterfaceAction(InterfaceActionBase): class InterfaceAction(InterfaceActionBase):
@ -17,12 +21,31 @@ class InterfaceAction(InterfaceActionBase):
positions = frozenset([]) positions = frozenset([])
separators = frozenset([]) separators = frozenset([])
popup_type = QToolButton.MenuPopup
#: Of the form: (text, icon_path, tooltip, keyboard shortcut)
#: tooltip and keybard shortcut can be None
#: shortcut must be a translated string if not None
action_spec = ('text', 'icon', None, None)
def do_genesis(self, gui): def do_genesis(self, gui):
self.gui = gui self.gui = gui
self.Dispatcher = partial(Dispatcher, parent=gui)
self.create_action()
self.genesis() self.genesis()
# Subclassable methods {{{ def create_action(self):
def genesis(self): text, icon, tooltip, shortcut = self.action_spec
raise NotImplementedError() action = QAction(QIcon(I(icon)), text, self)
# }}} text = tooltip if tooltip else text
action.setToolTip(text)
action.setStatusTip(text)
action.setWhatsThis(text)
action.setAutoRepeat(False)
if shortcut:
action.setShortcut(shortcut)
self.qaction = action
def genesis(self):
pass

View File

@ -8,10 +8,10 @@ __docformat__ = 'restructuredtext en'
import os import os
from functools import partial from functools import partial
from PyQt4.Qt import QInputDialog, QPixmap from PyQt4.Qt import QInputDialog, QPixmap, QMenu
from calibre.gui2 import error_dialog, Dispatcher, choose_files, \ from calibre.gui2 import error_dialog, choose_files, \
choose_dir, warning_dialog, info_dialog choose_dir, warning_dialog, info_dialog
from calibre.gui2.widgets import IMAGE_EXTENSIONS from calibre.gui2.widgets import IMAGE_EXTENSIONS
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
@ -21,20 +21,41 @@ from calibre.gui2.actions import InterfaceAction
class AddAction(InterfaceAction): class AddAction(InterfaceAction):
name = 'Add Books'
action_spec = (_('Add books'), 'add_book.svg', None, _('A'))
positions = frozenset([
('toolbar', 'all', 0),
])
def genesis(self): def genesis(self):
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book, self._add_filesystem_book = self.Dispatcher(self.__add_filesystem_book)
parent=self.gui) self.add_menu = QMenu()
self.add_menu.addAction(_('Add books from a single directory'),
self.add_books)
self.add_menu.addAction(_('Add books from directories, including '
'sub-directories (One book per directory, assumes every ebook '
'file is the same book in a different format)'),
self.add_recursive_single)
self.add_menu.addAction(_('Add books from directories, including '
'sub directories (Multiple books per directory, assumes every '
'ebook file is a different book)'), self.add_recursive_multiple)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
'formats)'), self.add_empty)
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
self.qaction.setMenu(self.add_menu)
self.qaction.triggered.connect(self.add_books)
def add_recursive(self, single): def add_recursive(self, single):
root = choose_dir(self, 'recursive book import root dir dialog', root = choose_dir(self.gui, 'recursive book import root dir dialog',
'Select root folder') 'Select root folder')
if not root: if not root:
return return
from calibre.gui2.add import Adder from calibre.gui2.add import Adder
self._adder = Adder(self, self._adder = Adder(self.gui,
self.library_view.model().db, self.gui.library_view.model().db,
Dispatcher(self._files_added), spare_server=self.spare_server) self.Dispatcher(self._files_added), spare_server=self.gui.spare_server)
self._adder.add_recursive(root, single) self._adder.add_recursive(root, single)
def add_recursive_single(self, *args): def add_recursive_single(self, *args):
@ -56,13 +77,13 @@ class AddAction(InterfaceAction):
Add an empty book item to the library. This does not import any formats Add an empty book item to the library. This does not import any formats
from a book file. from a book file.
''' '''
num, ok = QInputDialog.getInt(self, _('How many empty books?'), num, ok = QInputDialog.getInt(self.gui, _('How many empty books?'),
_('How many empty books should be added?'), 1, 1, 100) _('How many empty books should be added?'), 1, 1, 100)
if ok: if ok:
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
for x in xrange(num): for x in xrange(num):
self.library_view.model().db.import_book(MetaInformation(None), []) self.gui.library_view.model().db.import_book(MetaInformation(None), [])
self.library_view.model().books_added(num) self.gui.library_view.model().books_added(num)
def add_isbns(self, isbns): def add_isbns(self, isbns):
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
@ -70,21 +91,21 @@ class AddAction(InterfaceAction):
for x in isbns: for x in isbns:
mi = MetaInformation(None) mi = MetaInformation(None)
mi.isbn = x mi.isbn = x
ids.add(self.library_view.model().db.import_book(mi, [])) ids.add(self.gui.library_view.model().db.import_book(mi, []))
self.library_view.model().books_added(len(isbns)) self.gui.library_view.model().books_added(len(isbns))
self.do_download_metadata(ids) self.gui.do_download_metadata(ids)
def files_dropped(self, paths): def files_dropped(self, paths):
to_device = self.stack.currentIndex() != 0 to_device = self.gui.stack.currentIndex() != 0
self._add_books(paths, to_device) self._add_books(paths, to_device)
def files_dropped_on_book(self, event, paths): def files_dropped_on_book(self, event, paths):
accept = False accept = False
if self.current_view() is not self.library_view: if self.gui.current_view() is not self.gui.library_view:
return return
db = self.library_view.model().db db = self.gui.library_view.model().db
current_idx = self.library_view.currentIndex() current_idx = self.gui.library_view.currentIndex()
if not current_idx.isValid(): return if not current_idx.isValid(): return
cid = db.id(current_idx.row()) cid = db.id(current_idx.row())
for path in paths: for path in paths:
@ -102,7 +123,7 @@ class AddAction(InterfaceAction):
accept = True accept = True
if accept: if accept:
event.accept() event.accept()
self.library_view.model().current_changed(current_idx, current_idx) self.gui.library_view.model().current_changed(current_idx, current_idx)
def __add_filesystem_book(self, paths, allow_device=True): def __add_filesystem_book(self, paths, allow_device=True):
if isinstance(paths, basestring): if isinstance(paths, basestring):
@ -111,10 +132,10 @@ class AddAction(InterfaceAction):
os.R_OK)] os.R_OK)]
if books: if books:
to_device = allow_device and self.stack.currentIndex() != 0 to_device = allow_device and self.gui.stack.currentIndex() != 0
self._add_books(books, to_device) self._add_books(books, to_device)
if to_device: if to_device:
self.status_bar.show_message(\ self.gui.status_bar.show_message(\
_('Uploading books to device.'), 2000) _('Uploading books to device.'), 2000)
@ -123,7 +144,7 @@ class AddAction(InterfaceAction):
def add_from_isbn(self, *args): def add_from_isbn(self, *args):
from calibre.gui2.dialogs.add_from_isbn import AddFromISBN from calibre.gui2.dialogs.add_from_isbn import AddFromISBN
d = AddFromISBN(self) d = AddFromISBN(self.gui)
if d.exec_() == d.Accepted: if d.exec_() == d.Accepted:
self.add_isbns(d.isbns) self.add_isbns(d.isbns)
@ -144,11 +165,11 @@ class AddAction(InterfaceAction):
(_('Comics'), ['cbz', 'cbr', 'cbc']), (_('Comics'), ['cbz', 'cbr', 'cbc']),
(_('Archives'), ['zip', 'rar']), (_('Archives'), ['zip', 'rar']),
] ]
to_device = self.stack.currentIndex() != 0 to_device = self.gui.stack.currentIndex() != 0
if to_device: if to_device:
filters = [(_('Supported books'), self.device_manager.device.FORMATS)] filters = [(_('Supported books'), self.gui.device_manager.device.FORMATS)]
books = choose_files(self, 'add books dialog dir', 'Select books', books = choose_files(self.gui, 'add books dialog dir', 'Select books',
filters=filters) filters=filters)
if not books: if not books:
return return
@ -156,32 +177,33 @@ class AddAction(InterfaceAction):
def _add_books(self, paths, to_device, on_card=None): def _add_books(self, paths, to_device, on_card=None):
if on_card is None: if on_card is None:
on_card = 'carda' if self.stack.currentIndex() == 2 else 'cardb' if self.stack.currentIndex() == 3 else None on_card = 'carda' if self.gui.stack.currentIndex() == 2 else \
'cardb' if self.gui.stack.currentIndex() == 3 else None
if not paths: if not paths:
return return
from calibre.gui2.add import Adder from calibre.gui2.add import Adder
self.__adder_func = partial(self._files_added, on_card=on_card) self.__adder_func = partial(self._files_added, on_card=on_card)
self._adder = Adder(self, self._adder = Adder(self.gui,
None if to_device else self.library_view.model().db, None if to_device else self.gui.library_view.model().db,
Dispatcher(self.__adder_func), spare_server=self.spare_server) self.Dispatcher(self.__adder_func), spare_server=self.gui.spare_server)
self._adder.add(paths) self._adder.add(paths)
def _files_added(self, paths=[], names=[], infos=[], on_card=None): def _files_added(self, paths=[], names=[], infos=[], on_card=None):
if paths: if paths:
self.upload_books(paths, self.gui.upload_books(paths,
list(map(ascii_filename, names)), list(map(ascii_filename, names)),
infos, on_card=on_card) infos, on_card=on_card)
self.status_bar.show_message( self.gui.status_bar.show_message(
_('Uploading books to device.'), 2000) _('Uploading books to device.'), 2000)
if getattr(self._adder, 'number_of_books_added', 0) > 0: if getattr(self._adder, 'number_of_books_added', 0) > 0:
self.library_view.model().books_added(self._adder.number_of_books_added) self.gui.library_view.model().books_added(self._adder.number_of_books_added)
if hasattr(self, 'db_images'): if hasattr(self.gui, 'db_images'):
self.db_images.reset() self.gui.db_images.reset()
if getattr(self._adder, 'merged_books', False): if getattr(self._adder, 'merged_books', False):
books = u'\n'.join([x if isinstance(x, unicode) else books = u'\n'.join([x if isinstance(x, unicode) else
x.decode(preferred_encoding, 'replace') for x in x.decode(preferred_encoding, 'replace') for x in
self._adder.merged_books]) self._adder.merged_books])
info_dialog(self, _('Merged some books'), info_dialog(self.gui, _('Merged some books'),
_('Some duplicates were found and merged into the ' _('Some duplicates were found and merged into the '
'following existing books:'), det_msg=books, show=True) 'following existing books:'), det_msg=books, show=True)
if getattr(self._adder, 'critical', None): if getattr(self._adder, 'critical', None):
@ -191,7 +213,7 @@ class AddAction(InterfaceAction):
name = name.decode(filesystem_encoding, 'replace') name = name.decode(filesystem_encoding, 'replace')
det_msg.append(name+'\n'+log) det_msg.append(name+'\n'+log)
warning_dialog(self, _('Failed to read metadata'), warning_dialog(self.gui, _('Failed to read metadata'),
_('Failed to read metadata from the following')+':', _('Failed to read metadata from the following')+':',
det_msg='\n\n'.join(det_msg), show=True) det_msg='\n\n'.join(det_msg), show=True)
@ -205,17 +227,17 @@ class AddAction(InterfaceAction):
# set the in-library flags, and as a consequence send the library's # set the in-library flags, and as a consequence send the library's
# metadata for this book to the device. This sets the uuid to the # metadata for this book to the device. This sets the uuid to the
# correct value. # correct value.
self.set_books_in_library(booklists=[model.db], reset=True) self.gui.set_books_in_library(booklists=[model.db], reset=True)
model.reset() model.reset()
def add_books_from_device(self, view): def add_books_from_device(self, view):
rows = view.selectionModel().selectedRows() rows = view.selectionModel().selectedRows()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
d = error_dialog(self, _('Add to library'), _('No book selected')) d = error_dialog(self.gui, _('Add to library'), _('No book selected'))
d.exec_() d.exec_()
return return
paths = [p for p in view._model.paths(rows) if p is not None] paths = [p for p in view._model.paths(rows) if p is not None]
ve = self.device_manager.device.VIRTUAL_BOOK_EXTENSIONS ve = self.gui.device_manager.device.VIRTUAL_BOOK_EXTENSIONS
def ext(x): def ext(x):
ans = os.path.splitext(x)[1] ans = os.path.splitext(x)[1]
ans = ans[1:] if len(ans) > 1 else ans ans = ans[1:] if len(ans) > 1 else ans
@ -223,21 +245,21 @@ class AddAction(InterfaceAction):
remove = set([p for p in paths if ext(p) in ve]) remove = set([p for p in paths if ext(p) in ve])
if remove: if remove:
paths = [p for p in paths if p not in remove] paths = [p for p in paths if p not in remove]
info_dialog(self, _('Not Implemented'), info_dialog(self.gui, _('Not Implemented'),
_('The following books are virtual and cannot be added' _('The following books are virtual and cannot be added'
' to the calibre library:'), '\n'.join(remove), ' to the calibre library:'), '\n'.join(remove),
show=True) show=True)
if not paths: if not paths:
return return
if not paths or len(paths) == 0: if not paths or len(paths) == 0:
d = error_dialog(self, _('Add to library'), _('No book files found')) d = error_dialog(self.gui, _('Add to library'), _('No book files found'))
d.exec_() d.exec_()
return return
from calibre.gui2.add import Adder from calibre.gui2.add import Adder
self.__adder_func = partial(self._add_from_device_adder, on_card=None, self.__adder_func = partial(self._add_from_device_adder, on_card=None,
model=view._model) model=view._model)
self._adder = Adder(self, self.library_view.model().db, self._adder = Adder(self.gui, self.gui.library_view.model().db,
Dispatcher(self.__adder_func), spare_server=self.spare_server) self.Dispatcher(self.__adder_func), spare_server=self.gui.spare_server)
self._adder.add(paths) self._adder.add(paths)

View File

@ -63,7 +63,8 @@ class LibraryViewMixin(object): # {{{
add_to_library = None, add_to_library = None,
edit_device_collections=None, edit_device_collections=None,
similar_menu=similar_menu) similar_menu=similar_menu)
add_to_library = (_('Add books to library'), self.add_books_from_device) add_to_library = (_('Add books to library'),
self.iactions['Add Books'].add_books_from_device)
edit_device_collections = (_('Manage collections'), edit_device_collections = (_('Manage collections'),
partial(self.edit_device_collections, oncard=None)) partial(self.edit_device_collections, oncard=None))
@ -89,7 +90,7 @@ class LibraryViewMixin(object): # {{{
add_to_library=add_to_library, add_to_library=add_to_library,
edit_device_collections=edit_device_collections) edit_device_collections=edit_device_collections)
self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection) self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection)
for func, args in [ for func, args in [
('connect_to_search_box', (self.search, ('connect_to_search_box', (self.search,
self.search_done)), self.search_done)),
@ -305,7 +306,7 @@ class LayoutMixin(object): # {{{
def finalize_layout(self): def finalize_layout(self):
self.status_bar.initialize(self.system_tray_icon) self.status_bar.initialize(self.system_tray_icon)
self.book_details.show_book_info.connect(self.show_book_info) self.book_details.show_book_info.connect(self.show_book_info)
self.book_details.files_dropped.connect(self.files_dropped_on_book) self.book_details.files_dropped.connect(self.iactions['Add Books'].files_dropped_on_book)
self.book_details.open_containing_folder.connect(self.view_folder_for_id) self.book_details.open_containing_folder.connect(self.view_folder_for_id)
self.book_details.view_specific_format.connect(self.view_format_by_id) self.book_details.view_specific_format.connect(self.view_format_by_id)

View File

@ -528,22 +528,6 @@ class MainWindowMixin(object):
md.addSeparator() md.addSeparator()
md.addAction(self.action_merge) md.addAction(self.action_merge)
self.add_menu = QMenu()
self.add_menu.addAction(_('Add books from a single directory'),
self.add_books)
self.add_menu.addAction(_('Add books from directories, including '
'sub-directories (One book per directory, assumes every ebook '
'file is the same book in a different format)'),
self.add_recursive_single)
self.add_menu.addAction(_('Add books from directories, including '
'sub directories (Multiple books per directory, assumes every '
'ebook file is a different book)'), self.add_recursive_multiple)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
'formats)'), self.add_empty)
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
self.action_add.setMenu(self.add_menu)
self.action_add.triggered.connect(self.add_books)
self.action_del.triggered.connect(self.delete_books) self.action_del.triggered.connect(self.delete_books)
self.action_edit.triggered.connect(self.edit_metadata) self.action_edit.triggered.connect(self.edit_metadata)
self.action_merge.triggered.connect(self.merge_books) self.action_merge.triggered.connect(self.merge_books)

View File

@ -155,7 +155,7 @@ class GuiRunner(QObject):
main.initialize(self.library_path, self.db, self.listener, self.actions) main.initialize(self.library_path, self.db, self.listener, self.actions)
if DEBUG: if DEBUG:
prints('Started up in', time.time() - self.startup_time) prints('Started up in', time.time() - self.startup_time)
add_filesystem_book = partial(main.add_filesystem_book, allow_device=False) add_filesystem_book = partial(main.iactions['Add Books'].add_filesystem_book, allow_device=False)
sys.excepthook = main.unhandled_exception sys.excepthook = main.unhandled_exception
if len(self.args) > 1: if len(self.args) > 1:
p = os.path.abspath(self.args[1]) p = os.path.abspath(self.args[1])

View File

@ -329,7 +329,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
if len(argv) > 1: if len(argv) > 1:
path = os.path.abspath(argv[1]) path = os.path.abspath(argv[1])
if os.access(path, os.R_OK): if os.access(path, os.R_OK):
self.add_filesystem_book(path) self.iactions['Add Books'].add_filesystem_book(path)
self.setWindowState(self.windowState() & \ self.setWindowState(self.windowState() & \
~Qt.WindowMinimized|Qt.WindowActive) ~Qt.WindowMinimized|Qt.WindowActive)
self.show_windows() self.show_windows()