An invalid database already exists at %s, delete it before trying to move the existing database.
Error: %s')%(newloc, str(err)))
d.exec_()
- newloc = self.database_path
- self.database_path = newloc
- settings = Settings()
- settings.setValue("database path", QVariant(self.database_path))
+ self.library_path = self.library_view.model().db.library_path
+ Settings().set('library path', self.library_path)
except Exception, err:
traceback.print_exc()
d = error_dialog(self, _('Could not move database'), unicode(err))
@@ -990,7 +992,6 @@ class Main(MainWindow, Ui_MainWindow):
self.status_bar.clearMessage()
self.search.clear_to_help()
self.status_bar.reset_info()
- self.library_view.set_database(self.database_path)
self.library_view.sortByColumn(3, Qt.DescendingOrder)
self.library_view.resizeRowsToContents()
if hasattr(d, 'directories'):
@@ -1085,24 +1086,41 @@ class Main(MainWindow, Ui_MainWindow):
ConversionErrorDialog(self, 'Conversion Error', msg, show=True)
+ def initialize_database(self, settings):
+ self.library_path = settings.get('library path', None)
+ self.olddb = None
+ if self.library_path is None: # Need to migrate to new database layout
+ dbpath = os.path.join(os.path.expanduser('~'), 'library1.db').decode(sys.getfilesystemencoding())
+ self.database_path = qstring_to_unicode(settings.value("database path",
+ QVariant(QString.fromUtf8(dbpath.encode('utf-8')))).toString())
+ if not os.access(os.path.dirname(self.database_path), os.W_OK):
+ error_dialog(self, _('Database does not exist'), _('The directory in which the database should be: %s no longer exists. Please choose a new database location.')%self.database_path).exec_()
+ self.database_path = choose_dir(self, 'database path dialog', 'Choose new location for database')
+ if not self.database_path:
+ self.database_path = os.path.expanduser('~').decode(sys.getfilesystemencoding())
+ if not os.path.exists(self.database_path):
+ os.makedirs(self.database_path)
+ self.database_path = os.path.join(self.database_path, 'library1.db')
+ settings.setValue('database path', QVariant(QString.fromUtf8(self.database_path.encode('utf-8'))))
+ home = os.path.dirname(self.database_path)
+ if not os.path.exists(home):
+ home = os.getcwd()
+ from PyQt4.QtGui import QFileDialog
+ dir = qstring_to_unicode(QFileDialog.getExistingDirectory(self, _('Choose a location for your ebook library.'), home))
+ if not dir:
+ dir = os.path.dirname(self.database_path)
+ self.library_path = os.path.abspath(dir)
+ self.olddb = LibraryDatabase(self.database_path)
+
+
+
def read_settings(self):
settings = Settings()
settings.beginGroup("Main Window")
geometry = settings.value('main window geometry', QVariant()).toByteArray()
self.restoreGeometry(geometry)
settings.endGroup()
- dbpath = os.path.join(os.path.expanduser('~'), 'library1.db').decode(sys.getfilesystemencoding())
- self.database_path = qstring_to_unicode(settings.value("database path",
- QVariant(QString.fromUtf8(dbpath.encode('utf-8')))).toString())
- if not os.access(os.path.dirname(self.database_path), os.W_OK):
- error_dialog(self, _('Database does not exist'), _('The directory in which the database should be: %s no longer exists. Please choose a new database location.')%self.database_path).exec_()
- self.database_path = choose_dir(self, 'database path dialog', 'Choose new location for database')
- if not self.database_path:
- self.database_path = os.path.expanduser('~').decode(sys.getfilesystemencoding())
- if not os.path.exists(self.database_path):
- os.makedirs(self.database_path)
- self.database_path = os.path.join(self.database_path, 'library1.db')
- settings.setValue('database path', QVariant(QString.fromUtf8(self.database_path.encode('utf-8'))))
+ self.initialize_database(settings)
set_sidebar_directories(None)
set_filename_pat(qstring_to_unicode(settings.value('filename pattern', QVariant(get_filename_pat())).toString()))
self.tool_bar.setIconSize(settings.value('toolbar icon size', QVariant(QSize(48, 48))).toSize())
@@ -1138,9 +1156,11 @@ class Main(MainWindow, Ui_MainWindow):
self.job_manager.terminate_all_jobs()
self.write_settings()
self.detector.keep_going = False
+ self.cover_cache.stop()
self.hide()
time.sleep(2)
self.detector.terminate()
+ self.cover_cache.terminate()
e.accept()
def update_found(self, version):
diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py
index b13a7a3680..f440ba108a 100644
--- a/src/calibre/library/cli.py
+++ b/src/calibre/library/cli.py
@@ -10,26 +10,26 @@ Command line interface to the calibre database.
import sys, os
from textwrap import TextWrapper
-from PyQt4.QtCore import QVariant
-
from calibre import OptionParser, Settings, terminal_controller, preferred_encoding
from calibre.gui2 import SingleApplication
from calibre.ebooks.metadata.meta import get_metadata
-from calibre.library.database import LibraryDatabase, text_to_tokens
+from calibre.library.database2 import LibraryDatabase2
+from calibre.library.database import text_to_tokens
FIELDS = set(['title', 'authors', 'publisher', 'rating', 'timestamp', 'size', 'tags', 'comments', 'series', 'series_index', 'formats'])
def get_parser(usage):
parser = OptionParser(usage)
go = parser.add_option_group('GLOBAL OPTIONS')
- go.add_option('--database', default=None, help=_('Path to the calibre database. Default is to use the path stored in the settings.'))
+ go.add_option('--library-path', default=None, help=_('Path to the calibre library. Default is to use the path stored in the settings.'))
return parser
def get_db(dbpath, options):
- if options.database is not None:
- dbpath = options.database
+ if options.library_path is not None:
+ dbpath = options.library_path
dbpath = os.path.abspath(dbpath)
- return LibraryDatabase(dbpath, row_factory=True)
+ print _('Using library at'), dbpath
+ return LibraryDatabase2(dbpath, row_factory=True)
def do_list(db, fields, sort_by, ascending, search_text):
db.refresh(sort_by, ascending)
@@ -330,7 +330,7 @@ For help on an individual command: %%prog command --help
return 1
command = eval('command_'+args[1])
- dbpath = unicode(Settings().value('database path', QVariant(os.path.expanduser('~/library1.db'))).toString())
+ dbpath = Settings().get('library path', os.path.expanduser('~'))
return command(args[2:], dbpath)
diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py
index 580e4a327d..a09505b007 100644
--- a/src/calibre/library/database.py
+++ b/src/calibre/library/database.py
@@ -794,14 +794,6 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
conn.commit()
- def __del__(self):
- global _lock_file
- import os
- if _lock_file is not None:
- _lock_file.close()
- if os.path.exists(_lock_file.name):
- os.unlink(_lock_file.name)
-
def __init__(self, dbpath, row_factory=False):
self.dbpath = dbpath
self.conn = _connect(dbpath)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
new file mode 100644
index 0000000000..1c5eecad44
--- /dev/null
+++ b/src/calibre/library/database2.py
@@ -0,0 +1,523 @@
+#!/usr/bin/env python
+__license__ = 'GPL v3'
+__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
+__docformat__ = 'restructuredtext en'
+
+'''
+The database used to store ebook metadata
+'''
+import os, re, sys, shutil, cStringIO, glob, collections
+import sqlite3 as sqlite
+from itertools import repeat
+
+from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock
+from PyQt4.QtGui import QApplication, QPixmap, QImage
+__app = None
+
+from calibre.library.database import LibraryDatabase
+
+copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
+filesystem_encoding = sys.getfilesystemencoding()
+if filesystem_encoding is None: filesystem_encoding = 'utf-8'
+
+_filename_sanitize = re.compile(r'[\0\\|\?\*<":>\+\[\]/]')
+def sanitize_file_name(name, substitute='_'):
+ '''
+ Sanitize the filename `name`. All invalid characters are replaced by `substitute`.
+ The set of invalid characters is the union of the invalid characters in Windows,
+ OS X and Linux.
+ **WARNING:** This function also replaces path separators, so only pass file names
+ and not full paths to it.
+ *NOTE:* This function always returns byte strings, not unicode objects. The byte strings
+ are encoded in the filesystem encoding of the platform, or UTF-8.
+ '''
+ if isinstance(name, unicode):
+ name = name.encode(filesystem_encoding, 'ignore')
+ return _filename_sanitize.sub(substitute, name)
+
+class CoverCache(QThread):
+
+ def __init__(self, library_path, parent=None):
+ QThread.__init__(self, parent)
+ self.library_path = library_path
+ self.id_map = None
+ self.id_map_lock = QReadWriteLock()
+ self.load_queue = collections.deque()
+ self.load_queue_lock = QReadWriteLock(QReadWriteLock.Recursive)
+ self.cache = {}
+ self.cache_lock = QReadWriteLock()
+ self.keep_running = True
+
+ def build_id_map(self):
+ self.id_map_lock.lockForWrite()
+ self.id_map = {}
+ for f in glob.glob(os.path.join(self.library_path, '*', '* (*)', 'cover.jpg')):
+ c = os.path.basename(os.path.dirname(f))
+ try:
+ id = int(re.search(r'\((\d+)\)', c[c.rindex('('):]).group(1))
+ self.id_map[id] = f
+ except:
+ continue
+ self.id_map_lock.unlock()
+
+
+ def set_cache(self, ids):
+ self.cache_lock.lockForWrite()
+ already_loaded = set([])
+ for id in self.cache.keys():
+ if id in ids:
+ already_loaded.add(id)
+ else:
+ self.cache.pop(id)
+ self.cache_lock.unlock()
+ ids = [i for i in ids if i not in already_loaded]
+ self.load_queue_lock.lockForWrite()
+ self.load_queue = collections.deque(ids)
+ self.load_queue_lock.unlock()
+
+
+ def run(self):
+ while self.keep_running:
+ if self.id_map is None:
+ self.build_id_map()
+ while True:
+ self.load_queue_lock.lockForWrite()
+ try:
+ id = self.load_queue.popleft()
+ except IndexError:
+ break
+ finally:
+ self.load_queue_lock.unlock()
+
+ self.cache_lock.lockForRead()
+ need = True
+ if id in self.cache.keys():
+ need = False
+ self.cache_lock.unlock()
+ if not need:
+ continue
+ path = None
+ self.id_map_lock.lockForRead()
+ if id in self.id_map.keys():
+ path = self.id_map[id]
+ self.id_map_lock.unlock()
+ if path and os.access(path, os.R_OK):
+ try:
+ img = QImage()
+ data = open(path, 'rb').read()
+ img.loadFromData(data)
+ if img.isNull():
+ continue
+ except:
+ continue
+ self.cache_lock.lockForWrite()
+ self.cache[id] = img
+ self.cache_lock.unlock()
+
+ self.sleep(1)
+
+ def stop(self):
+ self.keep_running = False
+
+ def cover(self, id):
+ val = None
+ if self.cache_lock.tryLockForRead(50):
+ val = self.cache.get(id, None)
+ self.cache_lock.unlock()
+ return val
+
+ def clear_cache(self):
+ self.cache_lock.lockForWrite()
+ self.cache = {}
+ self.cache_lock.unlock()
+
+class Concatenate(object):
+ '''String concatenation aggregator for sqlite'''
+ def __init__(self, sep=','):
+ self.sep = sep
+ self.ans = ''
+
+ def step(self, value):
+ if value is not None:
+ self.ans += value + self.sep
+
+ def finalize(self):
+ if not self.ans:
+ return None
+ if self.sep:
+ return self.ans[:-len(self.sep)]
+ return self.ans
+
+
+class LibraryDatabase2(LibraryDatabase):
+ '''
+ An ebook metadata database that stores references to ebook files on disk.
+ '''
+ PATH_LIMIT = 40 if 'win32' in sys.platform else 100
+ @apply
+ def user_version():
+ doc = 'The user version of this database'
+ def fget(self):
+ return self.conn.execute('pragma user_version;').next()[0]
+ def fset(self, val):
+ self.conn.execute('pragma user_version=%d'%int(val))
+ self.conn.commit()
+ return property(doc=doc, fget=fget, fset=fset)
+
+ def connect(self):
+ if 'win32' in sys.platform and len(self.library_path) + 4*self.PATH_LIMIT + 10 > 259:
+ raise ValueError('Path to library too long. Must be less than %d characters.'%(259-4*self.PATH_LIMIT-10))
+ exists = os.path.exists(self.dbpath)
+ self.conn = sqlite.connect(self.dbpath,
+ detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
+ if exists and self.user_version == 0:
+ self.conn.close()
+ os.remove(self.dbpath)
+ self.conn = sqlite.connect(self.dbpath,
+ detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
+ self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)
+ self.conn.create_aggregate('concat', 1, Concatenate)
+ title_pat = re.compile('^(A|The|An)\s+', re.IGNORECASE)
+
+ def title_sort(title):
+ match = title_pat.search(title)
+ if match:
+ prep = match.group(1)
+ title = title.replace(prep, '') + ', ' + prep
+ return title.strip()
+
+ self.conn.create_function('title_sort', 1, title_sort)
+ if self.user_version == 0:
+ self.initialize_database()
+
+ def __init__(self, library_path, row_factory=False):
+ if not os.path.exists(library_path):
+ os.makedirs(library_path)
+ self.library_path = os.path.abspath(library_path)
+ self.row_factory = row_factory
+ self.dbpath = os.path.join(library_path, 'metadata.db')
+ if isinstance(self.dbpath, unicode):
+ self.dbpath = self.dbpath.encode(filesystem_encoding)
+ self.connect()
+
+
+ def initialize_database(self):
+ from calibre.resources import metadata_sqlite
+ self.conn.executescript(metadata_sqlite)
+ self.user_version = 1
+
+ def path(self, index, index_is_id=False):
+ 'Return the relative path to the directory containing this books files as a unicode string.'
+ id = index if index_is_id else self.id()
+ path = self.conn.execute('SELECT path FROM books WHERE id=?', (id,)).fetchone()[0].replace('/', os.sep)
+ return path
+
+
+ def set_path(self, index, index_is_id=False):
+ '''
+ Set the path to the directory containing this books files based on its
+ current title and author. If there was a previous directory, its contents
+ are copied and it is deleted.
+ '''
+ id = index if index_is_id else self.id(index)
+ authors = self.authors(id, index_is_id=True)
+ if not authors:
+ authors = _('Unknown')
+ author = sanitize_file_name(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding)
+ title = sanitize_file_name(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding)
+ path = author + '/' + title + ' (%d)'%id
+ current_path = self.path(id, index_is_id=True).replace(os.sep, '/')
+ if path == current_path:
+ return
+ tpath = os.path.join(self.library_path, *path.split('/'))
+ if not os.path.exists(tpath):
+ os.makedirs(tpath)
+ spath = os.path.join(self.library_path, *current_path.split('/'))
+ if current_path and os.path.exists(spath):
+ for f in os.listdir(spath):
+ copyfile(os.path.join(spath, f), os.path.join(tpath, f))
+ self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
+ self.conn.commit()
+ if current_path and os.path.exists(spath):
+ shutil.rmtree(spath)
+
+ def cover(self, index, index_is_id=False, as_file=False, as_image=False):
+ '''
+ Return the cover image as a bytestring (in JPEG format) or None.
+
+ `as_file` : If True return the image as an open file object
+ `as_image`: If True return the image as a QImage object
+ '''
+ id = index if index_is_id else self.id(index)
+ path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
+ if os.access(path, os.R_OK):
+ f = open(path, 'rb')
+ if as_image:
+ img = QImage()
+ img.loadFromData(f.read())
+ return img
+ return f if as_file else f.read()
+
+ def set_cover(self, id, data):
+ '''
+ Set the cover for this book.
+
+ `data`: Can be either a QImage, QPixmap, file object or bytestring
+ '''
+ path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
+ if callable(getattr(data, 'save', None)):
+ data.save(path)
+ else:
+ if not QCoreApplication.instance():
+ global __app
+ __app = QApplication([])
+ p = QPixmap()
+ if callable(getattr(data, 'read', None)):
+ data = data.read()
+ p.loadFromData(data)
+ p.save(path)
+
+ def format(self, index, format, index_is_id=False, as_file=False, mode='r+b'):
+ '''
+ Return the ebook format as a bytestring or `None` if the format doesn't exist,
+ or we don't have permission to write to the ebook file.
+
+ `as_file`: If True the ebook format is returned as a file object opened in `mode`
+ '''
+ id = index if index_is_id else self.id(index)
+ path = os.path.join(self.library_path, self.path(id, index_is_id=True))
+ name = self.conn.execute('SELECT name FROM data WHERE book=? AND format=?', (id, format)).fetchone()[0]
+ if name:
+ format = ('.' + format.lower()) if format else ''
+ path = os.path.join(path, name+format)
+ if os.access(path, os.R_OK|os.W_OK):
+ f = open(path, mode)
+ return f if as_file else f.read()
+
+ def add_format(self, index, format, stream, index_is_id=False):
+ id = index if index_is_id else self.id(index)
+ authors = self.authors(id, index_is_id=True)
+ if not authors:
+ authors = _('Unknown')
+ path = os.path.join(self.library_path, self.path(id, index_is_id=True))
+ author = sanitize_file_name(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding)
+ title = sanitize_file_name(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding)
+ name = self.conn.execute('SELECT name FROM data WHERE book=? AND format=?', (id, format)).fetchone()
+ if name:
+ self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, format))
+ name = title + ' - ' + author
+ ext = ('.' + format.lower()) if format else ''
+ shutil.copyfileobj(stream, open(os.path.join(path, name+ext), 'wb'))
+ stream.seek(0, 2)
+ size=stream.tell()
+ self.conn.execute('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)',
+ (id, format.upper(), size, name))
+ self.conn.commit()
+
+ def delete_book(self, id):
+ '''
+ Removes book from self.cache, self.data and underlying database.
+ '''
+ try:
+ self.cache.pop(self.index(id, cache=True))
+ self.data.pop(self.index(id, cache=False))
+ except TypeError: #If data and cache are the same object
+ pass
+ path = os.path.join(self.library_path, self.path(id, True))
+ if os.path.exists(path):
+ shutil.rmtree(path)
+ self.conn.execute('DELETE FROM books WHERE id=?', (id,))
+ self.conn.commit()
+
+ def remove_format(self, index, format, index_is_id=False):
+ id = index if index_is_id else self.id(index)
+ path = os.path.join(self.library_path, self.path(id, index_is_id=True))
+ name = self.conn.execute('SELECT name FROM data WHERE book=? AND format=?', (id, format)).fetchone()[0]
+ if name:
+ ext = ('.' + format.lower()) if format else ''
+ path = os.path.join(path, name+ext)
+ if os.access(path, os.W_OK):
+ os.unlink(path)
+ self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, format.upper()))
+ self.conn.commit()
+
+ def set_metadata(self, id, mi):
+ '''
+ Set metadata for the book `id` from the `MetaInformation` object `mi`
+ '''
+ if not mi.authors:
+ mi.authors = ['Unknown']
+ authors = []
+ for a in mi.authors:
+ authors += a.split('&')
+ self.set_authors(id, authors)
+ if mi.author_sort:
+ self.set_author_sort(id, mi.author_sort)
+ if mi.publisher:
+ self.set_publisher(id, mi.publisher)
+ if mi.rating:
+ self.set_rating(id, mi.rating)
+ if mi.series:
+ self.set_series(id, mi.series)
+ if mi.cover_data[1] is not None:
+ self.set_cover(id, mi.cover_data[1])
+ self.set_path(id, True)
+
+ def set_authors(self, id, authors):
+ '''
+ `authors`: A list of authors.
+ '''
+ self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
+ for a in authors:
+ if not a:
+ continue
+ a = a.strip()
+ author = self.conn.execute('SELECT id from authors WHERE name=?', (a,)).fetchone()
+ if author:
+ aid = author[0]
+ # Handle change of case
+ self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid))
+ else:
+ aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid
+ try:
+ self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)', (id, aid))
+ except sqlite.IntegrityError: # Sometimes books specify the same author twice in their metadata
+ pass
+ self.set_path(id, True)
+
+ def set_title(self, id, title):
+ if not title:
+ return
+ self.conn.execute('UPDATE books SET title=? WHERE id=?', (title, id))
+ self.set_path(id, True)
+
+ def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
+ '''
+ Add a book to the database. self.data and self.cache are not updated.
+ @param paths: List of paths to book files of file-like objects
+ '''
+ formats, metadata, uris = iter(formats), iter(metadata), iter(uris)
+ duplicates = []
+ for path in paths:
+ mi = metadata.next()
+ format = formats.next()
+ try:
+ uri = uris.next()
+ except StopIteration:
+ uri = None
+ if not add_duplicates and self.has_book(mi):
+ duplicates.append((path, format, mi, uri))
+ continue
+ series_index = 1 if mi.series_index is None else mi.series_index
+ aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
+ obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
+ (mi.title, uri, series_index, aus))
+ id = obj.lastrowid
+ self.set_path(id, True)
+ self.conn.commit()
+ self.set_metadata(id, mi)
+ stream = path if hasattr(path, 'read') else open(path, 'rb')
+ stream.seek(0)
+
+ self.add_format(id, format, stream, index_is_id=True)
+ if not hasattr(path, 'read'):
+ stream.close()
+ self.conn.commit()
+ if duplicates:
+ paths = tuple(duplicate[0] for duplicate in duplicates)
+ formats = tuple(duplicate[1] for duplicate in duplicates)
+ metadata = tuple(duplicate[2] for duplicate in duplicates)
+ uris = tuple(duplicate[3] for duplicate in duplicates)
+ return (paths, formats, metadata, uris)
+ return None
+
+ def import_book(self, mi, formats):
+ series_index = 1 if mi.series_index is None else mi.series_index
+ if not mi.authors:
+ mi.authors = ['Unknown']
+ aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
+ obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
+ (mi.title, None, series_index, aus))
+ id = obj.lastrowid
+ self.set_path(id, True)
+ self.set_metadata(id, mi)
+ for path in formats:
+ ext = os.path.splitext(path)[1][1:].lower()
+ stream = open(path, 'rb')
+ self.add_format(id, ext, stream, index_is_id=True)
+ self.conn.commit()
+
+ def move_library_to(self, newloc):
+ if not os.path.exists(newloc):
+ os.makedirs(newloc)
+ old_dirs = set([])
+ for book in self.conn.execute('SELECT id, path FROM books').fetchall():
+ path = book[1]
+ if not path:
+ continue
+ dir = path.split('/')[0]
+ srcdir = os.path.join(self.library_path, dir)
+ tdir = os.path.join(newloc, dir)
+ if os.path.exists(tdir):
+ shutil.rmtree(tdir)
+ shutil.copytree(srcdir, tdir)
+ old_dirs.add(srcdir)
+
+ dbpath = os.path.join(newloc, os.path.basename(self.dbpath))
+ shutil.copyfile(self.dbpath, dbpath)
+ opath = self.dbpath
+ self.conn.close()
+ self.library_path, self.dbpath = newloc, dbpath
+ self.connect()
+ try:
+ os.unlink(opath)
+ for dir in old_dirs:
+ shutil.rmtree(dir)
+ except:
+ pass
+
+
+ def migrate_old(self, db, progress):
+ header = _(u'
Migrating old database to ebook library in %s