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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -142,9 +142,9 @@ class AddAction(InterfaceAction):
|
||||
def add_archive(self, single):
|
||||
paths = choose_files(
|
||||
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:
|
||||
self.do_add_recursive(paths[0], single)
|
||||
self.do_add_recursive(paths, single, list_of_archives=True)
|
||||
|
||||
def add_recursive(self, single):
|
||||
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)
|
||||
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
|
||||
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())
|
||||
|
||||
def add_recursive_single(self, *args):
|
||||
|
@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import shutil, os, weakref, traceback
|
||||
import shutil, os, weakref, traceback, tempfile
|
||||
from threading import Thread
|
||||
from collections import OrderedDict
|
||||
from Queue import Empty
|
||||
@ -33,21 +33,21 @@ def validate_source(source, parent=None): # {{{
|
||||
error_dialog(parent, _('Cannot add books'), _(
|
||||
'The path %s does not exist') % source, show=True)
|
||||
return False
|
||||
if os.path.isdir(source):
|
||||
if not os.access(source, os.X_OK|os.R_OK):
|
||||
error_dialog(parent, _('Cannot add books'), _(
|
||||
'You do not have permission to read %s') % source, show=True)
|
||||
return False
|
||||
else:
|
||||
if not os.access(source, os.R_OK):
|
||||
error_dialog(parent, _('Cannot add books'), _(
|
||||
'You do not have permission to read %s') % source, show=True)
|
||||
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
|
||||
|
||||
if not os.access(source, os.X_OK|os.R_OK):
|
||||
error_dialog(parent, _('Cannot add books'), _(
|
||||
'You do not have permission to read %s') % source, show=True)
|
||||
return False
|
||||
else:
|
||||
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'), _(
|
||||
'You do not have permission to read any of the selected files'),
|
||||
det_msg='\n'.join(source), show=True)
|
||||
return False
|
||||
return True
|
||||
# }}}
|
||||
|
||||
@ -55,11 +55,12 @@ class Adder(QObject):
|
||||
|
||||
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):
|
||||
return
|
||||
QObject.__init__(self, parent)
|
||||
self.single_book_per_directory = single_book_per_directory
|
||||
self.list_of_archives = list_of_archives
|
||||
self.callback = callback
|
||||
self.add_formats_to_existing = prefs['add_formats_to_existing']
|
||||
self.do_one_signal.connect(self.tick, type=Qt.QueuedConnection)
|
||||
@ -111,26 +112,46 @@ class Adder(QObject):
|
||||
self.do_one()
|
||||
|
||||
# Filesystem scan {{{
|
||||
|
||||
def scan(self):
|
||||
|
||||
def find_files(root):
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
for files in find_books_in_directory(dirpath, self.single_book_per_directory):
|
||||
if self.abort_scan:
|
||||
return
|
||||
if 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):
|
||||
if os.path.isdir(self.source):
|
||||
root = self.source
|
||||
else:
|
||||
root = self.extract()
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
for files in find_books_in_directory(dirpath, self.single_book_per_directory):
|
||||
if self.abort_scan:
|
||||
return
|
||||
if files:
|
||||
self.file_groups[len(self.file_groups)] = files
|
||||
find_files(self.source)
|
||||
else:
|
||||
unreadable_files = []
|
||||
for path in self.source:
|
||||
if self.abort_scan:
|
||||
return
|
||||
if os.access(path, os.R_OK):
|
||||
self.file_groups[len(self.file_groups)] = [path]
|
||||
if self.list_of_archives:
|
||||
find_files(extract(path))
|
||||
else:
|
||||
self.file_groups[len(self.file_groups)] = [path]
|
||||
else:
|
||||
unreadable_files.append(path)
|
||||
if unreadable_files:
|
||||
@ -145,22 +166,6 @@ class Adder(QObject):
|
||||
except Exception:
|
||||
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):
|
||||
self.scan_thread.join(0.05)
|
||||
if self.scan_thread.is_alive():
|
||||
|
Loading…
x
Reference in New Issue
Block a user