diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index b93efa1dc4..059fa036e3 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -10,7 +10,8 @@ from functools import partial from threading import Thread from contextlib import closing -from PyQt4.Qt import (QToolButton, QDialog, QGridLayout, QIcon, QLabel, QDialogButtonBox) +from PyQt4.Qt import (QToolButton, QDialog, QGridLayout, QIcon, QLabel, QDialogButtonBox, + QFormLayout, QCheckBox) from calibre.gui2.actions import InterfaceAction from calibre.gui2 import (error_dialog, Dispatcher, warning_dialog, gprefs, @@ -19,6 +20,8 @@ from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2.widgets import HistoryLineEdit from calibre.utils.config import prefs, tweaks from calibre.utils.date import now +from calibre.utils.icu import sort_key + class Worker(Thread): # {{{ @@ -47,11 +50,11 @@ class Worker(Thread): # {{{ self.done() - def add_formats(self, id, paths, newdb, replace=True): + def add_formats(self, id_, paths, newdb, replace=True): for path in paths: fmt = os.path.splitext(path)[-1].replace('.', '').upper() with open(path, 'rb') as f: - newdb.add_format(id, fmt, f, index_is_id=True, + newdb.add_format(id_, fmt, f, index_is_id=True, notify=False, replace=replace) def doit(self): @@ -174,6 +177,10 @@ class ChooseLibrary(QDialog): # {{{ return (unicode(self.le.text()), self.delete_after_copy) # }}} +# Static session-long set of pairs of libraries that have had their custom columns +# checked for compatibility +libraries_with_checked_columns = {} + class CopyToLibraryAction(InterfaceAction): name = 'Copy To Library' @@ -231,6 +238,11 @@ class CopyToLibraryAction(InterfaceAction): _('Cannot copy to current library.'), show=True) self.copy_to_library(path, delete_after) + def _column_is_compatible(self, source_metadata, dest_metadata): + return (source_metadata['datatype'] == dest_metadata['datatype'] and + (source_metadata['datatype'] != 'text' or + source_metadata['is_multiple'] == dest_metadata['is_multiple'])) + def copy_to_library(self, loc, delete_after=False): rows = self.gui.library_view.selectionModel().selectedRows() if not rows or len(rows) == 0: @@ -252,6 +264,39 @@ class CopyToLibraryAction(InterfaceAction): self.pd.set_msg(title) self.pd.set_value(idx) + # Open the new db so we can check the custom columns. + + global libraries_with_checked_columns + if db.library_id not in libraries_with_checked_columns: + libraries_with_checked_columns[db.library_id] = set() + + from calibre.db.legacy import LibraryDatabase + newdb = LibraryDatabase(loc, is_second_db=True) + + continue_processing = True; + with closing(newdb): + if newdb.library_id not in libraries_with_checked_columns[db.library_id]: + + newdb_meta = newdb.field_metadata.custom_field_metadata() + incompatible_columns = [] + missing_columns = [] + for k, m in db.field_metadata.custom_iteritems(): + if k not in newdb_meta: + missing_columns.append(k) + elif not self._column_is_compatible(m, newdb_meta[k]): + incompatible_columns.append(k) + + if missing_columns or incompatible_columns: + continue_processing = self.custom_column_dialog(db, newdb, + missing_columns, incompatible_columns) + if continue_processing: + libraries_with_checked_columns[db.library_id].add(newdb.library_id) + + newdb.break_cycles() + del newdb + if not continue_processing: + return; + self.worker = Worker(ids, db, loc, Dispatcher(progress), Dispatcher(self.pd.accept), delete_after) self.worker.start() @@ -295,4 +340,63 @@ class CopyToLibraryAction(InterfaceAction): _('You cannot use other libraries while using the environment' ' variable CALIBRE_OVERRIDE_DATABASE_PATH.'), show=True) + def custom_column_dialog(self, db, newdb, missing_cols, incompatible_cols): + source_metadata = db.field_metadata.custom_field_metadata(include_composites=True) + d = QDialog(self.gui) + d.setWindowTitle(_('Create link')) + l = QFormLayout() + d.setLayout(l) + d.setMinimumWidth(600) + d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) + + d.la = la = QLabel(_( + 'The custom columns in the source library are different from the ' + 'custom columns in the destination library. Incompatible columns ' + 'are columns with the same lookup key but different column ' + 'types. These cannot be copied. Missing columns are columns ' + 'in the source library but not in the destination library. ' + 'If you check the "Create" box, these columns will be added ' + 'to the destination and their values copied with the books.')) + la.setWordWrap(True) + la.setStyleSheet('QLabel { margin-bottom: 1.5ex }') + l.setWidget(0, l.SpanningRole, la) + if incompatible_cols: + l.addRow(_('Incompatible custom columns:'), + QLabel(', '.join(sorted(incompatible_cols, key=sort_key)))) + + incompatible_cols_widgets = [] + if missing_cols: + l.addRow(QLabel(_('Missing custom columns')), QLabel('')) + for k in missing_cols: + widgets = (k, QCheckBox(_('Add column to new library'))) + l.addRow(QLabel(k), widgets[1]) + incompatible_cols_widgets.append(widgets) + + l.addRow(d.bb) + d.bb.accepted.connect(d.accept) + d.bb.rejected.connect(d.reject) + d.resize(d.sizeHint()) + if d.exec_() == d.Accepted: + count = 0 + for k,cb in incompatible_cols_widgets: + if cb.isChecked(): + count += 1 + if count: + pd = ProgressDialog(_('Creating custom columns'), min=0, max=count, + parent=self.gui, cancelable=False) + pd.show() + done_count = 0 + for k,cb in incompatible_cols_widgets: + if cb.isChecked(): + pd.set_value(done_count) + pd.set_msg(_('Creating column {0}').format(k)) + done_count += 1 + col_meta = source_metadata[k] + newdb.create_custom_column( + col_meta['label'], col_meta['name'], col_meta['datatype'], + len(col_meta['is_multiple']) > 0, + col_meta['is_editable'], col_meta['display']) + pd.done(0) + return True + return False \ No newline at end of file