Port calibredb set_metadata

This commit is contained in:
Kovid Goyal 2017-05-01 14:33:15 +05:30
parent 249c71877c
commit ad5ba94f3a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 174 additions and 5 deletions

View File

@ -1244,6 +1244,7 @@ class Cache(object):
provided, but are never deleted. Also note that force_changes has no provided, but are never deleted. Also note that force_changes has no
effect on setting title or authors. effect on setting title or authors.
''' '''
dirtied = set()
try: try:
# Handle code passing in an OPF object instead of a Metadata object # Handle code passing in an OPF object instead of a Metadata object
@ -1252,7 +1253,7 @@ class Cache(object):
pass pass
def set_field(name, val): def set_field(name, val):
self._set_field(name, {book_id:val}, do_path_update=False, allow_case_change=allow_case_change) dirtied.update(self._set_field(name, {book_id:val}, do_path_update=False, allow_case_change=allow_case_change))
path_changed = False path_changed = False
if set_title and mi.title: if set_title and mi.title:
@ -1342,6 +1343,7 @@ class Cache(object):
# the db and Cache are in sync # the db and Cache are in sync
self._reload_from_db() self._reload_from_db()
raise raise
return dirtied
def _do_add_format(self, book_id, fmt, stream, name=None, mtime=None): def _do_add_format(self, book_id, fmt, stream, name=None, mtime=None):
path = self._field_for('path', book_id) path = self._field_for('path', book_id)

View File

@ -4,19 +4,182 @@
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
import os
from calibre import prints
from calibre.ebooks.metadata.book.base import field_from_string
from calibre.ebooks.metadata.book.serialize import read_cover
from calibre.ebooks.metadata.opf import get_metadata
from calibre.srv.changes import metadata
readonly = False readonly = False
version = 0 # change this if you change signature of implementation() version = 0 # change this if you change signature of implementation()
def implementation(db, notify_changes, *args): def implementation(db, notify_changes, action, *args):
is_remote = notify_changes is not None is_remote = notify_changes is not None
is_remote if action == 'field_metadata':
return db.field_metadata
if action == 'opf':
book_id, mi = args
with db.write_lock:
if not db.has_id(book_id):
return
changed_ids = db.set_metadata(book_id, mi, force_changes=True, allow_case_change=False)
if is_remote:
notify_changes(metadata(changed_ids))
return db.get_metadata(book_id)
if action == 'fields':
book_id, fvals = args
with db.write_lock:
if not db.has_id(book_id):
return
mi = db.get_metadata(book_id)
for field, val in fvals:
if field.endswith('_index'):
sname = mi.get(field[:-6])
if sname:
mi.set(field[:-6], sname, extra=val)
if field == 'series_index':
mi.series_index = val # extra has no effect for the builtin series field
elif field == 'cover':
if is_remote:
mi.cover_data = None, val[1]
else:
mi.cover = val
read_cover(mi)
else:
mi.set(field, val)
changed_ids = db.set_metadata(book_id, mi, force_changes=True, allow_case_change=True)
if is_remote:
notify_changes(metadata(changed_ids))
return db.get_metadata(book_id)
def option_parser(get_parser): def option_parser(get_parser):
pass parser = get_parser(
_(
'''
%prog set_metadata [options] id [/path/to/metadata.opf]
Set the metadata stored in the calibre database for the book identified by id
from the OPF file metadata.opf. id is an id number from the search command. You
can get a quick feel for the OPF format by using the --as-opf switch to the
show_metadata command. You can also set the metadata of individual fields with
the --field option. If you use the --field option, there is no need to specify
an OPF file.
'''
)
)
parser.add_option(
'-f',
'--field',
action='append',
default=[],
help=_(
'The field to set. Format is field_name:value, for example: '
'{0} tags:tag1,tag2. Use {1} to get a list of all field names. You '
'can specify this option multiple times to set multiple fields. '
'Note: For languages you must use the ISO639 language codes (e.g. '
'en for English, fr for French and so on). For identifiers, the '
'syntax is {0} {2}. For boolean (yes/no) fields use true and false '
'or yes and no.'
).format('--field', '--list-fields', 'identifiers:isbn:XXXX,doi:YYYYY')
)
parser.add_option(
'-l',
'--list-fields',
action='store_true',
default=False,
help=_(
'List the metadata field names that can be used'
' with the --field option'
)
)
return parser
def get_fields(dbctx):
fm = dbctx.run('set_metadata', 'field_metadata')
for key in sorted(fm.all_field_keys()):
m = fm[key]
if (key not in {'formats', 'series_sort', 'ondevice', 'path',
'last_modified'} and m['is_editable'] and m['name']):
yield key, m
if m['datatype'] == 'series':
si = m.copy()
si['name'] = m['name'] + ' Index'
si['datatype'] = 'float'
yield key + '_index', si
c = fm['cover'].copy()
c['datatype'] = 'text'
yield 'cover', c
def main(opts, args, dbctx): def main(opts, args, dbctx):
raise NotImplementedError('TODO: implement this') if opts.list_fields:
ans = get_fields(dbctx)
prints('%-40s' % _('Title'), _('Field name'), '\n')
for key, m in ans:
prints('%-40s' % m['name'], key)
return 0
def verify_int(x):
try:
int(x)
return True
except:
return False
if len(args) < 1 or not verify_int(args[0]):
raise SystemExit(_(
'You must specify a record id as the '
'first argument'
))
if len(args) < 2 and not opts.field:
raise SystemExit(_('You must specify either a field or an opf file'))
book_id = int(args[0])
if len(args) > 1:
opf = os.path.abspath(args[1])
if not os.path.exists(opf):
raise SystemExit(_('The OPF file %s does not exist') % opf)
with lopen(opf, 'rb') as stream:
mi = get_metadata(stream)[0]
if mi.cover:
mi.cover = os.path.join(os.path.dirname(opf), os.path.relpath(mi.cover, os.getcwdu()))
final_mi = dbctx.run('set_metadata', 'opf', book_id, read_cover(mi))
if not final_mi:
raise SystemExit(_('No book with id: %s in the database') % book_id)
if opts.field:
fields = {k: v for k, v in get_fields(dbctx)}
fields['title_sort'] = fields['sort']
vals = {}
for x in opts.field:
field, val = x.partition(':')[::2]
if field == 'sort':
field = 'title_sort'
if field not in fields:
raise SystemExit(_('%s is not a known field' % field))
if field == 'cover':
val = dbctx.path(os.path.abspath(os.path.expanduser(val)))
else:
val = field_from_string(field, val, fields[field])
vals[field] = val
fvals = []
for field, val in sorted( # ensure series_index fields are set last
vals.iteritems(), key=lambda k: 1 if k[0].endswith('_index') else 0):
if field.endswith('_index'):
try:
val = float(val)
except Exception:
raise SystemExit('The value %r is not a valid series index' % val)
fvals.append((field, val))
final_mi = dbctx.run('set_metadata', 'fields', book_id, fvals)
if not final_mi:
raise SystemExit(_('No book with id: %s in the database') % book_id)
prints(unicode(final_mi))
return 0 return 0

View File

@ -21,3 +21,7 @@ def formats_removed(formats_map):
def books_deleted(book_ids): def books_deleted(book_ids):
pass pass
def metadata(book_ids):
pass