mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Fix various bugs in column coloring. Fix the NOT template function. Migrate is_multiple to a dict
This commit is contained in:
commit
06de23b162
@ -204,7 +204,8 @@ class CollectionsBookList(BookList):
|
||||
elif fm['datatype'] == 'text' and fm['is_multiple']:
|
||||
val = orig_val
|
||||
elif fm['datatype'] == 'composite' and fm['is_multiple']:
|
||||
val = [v.strip() for v in val.split(fm['is_multiple'])]
|
||||
val = [v.strip() for v in
|
||||
val.split(fm['is_multiple']['ui_to_list'])]
|
||||
else:
|
||||
val = [val]
|
||||
|
||||
|
@ -621,10 +621,7 @@ class Metadata(object):
|
||||
orig_res = res
|
||||
datatype = cmeta['datatype']
|
||||
if datatype == 'text' and cmeta['is_multiple']:
|
||||
if cmeta['display'].get('is_names', False):
|
||||
res = u' & '.join(res)
|
||||
else:
|
||||
res = u', '.join(sorted(res, key=sort_key))
|
||||
res = cmeta['is_multiple']['list_to_ui'].join(res)
|
||||
elif datatype == 'series' and series_with_index:
|
||||
if self.get_extra(key) is not None:
|
||||
res = res + \
|
||||
@ -668,7 +665,7 @@ class Metadata(object):
|
||||
elif datatype == 'text' and fmeta['is_multiple']:
|
||||
if isinstance(res, dict):
|
||||
res = [k + ':' + v for k,v in res.items()]
|
||||
res = u', '.join(sorted(res, key=sort_key))
|
||||
res = fmeta['is_multiple']['list_to_ui'].join(sorted(res, key=sort_key))
|
||||
elif datatype == 'series' and series_with_index:
|
||||
res = res + ' [%s]'%self.format_series_index()
|
||||
elif datatype == 'datetime':
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
lxml based OPF parser.
|
||||
'''
|
||||
|
||||
import re, sys, unittest, functools, os, uuid, glob, cStringIO, json
|
||||
import re, sys, unittest, functools, os, uuid, glob, cStringIO, json, copy
|
||||
from urllib import unquote
|
||||
from urlparse import urlparse
|
||||
|
||||
@ -457,6 +457,14 @@ def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8))
|
||||
|
||||
for name, fm in all_user_metadata.items():
|
||||
try:
|
||||
if fm.get('is_multiple'):
|
||||
# migrate is_multiple back to a character
|
||||
fm = copy.copy(fm)
|
||||
dt = fm.get('datatype', None)
|
||||
if dt == 'composite':
|
||||
fm['is_multiple'] = ','
|
||||
else:
|
||||
fm['is_multiple'] = '|'
|
||||
fm = object_to_unicode(fm)
|
||||
fm = json.dumps(fm, default=to_json, ensure_ascii=False)
|
||||
except:
|
||||
@ -585,6 +593,17 @@ class OPF(object): # {{{
|
||||
fm = elem.get('content')
|
||||
try:
|
||||
fm = json.loads(fm, object_hook=from_json)
|
||||
im = fm.get('is_multiple', None)
|
||||
if im and not isinstance(im, dict):
|
||||
# Must migrate the is_multiple from char to dict
|
||||
dt = fm.get('datatype', None)
|
||||
if dt == 'composite':
|
||||
im = {'cache_to_list': ',', 'ui_to_list': ',', 'list_to_ui': ', '}
|
||||
elif fm.get('display', {}).get('is_names', False):
|
||||
im = {'cache_to_list': '|', 'ui_to_list': '&', 'list_to_ui': ', '}
|
||||
else:
|
||||
im = {'cache_to_list': '|', 'ui_to_list': ',', 'list_to_ui': ', '}
|
||||
fm['is_multiple'] = im
|
||||
temp.set_user_metadata(name, fm)
|
||||
except:
|
||||
prints('Failed to read user metadata:', name)
|
||||
|
@ -226,16 +226,14 @@ class Comments(Base):
|
||||
class Text(Base):
|
||||
|
||||
def setup_ui(self, parent):
|
||||
if self.col_metadata['display'].get('is_names', False):
|
||||
self.sep = u' & '
|
||||
else:
|
||||
self.sep = u', '
|
||||
self.sep = self.col_metadata['multiple_seps']
|
||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||
values.sort(key=sort_key)
|
||||
|
||||
if self.col_metadata['is_multiple']:
|
||||
w = MultiCompleteLineEdit(parent)
|
||||
w.set_separator(self.sep.strip())
|
||||
if self.sep == u' & ':
|
||||
w.set_separator(self.sep['ui_to_list'])
|
||||
if self.sep['ui_to_list'] == '&':
|
||||
w.set_space_before_sep(True)
|
||||
w.set_add_separator(tweaks['authors_completer_append_separator'])
|
||||
w.update_items_cache(values)
|
||||
@ -269,12 +267,12 @@ class Text(Base):
|
||||
if self.col_metadata['is_multiple']:
|
||||
if not val:
|
||||
val = []
|
||||
self.widgets[1].setText(self.sep.join(val))
|
||||
self.widgets[1].setText(self.sep['list_to_ui'].join(val))
|
||||
|
||||
def getter(self):
|
||||
if self.col_metadata['is_multiple']:
|
||||
val = unicode(self.widgets[1].text()).strip()
|
||||
ans = [x.strip() for x in val.split(self.sep.strip()) if x.strip()]
|
||||
ans = [x.strip() for x in val.split(self.sep['ui_to_list']) if x.strip()]
|
||||
if not ans:
|
||||
ans = None
|
||||
return ans
|
||||
@ -899,9 +897,10 @@ class BulkText(BulkBase):
|
||||
if not self.a_c_checkbox.isChecked():
|
||||
return
|
||||
if self.col_metadata['is_multiple']:
|
||||
ism = self.col_metadata['multiple_seps']
|
||||
if self.col_metadata['display'].get('is_names', False):
|
||||
val = self.gui_val
|
||||
add = [v.strip() for v in val.split('&') if v.strip()]
|
||||
add = [v.strip() for v in val.split(ism['ui_to_list']) if v.strip()]
|
||||
self.db.set_custom_bulk(book_ids, add, num=self.col_id)
|
||||
else:
|
||||
remove_all, adding, rtext = self.gui_val
|
||||
@ -911,10 +910,10 @@ class BulkText(BulkBase):
|
||||
else:
|
||||
txt = rtext
|
||||
if txt:
|
||||
remove = set([v.strip() for v in txt.split(',')])
|
||||
remove = set([v.strip() for v in txt.split(ism['ui_to_list'])])
|
||||
txt = adding
|
||||
if txt:
|
||||
add = set([v.strip() for v in txt.split(',')])
|
||||
add = set([v.strip() for v in txt.split(ism['ui_to_list'])])
|
||||
else:
|
||||
add = set()
|
||||
self.db.set_custom_bulk_multiple(book_ids, add=add,
|
||||
|
@ -520,7 +520,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
||||
elif not fm['is_multiple']:
|
||||
val = [val]
|
||||
elif fm['datatype'] == 'composite':
|
||||
val = [v.strip() for v in val.split(fm['is_multiple'])]
|
||||
val = [v.strip() for v in val.split(fm['is_multiple']['ui_to_list'])]
|
||||
elif field == 'authors':
|
||||
val = [v.replace('|', ',') for v in val]
|
||||
else:
|
||||
@ -655,19 +655,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
||||
|
||||
if self.destination_field_fm['is_multiple']:
|
||||
if self.comma_separated.isChecked():
|
||||
if dest == 'authors' or \
|
||||
(self.destination_field_fm['is_custom'] and
|
||||
self.destination_field_fm['datatype'] == 'text' and
|
||||
self.destination_field_fm['display'].get('is_names', False)):
|
||||
splitter = ' & '
|
||||
else:
|
||||
splitter = ','
|
||||
|
||||
splitter = self.destination_field_fm['is_multiple']['ui_to_list']
|
||||
res = []
|
||||
for v in val:
|
||||
for x in v.split(splitter):
|
||||
if x.strip():
|
||||
res.append(x.strip())
|
||||
res.extend([x.strip() for x in v.split(splitter) if x.strip()])
|
||||
val = res
|
||||
else:
|
||||
val = [v.replace(',', '') for v in val]
|
||||
|
@ -254,6 +254,15 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.textbox_changed()
|
||||
self.rule = (None, '')
|
||||
|
||||
tt = _('Template language tutorial')
|
||||
self.template_tutorial.setText(
|
||||
'<a href="http://manual.calibre-ebook.com/template_lang.html">'
|
||||
'%s</a>'%tt)
|
||||
tt = _('Template function reference')
|
||||
self.template_func_reference.setText(
|
||||
'<a href="http://manual.calibre-ebook.com/template_ref.html">'
|
||||
'%s</a>'%tt)
|
||||
|
||||
def textbox_changed(self):
|
||||
cur_text = unicode(self.textbox.toPlainText())
|
||||
if self.last_text != cur_text:
|
||||
@ -299,4 +308,4 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
return
|
||||
|
||||
self.rule = (unicode(self.colored_field.currentText()), txt)
|
||||
QDialog.accept(self)
|
||||
QDialog.accept(self)
|
||||
|
@ -125,6 +125,20 @@
|
||||
<item row="9" column="1">
|
||||
<widget class="QPlainTextEdit" name="source_code"/>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QLabel" name="template_tutorial">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QLabel" name="template_func_reference">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -608,10 +608,11 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
|
||||
def text_type(r, mult=None, idx=-1):
|
||||
text = self.db.data[r][idx]
|
||||
if text and mult is not None:
|
||||
if mult:
|
||||
return QVariant(u' & '.join(text.split('|')))
|
||||
return QVariant(u', '.join(sorted(text.split('|'),key=sort_key)))
|
||||
if text and mult:
|
||||
jv = mult['list_to_ui']
|
||||
sv = mult['cache_to_list']
|
||||
return QVariant(jv.join(
|
||||
sorted([t.strip() for t in text.split(sv)], key=sort_key)))
|
||||
return QVariant(text)
|
||||
|
||||
def decorated_text_type(r, idx=-1):
|
||||
@ -665,8 +666,6 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
datatype = self.custom_columns[col]['datatype']
|
||||
if datatype in ('text', 'comments', 'composite', 'enumeration'):
|
||||
mult=self.custom_columns[col]['is_multiple']
|
||||
if mult is not None:
|
||||
mult = self.custom_columns[col]['display'].get('is_names', False)
|
||||
self.dc[col] = functools.partial(text_type, idx=idx, mult=mult)
|
||||
if datatype in ['text', 'composite', 'enumeration'] and not mult:
|
||||
if self.custom_columns[col]['display'].get('use_decorations', False):
|
||||
@ -722,9 +721,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
if id_ in self.color_cache:
|
||||
if key in self.color_cache[id_]:
|
||||
return self.color_cache[id_][key]
|
||||
if mi is None:
|
||||
mi = self.db.get_metadata(id_, index_is_id=True)
|
||||
try:
|
||||
if mi is None:
|
||||
mi = self.db.get_metadata(id_, index_is_id=True)
|
||||
color = composite_formatter.safe_format(fmt, mi, '', mi)
|
||||
if color in self.colors:
|
||||
color = QColor(color)
|
||||
|
@ -192,6 +192,8 @@ class ConditionEditor(QWidget): # {{{
|
||||
action = self.current_action
|
||||
if not action:
|
||||
return
|
||||
m = self.fm[col]
|
||||
dt = m['datatype']
|
||||
tt = ''
|
||||
if col == 'identifiers':
|
||||
tt = _('Enter either an identifier type or an '
|
||||
@ -209,7 +211,7 @@ class ConditionEditor(QWidget): # {{{
|
||||
tt = _('Enter a regular expression')
|
||||
elif m.get('is_multiple', False):
|
||||
tt += '\n' + _('You can match multiple values by separating'
|
||||
' them with %s')%m['is_multiple']
|
||||
' them with %s')%m['is_multiple']['ui_to_list']
|
||||
self.value_box.setToolTip(tt)
|
||||
if action in ('is set', 'is not set', 'is true', 'is false',
|
||||
'is undefined'):
|
||||
|
@ -13,6 +13,9 @@ from calibre.gui2 import error_dialog
|
||||
|
||||
class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
|
||||
# Note: in this class, we are treating is_multiple as the boolean that
|
||||
# custom_columns expects to find in its structure. It does not use the dict
|
||||
|
||||
column_types = {
|
||||
0:{'datatype':'text',
|
||||
'text':_('Text, column shown in the tag browser'),
|
||||
|
@ -509,7 +509,8 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
valq_mkind, valq = self._matchkind(query)
|
||||
|
||||
loc = self.field_metadata[location]['rec_index']
|
||||
split_char = self.field_metadata[location]['is_multiple']
|
||||
split_char = self.field_metadata[location]['is_multiple'].get(
|
||||
'cache_to_list', ',')
|
||||
for id_ in candidates:
|
||||
item = self._data[id_]
|
||||
if item is None:
|
||||
@ -665,7 +666,8 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
if fm['is_multiple'] and \
|
||||
len(query) > 1 and query.startswith('#') and \
|
||||
query[1:1] in '=<>!':
|
||||
vf = lambda item, loc=fm['rec_index'], ms=fm['is_multiple']:\
|
||||
vf = lambda item, loc=fm['rec_index'], \
|
||||
ms=fm['is_multiple']['cache_to_list']:\
|
||||
len(item[loc].split(ms)) if item[loc] is not None else 0
|
||||
return self.get_numeric_matches(location, query[1:],
|
||||
candidates, val_func=vf)
|
||||
@ -703,7 +705,8 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
['composite', 'text', 'comments', 'series', 'enumeration']:
|
||||
exclude_fields.append(db_col[x])
|
||||
col_datatype[db_col[x]] = self.field_metadata[x]['datatype']
|
||||
is_multiple_cols[db_col[x]] = self.field_metadata[x]['is_multiple']
|
||||
is_multiple_cols[db_col[x]] = \
|
||||
self.field_metadata[x]['is_multiple'].get('cache_to_list', None)
|
||||
|
||||
try:
|
||||
rating_query = int(query) * 2
|
||||
@ -1045,13 +1048,14 @@ class SortKeyGenerator(object):
|
||||
|
||||
elif dt in ('text', 'comments', 'composite', 'enumeration'):
|
||||
if val:
|
||||
sep = fm['is_multiple']
|
||||
if sep:
|
||||
if fm['display'].get('is_names', False):
|
||||
val = sep.join(
|
||||
[author_to_author_sort(v) for v in val.split(sep)])
|
||||
if fm['is_multiple']:
|
||||
jv = fm['is_multiple']['list_to_ui']
|
||||
sv = fm['is_multiple']['cache_to_list']
|
||||
if '&' in jv:
|
||||
val = jv.join(
|
||||
[author_to_author_sort(v) for v in val.split(sv)])
|
||||
else:
|
||||
val = sep.join(sorted(val.split(sep),
|
||||
val = jv.join(sorted(val.split(sv),
|
||||
key=self.string_sort_key))
|
||||
val = self.string_sort_key(val)
|
||||
|
||||
|
@ -79,16 +79,19 @@ class Rule(object): # {{{
|
||||
if dt == 'bool':
|
||||
return self.bool_condition(col, action, val)
|
||||
|
||||
if dt in ('int', 'float', 'rating'):
|
||||
if dt in ('int', 'float'):
|
||||
return self.number_condition(col, action, val)
|
||||
|
||||
if dt == 'rating':
|
||||
return self.rating_condition(col, action, val)
|
||||
|
||||
if dt == 'datetime':
|
||||
return self.date_condition(col, action, val)
|
||||
|
||||
if dt in ('comments', 'series', 'text', 'enumeration', 'composite'):
|
||||
ism = m.get('is_multiple', False)
|
||||
if ism:
|
||||
return self.multiple_condition(col, action, val, ',' if ism == '|' else ism)
|
||||
return self.multiple_condition(col, action, val, ism['ui_to_list'])
|
||||
return self.text_condition(col, action, val)
|
||||
|
||||
def identifiers_condition(self, col, action, val):
|
||||
@ -114,9 +117,16 @@ class Rule(object): # {{{
|
||||
'lt': ('1', '', ''),
|
||||
'gt': ('', '', '1')
|
||||
}[action]
|
||||
lt, eq, gt = '', '1', ''
|
||||
return "cmp(raw_field('%s'), %s, '%s', '%s', '%s')" % (col, val, lt, eq, gt)
|
||||
|
||||
def rating_condition(self, col, action, val):
|
||||
lt, eq, gt = {
|
||||
'eq': ('', '1', ''),
|
||||
'lt': ('1', '', ''),
|
||||
'gt': ('', '', '1')
|
||||
}[action]
|
||||
return "cmp(field('%s'), %s, '%s', '%s', '%s')" % (col, val, lt, eq, gt)
|
||||
|
||||
def date_condition(self, col, action, val):
|
||||
lt, eq, gt = {
|
||||
'eq': ('', '1', ''),
|
||||
|
@ -78,6 +78,18 @@ class CustomColumns(object):
|
||||
}
|
||||
if data['display'] is None:
|
||||
data['display'] = {}
|
||||
# set up the is_multiple separator dict
|
||||
if data['is_multiple']:
|
||||
if data['display'].get('is_names', False):
|
||||
seps = {'cache_to_list': '|', 'ui_to_list': '&', 'list_to_ui': ' & '}
|
||||
elif data['datatype'] == 'composite':
|
||||
seps = {'cache_to_list': ',', 'ui_to_list': ',', 'list_to_ui': ', '}
|
||||
else:
|
||||
seps = {'cache_to_list': '|', 'ui_to_list': ',', 'list_to_ui': ', '}
|
||||
else:
|
||||
seps = {}
|
||||
data['multiple_seps'] = seps
|
||||
|
||||
table, lt = self.custom_table_names(data['num'])
|
||||
if table not in custom_tables or (data['normalized'] and lt not in
|
||||
custom_tables):
|
||||
@ -119,7 +131,7 @@ class CustomColumns(object):
|
||||
if x is None:
|
||||
return []
|
||||
if isinstance(x, (str, unicode, bytes)):
|
||||
x = x.split('&' if d['display'].get('is_names', False) else',')
|
||||
x = x.split(d['multiple_seps']['ui_to_list'])
|
||||
x = [y.strip() for y in x if y.strip()]
|
||||
x = [y.decode(preferred_encoding, 'replace') if not isinstance(y,
|
||||
unicode) else y for y in x]
|
||||
@ -181,10 +193,7 @@ class CustomColumns(object):
|
||||
is_category = True
|
||||
else:
|
||||
is_category = False
|
||||
if v['is_multiple']:
|
||||
is_m = ',' if v['datatype'] == 'composite' else '|'
|
||||
else:
|
||||
is_m = None
|
||||
is_m = v['multiple_seps']
|
||||
tn = 'custom_column_{0}'.format(v['num'])
|
||||
self.field_metadata.add_custom_field(label=v['label'],
|
||||
table=tn, column='value', datatype=v['datatype'],
|
||||
@ -200,7 +209,7 @@ class CustomColumns(object):
|
||||
row = self.data._data[idx] if index_is_id else self.data[idx]
|
||||
ans = row[self.FIELD_MAP[data['num']]]
|
||||
if data['is_multiple'] and data['datatype'] == 'text':
|
||||
ans = ans.split('|') if ans else []
|
||||
ans = ans.split(data['multiple_seps']['cache_to_list']) if ans else []
|
||||
if data['display'].get('sort_alpha', False):
|
||||
ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower()))
|
||||
return ans
|
||||
@ -571,9 +580,17 @@ class CustomColumns(object):
|
||||
if data['normalized']:
|
||||
query = '%s.value'
|
||||
if data['is_multiple']:
|
||||
query = 'group_concat(%s.value, "|")'
|
||||
if not display.get('sort_alpha', False):
|
||||
query = 'sort_concat(link.id, %s.value)'
|
||||
# query = 'group_concat(%s.value, "{0}")'.format(
|
||||
# data['multiple_seps']['cache_to_list'])
|
||||
# if not display.get('sort_alpha', False):
|
||||
if data['multiple_seps']['cache_to_list'] == '|':
|
||||
query = 'sortconcat_bar(link.id, %s.value)'
|
||||
elif data['multiple_seps']['cache_to_list'] == '&':
|
||||
query = 'sortconcat_amper(link.id, %s.value)'
|
||||
else:
|
||||
prints('WARNING: unknown value in multiple_seps',
|
||||
data['multiple_seps']['cache_to_list'])
|
||||
query = 'sortconcat_bar(link.id, %s.value)'
|
||||
line = '''(SELECT {query} FROM {lt} AS link INNER JOIN
|
||||
{table} ON(link.value={table}.id) WHERE link.book=books.id)
|
||||
custom_{num}
|
||||
|
@ -1250,7 +1250,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
dex = field['rec_index']
|
||||
for book in self.data.iterall():
|
||||
if field['is_multiple']:
|
||||
vals = [v.strip() for v in book[dex].split(field['is_multiple'])
|
||||
vals = [v.strip() for v in
|
||||
book[dex].split(field['is_multiple']['cache_to_list'])
|
||||
if v.strip()]
|
||||
if id_ in vals:
|
||||
ans.add(book[0])
|
||||
@ -1378,7 +1379,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
tcategories[category] = {}
|
||||
# create a list of category/field_index for the books scan to use.
|
||||
# This saves iterating through field_metadata for each book
|
||||
md.append((category, cat['rec_index'], cat['is_multiple'], False))
|
||||
md.append((category, cat['rec_index'],
|
||||
cat['is_multiple'].get('cache_to_list', None), False))
|
||||
|
||||
for category in tb_cats.iterkeys():
|
||||
cat = tb_cats[category]
|
||||
@ -1386,7 +1388,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
cat['display'].get('make_category', False):
|
||||
tids[category] = {}
|
||||
tcategories[category] = {}
|
||||
md.append((category, cat['rec_index'], cat['is_multiple'],
|
||||
md.append((category, cat['rec_index'],
|
||||
cat['is_multiple'].get('cache_to_list', None),
|
||||
cat['datatype'] == 'composite'))
|
||||
#print 'end phase "collection":', time.clock() - last, 'seconds'
|
||||
#last = time.clock()
|
||||
|
@ -50,9 +50,16 @@ class FieldMetadata(dict):
|
||||
|
||||
datatype: the type of information in the field. Valid values are listed in
|
||||
VALID_DATA_TYPES below.
|
||||
is_multiple: valid for the text datatype. If None, the field is to be
|
||||
treated as a single term. If not None, it contains a string, and the field
|
||||
is assumed to contain a list of terms separated by that string
|
||||
is_multiple: valid for the text datatype. If {}, the field is to be
|
||||
treated as a single term. If not None, it contains a dict of the form
|
||||
{'cache_to_list': ',',
|
||||
'ui_to_list': ',',
|
||||
'list_to_ui': ', '}
|
||||
where the cache_to_list contains the character used to split the value in
|
||||
the meta2 table, ui_to_list contains the character used to create a list
|
||||
from a value shown in the ui (each resulting value must be strip()ed and
|
||||
empty values removed), and list_to_ui contains the string used in join()
|
||||
to create a displayable string from the list.
|
||||
|
||||
kind == field: is a db field.
|
||||
kind == category: standard tag category that isn't a field. see news.
|
||||
@ -97,7 +104,9 @@ class FieldMetadata(dict):
|
||||
'link_column':'author',
|
||||
'category_sort':'sort',
|
||||
'datatype':'text',
|
||||
'is_multiple':',',
|
||||
'is_multiple':{'cache_to_list': ',',
|
||||
'ui_to_list': '&',
|
||||
'list_to_ui': ' & '},
|
||||
'kind':'field',
|
||||
'name':_('Authors'),
|
||||
'search_terms':['authors', 'author'],
|
||||
@ -109,7 +118,7 @@ class FieldMetadata(dict):
|
||||
'link_column':'series',
|
||||
'category_sort':'(title_sort(name))',
|
||||
'datatype':'series',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Series'),
|
||||
'search_terms':['series'],
|
||||
@ -119,7 +128,9 @@ class FieldMetadata(dict):
|
||||
('formats', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':',',
|
||||
'is_multiple':{'cache_to_list': ',',
|
||||
'ui_to_list': ',',
|
||||
'list_to_ui': ', '},
|
||||
'kind':'field',
|
||||
'name':_('Formats'),
|
||||
'search_terms':['formats', 'format'],
|
||||
@ -131,7 +142,7 @@ class FieldMetadata(dict):
|
||||
'link_column':'publisher',
|
||||
'category_sort':'name',
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Publishers'),
|
||||
'search_terms':['publisher'],
|
||||
@ -143,7 +154,7 @@ class FieldMetadata(dict):
|
||||
'link_column':'rating',
|
||||
'category_sort':'rating',
|
||||
'datatype':'rating',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Ratings'),
|
||||
'search_terms':['rating'],
|
||||
@ -154,7 +165,7 @@ class FieldMetadata(dict):
|
||||
'column':'name',
|
||||
'category_sort':'name',
|
||||
'datatype':None,
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'category',
|
||||
'name':_('News'),
|
||||
'search_terms':[],
|
||||
@ -166,7 +177,9 @@ class FieldMetadata(dict):
|
||||
'link_column': 'tag',
|
||||
'category_sort':'name',
|
||||
'datatype':'text',
|
||||
'is_multiple':',',
|
||||
'is_multiple':{'cache_to_list': ',',
|
||||
'ui_to_list': ',',
|
||||
'list_to_ui': ', '},
|
||||
'kind':'field',
|
||||
'name':_('Tags'),
|
||||
'search_terms':['tags', 'tag'],
|
||||
@ -176,7 +189,9 @@ class FieldMetadata(dict):
|
||||
('identifiers', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':',',
|
||||
'is_multiple':{'cache_to_list': ',',
|
||||
'ui_to_list': ',',
|
||||
'list_to_ui': ', '},
|
||||
'kind':'field',
|
||||
'name':_('Identifiers'),
|
||||
'search_terms':['identifiers', 'identifier', 'isbn'],
|
||||
@ -186,7 +201,7 @@ class FieldMetadata(dict):
|
||||
('author_sort',{'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Author Sort'),
|
||||
'search_terms':['author_sort'],
|
||||
@ -196,7 +211,9 @@ class FieldMetadata(dict):
|
||||
('au_map', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':',',
|
||||
'is_multiple':{'cache_to_list': ',',
|
||||
'ui_to_list': None,
|
||||
'list_to_ui': None},
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'search_terms':[],
|
||||
@ -206,7 +223,7 @@ class FieldMetadata(dict):
|
||||
('comments', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Comments'),
|
||||
'search_terms':['comments', 'comment'],
|
||||
@ -216,7 +233,7 @@ class FieldMetadata(dict):
|
||||
('cover', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'int',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'search_terms':['cover'],
|
||||
@ -226,7 +243,7 @@ class FieldMetadata(dict):
|
||||
('id', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'int',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'search_terms':[],
|
||||
@ -236,7 +253,7 @@ class FieldMetadata(dict):
|
||||
('last_modified', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'datetime',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Modified'),
|
||||
'search_terms':['last_modified'],
|
||||
@ -246,7 +263,7 @@ class FieldMetadata(dict):
|
||||
('ondevice', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('On Device'),
|
||||
'search_terms':['ondevice'],
|
||||
@ -256,7 +273,7 @@ class FieldMetadata(dict):
|
||||
('path', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Path'),
|
||||
'search_terms':[],
|
||||
@ -266,7 +283,7 @@ class FieldMetadata(dict):
|
||||
('pubdate', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'datetime',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Published'),
|
||||
'search_terms':['pubdate'],
|
||||
@ -276,7 +293,7 @@ class FieldMetadata(dict):
|
||||
('marked', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name': None,
|
||||
'search_terms':['marked'],
|
||||
@ -286,7 +303,7 @@ class FieldMetadata(dict):
|
||||
('series_index',{'table':None,
|
||||
'column':None,
|
||||
'datatype':'float',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'search_terms':['series_index'],
|
||||
@ -296,7 +313,7 @@ class FieldMetadata(dict):
|
||||
('sort', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Title Sort'),
|
||||
'search_terms':['title_sort'],
|
||||
@ -306,7 +323,7 @@ class FieldMetadata(dict):
|
||||
('size', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'float',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Size'),
|
||||
'search_terms':['size'],
|
||||
@ -316,7 +333,7 @@ class FieldMetadata(dict):
|
||||
('timestamp', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'datetime',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Date'),
|
||||
'search_terms':['date'],
|
||||
@ -326,7 +343,7 @@ class FieldMetadata(dict):
|
||||
('title', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':_('Title'),
|
||||
'search_terms':['title'],
|
||||
@ -336,7 +353,7 @@ class FieldMetadata(dict):
|
||||
('uuid', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
'is_multiple':None,
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'search_terms':[],
|
||||
@ -508,7 +525,7 @@ class FieldMetadata(dict):
|
||||
if datatype == 'series':
|
||||
key += '_index'
|
||||
self._tb_cats[key] = {'table':None, 'column':None,
|
||||
'datatype':'float', 'is_multiple':None,
|
||||
'datatype':'float', 'is_multiple':{},
|
||||
'kind':'field', 'name':'',
|
||||
'search_terms':[key], 'label':label+'_index',
|
||||
'colnum':None, 'display':{},
|
||||
@ -560,7 +577,7 @@ class FieldMetadata(dict):
|
||||
if icu_lower(label) != label:
|
||||
st.append(icu_lower(label))
|
||||
self._tb_cats[label] = {'table':None, 'column':None,
|
||||
'datatype':None, 'is_multiple':None,
|
||||
'datatype':None, 'is_multiple':{},
|
||||
'kind':'user', 'name':name,
|
||||
'search_terms':st, 'is_custom':False,
|
||||
'is_category':True, 'is_csp': False}
|
||||
@ -570,7 +587,7 @@ class FieldMetadata(dict):
|
||||
if label in self._tb_cats:
|
||||
raise ValueError('Duplicate user field [%s]'%(label))
|
||||
self._tb_cats[label] = {'table':None, 'column':None,
|
||||
'datatype':None, 'is_multiple':None,
|
||||
'datatype':None, 'is_multiple':{},
|
||||
'kind':'search', 'name':name,
|
||||
'search_terms':[], 'is_custom':False,
|
||||
'is_category':True, 'is_csp': False}
|
||||
|
@ -171,7 +171,7 @@ class Restore(Thread):
|
||||
for x in fields:
|
||||
if x in cfm:
|
||||
if x == 'is_multiple':
|
||||
args.append(cfm[x] is not None)
|
||||
args.append(bool(cfm[x]))
|
||||
else:
|
||||
args.append(cfm[x])
|
||||
if len(args) == len(fields):
|
||||
|
@ -231,7 +231,8 @@ class MobileServer(object):
|
||||
book['size'] = human_readable(book['size'])
|
||||
|
||||
aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown')
|
||||
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
|
||||
aut_is = CFM['authors']['is_multiple']
|
||||
authors = aut_is['list_to_ui'].join([i.replace('|', ',') for i in aus.split(',')])
|
||||
book['authors'] = authors
|
||||
book['series_index'] = fmt_sidx(float(record[FM['series_index']]))
|
||||
book['series'] = record[FM['series']]
|
||||
@ -254,8 +255,10 @@ class MobileServer(object):
|
||||
continue
|
||||
if datatype == 'text' and CFM[key]['is_multiple']:
|
||||
book[key] = concat(name,
|
||||
format_tag_string(val, ',',
|
||||
no_tag_count=True))
|
||||
format_tag_string(val,
|
||||
CFM[key]['is_multiple']['ui_to_list'],
|
||||
no_tag_count=True,
|
||||
joinval=CFM[key]['is_multiple']['list_to_ui']))
|
||||
else:
|
||||
book[key] = concat(name, val)
|
||||
|
||||
|
@ -180,9 +180,12 @@ def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix):
|
||||
if val:
|
||||
datatype = CFM[key]['datatype']
|
||||
if datatype == 'text' and CFM[key]['is_multiple']:
|
||||
extra.append('%s: %s<br />'%(xml(name), xml(format_tag_string(val, ',',
|
||||
ignore_max=True,
|
||||
no_tag_count=True))))
|
||||
extra.append('%s: %s<br />'%
|
||||
(xml(name),
|
||||
xml(format_tag_string(val,
|
||||
CFM[key]['is_multiple']['ui_to_list'],
|
||||
ignore_max=True, no_tag_count=True,
|
||||
joinval=CFM[key]['is_multiple']['list_to_ui']))))
|
||||
elif datatype == 'comments':
|
||||
extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val))))
|
||||
else:
|
||||
|
@ -68,7 +68,7 @@ def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
|
||||
except:
|
||||
return _strftime(fmt, nowf().timetuple())
|
||||
|
||||
def format_tag_string(tags, sep, ignore_max=False, no_tag_count=False):
|
||||
def format_tag_string(tags, sep, ignore_max=False, no_tag_count=False, joinval=', '):
|
||||
MAX = sys.maxint if ignore_max else tweaks['max_content_server_tags_shown']
|
||||
if tags:
|
||||
tlist = [t.strip() for t in tags.split(sep)]
|
||||
@ -78,10 +78,10 @@ def format_tag_string(tags, sep, ignore_max=False, no_tag_count=False):
|
||||
if len(tlist) > MAX:
|
||||
tlist = tlist[:MAX]+['...']
|
||||
if no_tag_count:
|
||||
return ', '.join(tlist) if tlist else ''
|
||||
return joinval.join(tlist) if tlist else ''
|
||||
else:
|
||||
return u'%s:&:%s'%(tweaks['max_content_server_tags_shown'],
|
||||
', '.join(tlist)) if tlist else ''
|
||||
joinval.join(tlist)) if tlist else ''
|
||||
|
||||
def quote(s):
|
||||
if isinstance(s, unicode):
|
||||
|
@ -121,8 +121,12 @@ class XMLServer(object):
|
||||
name = CFM[key]['name']
|
||||
custcols.append(k)
|
||||
if datatype == 'text' and CFM[key]['is_multiple']:
|
||||
kwargs[k] = concat('#T#'+name, format_tag_string(val,',',
|
||||
ignore_max=True))
|
||||
kwargs[k] = \
|
||||
concat('#T#'+name,
|
||||
format_tag_string(val,
|
||||
CFM[key]['is_multiple']['ui_to_list'],
|
||||
ignore_max=True,
|
||||
joinval=CFM[key]['is_multiple']['list_to_ui']))
|
||||
else:
|
||||
kwargs[k] = concat(name, val)
|
||||
kwargs['custcols'] = ','.join(custcols)
|
||||
|
@ -121,9 +121,12 @@ class SortedConcatenate(object):
|
||||
return None
|
||||
return self.sep.join(map(self.ans.get, sorted(self.ans.keys())))
|
||||
|
||||
class SafeSortedConcatenate(SortedConcatenate):
|
||||
class SortedConcatenateBar(SortedConcatenate):
|
||||
sep = '|'
|
||||
|
||||
class SortedConcatenateAmper(SortedConcatenate):
|
||||
sep = '&'
|
||||
|
||||
class IdentifiersConcat(object):
|
||||
'''String concatenation aggregator for the identifiers map'''
|
||||
def __init__(self):
|
||||
@ -220,7 +223,8 @@ class DBThread(Thread):
|
||||
self.conn.execute('pragma cache_size=5000')
|
||||
encoding = self.conn.execute('pragma encoding').fetchone()[0]
|
||||
self.conn.create_aggregate('sortconcat', 2, SortedConcatenate)
|
||||
self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate)
|
||||
self.conn.create_aggregate('sortconcat_bar', 2, SortedConcatenateBar)
|
||||
self.conn.create_aggregate('sortconcat_amper', 2, SortedConcatenateAmper)
|
||||
self.conn.create_aggregate('identifiers_concat', 2, IdentifiersConcat)
|
||||
load_c_extensions(self.conn)
|
||||
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)
|
||||
|
@ -141,6 +141,22 @@ static void sort_concat_finalize2(sqlite3_context *context) {
|
||||
|
||||
}
|
||||
|
||||
static void sort_concat_finalize3(sqlite3_context *context) {
|
||||
SortConcatList *list;
|
||||
unsigned char *ans;
|
||||
|
||||
list = (SortConcatList*) sqlite3_aggregate_context(context, sizeof(*list));
|
||||
|
||||
if (list != NULL && list->vals != NULL && list->count > 0) {
|
||||
qsort(list->vals, list->count, sizeof(list->vals[0]), sort_concat_cmp);
|
||||
ans = sort_concat_do_finalize(list, '&');
|
||||
if (ans != NULL) sqlite3_result_text(context, (char*)ans, -1, SQLITE_TRANSIENT);
|
||||
free(ans);
|
||||
sort_concat_free(list);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// identifiers_concat {{{
|
||||
@ -237,7 +253,8 @@ MYEXPORT int sqlite3_extension_init(
|
||||
sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi){
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
sqlite3_create_function(db, "sortconcat", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize);
|
||||
sqlite3_create_function(db, "sort_concat", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize2);
|
||||
sqlite3_create_function(db, "sortconcat_bar", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize2);
|
||||
sqlite3_create_function(db, "sortconcat_amper", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize3);
|
||||
sqlite3_create_function(db, "identifiers_concat", 2, SQLITE_UTF8, NULL, NULL, identifiers_concat_step, identifiers_concat_finalize);
|
||||
return 0;
|
||||
}
|
||||
|
@ -727,13 +727,8 @@ class BuiltinNot(BuiltinFormatterFunction):
|
||||
'returns the empty string. This function works well with test or '
|
||||
'first_non_empty. You can have as many values as you want.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i]:
|
||||
return '1'
|
||||
i += 1
|
||||
return ''
|
||||
def evaluate(self, formatter, kwargs, mi, locals, val):
|
||||
return '' if val else '1'
|
||||
|
||||
class BuiltinMergeLists(BuiltinFormatterFunction):
|
||||
name = 'merge_lists'
|
||||
|
Loading…
x
Reference in New Issue
Block a user