mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on deleting files
This commit is contained in:
parent
8938d40298
commit
8b607437c4
@ -12,6 +12,7 @@ from collections import defaultdict
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from urllib import unquote as urlunquote, quote as urlquote
|
from urllib import unquote as urlunquote, quote as urlquote
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
from future_builtins import zip
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@ -228,6 +229,14 @@ class Container(object): # {{{
|
|||||||
data, self.used_encoding = xml_to_unicode(data)
|
data, self.used_encoding = xml_to_unicode(data)
|
||||||
return fix_data(data)
|
return fix_data(data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def names_that_need_not_be_manifested(self):
|
||||||
|
return {self.opf_name}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def names_that_must_not_be_removed(self):
|
||||||
|
return {self.opf_name}
|
||||||
|
|
||||||
def parse_xml(self, data):
|
def parse_xml(self, data):
|
||||||
data, self.used_encoding = xml_to_unicode(
|
data, self.used_encoding = xml_to_unicode(
|
||||||
data, strip_encoding_pats=True, assume_utf8=True, resolve_entities=True)
|
data, strip_encoding_pats=True, assume_utf8=True, resolve_entities=True)
|
||||||
@ -309,9 +318,8 @@ class Container(object): # {{{
|
|||||||
for item in self.opf_xpath('//opf:guide/opf:reference[@href and @type]')}
|
for item in self.opf_xpath('//opf:guide/opf:reference[@href and @type]')}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spine_names(self):
|
def spine_iter(self):
|
||||||
manifest_id_map = self.manifest_id_map
|
manifest_id_map = self.manifest_id_map
|
||||||
|
|
||||||
non_linear = []
|
non_linear = []
|
||||||
for item in self.opf_xpath('//opf:spine/opf:itemref[@idref]'):
|
for item in self.opf_xpath('//opf:spine/opf:itemref[@idref]'):
|
||||||
idref = item.get('idref')
|
idref = item.get('idref')
|
||||||
@ -319,17 +327,34 @@ class Container(object): # {{{
|
|||||||
path = self.name_path_map.get(name, None)
|
path = self.name_path_map.get(name, None)
|
||||||
if path:
|
if path:
|
||||||
if item.get('linear', 'yes') == 'yes':
|
if item.get('linear', 'yes') == 'yes':
|
||||||
yield name, True
|
yield item, name, True
|
||||||
else:
|
else:
|
||||||
non_linear.append(name)
|
non_linear.append((item, name))
|
||||||
for name in non_linear:
|
for item, name in non_linear:
|
||||||
yield name, False
|
yield item, name, False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def spine_names(self):
|
||||||
|
for item, name, linear in self.spine_iter:
|
||||||
|
yield name, linear
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spine_items(self):
|
def spine_items(self):
|
||||||
for name, linear in self.spine_names:
|
for name, linear in self.spine_names:
|
||||||
yield self.name_path_map[name]
|
yield self.name_path_map[name]
|
||||||
|
|
||||||
|
def remove_from_spine(self, spine_items, remove_if_no_longer_in_spine=True):
|
||||||
|
nixed = set()
|
||||||
|
for (name, remove), (item, xname, linear) in zip(spine_items, self.spine_iter):
|
||||||
|
if remove and name == xname:
|
||||||
|
self.remove_from_xml(item)
|
||||||
|
nixed.add(name)
|
||||||
|
if remove_if_no_longer_in_spine:
|
||||||
|
# Remove from the book if no longer in spine
|
||||||
|
nixed -= {name for name, linear in self.spine_names}
|
||||||
|
for name in nixed:
|
||||||
|
self.remove_item(name)
|
||||||
|
|
||||||
def remove_item(self, name):
|
def remove_item(self, name):
|
||||||
'''
|
'''
|
||||||
Remove the item identified by name from this container. This removes all
|
Remove the item identified by name from this container. This removes all
|
||||||
@ -623,6 +648,34 @@ class EpubContainer(Container):
|
|||||||
ans['container'] = copy.deepcopy(self.container)
|
ans['container'] = copy.deepcopy(self.container)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
@property
|
||||||
|
def names_that_need_not_be_manifested(self):
|
||||||
|
return super(EpubContainer, self).names_that_need_not_be_manifested | {'META-INF/' + x for x in self.META_INF}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def names_that_must_not_be_removed(self):
|
||||||
|
return super(EpubContainer, self).names_that_must_not_be_removed | {'META-INF/container.xml'}
|
||||||
|
|
||||||
|
def remove_item(self, name):
|
||||||
|
# Handle removal of obfuscated fonts
|
||||||
|
if name == 'META-INF/encryption.xml':
|
||||||
|
self.obfuscated_fonts.clear()
|
||||||
|
if name in self.obfuscated_fonts:
|
||||||
|
self.obfuscated_fonts.pop(name, None)
|
||||||
|
enc = self.parsed('META-INF/encryption.xml')
|
||||||
|
for em in enc.xpath('//*[local-name()="EncryptionMethod" and @Algorithm]'):
|
||||||
|
alg = em.get('Algorithm')
|
||||||
|
if alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
cr = em.getparent().xpath('descendant::*[local-name()="CipherReference" and @URI]')[0]
|
||||||
|
except (IndexError, ValueError, KeyError):
|
||||||
|
continue
|
||||||
|
if name == self.href_to_name(cr.get('URI')):
|
||||||
|
self.remove_from_xml(em.getparent())
|
||||||
|
self.dirty('META-INF/encryption.xml')
|
||||||
|
super(EpubContainer, self).remove_item(name)
|
||||||
|
|
||||||
def process_encryption(self):
|
def process_encryption(self):
|
||||||
fonts = {}
|
fonts = {}
|
||||||
enc = self.parsed('META-INF/encryption.xml')
|
enc = self.parsed('META-INF/encryption.xml')
|
||||||
@ -630,7 +683,10 @@ class EpubContainer(Container):
|
|||||||
alg = em.get('Algorithm')
|
alg = em.get('Algorithm')
|
||||||
if alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
|
if alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
|
||||||
raise DRMError()
|
raise DRMError()
|
||||||
cr = em.getparent().xpath('descendant::*[local-name()="CipherReference" and @URI]')[0]
|
try:
|
||||||
|
cr = em.getparent().xpath('descendant::*[local-name()="CipherReference" and @URI]')[0]
|
||||||
|
except (IndexError, ValueError, KeyError):
|
||||||
|
continue
|
||||||
name = self.href_to_name(cr.get('URI'))
|
name = self.href_to_name(cr.get('URI'))
|
||||||
path = self.name_path_map.get(name, None)
|
path = self.name_path_map.get(name, None)
|
||||||
if path is not None:
|
if path is not None:
|
||||||
|
@ -8,15 +8,18 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon,
|
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon,
|
||||||
QStyledItemDelegate, QStyle, QPixmap, QPainter)
|
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal)
|
||||||
|
|
||||||
from calibre import guess_type, human_readable
|
from calibre import human_readable
|
||||||
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
|
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
|
||||||
|
from calibre.ebooks.oeb.polish.container import guess_type
|
||||||
from calibre.ebooks.oeb.polish.cover import get_cover_page_name, get_raster_cover_name
|
from calibre.ebooks.oeb.polish.cover import get_cover_page_name, get_raster_cover_name
|
||||||
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.gui2.tweak_book import current_container
|
from calibre.gui2.tweak_book import current_container
|
||||||
|
|
||||||
TOP_ICON_SIZE = 24
|
TOP_ICON_SIZE = 24
|
||||||
NAME_ROLE = Qt.UserRole
|
NAME_ROLE = Qt.UserRole
|
||||||
|
CATEGORY_ROLE = NAME_ROLE + 1
|
||||||
NBSP = '\xa0'
|
NBSP = '\xa0'
|
||||||
|
|
||||||
class ItemDelegate(QStyledItemDelegate): # {{{
|
class ItemDelegate(QStyledItemDelegate): # {{{
|
||||||
@ -50,6 +53,8 @@ class ItemDelegate(QStyledItemDelegate): # {{{
|
|||||||
|
|
||||||
class FileList(QTreeWidget):
|
class FileList(QTreeWidget):
|
||||||
|
|
||||||
|
delete_requested = pyqtSignal(object, object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QTreeWidget.__init__(self, parent)
|
QTreeWidget.__init__(self, parent)
|
||||||
self.delegate = ItemDelegate(self)
|
self.delegate = ItemDelegate(self)
|
||||||
@ -116,7 +121,7 @@ class FileList(QTreeWidget):
|
|||||||
for names in container.manifest_type_map.itervalues():
|
for names in container.manifest_type_map.itervalues():
|
||||||
manifested_names |= set(names)
|
manifested_names |= set(names)
|
||||||
|
|
||||||
font_types = {guess_type('a.'+x)[0] for x in ('ttf', 'otf', 'woff')}
|
font_types = {guess_type('a.'+x) for x in ('ttf', 'otf', 'woff')}
|
||||||
|
|
||||||
def get_category(mt):
|
def get_category(mt):
|
||||||
category = 'misc'
|
category = 'misc'
|
||||||
@ -171,8 +176,10 @@ class FileList(QTreeWidget):
|
|||||||
icon = self.rendered_emblem_cache[emblems] = canvas
|
icon = self.rendered_emblem_cache[emblems] = canvas
|
||||||
item.setData(0, Qt.DecorationRole, icon)
|
item.setData(0, Qt.DecorationRole, icon)
|
||||||
|
|
||||||
|
ok_to_be_unmanifested = container.names_that_need_not_be_manifested
|
||||||
|
|
||||||
def create_item(name, linear=None):
|
def create_item(name, linear=None):
|
||||||
imt = container.mime_map.get(name, guess_type(name)[0])
|
imt = container.mime_map.get(name, guess_type(name))
|
||||||
icat = get_category(imt)
|
icat = get_category(imt)
|
||||||
category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat))
|
category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat))
|
||||||
item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1)
|
item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1)
|
||||||
@ -182,12 +189,13 @@ class FileList(QTreeWidget):
|
|||||||
item.setFlags(flags)
|
item.setFlags(flags)
|
||||||
item.setStatusTip(0, _('Full path: ') + name)
|
item.setStatusTip(0, _('Full path: ') + name)
|
||||||
item.setData(0, NAME_ROLE, name)
|
item.setData(0, NAME_ROLE, name)
|
||||||
|
item.setData(0, CATEGORY_ROLE, category)
|
||||||
set_display_name(name, item)
|
set_display_name(name, item)
|
||||||
# TODO: Add appropriate tooltips based on the emblems
|
# TODO: Add appropriate tooltips based on the emblems
|
||||||
emblems = []
|
emblems = []
|
||||||
if name in {cover_page_name, cover_image_name}:
|
if name in {cover_page_name, cover_image_name}:
|
||||||
emblems.append('default_cover.png')
|
emblems.append('default_cover.png')
|
||||||
if name not in manifested_names and name not in {container.opf_name, 'META-INF/container.xml', 'META-INF/encryption.xml'}:
|
if name not in manifested_names and name not in ok_to_be_unmanifested:
|
||||||
emblems.append('dialog_question.png')
|
emblems.append('dialog_question.png')
|
||||||
if linear is False:
|
if linear is False:
|
||||||
emblems.append('arrow-down.png')
|
emblems.append('arrow-down.png')
|
||||||
@ -205,7 +213,7 @@ class FileList(QTreeWidget):
|
|||||||
processed[name] = create_item(name, linear=linear)
|
processed[name] = create_item(name, linear=linear)
|
||||||
|
|
||||||
all_files = list(container.manifest_type_map.iteritems())
|
all_files = list(container.manifest_type_map.iteritems())
|
||||||
all_files.append((guess_type('a.opf')[0], [container.opf_name]))
|
all_files.append((guess_type('a.opf'), [container.opf_name]))
|
||||||
|
|
||||||
for name in container.name_path_map:
|
for name in container.name_path_map:
|
||||||
if name in processed:
|
if name in processed:
|
||||||
@ -218,14 +226,54 @@ class FileList(QTreeWidget):
|
|||||||
def show_context_menu(self, point):
|
def show_context_menu(self, point):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def keyPressEvent(self, ev):
|
||||||
|
if ev.key() in (Qt.Key_Delete, Qt.Key_Backspace):
|
||||||
|
ev.accept()
|
||||||
|
self.request_delete()
|
||||||
|
else:
|
||||||
|
return QTreeWidget.keyPressEvent(self, ev)
|
||||||
|
|
||||||
|
def request_delete(self):
|
||||||
|
names = {unicode(item.data(0, NAME_ROLE).toString()) for item in self.selectedItems()}
|
||||||
|
bad = names & current_container().names_that_must_not_be_removed
|
||||||
|
if bad:
|
||||||
|
return error_dialog(self, _('Cannot delete'),
|
||||||
|
_('The file(s) %s cannot be deleted.') % ('<b>%s</b>' % ', '.join(bad)), show=True)
|
||||||
|
|
||||||
|
text = self.categories['text']
|
||||||
|
children = (text.child(i) for i in xrange(text.childCount()))
|
||||||
|
spine_removals = [(unicode(item.data(0, NAME_ROLE).toString()), item.isSelected()) for item in children]
|
||||||
|
other_removals = {unicode(item.data(0, NAME_ROLE).toString()) for item in self.selectedItems()
|
||||||
|
if unicode(item.data(0, CATEGORY_ROLE).toString()) != 'text'}
|
||||||
|
self.delete_requested.emit(spine_removals, other_removals)
|
||||||
|
|
||||||
|
def delete_done(self, spine_removals, other_removals):
|
||||||
|
removals = []
|
||||||
|
for i, (name, remove) in enumerate(spine_removals):
|
||||||
|
if remove:
|
||||||
|
removals.append(self.categories['text'].child(i))
|
||||||
|
for category, parent in self.categories.iteritems():
|
||||||
|
if category != 'text':
|
||||||
|
for i in xrange(parent.childCount()):
|
||||||
|
child = parent.child(i)
|
||||||
|
if unicode(child.data(0, NAME_ROLE).toString()) in other_removals:
|
||||||
|
removals.append(child)
|
||||||
|
|
||||||
|
for c in removals:
|
||||||
|
c.parent().removeChild(c.parent().indexOfChild(c))
|
||||||
|
|
||||||
class FileListWidget(QWidget):
|
class FileListWidget(QWidget):
|
||||||
|
|
||||||
|
delete_requested = pyqtSignal(object, object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
self.setLayout(QGridLayout(self))
|
self.setLayout(QGridLayout(self))
|
||||||
self.file_list = FileList(self)
|
self.file_list = FileList(self)
|
||||||
self.layout().addWidget(self.file_list)
|
self.layout().addWidget(self.file_list)
|
||||||
self.layout().setContentsMargins(0, 0, 0, 0)
|
self.layout().setContentsMargins(0, 0, 0, 0)
|
||||||
|
for x in ('delete_requested',):
|
||||||
|
getattr(self.file_list, x).connect(getattr(self, x))
|
||||||
|
|
||||||
def build(self, container):
|
def build(self, container):
|
||||||
self.file_list.build(container)
|
self.file_list.build(container)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user