Implement serialization of Metadata instances

This commit is contained in:
Kovid Goyal 2017-04-29 22:12:12 +05:30
parent 530cb0d00a
commit 1e4dae7e16
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 99 additions and 11 deletions

View File

@ -237,6 +237,17 @@ class ReadingTest(BaseTest):
self.compare_metadata(mi1, mi2) self.compare_metadata(mi1, mi2)
# }}} # }}}
def test_serialize_metadata(self): # {{{
from calibre.utils.serialize import json_dumps, json_loads, msgpack_dumps, msgpack_loads
cache = self.init_cache(self.library_path)
for i in xrange(1, 4):
mi = cache.get_metadata(i, get_cover=True, cover_as_data=True)
rmi = msgpack_loads(msgpack_dumps(mi))
self.compare_metadata(mi, rmi, exclude='format_metadata has_cover formats id'.split())
rmi = json_loads(json_dumps(mi))
self.compare_metadata(mi, rmi, exclude='format_metadata has_cover formats id'.split())
# }}}
def test_get_cover(self): # {{{ def test_get_cover(self): # {{{
'Test cover() returns the same data for both backends' 'Test cover() returns the same data for both backends'
from calibre.library.database2 import LibraryDatabase2 from calibre.library.database2 import LibraryDatabase2

View File

@ -0,0 +1,66 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
import base64
from calibre.constants import preferred_encoding
from calibre.ebooks.metadata.book import SERIALIZABLE_FIELDS
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.imghdr import what
def ensure_unicode(obj, enc=preferred_encoding):
if isinstance(obj, unicode):
return obj
if isinstance(obj, bytes):
return obj.decode(enc, 'replace')
if isinstance(obj, (list, tuple)):
return [ensure_unicode(x) for x in obj]
if isinstance(obj, dict):
return {ensure_unicode(k): ensure_unicode(v) for k, v in obj.iteritems()}
return obj
def read_cover(mi):
if mi.cover_data and mi.cover_data[1]:
return
if mi.cover:
try:
with lopen(mi.cover, 'rb') as f:
cd = f.read()
mi.cover_data = what(cd), cd
except EnvironmentError:
pass
return mi
def metadata_as_dict(mi, encode_cover_data=False):
if hasattr(mi, 'to_book_metadata'):
mi = mi.to_book_metadata()
ans = {}
for field in SERIALIZABLE_FIELDS:
if field != 'cover' and not mi.is_null(field):
val = getattr(mi, field)
ans[field] = ensure_unicode(val)
if mi.cover_data and mi.cover_data[1]:
if encode_cover_data:
ans['cover_data'] = [mi.cover_data[0], base64.standard_b64encode(bytes(mi.cover_data[1]))]
else:
ans['cover_data'] = mi.cover_data
um = mi.get_all_user_metadata(False)
if um:
ans['user_metadata'] = um
return ans
def metadata_from_dict(src):
ans = Metadata('Unknown')
for key, value in src.iteritems():
if key == 'user_metadata':
ans.set_all_user_metadata(value)
else:
setattr(ans, key, value)
return ans

View File

@ -4,17 +4,23 @@
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
import json import json, base64
from functools import partial
from datetime import datetime from datetime import datetime
from calibre.utils.iso8601 import parse_iso8601
from calibre.utils.iso8601 import parse_iso8601
MSGPACK_MIME = 'application/x-msgpack' MSGPACK_MIME = 'application/x-msgpack'
def encoder(obj): def encoder(obj, for_json=False):
if isinstance(obj, datetime): if isinstance(obj, datetime):
return {'__datetime__': obj.isoformat()} return {'__datetime__': unicode(obj.isoformat())}
if obj.__class__.__name__ == 'Metadata':
from calibre.ebooks.metadata.book.base import Metadata
if isinstance(obj, Metadata):
from calibre.ebooks.metadata.book.serialize import metadata_as_dict
obj = {'__metadata__': metadata_as_dict(obj, encode_cover_data=for_json)}
return obj return obj
@ -24,7 +30,7 @@ def msgpack_dumps(data):
def json_dumps(data, **kw): def json_dumps(data, **kw):
kw['default'] = encoder kw['default'] = partial(encoder, for_json=True)
kw['ensure_ascii'] = False kw['ensure_ascii'] = False
ans = json.dumps(data, **kw) ans = json.dumps(data, **kw)
if not isinstance(ans, bytes): if not isinstance(ans, bytes):
@ -32,19 +38,24 @@ def json_dumps(data, **kw):
return ans return ans
def decoder(obj): def decoder(obj, for_json=False):
dt = obj.get('__datetime__') dt = obj.get('__datetime__')
if dt is not None: if dt is not None:
obj = parse_iso8601(dt, assume_utc=True) return parse_iso8601(dt, assume_utc=True)
m = obj.get('__metadata__')
if m is not None:
from calibre.ebooks.metadata.book.serialize import metadata_from_dict
obj = metadata_from_dict(m)
if for_json and obj.cover_data and obj.cover_data[1]:
obj.cover_data = obj.cover_data[0], base64.standard_b64decode(obj.cover_data[1])
return obj
return obj return obj
def msgpack_loads(data): def msgpack_loads(data):
import msgpack import msgpack
return msgpack.unpackb( return msgpack.unpackb(data, encoding='utf-8', object_hook=decoder)
data, encoding='utf-8', use_list=False, object_hook=decoder
)
def json_loads(data): def json_loads(data):
return json.loads(data, object_hook=decoder) return json.loads(data, object_hook=partial(decoder, for_json=True))