Add option to specify timefmt when sending to device. Re-enable syntactic validation of save templates.

This commit is contained in:
Kovid Goyal 2010-09-16 09:17:53 -06:00
commit c4cef6a76c
10 changed files with 92 additions and 59 deletions

View file

@ -94,12 +94,12 @@ class CollectionsBookList(BookList):
def supports_collections(self):
return True
def compute_category_name(self, attr, category, cust_field_meta):
def compute_category_name(self, attr, category, field_meta):
renames = tweaks['sony_collection_renaming_rules']
attr_name = renames.get(attr, None)
if attr_name is None:
if attr in cust_field_meta:
attr_name = '(%s)'%cust_field_meta[attr]['name']
if field_meta['is_custom']:
attr_name = '(%s)'%field_meta['name']
else:
attr_name = ''
elif attr_name != '':
@ -138,23 +138,23 @@ def get_collections(self, collection_attributes):
# specified 'on_connect'
attrs = collection_attributes
meta_vals = book.get_all_non_none_attributes()
cust_field_meta = book.get_all_user_metadata(make_copy=False)
for attr in attrs:
attr = attr.strip()
ign, val = book.format_field(attr,
ignore_series_index=True,
return_multiples_as_list=True)
ign, val, orig_val, fm = book.format_field_extended(attr)
if not val: continue
if isbytestring(val):
val = val.decode(preferred_encoding, 'replace')
if isinstance(val, (list, tuple)):
val = list(val)
elif fm['datatype'] == 'series':
val = [orig_val]
elif fm['datatype'] == 'text' and fm['is_multiple']:
val = orig_val
else:
val = [val]
for category in val:
is_series = False
if attr in cust_field_meta: # is a custom field
fm = cust_field_meta[attr]
if fm['is_custom']: # is a custom field
if fm['datatype'] == 'text' and len(category) > 1 and \
category[0] == '[' and category[-1] == ']':
continue
@ -168,8 +168,7 @@ def get_collections(self, collection_attributes):
('series' in collection_attributes and
meta_vals.get('series', None) == category):
is_series = True
cat_name = self.compute_category_name(attr, category,
cust_field_meta)
cat_name = self.compute_category_name(attr, category, fm)
if cat_name not in collections:
collections[cat_name] = []
collections_lpaths[cat_name] = set()

View file

@ -829,12 +829,14 @@ def create_upload_path(self, path, mdata, fname, create_dirs=True):
ext = os.path.splitext(fname)[1]
from calibre.library.save_to_disk import get_components
from calibre.library.save_to_disk import config
opts = config().parse()
if not isinstance(template, unicode):
template = template.decode('utf-8')
app_id = str(getattr(mdata, 'application_id', ''))
# The db id will be in the created filename
extra_components = get_components(template, mdata, fname,
length=250-len(app_id)-1)
timefmt=opts.send_timefmt, length=250-len(app_id)-1)
if not extra_components:
extra_components.append(sanitize(self.filename_callback(fname,
mdata)))

View file

@ -343,8 +343,11 @@ def format_tags(self):
def format_rating(self):
return unicode(self.rating)
def format_field(self, key, ignore_series_index=False,
return_multiples_as_list=False):
def format_field(self, key):
name, val, ign, ign = self.format_field_extended(key)
return (name, val)
def format_field_extended(self, key):
from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (field_name, formatted_value)
@ -352,43 +355,41 @@ def format_field(self, key, ignore_series_index=False,
if key in self.user_metadata_keys:
res = self.get(key, None)
if res is None or res == '':
return (None, None)
return (None, None, None, None)
orig_res = res
cmeta = self.get_user_metadata(key, make_copy=False)
name = unicode(cmeta['name'])
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
if not return_multiples_as_list:
res = u', '.join(res)
res = u', '.join(res)
elif datatype == 'series':
if not ignore_series_index:
res = res + \
' [%s]'%self.format_series_index(val=self.get_extra(key))
res = res + \
' [%s]'%self.format_series_index(val=self.get_extra(key))
elif datatype == 'datetime':
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
return (name, res)
return (name, res, orig_res, cmeta)
if key in field_metadata and field_metadata[key]['kind'] == 'field':
res = self.get(key, None)
if res is None or res == '':
return (None, None)
return (None, None, None, None)
orig_res = res
fmeta = field_metadata[key]
name = unicode(fmeta['name'])
datatype = fmeta['datatype']
if key == 'authors':
res = authors_to_string(res)
elif datatype == 'text' and fmeta['is_multiple']:
if not return_multiples_as_list:
res = u', '.join(res)
res = u', '.join(res)
elif datatype == 'series':
if not ignore_series_index:
res = res + ' [%s]'%self.format_series_index()
res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
return (name, res)
return (name, res, orig_res, fmeta)
return (None, None)
return (None, None, None, None)
def __unicode__(self):
from calibre.ebooks.metadata import authors_to_string

View file

@ -1415,9 +1415,11 @@ def test_user_metadata():
mi = Metadata('Test title', ['test author1', 'test author2'])
um = {
'#myseries': { '#value#': u'test series\xe4', 'datatype':'text',
'is_multiple': False, 'name': u'My Series'},
'is_multiple': None, 'name': u'My Series'},
'#myseries_index': { '#value#': 2.45, 'datatype': 'float',
'is_multiple': False}
'is_multiple': None},
'#mytags': {'#value#':['t1','t2','t3'], 'datatype':'text',
'is_multiple': '|', 'name': u'My Tags'}
}
mi.set_all_user_metadata(um)
raw = metadata_to_opf(mi)

View file

@ -230,7 +230,7 @@ def _add_from_device_adder(self, paths=[], names=[], infos=[],
self._files_added(paths, names, infos, on_card=on_card)
# 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
# correct value.
# correct value. Note that set_books_in_library might sync_booklists
self.gui.set_books_in_library(booklists=[model.db], reset=True)
model.reset()

View file

@ -745,6 +745,7 @@ def metadata_downloaded(self, job):
if job.failed:
self.device_job_exception(job)
return
# set_books_in_library might schedule a sync_booklists job
self.set_books_in_library(job.result, reset=True)
mainlist, cardalist, cardblist = job.result
self.memory_view.set_database(mainlist)
@ -789,11 +790,12 @@ def books_deleted(self, job):
self.device_manager.remove_books_from_metadata(paths,
self.booklists())
model.paths_deleted(paths)
self.upload_booklists()
# Force recomputation the library's ondevice info. We need to call
# set_books_in_library even though books were not added because
# the deleted book might have been an exact match.
self.set_books_in_library(self.booklists(), reset=True)
# the deleted book might have been an exact match. Upload the booklists
# if set_books_in_library did not.
if not self.set_books_in_library(self.booklists(), reset=True):
self.upload_booklists()
self.book_on_device(None, None, reset=True)
# We need to reset the ondevice flags in the library. Use a big hammer,
# so we don't need to worry about whether some succeeded or not.
@ -1280,8 +1282,6 @@ def books_uploaded(self, job):
self.device_manager.add_books_to_metadata(job.result,
metadata, self.booklists())
self.upload_booklists()
books_to_be_deleted = []
if memory and memory[1]:
books_to_be_deleted = memory[1]
@ -1291,12 +1291,15 @@ def books_uploaded(self, job):
# book already there with a different book. This happens frequently in
# news. When this happens, the book match indication will be wrong
# because the UUID changed. Force both the device and the library view
# to refresh the flags.
self.set_books_in_library(self.booklists(), reset=True)
# to refresh the flags. Set_books_in_library could upload the booklists.
# If it does not, then do it here.
if not self.set_books_in_library(self.booklists(), reset=True):
self.upload_booklists()
self.book_on_device(None, reset=True)
self.refresh_ondevice_info(device_connected = True)
view = self.card_a_view if on_card == 'carda' else self.card_b_view if on_card == 'cardb' else self.memory_view
view = self.card_a_view if on_card == 'carda' else \
self.card_b_view if on_card == 'cardb' else self.memory_view
view.model().resort(reset=False)
view.model().research()
for f in files:
@ -1371,7 +1374,7 @@ def clean_string(x):
try:
db = self.library_view.model().db
except:
return
return False
# Build a cache (map) of the library, so the search isn't On**2
self.db_book_title_cache = {}
self.db_book_uuid_cache = {}
@ -1466,11 +1469,13 @@ def clean_string(x):
# Set author_sort if it isn't already
asort = getattr(book, 'author_sort', None)
if not asort and book.authors:
book.author_sort = self.library_view.model().db.author_sort_from_authors(book.authors)
book.author_sort = self.library_view.model().db.\
author_sort_from_authors(book.authors)
if update_metadata:
if self.device_manager.is_device_connected:
self.device_manager.sync_booklists(
Dispatcher(self.metadata_synced), booklists)
return update_metadata
# }}}

View file

@ -8,8 +8,10 @@
from PyQt4.Qt import QWidget, pyqtSignal
from calibre.gui2 import error_dialog
from calibre.gui2.preferences.save_template_ui import Ui_Form
from calibre.library.save_to_disk import FORMAT_ARG_DESCS
from calibre.library.save_to_disk import FORMAT_ARG_DESCS, preprocess_template,\
safe_format
class SaveTemplate(QWidget, Ui_Form):
@ -24,8 +26,11 @@ def initialize(self, name, default, help):
variables = sorted(FORMAT_ARG_DESCS.keys())
rows = []
for var in variables:
rows.append(u'<tr><td>%s</td><td>%s</td></tr>'%
rows.append(u'<tr><td>%s</td><td>&nbsp;</td><td>%s</td></tr>'%
(var, FORMAT_ARG_DESCS[var]))
rows.append(u'<tr><td>%s&nbsp;</td><td>&nbsp;</td><td>%s</td></tr>'%(
_('Any custom field'),
_('The lookup name of any custom field. These names begin with "#")')))
table = u'<table>%s</table>'%(u'\n'.join(rows))
self.template_variables.setText(table)
@ -39,21 +44,21 @@ def changed(self, *args):
self.changed_signal.emit()
def validate(self):
# TODO: NEWMETA: I haven't figured out how to get the custom columns
# into here, so for the moment make all templates valid.
'''
Do a syntax check on the format string. Doing a semantic check
(verifying that the fields exist) is not useful in the presence of
custom fields, because they may or may not exist.
'''
tmpl = preprocess_template(self.opt_template.text())
fa = {}
try:
safe_format(tmpl, fa)
except Exception, err:
error_dialog(self, _('Invalid template'),
'<p>'+_('The template %s is invalid:')%tmpl + \
'<br>'+str(err), show=True)
return False
return True
# tmpl = preprocess_template(self.opt_template.text())
# fa = {}
# for x in FORMAT_ARG_DESCS.keys():
# fa[x]='random long string'
# try:
# tmpl.format(**fa)
# except Exception, err:
# error_dialog(self, _('Invalid template'),
# '<p>'+_('The template %s is invalid:')%tmpl + \
# '<br>'+str(err), show=True)
# return False
# return True
def set_value(self, val):
self.opt_template.set_value(val)

View file

@ -22,6 +22,9 @@ def genesis(self, gui):
r = self.register
for x in ('send_timefmt',):
r(x, self.proxy)
choices = [(_('Manual management'), 'manual'),
(_('Only on send'), 'on_send'),
(_('Automatic management'), 'on_connect')]

View file

@ -80,7 +80,20 @@
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Format &amp;dates as:</string>
</property>
<property name="buddy">
<cstring>opt_send_timefmt</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="opt_send_timefmt"/>
</item>
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="label_43">
<property name="text">
<string>Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences-&gt;Advanced-&gt;Plugins</string>
@ -90,7 +103,7 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<item row="4" column="0" colspan="3">
<widget class="SaveTemplate" name="send_template" native="true"/>
</item>
</layout>

View file

@ -84,6 +84,9 @@ def config(defaults=None):
x('timefmt', default='%b, %Y',
help=_('The format in which to display dates. %d - day, %b - month, '
'%Y - year. Default is: %b, %Y'))
x('send_timefmt', default='%b, %Y',
help=_('The format in which to display dates. %d - day, %b - month, '
'%Y - year. Default is: %b, %Y'))
x('to_lowercase', default=False,
help=_('Convert paths to lowercase.'))
x('replace_whitespace', default=False,