First implementation of plugboards

This commit is contained in:
Charles Haley 2010-09-27 15:12:25 +01:00
parent 73cdb91e61
commit 1dee223f14
6 changed files with 484 additions and 9 deletions

View File

@ -796,6 +796,17 @@ class Sending(PreferencesPlugin):
description = _('Control how calibre transfers files to your '
'ebook reader')
class Plugboard(PreferencesPlugin):
name = 'Plugboard'
icon = I('plugboard.png')
gui_name = _('Metadata plugboard')
category = 'Import/Export'
gui_category = _('Import/Export')
category_order = 3
name_order = 4
config_widget = 'calibre.gui2.preferences.plugboard'
description = _('Change metadata fields before saving/sending')
class Email(PreferencesPlugin):
name = 'Email'
icon = I('mail.png')
@ -856,8 +867,8 @@ class Misc(PreferencesPlugin):
description = _('Miscellaneous advanced configuration')
plugins += [LookAndFeel, Behavior, Columns, Toolbar, InputOptions,
CommonOptions, OutputOptions, Adding, Saving, Sending, Email, Server,
Plugins, Tweaks, Misc]
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
Email, Server, Plugins, Tweaks, Misc]
#}}}

View File

@ -182,7 +182,7 @@ class Metadata(object):
return metadata describing a standard or custom field.
'''
if key not in self.custom_field_keys():
return self.get_standard_metadata(self, key, make_copy=False)
return self.get_standard_metadata(key, make_copy=False)
return self.get_user_metadata(key, make_copy=False)
def all_non_none_fields(self):
@ -294,6 +294,33 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data')
_data['user_metadata'][field] = metadata
def copy_specific_attributes(self, other, attrs):
'''
Takes a dict {src:dest, src:dest} and copys other[src] to self[dest].
This is on a best-efforts basis. Some assignments can make no sense.
'''
if not attrs:
return
for src in attrs:
try:
print src
sfm = other.metadata_for_field(src)
dfm = self.metadata_for_field(attrs[src])
if dfm['is_multiple']:
if sfm['is_multiple']:
self.set(attrs[src], other.get(src))
else:
self.set(attrs[src],
[f.strip() for f in other.get(src).split(',')
if f.strip()])
elif sfm['is_multiple']:
self.set(attrs[src], ','.join(other.get(src)))
else:
self.set(attrs[src], other.get(src))
except:
traceback.print_exc()
pass
# Old Metadata API {{{
def print_all_attributes(self):
for x in STANDARD_METADATA_FIELDS:

View File

@ -317,19 +317,40 @@ class DeviceManager(Thread): # {{{
args=[booklist, on_card],
description=_('Send collections to device'))
def _upload_books(self, files, names, on_card=None, metadata=None):
def _upload_books(self, files, names, on_card=None, metadata=None, plugboards=None):
'''Upload books to device: '''
if metadata and files and len(metadata) == len(files):
for f, mi in zip(files, metadata):
if isinstance(f, unicode):
ext = f.rpartition('.')[-1].lower()
dev_name = self.connected_device.name
cpb = None
if ext in plugboards:
cpb = plugboards[ext]
elif ' any' in plugboards:
cpb = plugboards[' any']
if cpb is not None:
if dev_name in cpb:
cpb = cpb[dev_name]
elif ' any' in plugboards[ext]:
cpb = cpb[' any']
else:
cpb = None
if DEBUG:
prints('Using plugboard', cpb)
if ext:
try:
if DEBUG:
prints('Setting metadata in:', mi.title, 'at:',
f, file=sys.__stdout__)
with open(f, 'r+b') as stream:
set_metadata(stream, mi, stream_type=ext)
if cpb:
newmi = mi.deepcopy()
newmi.copy_specific_attributes(mi, cpb)
else:
newmi = mi
set_metadata(stream, newmi, stream_type=ext)
except:
if DEBUG:
prints(traceback.format_exc(), file=sys.__stdout__)
@ -338,12 +359,12 @@ class DeviceManager(Thread): # {{{
metadata=metadata, end_session=False)
def upload_books(self, done, files, names, on_card=None, titles=None,
metadata=None):
metadata=None, plugboards=None):
desc = _('Upload %d books to device')%len(names)
if titles:
desc += u':' + u', '.join(titles)
return self.create_job(self._upload_books, done, args=[files, names],
kwargs={'on_card':on_card,'metadata':metadata}, description=desc)
kwargs={'on_card':on_card,'metadata':metadata,'plugboards':plugboards}, description=desc)
def add_books_to_metadata(self, locations, metadata, booklists):
self.device.add_books_to_metadata(locations, metadata, booklists)
@ -1257,10 +1278,11 @@ class DeviceMixin(object): # {{{
:param files: List of either paths to files or file like objects
'''
titles = [i.title for i in metadata]
plugboards = self.library_view.model().db.prefs.get('plugboards', None)
job = self.device_manager.upload_books(
Dispatcher(self.books_uploaded),
files, names, on_card=on_card,
metadata=metadata, titles=titles
metadata=metadata, titles=titles, plugboards=plugboards
)
self.upload_memory[job] = (metadata, on_card, memory, files)

View File

@ -0,0 +1,257 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4 import QtGui
from calibre.gui2 import error_dialog
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \
AbortCommit
from calibre.gui2.preferences.plugboard_ui import Ui_Form
from calibre.customize.ui import metadata_writers, device_plugins
class ConfigWidget(ConfigWidgetBase, Ui_Form):
def genesis(self, gui):
self.gui = gui
self.db = gui.library_view.model().db
self.current_plugboards = self.db.prefs.get('plugboards', {'epub': {' any': {'title':'authors', 'authors':'tags'}}})
self.current_device = None
self.current_format = None
# self.proxy = ConfigProxy(config())
#
# r = self.register
#
# for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf',
# 'replace_whitespace', 'to_lowercase', 'formats', 'timefmt'):
# r(x, self.proxy)
#
# self.save_template.changed_signal.connect(self.changed_signal.emit)
def clear_fields(self, edit_boxes=False, new_boxes=False):
self.ok_button.setEnabled(False)
for w in self.source_widgets:
w.clear()
for w in self.dest_widgets:
w.clear()
if edit_boxes:
self.edit_device.setCurrentIndex(0)
self.edit_format.setCurrentIndex(0)
if new_boxes:
self.new_device.setCurrentIndex(0)
self.new_format.setCurrentIndex(0)
def set_fields(self):
self.ok_button.setEnabled(True)
for w in self.source_widgets:
w.addItems(self.fields)
for w in self.dest_widgets:
w.addItems(self.fields)
def set_field(self, i, src, dst):
print i, src, dst
idx = self.fields.index(src)
self.source_widgets[i].setCurrentIndex(idx)
idx = self.fields.index(dst)
self.dest_widgets[i].setCurrentIndex(idx)
def edit_device_changed(self, txt):
if txt == '':
self.current_device = None
return
print 'edit device changed'
self.clear_fields(new_boxes=True)
self.current_device = unicode(txt)
fpb = self.current_plugboards.get(self.current_format, None)
if fpb is None:
print 'None format!'
return
dpb = fpb.get(self.current_device, None)
if dpb is None:
print 'none device!'
return
self.set_fields()
for i,src in enumerate(dpb):
self.set_field(i, src, dpb[src])
self.ok_button.setEnabled(True)
def edit_format_changed(self, txt):
if txt == '':
self.edit_device.setCurrentIndex(0)
self.current_format = None
self.current_device = None
return
print 'edit_format_changed'
self.clear_fields(new_boxes=True)
txt = unicode(txt)
fpb = self.current_plugboards.get(txt, None)
if fpb is None:
print 'None editable format!'
return
self.current_format = txt
devices = ['']
for d in fpb:
devices.append(d)
self.edit_device.clear()
self.edit_device.addItems(devices)
self.edit_device.setCurrentIndex(0)
def new_device_changed(self, txt):
if txt == '':
self.current_device = None
return
print 'new_device_changed'
self.clear_fields(edit_boxes=True)
self.current_device = unicode(txt)
error = False
if self.current_format == ' any':
for f in self.current_plugboards:
if self.current_device == ' any' and len(self.current_plugboards[f]):
error = True
break
if self.current_device in self.current_plugboards[f]:
error = True
break
if ' any' in self.current_plugboards[f]:
error = True
break
else:
fpb = self.current_plugboards.get(self.current_format, None)
if fpb is not None:
if ' any' in fpb:
error = True
else:
dpb = fpb.get(self.current_device, None)
if dpb is not None:
error = True
if error:
error_dialog(self, '',
_('That format and device already has a plugboard'),
show=True)
self.new_device.setCurrentIndex(0)
return
self.set_fields()
def new_format_changed(self, txt):
if txt == '':
self.current_format = None
self.current_device = None
return
print 'new_format_changed'
self.clear_fields(edit_boxes=True)
self.current_format = unicode(txt)
self.new_device.setCurrentIndex(0)
def ok_clicked(self):
pb = {}
print self.current_format, self.current_device
for i in range(0, len(self.source_widgets)):
s = self.source_widgets[i].currentIndex()
if s != 0:
d = self.dest_widgets[i].currentIndex()
if d != 0:
pb[self.fields[s]] = self.fields[d]
if len(pb) == 0:
if self.current_format in self.current_plugboards:
fpb = self.current_plugboards[self.current_format]
if self.current_device in fpb:
del fpb[self.current_device]
if len(fpb) == 0:
del self.current_plugboards[self.current_format]
else:
if self.current_format not in self.current_plugboards:
self.current_plugboards[self.current_format] = {}
fpb = self.current_plugboards[self.current_format]
fpb[self.current_device] = pb
self.changed_signal.emit()
self.refill_all_boxes()
def refill_all_boxes(self):
self.current_device = None
self.current_format = None
self.clear_fields(new_boxes=True)
self.edit_format.clear()
self.edit_format.addItem('')
for format in self.current_plugboards:
self.edit_format.addItem(format)
self.edit_format.setCurrentIndex(0)
self.edit_device.clear()
self.ok_button.setEnabled(False)
def initialize(self):
def field_cmp(x, y):
if x.startswith('#'):
if y.startswith('#'):
return cmp(x.lower(), y.lower())
else:
return 1
elif y.startswith('#'):
return -1
else:
return cmp(x.lower(), y.lower())
ConfigWidgetBase.initialize(self)
self.devices = ['', ' any', 'save to disk']
for device in device_plugins():
self.devices.append(device.name)
self.devices.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
self.new_device.addItems(self.devices)
self.formats = ['', ' any']
for w in metadata_writers():
for f in w.file_types:
self.formats.append(f)
self.formats.sort()
self.new_format.addItems(self.formats)
self.fields = ['']
for f in self.db.all_field_keys():
if self.db.field_metadata[f].get('rec_index', None) is not None and\
self.db.field_metadata[f]['datatype'] is not None and \
self.db.field_metadata[f]['search_terms']:
self.fields.append(f)
self.fields.sort(cmp=field_cmp)
self.source_widgets = []
self.dest_widgets = []
for i in range(0, 10):
w = QtGui.QComboBox(self)
self.source_widgets.append(w)
self.fields_layout.addWidget(w, 5+i, 0, 1, 1)
w = QtGui.QComboBox(self)
self.dest_widgets.append(w)
self.fields_layout.addWidget(w, 5+i, 1, 1, 1)
self.edit_device.currentIndexChanged[str].connect(self.edit_device_changed)
self.edit_format.currentIndexChanged[str].connect(self.edit_format_changed)
self.new_device.currentIndexChanged[str].connect(self.new_device_changed)
self.new_format.currentIndexChanged[str].connect(self.new_format_changed)
self.ok_button.clicked.connect(self.ok_clicked)
self.refill_all_boxes()
def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self)
self.current_plugboards = {}
self.refill_all_boxes()
self.changed_signal.emit()
def commit(self):
self.db.prefs.set('plugboards', self.current_plugboards)
return ConfigWidgetBase.commit(self)
def refresh_gui(self, gui):
pass
if __name__ == '__main__':
from PyQt4.Qt import QApplication
app = QApplication([])
test_widget('Import/Export', 'plugboards')

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>707</width>
<height>340</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Here you can control what metadata calibre uses when saving or sending books:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Add new plugboard</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Edit existing plugboard</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="new_format"/>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="new_device"/>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="edit_format"/>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="edit_device"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Format (choose first)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Device (choose second)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QGridLayout" name="fields_layout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Source field</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Destination field</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="20" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="19" column="0" colspan="2">
<widget class="QPushButton" name="ok_button">
<property name="text">
<string>Done</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -232,6 +232,21 @@ def save_book_to_disk(id, db, root, opts, length):
written = False
for fmt in formats:
dev_name = 'save to disk'
plugboards = db.prefs.get('plugboards', None)
cpb = None
if fmt in plugboards:
cpb = plugboards[fmt]
elif ' any' in plugboards:
cpb = plugboards[' any']
if cpb is not None:
if dev_name in cpb:
cpb = cpb[dev_name]
elif ' any' in plugboards[fmt]:
cpb = cpb[' any']
else:
cpb = None
data = db.format(id, fmt, index_is_id=True)
if data is None:
continue
@ -242,7 +257,12 @@ def save_book_to_disk(id, db, root, opts, length):
stream.write(data)
stream.seek(0)
try:
set_metadata(stream, mi, fmt)
if cpb:
newmi = mi.deepcopy()
newmi.copy_specific_attributes(mi, cpb)
else:
newmi = mi
set_metadata(stream, newmi, fmt)
except:
traceback.print_exc()
stream.seek(0)