mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
First implementation of plugboards
This commit is contained in:
parent
73cdb91e61
commit
1dee223f14
@ -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]
|
||||
|
||||
#}}}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
257
src/calibre/gui2/preferences/plugboard.py
Normal file
257
src/calibre/gui2/preferences/plugboard.py
Normal 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')
|
||||
|
138
src/calibre/gui2/preferences/plugboard.ui
Normal file
138
src/calibre/gui2/preferences/plugboard.ui
Normal 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>
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user