Change is_multiple to a dict containing the split/join characters to use in various situations.

This commit is contained in:
Charles Haley 2011-06-04 09:24:10 +01:00
parent 3fc513ec2a
commit 6de35843fb
14 changed files with 151 additions and 86 deletions

View File

@ -204,7 +204,8 @@ class CollectionsBookList(BookList):
elif fm['datatype'] == 'text' and fm['is_multiple']: elif fm['datatype'] == 'text' and fm['is_multiple']:
val = orig_val val = orig_val
elif fm['datatype'] == 'composite' and fm['is_multiple']: 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: else:
val = [val] val = [val]

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
lxml based OPF parser. 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 urllib import unquote
from urlparse import urlparse 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(): for name, fm in all_user_metadata.items():
try: 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 = object_to_unicode(fm)
fm = json.dumps(fm, default=to_json, ensure_ascii=False) fm = json.dumps(fm, default=to_json, ensure_ascii=False)
except: except:
@ -585,6 +593,17 @@ class OPF(object): # {{{
fm = elem.get('content') fm = elem.get('content')
try: try:
fm = json.loads(fm, object_hook=from_json) 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) temp.set_user_metadata(name, fm)
except: except:
prints('Failed to read user metadata:', name) prints('Failed to read user metadata:', name)

View File

@ -226,16 +226,14 @@ class Comments(Base):
class Text(Base): class Text(Base):
def setup_ui(self, parent): def setup_ui(self, parent):
if self.col_metadata['display'].get('is_names', False): self.sep = self.col_metadata['multiple_seps']
self.sep = u' & '
else:
self.sep = u', '
values = self.all_values = list(self.db.all_custom(num=self.col_id)) values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key) values.sort(key=sort_key)
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
w = MultiCompleteLineEdit(parent) w = MultiCompleteLineEdit(parent)
w.set_separator(self.sep.strip()) w.set_separator(self.sep['ui_to_list'])
if self.sep == u' & ': if self.sep['ui_to_list'] == '&':
w.set_space_before_sep(True) w.set_space_before_sep(True)
w.set_add_separator(tweaks['authors_completer_append_separator']) w.set_add_separator(tweaks['authors_completer_append_separator'])
w.update_items_cache(values) w.update_items_cache(values)
@ -269,12 +267,12 @@ class Text(Base):
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
if not val: if not val:
val = [] val = []
self.widgets[1].setText(self.sep.join(val)) self.widgets[1].setText(self.sep['list_to_ui'].join(val))
def getter(self): def getter(self):
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
val = unicode(self.widgets[1].text()).strip() 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: if not ans:
ans = None ans = None
return ans return ans
@ -899,9 +897,10 @@ class BulkText(BulkBase):
if not self.a_c_checkbox.isChecked(): if not self.a_c_checkbox.isChecked():
return return
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
ism = self.col_metadata['multiple_seps']
if self.col_metadata['display'].get('is_names', False): if self.col_metadata['display'].get('is_names', False):
val = self.gui_val 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) self.db.set_custom_bulk(book_ids, add, num=self.col_id)
else: else:
remove_all, adding, rtext = self.gui_val remove_all, adding, rtext = self.gui_val
@ -911,10 +910,10 @@ class BulkText(BulkBase):
else: else:
txt = rtext txt = rtext
if txt: 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 txt = adding
if txt: 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: else:
add = set() add = set()
self.db.set_custom_bulk_multiple(book_ids, add=add, self.db.set_custom_bulk_multiple(book_ids, add=add,

View File

@ -520,7 +520,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
elif not fm['is_multiple']: elif not fm['is_multiple']:
val = [val] val = [val]
elif fm['datatype'] == 'composite': 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': elif field == 'authors':
val = [v.replace('|', ',') for v in val] val = [v.replace('|', ',') for v in val]
else: else:
@ -655,14 +655,14 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if self.destination_field_fm['is_multiple']: if self.destination_field_fm['is_multiple']:
if self.comma_separated.isChecked(): if self.comma_separated.isChecked():
if dest == 'authors' or \ splitter = self.destination_field_fm['is_multiple']['ui_to_list']
(self.destination_field_fm['is_custom'] and # if dest == 'authors' or \
self.destination_field_fm['datatype'] == 'text' and # (self.destination_field_fm['is_custom'] and
self.destination_field_fm['display'].get('is_names', False)): # self.destination_field_fm['datatype'] == 'text' and
splitter = ' & ' # self.destination_field_fm['display'].get('is_names', False)):
else: # splitter = ' & '
splitter = ',' # else:
# splitter = ','
res = [] res = []
for v in val: for v in val:
for x in v.split(splitter): for x in v.split(splitter):

View File

@ -608,10 +608,11 @@ class BooksModel(QAbstractTableModel): # {{{
def text_type(r, mult=None, idx=-1): def text_type(r, mult=None, idx=-1):
text = self.db.data[r][idx] text = self.db.data[r][idx]
if text and mult is not None: if text and mult:
if mult: jv = mult['list_to_ui']
return QVariant(u' & '.join(text.split('|'))) sv = mult['cache_to_list']
return QVariant(u', '.join(sorted(text.split('|'),key=sort_key))) return QVariant(jv.join(
sorted([t.strip() for t in text.split(sv)], key=sort_key)))
return QVariant(text) return QVariant(text)
def decorated_text_type(r, idx=-1): def decorated_text_type(r, idx=-1):
@ -665,8 +666,6 @@ class BooksModel(QAbstractTableModel): # {{{
datatype = self.custom_columns[col]['datatype'] datatype = self.custom_columns[col]['datatype']
if datatype in ('text', 'comments', 'composite', 'enumeration'): if datatype in ('text', 'comments', 'composite', 'enumeration'):
mult=self.custom_columns[col]['is_multiple'] 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) self.dc[col] = functools.partial(text_type, idx=idx, mult=mult)
if datatype in ['text', 'composite', 'enumeration'] and not mult: if datatype in ['text', 'composite', 'enumeration'] and not mult:
if self.custom_columns[col]['display'].get('use_decorations', False): if self.custom_columns[col]['display'].get('use_decorations', False):
@ -722,9 +721,9 @@ class BooksModel(QAbstractTableModel): # {{{
if id_ in self.color_cache: if id_ in self.color_cache:
if key in self.color_cache[id_]: if key in self.color_cache[id_]:
return self.color_cache[id_][key] return self.color_cache[id_][key]
if mi is None:
mi = self.db.get_metadata(id_, index_is_id=True)
try: try:
if mi is None:
mi = self.db.get_metadata(id_, index_is_id=True)
color = composite_formatter.safe_format(fmt, mi, '', mi) color = composite_formatter.safe_format(fmt, mi, '', mi)
if color in self.colors: if color in self.colors:
color = QColor(color) color = QColor(color)

View File

@ -205,7 +205,7 @@ class ConditionEditor(QWidget): # {{{
tt = _('Enter a regular expression') tt = _('Enter a regular expression')
elif m.get('is_multiple', False): elif m.get('is_multiple', False):
tt += '\n' + _('You can match multiple values by separating' 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) self.value_box.setToolTip(tt)
if action in ('is set', 'is not set', 'is true', 'is false', if action in ('is set', 'is not set', 'is true', 'is false',
'is undefined'): 'is undefined'):

View File

@ -13,6 +13,9 @@ from calibre.gui2 import error_dialog
class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): 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 = { column_types = {
0:{'datatype':'text', 0:{'datatype':'text',
'text':_('Text, column shown in the tag browser'), 'text':_('Text, column shown in the tag browser'),

View File

@ -509,7 +509,7 @@ class ResultCache(SearchQueryParser): # {{{
valq_mkind, valq = self._matchkind(query) valq_mkind, valq = self._matchkind(query)
loc = self.field_metadata[location]['rec_index'] loc = self.field_metadata[location]['rec_index']
split_char = self.field_metadata[location]['is_multiple'] split_char = self.field_metadata[location]['is_multiple']['cache_to_list']
for id_ in candidates: for id_ in candidates:
item = self._data[id_] item = self._data[id_]
if item is None: if item is None:
@ -665,7 +665,8 @@ class ResultCache(SearchQueryParser): # {{{
if fm['is_multiple'] and \ if fm['is_multiple'] and \
len(query) > 1 and query.startswith('#') and \ len(query) > 1 and query.startswith('#') and \
query[1:1] in '=<>!': 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 len(item[loc].split(ms)) if item[loc] is not None else 0
return self.get_numeric_matches(location, query[1:], return self.get_numeric_matches(location, query[1:],
candidates, val_func=vf) candidates, val_func=vf)
@ -703,7 +704,8 @@ class ResultCache(SearchQueryParser): # {{{
['composite', 'text', 'comments', 'series', 'enumeration']: ['composite', 'text', 'comments', 'series', 'enumeration']:
exclude_fields.append(db_col[x]) exclude_fields.append(db_col[x])
col_datatype[db_col[x]] = self.field_metadata[x]['datatype'] 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: try:
rating_query = int(query) * 2 rating_query = int(query) * 2
@ -1045,13 +1047,14 @@ class SortKeyGenerator(object):
elif dt in ('text', 'comments', 'composite', 'enumeration'): elif dt in ('text', 'comments', 'composite', 'enumeration'):
if val: if val:
sep = fm['is_multiple'] if fm['is_multiple']:
if sep: jv = fm['is_multiple']['list_to_ui']
if fm['display'].get('is_names', False): sv = fm['is_multiple']['cache_to_list']
val = sep.join( if '&' in jv:
[author_to_author_sort(v) for v in val.split(sep)]) val = jv.join(
[author_to_author_sort(v) for v in val.split(sv)])
else: else:
val = sep.join(sorted(val.split(sep), val = jv.join(sorted(val.split(sv),
key=self.string_sort_key)) key=self.string_sort_key))
val = self.string_sort_key(val) val = self.string_sort_key(val)

View File

@ -88,7 +88,7 @@ class Rule(object): # {{{
if dt in ('comments', 'series', 'text', 'enumeration', 'composite'): if dt in ('comments', 'series', 'text', 'enumeration', 'composite'):
ism = m.get('is_multiple', False) ism = m.get('is_multiple', False)
if ism: 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) return self.text_condition(col, action, val)
def identifiers_condition(self, col, action, val): def identifiers_condition(self, col, action, val):

View File

@ -78,6 +78,18 @@ class CustomColumns(object):
} }
if data['display'] is None: if data['display'] is None:
data['display'] = {} 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']) table, lt = self.custom_table_names(data['num'])
if table not in custom_tables or (data['normalized'] and lt not in if table not in custom_tables or (data['normalized'] and lt not in
custom_tables): custom_tables):
@ -119,7 +131,7 @@ class CustomColumns(object):
if x is None: if x is None:
return [] return []
if isinstance(x, (str, unicode, bytes)): 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.strip() for y in x if y.strip()]
x = [y.decode(preferred_encoding, 'replace') if not isinstance(y, x = [y.decode(preferred_encoding, 'replace') if not isinstance(y,
unicode) else y for y in x] unicode) else y for y in x]
@ -181,10 +193,7 @@ class CustomColumns(object):
is_category = True is_category = True
else: else:
is_category = False is_category = False
if v['is_multiple']: is_m = v['multiple_seps']
is_m = ',' if v['datatype'] == 'composite' else '|'
else:
is_m = None
tn = 'custom_column_{0}'.format(v['num']) tn = 'custom_column_{0}'.format(v['num'])
self.field_metadata.add_custom_field(label=v['label'], self.field_metadata.add_custom_field(label=v['label'],
table=tn, column='value', datatype=v['datatype'], 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] row = self.data._data[idx] if index_is_id else self.data[idx]
ans = row[self.FIELD_MAP[data['num']]] ans = row[self.FIELD_MAP[data['num']]]
if data['is_multiple'] and data['datatype'] == 'text': 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): if data['display'].get('sort_alpha', False):
ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower())) ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower()))
return ans return ans
@ -571,9 +580,17 @@ class CustomColumns(object):
if data['normalized']: if data['normalized']:
query = '%s.value' query = '%s.value'
if data['is_multiple']: if data['is_multiple']:
query = 'group_concat(%s.value, "|")' # query = 'group_concat(%s.value, "{0}")'.format(
if not display.get('sort_alpha', False): # data['multiple_seps']['cache_to_list'])
query = 'sort_concat(link.id, %s.value)' # 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 line = '''(SELECT {query} FROM {lt} AS link INNER JOIN
{table} ON(link.value={table}.id) WHERE link.book=books.id) {table} ON(link.value={table}.id) WHERE link.book=books.id)
custom_{num} custom_{num}

View File

@ -1250,7 +1250,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
dex = field['rec_index'] dex = field['rec_index']
for book in self.data.iterall(): for book in self.data.iterall():
if field['is_multiple']: 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 v.strip()]
if id_ in vals: if id_ in vals:
ans.add(book[0]) ans.add(book[0])
@ -1378,7 +1379,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tcategories[category] = {} tcategories[category] = {}
# create a list of category/field_index for the books scan to use. # create a list of category/field_index for the books scan to use.
# This saves iterating through field_metadata for each book # 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(): for category in tb_cats.iterkeys():
cat = tb_cats[category] cat = tb_cats[category]
@ -1386,7 +1388,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
cat['display'].get('make_category', False): cat['display'].get('make_category', False):
tids[category] = {} tids[category] = {}
tcategories[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')) cat['datatype'] == 'composite'))
#print 'end phase "collection":', time.clock() - last, 'seconds' #print 'end phase "collection":', time.clock() - last, 'seconds'
#last = time.clock() #last = time.clock()

View File

@ -50,9 +50,16 @@ class FieldMetadata(dict):
datatype: the type of information in the field. Valid values are listed in datatype: the type of information in the field. Valid values are listed in
VALID_DATA_TYPES below. VALID_DATA_TYPES below.
is_multiple: valid for the text datatype. If None, the field is to be is_multiple: valid for the text datatype. If {}, the field is to be
treated as a single term. If not None, it contains a string, and the field treated as a single term. If not None, it contains a dict of the form
is assumed to contain a list of terms separated by that string {'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 == field: is a db field.
kind == category: standard tag category that isn't a field. see news. kind == category: standard tag category that isn't a field. see news.
@ -97,7 +104,9 @@ class FieldMetadata(dict):
'link_column':'author', 'link_column':'author',
'category_sort':'sort', 'category_sort':'sort',
'datatype':'text', 'datatype':'text',
'is_multiple':',', 'is_multiple':{'cache_to_list': ',',
'ui_to_list': '&',
'list_to_ui': ' & '},
'kind':'field', 'kind':'field',
'name':_('Authors'), 'name':_('Authors'),
'search_terms':['authors', 'author'], 'search_terms':['authors', 'author'],
@ -109,7 +118,7 @@ class FieldMetadata(dict):
'link_column':'series', 'link_column':'series',
'category_sort':'(title_sort(name))', 'category_sort':'(title_sort(name))',
'datatype':'series', 'datatype':'series',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Series'), 'name':_('Series'),
'search_terms':['series'], 'search_terms':['series'],
@ -119,7 +128,9 @@ class FieldMetadata(dict):
('formats', {'table':None, ('formats', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':',', 'is_multiple':{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '},
'kind':'field', 'kind':'field',
'name':_('Formats'), 'name':_('Formats'),
'search_terms':['formats', 'format'], 'search_terms':['formats', 'format'],
@ -131,7 +142,7 @@ class FieldMetadata(dict):
'link_column':'publisher', 'link_column':'publisher',
'category_sort':'name', 'category_sort':'name',
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Publishers'), 'name':_('Publishers'),
'search_terms':['publisher'], 'search_terms':['publisher'],
@ -143,7 +154,7 @@ class FieldMetadata(dict):
'link_column':'rating', 'link_column':'rating',
'category_sort':'rating', 'category_sort':'rating',
'datatype':'rating', 'datatype':'rating',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Ratings'), 'name':_('Ratings'),
'search_terms':['rating'], 'search_terms':['rating'],
@ -154,7 +165,7 @@ class FieldMetadata(dict):
'column':'name', 'column':'name',
'category_sort':'name', 'category_sort':'name',
'datatype':None, 'datatype':None,
'is_multiple':None, 'is_multiple':{},
'kind':'category', 'kind':'category',
'name':_('News'), 'name':_('News'),
'search_terms':[], 'search_terms':[],
@ -166,7 +177,9 @@ class FieldMetadata(dict):
'link_column': 'tag', 'link_column': 'tag',
'category_sort':'name', 'category_sort':'name',
'datatype':'text', 'datatype':'text',
'is_multiple':',', 'is_multiple':{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '},
'kind':'field', 'kind':'field',
'name':_('Tags'), 'name':_('Tags'),
'search_terms':['tags', 'tag'], 'search_terms':['tags', 'tag'],
@ -176,7 +189,9 @@ class FieldMetadata(dict):
('identifiers', {'table':None, ('identifiers', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':',', 'is_multiple':{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '},
'kind':'field', 'kind':'field',
'name':_('Identifiers'), 'name':_('Identifiers'),
'search_terms':['identifiers', 'identifier', 'isbn'], 'search_terms':['identifiers', 'identifier', 'isbn'],
@ -186,7 +201,7 @@ class FieldMetadata(dict):
('author_sort',{'table':None, ('author_sort',{'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Author Sort'), 'name':_('Author Sort'),
'search_terms':['author_sort'], 'search_terms':['author_sort'],
@ -196,7 +211,9 @@ class FieldMetadata(dict):
('au_map', {'table':None, ('au_map', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':',', 'is_multiple':{'cache_to_list': ',',
'ui_to_list': None,
'list_to_ui': None},
'kind':'field', 'kind':'field',
'name':None, 'name':None,
'search_terms':[], 'search_terms':[],
@ -206,7 +223,7 @@ class FieldMetadata(dict):
('comments', {'table':None, ('comments', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Comments'), 'name':_('Comments'),
'search_terms':['comments', 'comment'], 'search_terms':['comments', 'comment'],
@ -216,7 +233,7 @@ class FieldMetadata(dict):
('cover', {'table':None, ('cover', {'table':None,
'column':None, 'column':None,
'datatype':'int', 'datatype':'int',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':None, 'name':None,
'search_terms':['cover'], 'search_terms':['cover'],
@ -226,7 +243,7 @@ class FieldMetadata(dict):
('id', {'table':None, ('id', {'table':None,
'column':None, 'column':None,
'datatype':'int', 'datatype':'int',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':None, 'name':None,
'search_terms':[], 'search_terms':[],
@ -236,7 +253,7 @@ class FieldMetadata(dict):
('last_modified', {'table':None, ('last_modified', {'table':None,
'column':None, 'column':None,
'datatype':'datetime', 'datatype':'datetime',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Modified'), 'name':_('Modified'),
'search_terms':['last_modified'], 'search_terms':['last_modified'],
@ -246,7 +263,7 @@ class FieldMetadata(dict):
('ondevice', {'table':None, ('ondevice', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('On Device'), 'name':_('On Device'),
'search_terms':['ondevice'], 'search_terms':['ondevice'],
@ -256,7 +273,7 @@ class FieldMetadata(dict):
('path', {'table':None, ('path', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Path'), 'name':_('Path'),
'search_terms':[], 'search_terms':[],
@ -266,7 +283,7 @@ class FieldMetadata(dict):
('pubdate', {'table':None, ('pubdate', {'table':None,
'column':None, 'column':None,
'datatype':'datetime', 'datatype':'datetime',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Published'), 'name':_('Published'),
'search_terms':['pubdate'], 'search_terms':['pubdate'],
@ -276,7 +293,7 @@ class FieldMetadata(dict):
('marked', {'table':None, ('marked', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name': None, 'name': None,
'search_terms':['marked'], 'search_terms':['marked'],
@ -286,7 +303,7 @@ class FieldMetadata(dict):
('series_index',{'table':None, ('series_index',{'table':None,
'column':None, 'column':None,
'datatype':'float', 'datatype':'float',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':None, 'name':None,
'search_terms':['series_index'], 'search_terms':['series_index'],
@ -296,7 +313,7 @@ class FieldMetadata(dict):
('sort', {'table':None, ('sort', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Title Sort'), 'name':_('Title Sort'),
'search_terms':['title_sort'], 'search_terms':['title_sort'],
@ -306,7 +323,7 @@ class FieldMetadata(dict):
('size', {'table':None, ('size', {'table':None,
'column':None, 'column':None,
'datatype':'float', 'datatype':'float',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Size'), 'name':_('Size'),
'search_terms':['size'], 'search_terms':['size'],
@ -316,7 +333,7 @@ class FieldMetadata(dict):
('timestamp', {'table':None, ('timestamp', {'table':None,
'column':None, 'column':None,
'datatype':'datetime', 'datatype':'datetime',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Date'), 'name':_('Date'),
'search_terms':['date'], 'search_terms':['date'],
@ -326,7 +343,7 @@ class FieldMetadata(dict):
('title', {'table':None, ('title', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':_('Title'), 'name':_('Title'),
'search_terms':['title'], 'search_terms':['title'],
@ -336,7 +353,7 @@ class FieldMetadata(dict):
('uuid', {'table':None, ('uuid', {'table':None,
'column':None, 'column':None,
'datatype':'text', 'datatype':'text',
'is_multiple':None, 'is_multiple':{},
'kind':'field', 'kind':'field',
'name':None, 'name':None,
'search_terms':[], 'search_terms':[],
@ -508,7 +525,7 @@ class FieldMetadata(dict):
if datatype == 'series': if datatype == 'series':
key += '_index' key += '_index'
self._tb_cats[key] = {'table':None, 'column':None, self._tb_cats[key] = {'table':None, 'column':None,
'datatype':'float', 'is_multiple':None, 'datatype':'float', 'is_multiple':{},
'kind':'field', 'name':'', 'kind':'field', 'name':'',
'search_terms':[key], 'label':label+'_index', 'search_terms':[key], 'label':label+'_index',
'colnum':None, 'display':{}, 'colnum':None, 'display':{},
@ -560,7 +577,7 @@ class FieldMetadata(dict):
if icu_lower(label) != label: if icu_lower(label) != label:
st.append(icu_lower(label)) st.append(icu_lower(label))
self._tb_cats[label] = {'table':None, 'column':None, self._tb_cats[label] = {'table':None, 'column':None,
'datatype':None, 'is_multiple':None, 'datatype':None, 'is_multiple':{},
'kind':'user', 'name':name, 'kind':'user', 'name':name,
'search_terms':st, 'is_custom':False, 'search_terms':st, 'is_custom':False,
'is_category':True, 'is_csp': False} 'is_category':True, 'is_csp': False}
@ -570,7 +587,7 @@ class FieldMetadata(dict):
if label in self._tb_cats: if label in self._tb_cats:
raise ValueError('Duplicate user field [%s]'%(label)) raise ValueError('Duplicate user field [%s]'%(label))
self._tb_cats[label] = {'table':None, 'column':None, self._tb_cats[label] = {'table':None, 'column':None,
'datatype':None, 'is_multiple':None, 'datatype':None, 'is_multiple':{},
'kind':'search', 'name':name, 'kind':'search', 'name':name,
'search_terms':[], 'is_custom':False, 'search_terms':[], 'is_custom':False,
'is_category':True, 'is_csp': False} 'is_category':True, 'is_csp': False}

View File

@ -171,7 +171,7 @@ class Restore(Thread):
for x in fields: for x in fields:
if x in cfm: if x in cfm:
if x == 'is_multiple': if x == 'is_multiple':
args.append(cfm[x] is not None) args.append(bool(cfm[x]))
else: else:
args.append(cfm[x]) args.append(cfm[x])
if len(args) == len(fields): if len(args) == len(fields):

View File

@ -121,9 +121,12 @@ class SortedConcatenate(object):
return None return None
return self.sep.join(map(self.ans.get, sorted(self.ans.keys()))) return self.sep.join(map(self.ans.get, sorted(self.ans.keys())))
class SafeSortedConcatenate(SortedConcatenate): class SortedConcatenateBar(SortedConcatenate):
sep = '|' sep = '|'
class SortedConcatenateAmper(SortedConcatenate):
sep = '&'
class IdentifiersConcat(object): class IdentifiersConcat(object):
'''String concatenation aggregator for the identifiers map''' '''String concatenation aggregator for the identifiers map'''
def __init__(self): def __init__(self):
@ -220,7 +223,8 @@ class DBThread(Thread):
self.conn.execute('pragma cache_size=5000') self.conn.execute('pragma cache_size=5000')
encoding = self.conn.execute('pragma encoding').fetchone()[0] encoding = self.conn.execute('pragma encoding').fetchone()[0]
self.conn.create_aggregate('sortconcat', 2, SortedConcatenate) 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) self.conn.create_aggregate('identifiers_concat', 2, IdentifiersConcat)
load_c_extensions(self.conn) load_c_extensions(self.conn)
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row) self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)