mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Implement serialization of Metadata instances
This commit is contained in:
parent
530cb0d00a
commit
1e4dae7e16
@ -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
|
||||||
|
66
src/calibre/ebooks/metadata/book/serialize.py
Normal file
66
src/calibre/ebooks/metadata/book/serialize.py
Normal 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
|
@ -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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user