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,7 +17,7 @@ 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):
|
||||||
@ -414,6 +414,7 @@ class OPF(object):
|
|||||||
|
|
||||||
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")]')
|
||||||
@ -645,6 +646,26 @@ class OPF(object):
|
|||||||
|
|
||||||
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():
|
||||||
|
|
||||||
@ -795,12 +816,13 @@ class OPF(object):
|
|||||||
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):
|
||||||
@ -921,7 +943,7 @@ class OPFTest(unittest.TestCase):
|
|||||||
<?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>
|
||||||
@ -937,11 +959,13 @@ 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):
|
||||||
|
if opf is None:
|
||||||
opf = self.opf
|
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')
|
||||||
@ -951,18 +975,31 @@ class OPFTest(unittest.TestCase):
|
|||||||
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'))
|
||||||
|
@ -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