mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add Books: Allow adding books from multiple ZIP/RAR archives, each containing many books, by right clicking on the Add Books and choosing "Add from Archive"
This commit is contained in:
parent
99d36ca88a
commit
7226d2c639
@ -51,7 +51,7 @@ Add books
|
|||||||
|
|
||||||
3. **Add books from directories, including sub-directories (Multiple books per directory, assumes every ebook file is a different book)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library. calibre assumes that each directory contains many books. All ebook files with the same name in a directory are assumed to be the same book in different formats. Ebooks with different names are added as different books.
|
3. **Add books from directories, including sub-directories (Multiple books per directory, assumes every ebook file is a different book)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library. calibre assumes that each directory contains many books. All ebook files with the same name in a directory are assumed to be the same book in different formats. Ebooks with different names are added as different books.
|
||||||
|
|
||||||
4. **Add multiple books from archive (ZIP/RAR)**: Allows you to add multiple ebooks that are stored inside a single ZIP or RAR file. It is a convenient shortcut that avoids having to first unzip the archive and then add the books via one of the above two options.
|
4. **Add multiple books from archive (ZIP/RAR)**: Allows you to add multiple ebooks that are stored inside the selected ZIP or RAR files. It is a convenient shortcut that avoids having to first unzip the archive and then add the books via one of the above two options.
|
||||||
|
|
||||||
5. **Add empty book. (Book Entry with no formats)**: Allows you to create a blank book record. This can be used to then manually fill out the information about a book that you may not have yet in your collection.
|
5. **Add empty book. (Book Entry with no formats)**: Allows you to create a blank book record. This can be used to then manually fill out the information about a book that you may not have yet in your collection.
|
||||||
|
|
||||||
|
@ -142,9 +142,9 @@ class AddAction(InterfaceAction):
|
|||||||
def add_archive(self, single):
|
def add_archive(self, single):
|
||||||
paths = choose_files(
|
paths = choose_files(
|
||||||
self.gui, 'recursive-archive-add', _('Choose archive file'),
|
self.gui, 'recursive-archive-add', _('Choose archive file'),
|
||||||
filters=[(_('Archives'), ('zip', 'rar'))], all_files=False, select_only_single_file=True)
|
filters=[(_('Archives'), ('zip', 'rar'))], all_files=False, select_only_single_file=False)
|
||||||
if paths:
|
if paths:
|
||||||
self.do_add_recursive(paths[0], single)
|
self.do_add_recursive(paths, single, list_of_archives=True)
|
||||||
|
|
||||||
def add_recursive(self, single):
|
def add_recursive(self, single):
|
||||||
root = choose_dir(self.gui, 'recursive book import root dir dialog',
|
root = choose_dir(self.gui, 'recursive book import root dir dialog',
|
||||||
@ -157,9 +157,9 @@ class AddAction(InterfaceAction):
|
|||||||
'Cannot add books from the folder: %s as it contains the currently opened calibre library') % root, show=True)
|
'Cannot add books from the folder: %s as it contains the currently opened calibre library') % root, show=True)
|
||||||
self.do_add_recursive(root, single)
|
self.do_add_recursive(root, single)
|
||||||
|
|
||||||
def do_add_recursive(self, root, single):
|
def do_add_recursive(self, root, single, list_of_archives=False):
|
||||||
from calibre.gui2.add2 import Adder
|
from calibre.gui2.add2 import Adder
|
||||||
Adder(root, single_book_per_directory=single, db=self.gui.current_db,
|
Adder(root, single_book_per_directory=single, db=self.gui.current_db, list_of_archives=list_of_archives,
|
||||||
callback=self._files_added, parent=self.gui, pool=self.gui.spare_pool())
|
callback=self._files_added, parent=self.gui, pool=self.gui.spare_pool())
|
||||||
|
|
||||||
def add_recursive_single(self, *args):
|
def add_recursive_single(self, *args):
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import shutil, os, weakref, traceback
|
import shutil, os, weakref, traceback, tempfile
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from Queue import Empty
|
from Queue import Empty
|
||||||
@ -33,21 +33,21 @@ def validate_source(source, parent=None): # {{{
|
|||||||
error_dialog(parent, _('Cannot add books'), _(
|
error_dialog(parent, _('Cannot add books'), _(
|
||||||
'The path %s does not exist') % source, show=True)
|
'The path %s does not exist') % source, show=True)
|
||||||
return False
|
return False
|
||||||
if os.path.isdir(source):
|
|
||||||
if not os.access(source, os.X_OK|os.R_OK):
|
if not os.access(source, os.X_OK|os.R_OK):
|
||||||
error_dialog(parent, _('Cannot add books'), _(
|
error_dialog(parent, _('Cannot add books'), _(
|
||||||
'You do not have permission to read %s') % source, show=True)
|
'You do not have permission to read %s') % source, show=True)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if not os.access(source, os.R_OK):
|
ok = False
|
||||||
|
for path in source:
|
||||||
|
if os.access(path, os.R_OK):
|
||||||
|
ok = True
|
||||||
|
break
|
||||||
|
if not ok:
|
||||||
error_dialog(parent, _('Cannot add books'), _(
|
error_dialog(parent, _('Cannot add books'), _(
|
||||||
'You do not have permission to read %s') % source, show=True)
|
'You do not have permission to read any of the selected files'),
|
||||||
|
det_msg='\n'.join(source), show=True)
|
||||||
return False
|
return False
|
||||||
if not source.lower().rpartition(os.extsep)[-1] in {'zip', 'rar'}:
|
|
||||||
error_dialog(parent, _('Cannot add books'), _(
|
|
||||||
'The file %s is not a recognized archive format') % source, show=True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -55,11 +55,12 @@ class Adder(QObject):
|
|||||||
|
|
||||||
do_one_signal = pyqtSignal()
|
do_one_signal = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, source, single_book_per_directory=True, db=None, parent=None, callback=None, pool=None):
|
def __init__(self, source, single_book_per_directory=True, db=None, parent=None, callback=None, pool=None, list_of_archives=False):
|
||||||
if not validate_source(source, parent):
|
if not validate_source(source, parent):
|
||||||
return
|
return
|
||||||
QObject.__init__(self, parent)
|
QObject.__init__(self, parent)
|
||||||
self.single_book_per_directory = single_book_per_directory
|
self.single_book_per_directory = single_book_per_directory
|
||||||
|
self.list_of_archives = list_of_archives
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.add_formats_to_existing = prefs['add_formats_to_existing']
|
self.add_formats_to_existing = prefs['add_formats_to_existing']
|
||||||
self.do_one_signal.connect(self.tick, type=Qt.QueuedConnection)
|
self.do_one_signal.connect(self.tick, type=Qt.QueuedConnection)
|
||||||
@ -111,25 +112,45 @@ class Adder(QObject):
|
|||||||
self.do_one()
|
self.do_one()
|
||||||
|
|
||||||
# Filesystem scan {{{
|
# Filesystem scan {{{
|
||||||
|
|
||||||
def scan(self):
|
def scan(self):
|
||||||
try:
|
|
||||||
if isinstance(self.source, basestring):
|
def find_files(root):
|
||||||
if os.path.isdir(self.source):
|
|
||||||
root = self.source
|
|
||||||
else:
|
|
||||||
root = self.extract()
|
|
||||||
for dirpath, dirnames, filenames in os.walk(root):
|
for dirpath, dirnames, filenames in os.walk(root):
|
||||||
for files in find_books_in_directory(dirpath, self.single_book_per_directory):
|
for files in find_books_in_directory(dirpath, self.single_book_per_directory):
|
||||||
if self.abort_scan:
|
if self.abort_scan:
|
||||||
return
|
return
|
||||||
if files:
|
if files:
|
||||||
self.file_groups[len(self.file_groups)] = files
|
self.file_groups[len(self.file_groups)] = files
|
||||||
|
|
||||||
|
def extract(source):
|
||||||
|
tdir = tempfile.mkdtemp(suffix='_archive', dir=self.tdir)
|
||||||
|
if source.lower().endswith('.zip'):
|
||||||
|
from calibre.utils.zipfile import ZipFile
|
||||||
|
try:
|
||||||
|
with ZipFile(source) as zf:
|
||||||
|
zf.extractall(tdir)
|
||||||
|
except Exception:
|
||||||
|
prints('Corrupt ZIP file, trying to use local headers')
|
||||||
|
from calibre.utils.localunzip import extractall
|
||||||
|
extractall(source, tdir)
|
||||||
|
elif source.lower().endswith('.rar'):
|
||||||
|
from calibre.utils.unrar import extract
|
||||||
|
extract(source, tdir)
|
||||||
|
return tdir
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(self.source, basestring):
|
||||||
|
find_files(self.source)
|
||||||
else:
|
else:
|
||||||
unreadable_files = []
|
unreadable_files = []
|
||||||
for path in self.source:
|
for path in self.source:
|
||||||
if self.abort_scan:
|
if self.abort_scan:
|
||||||
return
|
return
|
||||||
if os.access(path, os.R_OK):
|
if os.access(path, os.R_OK):
|
||||||
|
if self.list_of_archives:
|
||||||
|
find_files(extract(path))
|
||||||
|
else:
|
||||||
self.file_groups[len(self.file_groups)] = [path]
|
self.file_groups[len(self.file_groups)] = [path]
|
||||||
else:
|
else:
|
||||||
unreadable_files.append(path)
|
unreadable_files.append(path)
|
||||||
@ -145,22 +166,6 @@ class Adder(QObject):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.scan_error = traceback.format_exc()
|
self.scan_error = traceback.format_exc()
|
||||||
|
|
||||||
def extract(self):
|
|
||||||
tdir = os.path.join(self.tdir, 'archive')
|
|
||||||
if self.source.lower().endswith('.zip'):
|
|
||||||
from calibre.utils.zipfile import ZipFile
|
|
||||||
try:
|
|
||||||
with ZipFile(self.source) as zf:
|
|
||||||
zf.extractall(tdir)
|
|
||||||
except Exception:
|
|
||||||
prints('Corrupt ZIP file, trying to use local headers')
|
|
||||||
from calibre.utils.localunzip import extractall
|
|
||||||
extractall(self.source, tdir)
|
|
||||||
elif self.path.lower().endswith('.rar'):
|
|
||||||
from calibre.utils.unrar import extract
|
|
||||||
extract(self.source, tdir)
|
|
||||||
return tdir
|
|
||||||
|
|
||||||
def monitor_scan(self):
|
def monitor_scan(self):
|
||||||
self.scan_thread.join(0.05)
|
self.scan_thread.join(0.05)
|
||||||
if self.scan_thread.is_alive():
|
if self.scan_thread.is_alive():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user