mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
IGN:Make database a little more robust when deleting files and when detecting case sensitivity
This commit is contained in:
parent
9786c65b40
commit
e444b169ea
@ -17,21 +17,21 @@ from calibre.ebooks.chardet import xml_to_unicode
|
|||||||
from calibre import relpath
|
from calibre import relpath
|
||||||
from calibre.constants import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
from calibre.ebooks.metadata.toc import TOC
|
from calibre.ebooks.metadata.toc import TOC
|
||||||
from calibre.ebooks.metadata import MetaInformation, get_parser
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
|
|
||||||
|
|
||||||
class Resource(object):
|
class Resource(object):
|
||||||
'''
|
'''
|
||||||
Represents a resource (usually a file on the filesystem or a URL pointing
|
Represents a resource (usually a file on the filesystem or a URL pointing
|
||||||
to the web. Such resources are commonly referred to in OPF files.
|
to the web. Such resources are commonly referred to in OPF files.
|
||||||
|
|
||||||
They have the interface:
|
They have the interface:
|
||||||
|
|
||||||
:member:`path`
|
:member:`path`
|
||||||
:member:`mime_type`
|
:member:`mime_type`
|
||||||
:method:`href`
|
:method:`href`
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, href_or_path, basedir=os.getcwd(), is_path=True):
|
def __init__(self, href_or_path, basedir=os.getcwd(), is_path=True):
|
||||||
self.orig = href_or_path
|
self.orig = href_or_path
|
||||||
self._href = None
|
self._href = None
|
||||||
@ -63,13 +63,13 @@ class Resource(object):
|
|||||||
pc = pc.decode('utf-8')
|
pc = pc.decode('utf-8')
|
||||||
self.path = os.path.abspath(os.path.join(basedir, pc.replace('/', os.sep)))
|
self.path = os.path.abspath(os.path.join(basedir, pc.replace('/', os.sep)))
|
||||||
self.fragment = url[-1]
|
self.fragment = url[-1]
|
||||||
|
|
||||||
|
|
||||||
def href(self, basedir=None):
|
def href(self, basedir=None):
|
||||||
'''
|
'''
|
||||||
Return a URL pointing to this resource. If it is a file on the filesystem
|
Return a URL pointing to this resource. If it is a file on the filesystem
|
||||||
the URL is relative to `basedir`.
|
the URL is relative to `basedir`.
|
||||||
|
|
||||||
`basedir`: If None, the basedir of this resource is used (see :method:`set_basedir`).
|
`basedir`: If None, the basedir of this resource is used (see :method:`set_basedir`).
|
||||||
If this resource has no basedir, then the current working directory is used as the basedir.
|
If this resource has no basedir, then the current working directory is used as the basedir.
|
||||||
'''
|
'''
|
||||||
@ -91,54 +91,54 @@ class Resource(object):
|
|||||||
if isinstance(rpath, unicode):
|
if isinstance(rpath, unicode):
|
||||||
rpath = rpath.encode('utf-8')
|
rpath = rpath.encode('utf-8')
|
||||||
return rpath.replace(os.sep, '/')+frag
|
return rpath.replace(os.sep, '/')+frag
|
||||||
|
|
||||||
def set_basedir(self, path):
|
def set_basedir(self, path):
|
||||||
self._basedir = path
|
self._basedir = path
|
||||||
|
|
||||||
def basedir(self):
|
def basedir(self):
|
||||||
return self._basedir
|
return self._basedir
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Resource(%s, %s)'%(repr(self.path), repr(self.href()))
|
return 'Resource(%s, %s)'%(repr(self.path), repr(self.href()))
|
||||||
|
|
||||||
|
|
||||||
class ResourceCollection(object):
|
class ResourceCollection(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._resources = []
|
self._resources = []
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for r in self._resources:
|
for r in self._resources:
|
||||||
yield r
|
yield r
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._resources)
|
return len(self._resources)
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
return self._resources[index]
|
return self._resources[index]
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return len(self._resources) > 0
|
return len(self._resources) > 0
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
resources = map(repr, self)
|
resources = map(repr, self)
|
||||||
return '[%s]'%', '.join(resources)
|
return '[%s]'%', '.join(resources)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
def append(self, resource):
|
def append(self, resource):
|
||||||
if not isinstance(resource, Resource):
|
if not isinstance(resource, Resource):
|
||||||
raise ValueError('Can only append objects of type Resource')
|
raise ValueError('Can only append objects of type Resource')
|
||||||
self._resources.append(resource)
|
self._resources.append(resource)
|
||||||
|
|
||||||
def remove(self, resource):
|
def remove(self, resource):
|
||||||
self._resources.remove(resource)
|
self._resources.remove(resource)
|
||||||
|
|
||||||
def replace(self, start, end, items):
|
def replace(self, start, end, items):
|
||||||
'Same as list[start:end] = items'
|
'Same as list[start:end] = items'
|
||||||
self._resources[start:end] = items
|
self._resources[start:end] = items
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_directory_contents(top, topdown=True):
|
def from_directory_contents(top, topdown=True):
|
||||||
collection = ResourceCollection()
|
collection = ResourceCollection()
|
||||||
@ -148,16 +148,16 @@ class ResourceCollection(object):
|
|||||||
res.set_basedir(top)
|
res.set_basedir(top)
|
||||||
collection.append(res)
|
collection.append(res)
|
||||||
return collection
|
return collection
|
||||||
|
|
||||||
def set_basedir(self, path):
|
def set_basedir(self, path):
|
||||||
for res in self:
|
for res in self:
|
||||||
res.set_basedir(path)
|
res.set_basedir(path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ManifestItem(Resource):
|
class ManifestItem(Resource):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_opf_manifest_item(item, basedir):
|
def from_opf_manifest_item(item, basedir):
|
||||||
href = item.get('href', None)
|
href = item.get('href', None)
|
||||||
@ -167,7 +167,7 @@ class ManifestItem(Resource):
|
|||||||
if mt:
|
if mt:
|
||||||
res.mime_type = mt
|
res.mime_type = mt
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def media_type():
|
def media_type():
|
||||||
def fget(self):
|
def fget(self):
|
||||||
@ -175,18 +175,18 @@ class ManifestItem(Resource):
|
|||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
self.mime_type = val
|
self.mime_type = val
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'<item id="%s" href="%s" media-type="%s" />'%(self.id, self.href(), self.media_type)
|
return u'<item id="%s" href="%s" media-type="%s" />'%(self.id, self.href(), self.media_type)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return unicode(self).encode('utf-8')
|
return unicode(self).encode('utf-8')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return unicode(self)
|
return unicode(self)
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
if index == 0:
|
if index == 0:
|
||||||
return self.href()
|
return self.href()
|
||||||
@ -196,7 +196,7 @@ class ManifestItem(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class Manifest(ResourceCollection):
|
class Manifest(ResourceCollection):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_opf_manifest_element(items, dir):
|
def from_opf_manifest_element(items, dir):
|
||||||
m = Manifest()
|
m = Manifest()
|
||||||
@ -211,7 +211,7 @@ class Manifest(ResourceCollection):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
return m
|
return m
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_paths(entries):
|
def from_paths(entries):
|
||||||
'''
|
'''
|
||||||
@ -226,7 +226,7 @@ class Manifest(ResourceCollection):
|
|||||||
m.next_id += 1
|
m.next_id += 1
|
||||||
m.append(mi)
|
m.append(mi)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def add_item(self, path, mime_type=None):
|
def add_item(self, path, mime_type=None):
|
||||||
mi = ManifestItem(path, is_path=True)
|
mi = ManifestItem(path, is_path=True)
|
||||||
if mime_type:
|
if mime_type:
|
||||||
@ -235,37 +235,37 @@ class Manifest(ResourceCollection):
|
|||||||
self.next_id += 1
|
self.next_id += 1
|
||||||
self.append(mi)
|
self.append(mi)
|
||||||
return mi.id
|
return mi.id
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ResourceCollection.__init__(self)
|
ResourceCollection.__init__(self)
|
||||||
self.next_id = 1
|
self.next_id = 1
|
||||||
|
|
||||||
|
|
||||||
def item(self, id):
|
def item(self, id):
|
||||||
for i in self:
|
for i in self:
|
||||||
if i.id == id:
|
if i.id == id:
|
||||||
return i
|
return i
|
||||||
|
|
||||||
def id_for_path(self, path):
|
def id_for_path(self, path):
|
||||||
path = os.path.normpath(os.path.abspath(path))
|
path = os.path.normpath(os.path.abspath(path))
|
||||||
for i in self:
|
for i in self:
|
||||||
if i.path and os.path.normpath(i.path) == path:
|
if i.path and os.path.normpath(i.path) == path:
|
||||||
return i.id
|
return i.id
|
||||||
|
|
||||||
def path_for_id(self, id):
|
def path_for_id(self, id):
|
||||||
for i in self:
|
for i in self:
|
||||||
if i.id == id:
|
if i.id == id:
|
||||||
return i.path
|
return i.path
|
||||||
|
|
||||||
class Spine(ResourceCollection):
|
class Spine(ResourceCollection):
|
||||||
|
|
||||||
class Item(Resource):
|
class Item(Resource):
|
||||||
|
|
||||||
def __init__(self, idfunc, *args, **kwargs):
|
def __init__(self, idfunc, *args, **kwargs):
|
||||||
Resource.__init__(self, *args, **kwargs)
|
Resource.__init__(self, *args, **kwargs)
|
||||||
self.is_linear = True
|
self.is_linear = True
|
||||||
self.id = idfunc(self.path)
|
self.id = idfunc(self.path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_opf_spine_element(itemrefs, manifest):
|
def from_opf_spine_element(itemrefs, manifest):
|
||||||
s = Spine(manifest)
|
s = Spine(manifest)
|
||||||
@ -278,7 +278,7 @@ class Spine(ResourceCollection):
|
|||||||
r.is_linear = itemref.get('linear', 'yes') == 'yes'
|
r.is_linear = itemref.get('linear', 'yes') == 'yes'
|
||||||
s.append(r)
|
s.append(r)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_paths(paths, manifest):
|
def from_paths(paths, manifest):
|
||||||
s = Spine(manifest)
|
s = Spine(manifest)
|
||||||
@ -288,14 +288,14 @@ class Spine(ResourceCollection):
|
|||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, manifest):
|
def __init__(self, manifest):
|
||||||
ResourceCollection.__init__(self)
|
ResourceCollection.__init__(self)
|
||||||
self.manifest = manifest
|
self.manifest = manifest
|
||||||
|
|
||||||
|
|
||||||
def replace(self, start, end, ids):
|
def replace(self, start, end, ids):
|
||||||
'''
|
'''
|
||||||
Replace the items between start (inclusive) and end (not inclusive) with
|
Replace the items between start (inclusive) and end (not inclusive) with
|
||||||
@ -308,7 +308,7 @@ class Spine(ResourceCollection):
|
|||||||
raise ValueError('id %s not in manifest')
|
raise ValueError('id %s not in manifest')
|
||||||
items.append(Spine.Item(lambda x: id, path, is_path=True))
|
items.append(Spine.Item(lambda x: id, path, is_path=True))
|
||||||
ResourceCollection.replace(start, end, items)
|
ResourceCollection.replace(start, end, items)
|
||||||
|
|
||||||
def linear_items(self):
|
def linear_items(self):
|
||||||
for r in self:
|
for r in self:
|
||||||
if r.is_linear:
|
if r.is_linear:
|
||||||
@ -318,15 +318,15 @@ class Spine(ResourceCollection):
|
|||||||
for r in self:
|
for r in self:
|
||||||
if not r.is_linear:
|
if not r.is_linear:
|
||||||
yield r.path
|
yield r.path
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
for i in self:
|
for i in self:
|
||||||
yield i.path
|
yield i.path
|
||||||
|
|
||||||
class Guide(ResourceCollection):
|
class Guide(ResourceCollection):
|
||||||
|
|
||||||
class Reference(Resource):
|
class Reference(Resource):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_opf_resource_item(ref, basedir):
|
def from_opf_resource_item(ref, basedir):
|
||||||
title, href, type = ref.get('title', ''), ref.get('href'), ref.get('type')
|
title, href, type = ref.get('title', ''), ref.get('href'), ref.get('type')
|
||||||
@ -334,14 +334,14 @@ class Guide(ResourceCollection):
|
|||||||
res.title = title
|
res.title = title
|
||||||
res.type = type
|
res.type = type
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
ans = '<reference type="%s" href="%s" '%(self.type, self.href())
|
ans = '<reference type="%s" href="%s" '%(self.type, self.href())
|
||||||
if self.title:
|
if self.title:
|
||||||
ans += 'title="%s" '%self.title
|
ans += 'title="%s" '%self.title
|
||||||
return ans + '/>'
|
return ans + '/>'
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_opf_guide(references, base_dir=os.getcwdu()):
|
def from_opf_guide(references, base_dir=os.getcwdu()):
|
||||||
coll = Guide()
|
coll = Guide()
|
||||||
@ -352,7 +352,7 @@ class Guide(ResourceCollection):
|
|||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
return coll
|
return coll
|
||||||
|
|
||||||
def set_cover(self, path):
|
def set_cover(self, path):
|
||||||
map(self.remove, [i for i in self if 'cover' in i.type.lower()])
|
map(self.remove, [i for i in self if 'cover' in i.type.lower()])
|
||||||
for type in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
|
for type in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
|
||||||
@ -362,13 +362,13 @@ class Guide(ResourceCollection):
|
|||||||
|
|
||||||
|
|
||||||
class MetadataField(object):
|
class MetadataField(object):
|
||||||
|
|
||||||
def __init__(self, name, is_dc=True, formatter=None, none_is=None):
|
def __init__(self, name, is_dc=True, formatter=None, none_is=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.is_dc = is_dc
|
self.is_dc = is_dc
|
||||||
self.formatter = formatter
|
self.formatter = formatter
|
||||||
self.none_is = none_is
|
self.none_is = none_is
|
||||||
|
|
||||||
def __real_get__(self, obj, type=None):
|
def __real_get__(self, obj, type=None):
|
||||||
ans = obj.get_metadata_element(self.name)
|
ans = obj.get_metadata_element(self.name)
|
||||||
if ans is None:
|
if ans is None:
|
||||||
@ -382,13 +382,13 @@ class MetadataField(object):
|
|||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def __get__(self, obj, type=None):
|
def __get__(self, obj, type=None):
|
||||||
ans = self.__real_get__(obj, type)
|
ans = self.__real_get__(obj, type)
|
||||||
if ans is None:
|
if ans is None:
|
||||||
ans = self.none_is
|
ans = self.none_is
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def __set__(self, obj, val):
|
def __set__(self, obj, val):
|
||||||
elem = obj.get_metadata_element(self.name)
|
elem = obj.get_metadata_element(self.name)
|
||||||
if elem is None:
|
if elem is None:
|
||||||
@ -410,10 +410,11 @@ class OPF(object):
|
|||||||
XPath = functools.partial(etree.XPath, namespaces=xpn)
|
XPath = functools.partial(etree.XPath, namespaces=xpn)
|
||||||
CONTENT = XPath('self::*[re:match(name(), "meta$", "i")]/@content')
|
CONTENT = XPath('self::*[re:match(name(), "meta$", "i")]/@content')
|
||||||
TEXT = XPath('string()')
|
TEXT = XPath('string()')
|
||||||
|
|
||||||
|
|
||||||
metadata_path = XPath('descendant::*[re:match(name(), "metadata", "i")]')
|
metadata_path = XPath('descendant::*[re:match(name(), "metadata", "i")]')
|
||||||
metadata_elem_path = XPath('descendant::*[re:match(name(), concat($name, "$"), "i") or (re:match(name(), "meta$", "i") and re:match(@name, concat("^calibre:", $name, "$"), "i"))]')
|
metadata_elem_path = XPath('descendant::*[re:match(name(), concat($name, "$"), "i") or (re:match(name(), "meta$", "i") and re:match(@name, concat("^calibre:", $name, "$"), "i"))]')
|
||||||
|
title_path = XPath('descendant::*[re:match(name(), "title", "i")]')
|
||||||
authors_path = XPath('descendant::*[re:match(name(), "creator", "i") and (@role="aut" or @opf:role="aut" or (not(@role) and not(@opf:role)))]')
|
authors_path = XPath('descendant::*[re:match(name(), "creator", "i") and (@role="aut" or @opf:role="aut" or (not(@role) and not(@opf:role)))]')
|
||||||
bkp_path = XPath('descendant::*[re:match(name(), "contributor", "i") and (@role="bkp" or @opf:role="bkp")]')
|
bkp_path = XPath('descendant::*[re:match(name(), "contributor", "i") and (@role="bkp" or @opf:role="bkp")]')
|
||||||
tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
|
tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
|
||||||
@ -423,10 +424,10 @@ class OPF(object):
|
|||||||
application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
|
application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
|
||||||
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
|
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
|
||||||
manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
|
manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
|
||||||
manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
|
manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
|
||||||
spine_path = XPath('descendant::*[re:match(name(), "spine", "i")]/*[re:match(name(), "itemref", "i")]')
|
spine_path = XPath('descendant::*[re:match(name(), "spine", "i")]/*[re:match(name(), "itemref", "i")]')
|
||||||
guide_path = XPath('descendant::*[re:match(name(), "guide", "i")]/*[re:match(name(), "reference", "i")]')
|
guide_path = XPath('descendant::*[re:match(name(), "guide", "i")]/*[re:match(name(), "reference", "i")]')
|
||||||
|
|
||||||
title = MetadataField('title')
|
title = MetadataField('title')
|
||||||
publisher = MetadataField('publisher')
|
publisher = MetadataField('publisher')
|
||||||
language = MetadataField('language')
|
language = MetadataField('language')
|
||||||
@ -435,8 +436,8 @@ class OPF(object):
|
|||||||
series = MetadataField('series', is_dc=False)
|
series = MetadataField('series', is_dc=False)
|
||||||
series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1)
|
series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1)
|
||||||
rating = MetadataField('rating', is_dc=False, formatter=int)
|
rating = MetadataField('rating', is_dc=False, formatter=int)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True):
|
def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True):
|
||||||
if not hasattr(stream, 'read'):
|
if not hasattr(stream, 'read'):
|
||||||
stream = open(stream, 'rb')
|
stream = open(stream, 'rb')
|
||||||
@ -463,7 +464,7 @@ class OPF(object):
|
|||||||
self.guide = Guide.from_opf_guide(guide, basedir) if guide else None
|
self.guide = Guide.from_opf_guide(guide, basedir) if guide else None
|
||||||
self.cover_data = (None, None)
|
self.cover_data = (None, None)
|
||||||
self.find_toc()
|
self.find_toc()
|
||||||
|
|
||||||
def find_toc(self):
|
def find_toc(self):
|
||||||
self.toc = None
|
self.toc = None
|
||||||
try:
|
try:
|
||||||
@ -480,7 +481,7 @@ class OPF(object):
|
|||||||
for item in self.manifest:
|
for item in self.manifest:
|
||||||
if 'toc' in item.href().lower():
|
if 'toc' in item.href().lower():
|
||||||
toc = item.path
|
toc = item.path
|
||||||
|
|
||||||
if toc is None: return
|
if toc is None: return
|
||||||
self.toc = TOC(base_path=self.base_dir)
|
self.toc = TOC(base_path=self.base_dir)
|
||||||
if toc.lower() in ('ncx', 'ncxtoc'):
|
if toc.lower() in ('ncx', 'ncxtoc'):
|
||||||
@ -494,10 +495,10 @@ class OPF(object):
|
|||||||
else:
|
else:
|
||||||
self.toc.read_html_toc(toc)
|
self.toc.read_html_toc(toc)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_text(self, elem):
|
def get_text(self, elem):
|
||||||
return u''.join(self.CONTENT(elem) or self.TEXT(elem))
|
return u''.join(self.CONTENT(elem) or self.TEXT(elem))
|
||||||
|
|
||||||
@ -506,10 +507,10 @@ class OPF(object):
|
|||||||
elem.attrib['content'] = content
|
elem.attrib['content'] = content
|
||||||
else:
|
else:
|
||||||
elem.text = content
|
elem.text = content
|
||||||
|
|
||||||
def itermanifest(self):
|
def itermanifest(self):
|
||||||
return self.manifest_path(self.root)
|
return self.manifest_path(self.root)
|
||||||
|
|
||||||
def create_manifest_item(self, href, media_type):
|
def create_manifest_item(self, href, media_type):
|
||||||
ids = [i.get('id', None) for i in self.itermanifest()]
|
ids = [i.get('id', None) for i in self.itermanifest()]
|
||||||
id = None
|
id = None
|
||||||
@ -519,11 +520,11 @@ class OPF(object):
|
|||||||
break
|
break
|
||||||
if not media_type:
|
if not media_type:
|
||||||
media_type = 'application/xhtml+xml'
|
media_type = 'application/xhtml+xml'
|
||||||
ans = etree.Element('{%s}item'%self.NAMESPACES['opf'],
|
ans = etree.Element('{%s}item'%self.NAMESPACES['opf'],
|
||||||
attrib={'id':id, 'href':href, 'media-type':media_type})
|
attrib={'id':id, 'href':href, 'media-type':media_type})
|
||||||
ans.tail = '\n\t\t'
|
ans.tail = '\n\t\t'
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def replace_manifest_item(self, item, items):
|
def replace_manifest_item(self, item, items):
|
||||||
items = [self.create_manifest_item(*i) for i in items]
|
items = [self.create_manifest_item(*i) for i in items]
|
||||||
for i, item2 in enumerate(items):
|
for i, item2 in enumerate(items):
|
||||||
@ -532,7 +533,7 @@ class OPF(object):
|
|||||||
index = manifest.index(item)
|
index = manifest.index(item)
|
||||||
manifest[index:index+1] = items
|
manifest[index:index+1] = items
|
||||||
return [i.get('id') for i in items]
|
return [i.get('id') for i in items]
|
||||||
|
|
||||||
def add_path_to_manifest(self, path, media_type):
|
def add_path_to_manifest(self, path, media_type):
|
||||||
has_path = False
|
has_path = False
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
@ -546,22 +547,22 @@ class OPF(object):
|
|||||||
item = self.create_manifest_item(href, media_type)
|
item = self.create_manifest_item(href, media_type)
|
||||||
manifest = self.manifest_ppath(self.root)[0]
|
manifest = self.manifest_ppath(self.root)[0]
|
||||||
manifest.append(item)
|
manifest.append(item)
|
||||||
|
|
||||||
def iterspine(self):
|
def iterspine(self):
|
||||||
return self.spine_path(self.root)
|
return self.spine_path(self.root)
|
||||||
|
|
||||||
def spine_items(self):
|
def spine_items(self):
|
||||||
for item in self.iterspine():
|
for item in self.iterspine():
|
||||||
idref = item.get('idref', '')
|
idref = item.get('idref', '')
|
||||||
for x in self.itermanifest():
|
for x in self.itermanifest():
|
||||||
if x.get('id', None) == idref:
|
if x.get('id', None) == idref:
|
||||||
yield x.get('href', '')
|
yield x.get('href', '')
|
||||||
|
|
||||||
def create_spine_item(self, idref):
|
def create_spine_item(self, idref):
|
||||||
ans = etree.Element('{%s}itemref'%self.NAMESPACES['opf'], idref=idref)
|
ans = etree.Element('{%s}itemref'%self.NAMESPACES['opf'], idref=idref)
|
||||||
ans.tail = '\n\t\t'
|
ans.tail = '\n\t\t'
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def replace_spine_items_by_idref(self, idref, new_idrefs):
|
def replace_spine_items_by_idref(self, idref, new_idrefs):
|
||||||
items = list(map(self.create_spine_item, new_idrefs))
|
items = list(map(self.create_spine_item, new_idrefs))
|
||||||
spine = self.XPath('/opf:package/*[re:match(name(), "spine", "i")]')(self.root)[0]
|
spine = self.XPath('/opf:package/*[re:match(name(), "spine", "i")]')(self.root)[0]
|
||||||
@ -569,31 +570,31 @@ class OPF(object):
|
|||||||
for x in old:
|
for x in old:
|
||||||
i = spine.index(x)
|
i = spine.index(x)
|
||||||
spine[i:i+1] = items
|
spine[i:i+1] = items
|
||||||
|
|
||||||
def create_guide_element(self):
|
def create_guide_element(self):
|
||||||
e = etree.SubElement(self.root, '{%s}guide'%self.NAMESPACES['opf'])
|
e = etree.SubElement(self.root, '{%s}guide'%self.NAMESPACES['opf'])
|
||||||
e.text = '\n '
|
e.text = '\n '
|
||||||
e.tail = '\n'
|
e.tail = '\n'
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def remove_guide(self):
|
def remove_guide(self):
|
||||||
self.guide = None
|
self.guide = None
|
||||||
for g in self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'}):
|
for g in self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'}):
|
||||||
self.root.remove(g)
|
self.root.remove(g)
|
||||||
|
|
||||||
def create_guide_item(self, type, title, href):
|
def create_guide_item(self, type, title, href):
|
||||||
e = etree.Element('{%s}reference'%self.NAMESPACES['opf'],
|
e = etree.Element('{%s}reference'%self.NAMESPACES['opf'],
|
||||||
type=type, title=title, href=href)
|
type=type, title=title, href=href)
|
||||||
e.tail='\n'
|
e.tail='\n'
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def add_guide_item(self, type, title, href):
|
def add_guide_item(self, type, title, href):
|
||||||
g = self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'})[0]
|
g = self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'})[0]
|
||||||
g.append(self.create_guide_item(type, title, href))
|
g.append(self.create_guide_item(type, title, href))
|
||||||
|
|
||||||
def iterguide(self):
|
def iterguide(self):
|
||||||
return self.guide_path(self.root)
|
return self.guide_path(self.root)
|
||||||
|
|
||||||
def unquote_urls(self):
|
def unquote_urls(self):
|
||||||
def get_href(item):
|
def get_href(item):
|
||||||
raw = unquote(item.get('href', ''))
|
raw = unquote(item.get('href', ''))
|
||||||
@ -604,16 +605,16 @@ class OPF(object):
|
|||||||
item.set('href', get_href(item))
|
item.set('href', get_href(item))
|
||||||
for item in self.iterguide():
|
for item in self.iterguide():
|
||||||
item.set('href', get_href(item))
|
item.set('href', get_href(item))
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def authors():
|
def authors():
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
ans = []
|
ans = []
|
||||||
for elem in self.authors_path(self.metadata):
|
for elem in self.authors_path(self.metadata):
|
||||||
ans.extend([x.strip() for x in self.get_text(elem).split(',')])
|
ans.extend([x.strip() for x in self.get_text(elem).split(',')])
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
remove = list(self.authors_path(self.metadata))
|
remove = list(self.authors_path(self.metadata))
|
||||||
for elem in remove:
|
for elem in remove:
|
||||||
@ -622,12 +623,12 @@ class OPF(object):
|
|||||||
attrib = {'{%s}role'%self.NAMESPACES['opf']: 'aut'}
|
attrib = {'{%s}role'%self.NAMESPACES['opf']: 'aut'}
|
||||||
elem = self.create_metadata_element('creator', attrib=attrib)
|
elem = self.create_metadata_element('creator', attrib=attrib)
|
||||||
self.set_text(elem, author)
|
self.set_text(elem, author)
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def author_sort():
|
def author_sort():
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
matches = self.authors_path(self.metadata)
|
matches = self.authors_path(self.metadata)
|
||||||
if matches:
|
if matches:
|
||||||
@ -637,39 +638,59 @@ class OPF(object):
|
|||||||
ans = match.get('file-as', None)
|
ans = match.get('file-as', None)
|
||||||
if ans:
|
if ans:
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
matches = self.authors_path(self.metadata)
|
matches = self.authors_path(self.metadata)
|
||||||
if matches:
|
if matches:
|
||||||
matches[0].set('file-as', unicode(val))
|
matches[0].set('file-as', unicode(val))
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def title_sort():
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
matches = self.title_path(self.metadata)
|
||||||
|
if matches:
|
||||||
|
for match in matches:
|
||||||
|
ans = match.get('{%s}file-as'%self.NAMESPACES['opf'], None)
|
||||||
|
if not ans:
|
||||||
|
ans = match.get('file-as', None)
|
||||||
|
if ans:
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
matches = self.title_path(self.metadata)
|
||||||
|
if matches:
|
||||||
|
matches[0].set('file-as', unicode(val))
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def tags():
|
def tags():
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
ans = []
|
ans = []
|
||||||
for tag in self.tags_path(self.metadata):
|
for tag in self.tags_path(self.metadata):
|
||||||
ans.append(self.get_text(tag))
|
ans.append(self.get_text(tag))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
for tag in list(self.tags_path(self.metadata)):
|
for tag in list(self.tags_path(self.metadata)):
|
||||||
self.metadata.remove(tag)
|
self.metadata.remove(tag)
|
||||||
for tag in val:
|
for tag in val:
|
||||||
elem = self.create_metadata_element('subject')
|
elem = self.create_metadata_element('subject')
|
||||||
self.set_text(elem, unicode(tag))
|
self.set_text(elem, unicode(tag))
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def isbn():
|
def isbn():
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
for match in self.isbn_path(self.metadata):
|
for match in self.isbn_path(self.metadata):
|
||||||
return self.get_text(match) or None
|
return self.get_text(match) or None
|
||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
matches = self.isbn_path(self.metadata)
|
matches = self.isbn_path(self.metadata)
|
||||||
if not matches:
|
if not matches:
|
||||||
@ -682,11 +703,11 @@ class OPF(object):
|
|||||||
|
|
||||||
@apply
|
@apply
|
||||||
def application_id():
|
def application_id():
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
for match in self.application_id_path(self.metadata):
|
for match in self.application_id_path(self.metadata):
|
||||||
return self.get_text(match) or None
|
return self.get_text(match) or None
|
||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
matches = self.application_id_path(self.metadata)
|
matches = self.application_id_path(self.metadata)
|
||||||
if not matches:
|
if not matches:
|
||||||
@ -696,14 +717,14 @@ class OPF(object):
|
|||||||
self.set_text(matches[0], unicode(val))
|
self.set_text(matches[0], unicode(val))
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def book_producer():
|
def book_producer():
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
for match in self.bkp_path(self.metadata):
|
for match in self.bkp_path(self.metadata):
|
||||||
return self.get_text(match) or None
|
return self.get_text(match) or None
|
||||||
|
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
matches = self.bkp_path(self.metadata)
|
matches = self.bkp_path(self.metadata)
|
||||||
if not matches:
|
if not matches:
|
||||||
@ -712,8 +733,8 @@ class OPF(object):
|
|||||||
attrib=attrib)]
|
attrib=attrib)]
|
||||||
self.set_text(matches[0], unicode(val))
|
self.set_text(matches[0], unicode(val))
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
|
||||||
def guess_cover(self):
|
def guess_cover(self):
|
||||||
'''
|
'''
|
||||||
Try to guess a cover. Needed for some old/badly formed OPF files.
|
Try to guess a cover. Needed for some old/badly formed OPF files.
|
||||||
@ -733,11 +754,11 @@ class OPF(object):
|
|||||||
cpath = os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK)
|
cpath = os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK)
|
||||||
if os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK):
|
if os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK):
|
||||||
return cpath
|
return cpath
|
||||||
|
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
def cover():
|
def cover():
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
if self.guide is not None:
|
if self.guide is not None:
|
||||||
for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
|
for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
|
||||||
@ -748,19 +769,19 @@ class OPF(object):
|
|||||||
return self.guess_cover()
|
return self.guess_cover()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def fset(self, path):
|
def fset(self, path):
|
||||||
if self.guide is not None:
|
if self.guide is not None:
|
||||||
self.guide.set_cover(path)
|
self.guide.set_cover(path)
|
||||||
for item in list(self.iterguide()):
|
for item in list(self.iterguide()):
|
||||||
if 'cover' in item.get('type', ''):
|
if 'cover' in item.get('type', ''):
|
||||||
item.getparent().remove(item)
|
item.getparent().remove(item)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
g = self.create_guide_element()
|
g = self.create_guide_element()
|
||||||
self.guide = Guide()
|
self.guide = Guide()
|
||||||
self.guide.set_cover(path)
|
self.guide.set_cover(path)
|
||||||
etree.SubElement(g, 'opf:reference', nsmap=self.NAMESPACES,
|
etree.SubElement(g, 'opf:reference', nsmap=self.NAMESPACES,
|
||||||
attrib={'type':'cover', 'href':self.guide[-1].href()})
|
attrib={'type':'cover', 'href':self.guide[-1].href()})
|
||||||
id = self.manifest.id_for_path(self.cover)
|
id = self.manifest.id_for_path(self.cover)
|
||||||
if id is None:
|
if id is None:
|
||||||
@ -768,14 +789,14 @@ class OPF(object):
|
|||||||
for item in self.guide:
|
for item in self.guide:
|
||||||
if item.type.lower() == t:
|
if item.type.lower() == t:
|
||||||
self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0])
|
self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0])
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def get_metadata_element(self, name):
|
def get_metadata_element(self, name):
|
||||||
matches = self.metadata_elem_path(self.metadata, name=name)
|
matches = self.metadata_elem_path(self.metadata, name=name)
|
||||||
if matches:
|
if matches:
|
||||||
return matches[-1]
|
return matches[-1]
|
||||||
|
|
||||||
def create_metadata_element(self, name, attrib=None, is_dc=True):
|
def create_metadata_element(self, name, attrib=None, is_dc=True):
|
||||||
if is_dc:
|
if is_dc:
|
||||||
name = '{%s}%s' % (self.NAMESPACES['dc'], name)
|
name = '{%s}%s' % (self.NAMESPACES['dc'], name)
|
||||||
@ -787,24 +808,25 @@ class OPF(object):
|
|||||||
nsmap=self.NAMESPACES)
|
nsmap=self.NAMESPACES)
|
||||||
elem.tail = '\n'
|
elem.tail = '\n'
|
||||||
return elem
|
return elem
|
||||||
|
|
||||||
def render(self, encoding='utf-8'):
|
def render(self, encoding='utf-8'):
|
||||||
raw = etree.tostring(self.root, encoding=encoding, pretty_print=True)
|
raw = etree.tostring(self.root, encoding=encoding, pretty_print=True)
|
||||||
if not raw.lstrip().startswith('<?xml '):
|
if not raw.lstrip().startswith('<?xml '):
|
||||||
raw = '<?xml version="1.0" encoding="%s"?>\n'%encoding.upper()+raw
|
raw = '<?xml version="1.0" encoding="%s"?>\n'%encoding.upper()+raw
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
def smart_update(self, mi):
|
def smart_update(self, mi):
|
||||||
for attr in ('author_sort', 'title_sort', 'comments', 'category',
|
for attr in ('title', 'authors', 'author_sort', 'title_sort',
|
||||||
'publisher', 'series', 'series_index', 'rating',
|
'publisher', 'series', 'series_index', 'rating',
|
||||||
'isbn', 'language', 'tags', 'title', 'authors'):
|
'isbn', 'language', 'tags', 'category', 'comments'):
|
||||||
val = getattr(mi, attr, None)
|
val = getattr(mi, attr, None)
|
||||||
if val is not None and val != [] and val != (None, None):
|
if val is not None and val != [] and val != (None, None):
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
|
print self.render()
|
||||||
|
|
||||||
|
|
||||||
class OPFCreator(MetaInformation):
|
class OPFCreator(MetaInformation):
|
||||||
|
|
||||||
def __init__(self, base_path, *args, **kwargs):
|
def __init__(self, base_path, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Initialize.
|
Initialize.
|
||||||
@ -824,63 +846,63 @@ class OPFCreator(MetaInformation):
|
|||||||
self.guide = Guide()
|
self.guide = Guide()
|
||||||
if self.cover:
|
if self.cover:
|
||||||
self.guide.set_cover(self.cover)
|
self.guide.set_cover(self.cover)
|
||||||
|
|
||||||
|
|
||||||
def create_manifest(self, entries):
|
def create_manifest(self, entries):
|
||||||
'''
|
'''
|
||||||
Create <manifest>
|
Create <manifest>
|
||||||
|
|
||||||
`entries`: List of (path, mime-type) If mime-type is None it is autodetected
|
`entries`: List of (path, mime-type) If mime-type is None it is autodetected
|
||||||
'''
|
'''
|
||||||
entries = map(lambda x: x if os.path.isabs(x[0]) else
|
entries = map(lambda x: x if os.path.isabs(x[0]) else
|
||||||
(os.path.abspath(os.path.join(self.base_path, x[0])), x[1]),
|
(os.path.abspath(os.path.join(self.base_path, x[0])), x[1]),
|
||||||
entries)
|
entries)
|
||||||
self.manifest = Manifest.from_paths(entries)
|
self.manifest = Manifest.from_paths(entries)
|
||||||
self.manifest.set_basedir(self.base_path)
|
self.manifest.set_basedir(self.base_path)
|
||||||
|
|
||||||
def create_manifest_from_files_in(self, files_and_dirs):
|
def create_manifest_from_files_in(self, files_and_dirs):
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
def dodir(dir):
|
def dodir(dir):
|
||||||
for spec in os.walk(dir):
|
for spec in os.walk(dir):
|
||||||
root, files = spec[0], spec[-1]
|
root, files = spec[0], spec[-1]
|
||||||
for name in files:
|
for name in files:
|
||||||
path = os.path.join(root, name)
|
path = os.path.join(root, name)
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
entries.append((path, None))
|
entries.append((path, None))
|
||||||
|
|
||||||
for i in files_and_dirs:
|
for i in files_and_dirs:
|
||||||
if os.path.isdir(i):
|
if os.path.isdir(i):
|
||||||
dodir(i)
|
dodir(i)
|
||||||
else:
|
else:
|
||||||
entries.append((i, None))
|
entries.append((i, None))
|
||||||
|
|
||||||
self.create_manifest(entries)
|
self.create_manifest(entries)
|
||||||
|
|
||||||
def create_spine(self, entries):
|
def create_spine(self, entries):
|
||||||
'''
|
'''
|
||||||
Create the <spine> element. Must first call :method:`create_manifest`.
|
Create the <spine> element. Must first call :method:`create_manifest`.
|
||||||
|
|
||||||
`entries`: List of paths
|
`entries`: List of paths
|
||||||
'''
|
'''
|
||||||
entries = map(lambda x: x if os.path.isabs(x) else
|
entries = map(lambda x: x if os.path.isabs(x) else
|
||||||
os.path.abspath(os.path.join(self.base_path, x)), entries)
|
os.path.abspath(os.path.join(self.base_path, x)), entries)
|
||||||
self.spine = Spine.from_paths(entries, self.manifest)
|
self.spine = Spine.from_paths(entries, self.manifest)
|
||||||
|
|
||||||
def set_toc(self, toc):
|
def set_toc(self, toc):
|
||||||
'''
|
'''
|
||||||
Set the toc. You must call :method:`create_spine` before calling this
|
Set the toc. You must call :method:`create_spine` before calling this
|
||||||
method.
|
method.
|
||||||
|
|
||||||
:param toc: A :class:`TOC` object
|
:param toc: A :class:`TOC` object
|
||||||
'''
|
'''
|
||||||
self.toc = toc
|
self.toc = toc
|
||||||
|
|
||||||
def create_guide(self, guide_element):
|
def create_guide(self, guide_element):
|
||||||
self.guide = Guide.from_opf_guide(guide_element, self.base_path)
|
self.guide = Guide.from_opf_guide(guide_element, self.base_path)
|
||||||
self.guide.set_basedir(self.base_path)
|
self.guide.set_basedir(self.base_path)
|
||||||
|
|
||||||
def render(self, opf_stream=sys.stdout, ncx_stream=None,
|
def render(self, opf_stream=sys.stdout, ncx_stream=None,
|
||||||
ncx_manifest_entry=None):
|
ncx_manifest_entry=None):
|
||||||
from calibre.resources import opf_template
|
from calibre.resources import opf_template
|
||||||
from calibre.utils.genshi.template import MarkupTemplate
|
from calibre.utils.genshi.template import MarkupTemplate
|
||||||
@ -914,14 +936,14 @@ class OPFCreator(MetaInformation):
|
|||||||
|
|
||||||
|
|
||||||
class OPFTest(unittest.TestCase):
|
class OPFTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.stream = cStringIO.StringIO(
|
self.stream = cStringIO.StringIO(
|
||||||
'''\
|
'''\
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<package version="2.0" xmlns="http://www.idpf.org/2007/opf" >
|
<package version="2.0" xmlns="http://www.idpf.org/2007/opf" >
|
||||||
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
|
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
|
||||||
<dc:title>A Cool & © ß Title</dc:title>
|
<dc:title opf:file-as="Wow">A Cool & © ß Title</dc:title>
|
||||||
<creator opf:role="aut" file-as="Monkey">Monkey Kitchen, Next</creator>
|
<creator opf:role="aut" file-as="Monkey">Monkey Kitchen, Next</creator>
|
||||||
<dc:subject>One</dc:subject><dc:subject>Two</dc:subject>
|
<dc:subject>One</dc:subject><dc:subject>Two</dc:subject>
|
||||||
<dc:identifier scheme="ISBN">123456789</dc:identifier>
|
<dc:identifier scheme="ISBN">123456789</dc:identifier>
|
||||||
@ -936,33 +958,48 @@ class OPFTest(unittest.TestCase):
|
|||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
self.opf = OPF(self.stream, os.getcwd())
|
self.opf = OPF(self.stream, os.getcwd())
|
||||||
|
|
||||||
def testReading(self):
|
def testReading(self, opf=None):
|
||||||
opf = self.opf
|
if opf is None:
|
||||||
|
opf = self.opf
|
||||||
self.assertEqual(opf.title, u'A Cool & \xa9 \xdf Title')
|
self.assertEqual(opf.title, u'A Cool & \xa9 \xdf Title')
|
||||||
self.assertEqual(opf.authors, u'Monkey Kitchen,Next'.split(','))
|
self.assertEqual(opf.authors, u'Monkey Kitchen,Next'.split(','))
|
||||||
self.assertEqual(opf.author_sort, 'Monkey')
|
self.assertEqual(opf.author_sort, 'Monkey')
|
||||||
|
self.assertEqual(opf.title_sort, 'Wow')
|
||||||
self.assertEqual(opf.tags, ['One', 'Two'])
|
self.assertEqual(opf.tags, ['One', 'Two'])
|
||||||
self.assertEqual(opf.isbn, '123456789')
|
self.assertEqual(opf.isbn, '123456789')
|
||||||
self.assertEqual(opf.series, 'A one book series')
|
self.assertEqual(opf.series, 'A one book series')
|
||||||
self.assertEqual(opf.series_index, None)
|
self.assertEqual(opf.series_index, None)
|
||||||
self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b')
|
self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b')
|
||||||
|
|
||||||
def testWriting(self):
|
def testWriting(self):
|
||||||
for test in [('title', 'New & Title'), ('authors', ['One', 'Two']),
|
for test in [('title', 'New & Title'), ('authors', ['One', 'Two']),
|
||||||
('author_sort', "Kitchen"), ('tags', ['Three']),
|
('author_sort', "Kitchen"), ('tags', ['Three']),
|
||||||
('isbn', 'a'), ('rating', 3), ('series_index', 1)]:
|
('isbn', 'a'), ('rating', 3), ('series_index', 1),
|
||||||
|
('title_sort', 'ts')]:
|
||||||
setattr(self.opf, *test)
|
setattr(self.opf, *test)
|
||||||
self.assertEqual(getattr(self.opf, test[0]), test[1])
|
self.assertEqual(getattr(self.opf, test[0]), test[1])
|
||||||
|
|
||||||
self.opf.render()
|
self.opf.render()
|
||||||
|
|
||||||
|
def testCreator(self):
|
||||||
|
opf = OPFCreator(os.getcwd(), self.opf)
|
||||||
|
buf = cStringIO.StringIO()
|
||||||
|
opf.render(buf)
|
||||||
|
raw = buf.getvalue()
|
||||||
|
self.testReading(opf=OPF(cStringIO.StringIO(raw), os.getcwd()))
|
||||||
|
|
||||||
|
def testSmartUpdate(self):
|
||||||
|
self.opf.smart_update(self.opf)
|
||||||
|
self.testReading()
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
return unittest.TestLoader().loadTestsFromTestCase(OPFTest)
|
return unittest.TestLoader().loadTestsFromTestCase(OPFTest)
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
unittest.TextTestRunner(verbosity=2).run(suite())
|
||||||
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
parser = get_parser('opf')
|
parser = get_parser('opf')
|
||||||
parser.add_option('--language', default=None, help=_('Set the dc:language field'))
|
parser.add_option('--language', default=None, help=_('Set the dc:language field'))
|
||||||
@ -1010,7 +1047,7 @@ def main(args=sys.argv):
|
|||||||
print MetaInformation(OPF(open(opfpath, 'rb'), basedir))
|
print MetaInformation(OPF(open(opfpath, 'rb'), basedir))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -15,7 +15,7 @@ from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock
|
|||||||
from PyQt4.QtGui import QApplication, QPixmap, QImage
|
from PyQt4.QtGui import QApplication, QPixmap, QImage
|
||||||
__app = None
|
__app = None
|
||||||
|
|
||||||
from calibre.library import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.library.sqlite import connect, IntegrityError
|
from calibre.library.sqlite import connect, IntegrityError
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
@ -27,15 +27,6 @@ from calibre.customize.ui import run_plugins_on_import
|
|||||||
from calibre import sanitize_file_name
|
from calibre import sanitize_file_name
|
||||||
|
|
||||||
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
||||||
iscaseinsensitive = iswindows or isosx
|
|
||||||
|
|
||||||
def normpath(x):
|
|
||||||
# The builtin os.path.normcase doesn't work on OS X
|
|
||||||
x = os.path.abspath(x)
|
|
||||||
if iscaseinsensitive:
|
|
||||||
x = x.lower()
|
|
||||||
return x
|
|
||||||
|
|
||||||
|
|
||||||
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
|
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
|
||||||
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
|
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
|
||||||
@ -355,7 +346,8 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
if isinstance(self.dbpath, unicode):
|
if isinstance(self.dbpath, unicode):
|
||||||
self.dbpath = self.dbpath.encode(filesystem_encoding)
|
self.dbpath = self.dbpath.encode(filesystem_encoding)
|
||||||
self.connect()
|
self.connect()
|
||||||
self.is_case_sensitive = not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
|
self.is_case_sensitive = not iswindows and not isosx and \
|
||||||
|
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
|
||||||
# Upgrade database
|
# Upgrade database
|
||||||
while True:
|
while True:
|
||||||
meth = getattr(self, 'upgrade_version_%d'%self.user_version, None)
|
meth = getattr(self, 'upgrade_version_%d'%self.user_version, None)
|
||||||
@ -489,6 +481,16 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
name = title + ' - ' + author
|
name = title + ' - ' + author
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
def rmtree(self, path):
|
||||||
|
if not self.normpath(self.library_path).startswith(self.normpath(path)):
|
||||||
|
shutil.rmtree(path)
|
||||||
|
|
||||||
|
def normpath(self, path):
|
||||||
|
path = os.path.abspath(os.path.realpath(path))
|
||||||
|
if not self.is_case_sensitive:
|
||||||
|
path = path.lower()
|
||||||
|
return path
|
||||||
|
|
||||||
def set_path(self, index, index_is_id=False):
|
def set_path(self, index, index_is_id=False):
|
||||||
'''
|
'''
|
||||||
Set the path to the directory containing this books files based on its
|
Set the path to the directory containing this books files based on its
|
||||||
@ -532,11 +534,11 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
self.data.set(id, FIELD_MAP['path'], path, row_is_id=True)
|
self.data.set(id, FIELD_MAP['path'], path, row_is_id=True)
|
||||||
# Delete not needed directories
|
# Delete not needed directories
|
||||||
if current_path and os.path.exists(spath):
|
if current_path and os.path.exists(spath):
|
||||||
if normpath(spath) != normpath(tpath):
|
if self.normpath(spath) != self.normpath(tpath):
|
||||||
shutil.rmtree(spath)
|
self.rmtree(spath)
|
||||||
parent = os.path.dirname(spath)
|
parent = os.path.dirname(spath)
|
||||||
if len(os.listdir(parent)) == 0:
|
if len(os.listdir(parent)) == 0:
|
||||||
shutil.rmtree(parent)
|
self.rmtree(parent)
|
||||||
|
|
||||||
def add_listener(self, listener):
|
def add_listener(self, listener):
|
||||||
'''
|
'''
|
||||||
@ -699,10 +701,10 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
path = os.path.join(self.library_path, self.path(id, index_is_id=True))
|
path = os.path.join(self.library_path, self.path(id, index_is_id=True))
|
||||||
self.data.remove(id)
|
self.data.remove(id)
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
shutil.rmtree(path)
|
self.rmtree(path)
|
||||||
parent = os.path.dirname(path)
|
parent = os.path.dirname(path)
|
||||||
if len(os.listdir(parent)) == 0:
|
if len(os.listdir(parent)) == 0:
|
||||||
shutil.rmtree(parent)
|
self.rmtree(parent)
|
||||||
self.conn.execute('DELETE FROM books WHERE id=?', (id,))
|
self.conn.execute('DELETE FROM books WHERE id=?', (id,))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.clean()
|
self.clean()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user