diff --git a/src/calibre/ebooks/metadata/opf3.py b/src/calibre/ebooks/metadata/opf3.py index d1bb194304..61b4c83f90 100644 --- a/src/calibre/ebooks/metadata/opf3.py +++ b/src/calibre/ebooks/metadata/opf3.py @@ -2,23 +2,32 @@ # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2016, Kovid Goyal -from __future__ import (unicode_literals, division, absolute_import, - print_function) +from __future__ import absolute_import, division, print_function, unicode_literals + +import json +import re from collections import defaultdict, namedtuple from functools import wraps from future_builtins import map -import re, json from lxml import etree from calibre import prints -from calibre.ebooks.metadata import check_isbn, authors_to_string, string_to_authors +from calibre.ebooks.metadata import authors_to_string, check_isbn, string_to_authors from calibre.ebooks.metadata.book.base import Metadata -from calibre.ebooks.metadata.book.json_codec import object_to_unicode, decode_is_multiple, encode_is_multiple -from calibre.ebooks.metadata.utils import parse_opf, pretty_print_opf, ensure_unique, normalize_languages, create_manifest_item -from calibre.ebooks.oeb.base import OPF2_NSMAP, OPF, DC +from calibre.ebooks.metadata.book.json_codec import ( + decode_is_multiple, encode_is_multiple, object_to_unicode +) +from calibre.ebooks.metadata.utils import ( + create_manifest_item, ensure_unique, normalize_languages, parse_opf, + pretty_print_opf +) +from calibre.ebooks.oeb.base import DC, OPF, OPF2_NSMAP from calibre.utils.config import from_json, to_json -from calibre.utils.date import parse_date as parse_date_, fix_only_date, is_date_undefined, isoformat +from calibre.utils.date import ( + fix_only_date, is_date_undefined, isoformat, parse_date as parse_date_, utcnow, + w3cdtf +) from calibre.utils.iso8601 import parse_iso8601 from calibre.utils.localization import canonicalize_lang @@ -575,23 +584,23 @@ def read_timestamp(root, prefixes, refines): continue -def create_timestamp(m, val): +def create_timestamp(root, prefixes, m, val): if not is_date_undefined(val): - val = isoformat(val) + ensure_prefix(root, prefixes, 'calibre', CALIBRE_PREFIX) + ensure_prefix(root, prefixes, 'dcterms') + val = w3cdtf(val) d = m.makeelement(OPF('meta'), attrib={'property':'calibre:timestamp', 'scheme':'dcterms:W3CDTF'}) d.text = val m.append(d) def set_timestamp(root, prefixes, refines, val): - ensure_prefix(root, prefixes, 'calibre', CALIBRE_PREFIX) - ensure_prefix(root, prefixes, 'dcterms') pq = '%s:timestamp' % CALIBRE_PREFIX for meta in XPath('./opf:metadata/opf:meta')(root): prop = expand_prefix(meta.get('property'), prefixes) if prop.lower() == pq or meta.get('name') == 'calibre:timestamp': remove_element(meta, refines) - create_timestamp(XPath('./opf:metadata')(root)[0], val) + create_timestamp(root, prefixes, XPath('./opf:metadata')(root)[0], val) def read_last_modified(root, prefixes, refines): @@ -607,6 +616,23 @@ def read_last_modified(root, prefixes, refines): return parse_date(val, is_w3cdtf=scheme == sq) except Exception: continue + +def set_last_modified(root, prefixes, refines, val=None): + pq = '%s:modified' % reserved_prefixes['dcterms'] + sq = '%s:w3cdtf' % reserved_prefixes['dcterms'] + val = w3cdtf(val or utcnow()) + for meta in XPath('./opf:metadata/opf:meta[@property]')(root): + prop = expand_prefix(meta.get('property'), prefixes) + if prop.lower() == pq: + iid = meta.get('id') + if not iid or not refines[iid]: + break + else: + ensure_prefix(root, prefixes, 'dcterms') + m = XPath('./opf:metadata')(root)[0] + meta = m.makeelement(OPF('meta'), attrib={'property':'dcterms:modified', 'scheme':'dcterms:W3CDTF'}) + m.append(meta) + meta.text = val # }}} # Comments {{{ @@ -933,6 +959,11 @@ def first_spine_item(root, prefixes, refines): return item.get('href') or None +def set_last_modified_in_opf(root): + prefixes, refines = read_prefixes(root), read_refines(root) + set_last_modified(root, prefixes, refines) + + def read_metadata(root, ver=None, return_extra_data=False): ans = Metadata(_('Unknown'), [_('Unknown')]) prefixes, refines = read_prefixes(root), read_refines(root) diff --git a/src/calibre/ebooks/oeb/polish/container.py b/src/calibre/ebooks/oeb/polish/container.py index ef43d802a1..194aff4750 100644 --- a/src/calibre/ebooks/oeb/polish/container.py +++ b/src/calibre/ebooks/oeb/polish/container.py @@ -1294,7 +1294,14 @@ class EpubContainer(Container): f.write(raw) self.obfuscated_fonts[font] = (alg, tkey) + def update_modified_timestamp(self): + from calibre.ebooks.metadata.opf3 import set_last_modified_in_opf + set_last_modified_in_opf(self.opf) + self.dirty(self.opf_name) + def commit(self, outpath=None, keep_parsed=False): + if self.opf_version_parsed.major == 3: + self.update_modified_timestamp() super(EpubContainer, self).commit(keep_parsed=keep_parsed) container_path = join(self.root, 'META-INF', 'container.xml') if not exists(container_path): diff --git a/src/calibre/utils/date.py b/src/calibre/utils/date.py index 828cc00d84..2748a0bf01 100644 --- a/src/calibre/utils/date.py +++ b/src/calibre/utils/date.py @@ -193,6 +193,15 @@ def isoformat(date_time, assume_utc=False, as_utc=True, sep='T'): return unicode(date_time.isoformat(str(sep))) +def w3cdtf(date_time, assume_utc=False): + if hasattr(date_time, 'tzinfo'): + if date_time.tzinfo is None: + date_time = date_time.replace(tzinfo=_utc_tz if assume_utc else + _local_tz) + date_time = date_time.astimezone(_utc_tz if as_utc else _local_tz) + return unicode(date_time.strftime('%Y-%M-%dT%H:%M:%SZ')) + + def as_local_time(date_time, assume_utc=True): if not hasattr(date_time, 'tzinfo'): return date_time