mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Port calibredb set_metadata
This commit is contained in:
parent
249c71877c
commit
ad5ba94f3a
@ -1244,6 +1244,7 @@ class Cache(object):
|
||||
provided, but are never deleted. Also note that force_changes has no
|
||||
effect on setting title or authors.
|
||||
'''
|
||||
dirtied = set()
|
||||
|
||||
try:
|
||||
# Handle code passing in an OPF object instead of a Metadata object
|
||||
@ -1252,7 +1253,7 @@ class Cache(object):
|
||||
pass
|
||||
|
||||
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
|
||||
if set_title and mi.title:
|
||||
@ -1342,6 +1343,7 @@ class Cache(object):
|
||||
# the db and Cache are in sync
|
||||
self._reload_from_db()
|
||||
raise
|
||||
return dirtied
|
||||
|
||||
def _do_add_format(self, book_id, fmt, stream, name=None, mtime=None):
|
||||
path = self._field_for('path', book_id)
|
||||
|
@ -4,19 +4,182 @@
|
||||
|
||||
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
|
||||
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
|
||||
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):
|
||||
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):
|
||||
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
|
||||
|
@ -21,3 +21,7 @@ def formats_removed(formats_map):
|
||||
|
||||
def books_deleted(book_ids):
|
||||
pass
|
||||
|
||||
|
||||
def metadata(book_ids):
|
||||
pass
|
||||
|
Loading…
x
Reference in New Issue
Block a user