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
|
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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user