about summary refs log tree commit diff
path: root/kindlecomicconverter
diff options
context:
space:
mode:
authorPaweł Jastrzębski <pawelj@iosphe.re>2018-07-10 08:09:04 +0200
committerPaweł Jastrzębski <pawelj@iosphe.re>2018-07-10 08:09:04 +0200
commit7904662f25997ba1466c9f21c564f1f3f066fb9f (patch)
tree7b8ffec37720bde09c90ca932a4d8b2bc5879e75 /kindlecomicconverter
parentBump MAX_IMAGE_PIXELS (close #273) (diff)
downloadkcc-7904662f25997ba1466c9f21c564f1f3f066fb9f.tar.gz
kcc-7904662f25997ba1466c9f21c564f1f3f066fb9f.tar.bz2
kcc-7904662f25997ba1466c9f21c564f1f3f066fb9f.zip
Let 7-Zip handle all archive operations
Diffstat (limited to 'kindlecomicconverter')
-rw-r--r--kindlecomicconverter/KCC_gui.py77
-rw-r--r--kindlecomicconverter/cbxarchive.py89
-rwxr-xr-xkindlecomicconverter/comic2ebook.py40
-rw-r--r--kindlecomicconverter/comicarchive.py81
-rw-r--r--kindlecomicconverter/metadata.py71
-rw-r--r--kindlecomicconverter/rarfile.py1990
-rw-r--r--kindlecomicconverter/shared.py21
-rw-r--r--kindlecomicconverter/startup.py4
8 files changed, 140 insertions, 2233 deletions
diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py
index d3e6f10..d46353c 100644
--- a/kindlecomicconverter/KCC_gui.py
+++ b/kindlecomicconverter/KCC_gui.py
@@ -173,7 +173,7 @@ class VersionThread(QtCore.QThread):
                 move(path[0], path[0] + '.exe')
                 MW.hideProgressBar.emit()
                 MW.modeConvert.emit(1)
-                Popen(path[0] + '.exe  /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
+                Popen(path[0] + '.exe  /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, shell=True)
                 MW.forceShutdown.emit()
             except Exception:
                 MW.addMessage.emit('Failed to download the update!', 'warning', False)
@@ -238,6 +238,7 @@ class WorkerThread(QtCore.QThread):
         MW.addTrayMessage.emit('Conversion interrupted.', 'Critical')
         MW.modeConvert.emit(1)
 
+    # noinspection PyUnboundLocalVariable
     def run(self):
         MW.modeConvert.emit(0)
 
@@ -477,20 +478,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
         if self.needClean:
             self.needClean = False
             GUI.jobList.clear()
-        if self.UnRAR:
-            if self.sevenza:
-                fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
-                                                                'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)')
-            else:
-                fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
-                                                                'Comic (*.cbz *.cbr *.zip *.rar *.pdf)')
+        if self.sevenzip:
+            fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
+                                                            'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)')
         else:
-            if self.sevenza:
-                fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
-                                                                'Comic (*.cbz *.cb7 *.zip *.7z *.pdf)')
-            else:
-                fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
-                                                                'Comic (*.cbz *.zip *.pdf)')
+            fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.pdf)')
         for fname in fnames[0]:
             if fname != '':
                 if sys.platform.startswith('win'):
@@ -509,20 +501,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
                     sname = sname.replace('/', '\\')
                 self.lastPath = os.path.abspath(sname)
         else:
-            if self.UnRAR:
-                if self.sevenza:
-                    fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
-                                                                  'Comic (*.cbz *.cbr *.cb7)')
-                else:
-                    fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
-                                                                  'Comic (*.cbz *.cbr)')
+            if self.sevenzip:
+                fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
+                                                              'Comic (*.cbz *.cbr *.cb7)')
             else:
-                if self.sevenza:
-                    fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
-                                                                  'Comic (*.cbz *.cb7)')
-                else:
-                    fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
-                                                                  'Comic (*.cbz)')
+                fname = ['']
+                self.showDialog("Editor is disabled due to a lack of 7z.", 'error')
             if fname[0] != '':
                 if sys.platform.startswith('win'):
                     sname = fname[0].replace('/', '\\')
@@ -812,11 +796,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
             if self.needClean:
                 self.needClean = False
                 GUI.jobList.clear()
-            formats = ['.cbz', '.zip', '.pdf']
-            if self.UnRAR:
-                formats.extend(['.cbr', '.rar'])
-            if self.sevenza:
-                formats.extend(['.cb7', '.7z'])
+            formats = ['.pdf']
+            if self.sevenzip:
+                formats.extend(['.cb7', '.7z', '.cbz', '.zip', '.cbr', '.rar'])
             if os.path.isdir(message):
                 GUI.jobList.addItem(message)
                 GUI.jobList.scrollToBottom()
@@ -852,10 +834,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
                 os.chmod('/usr/local/bin/kindlegen', 0o755)
             except Exception:
                 pass
-        kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-        if kindleGenExitCode.wait() == 0:
+        kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
+        kindleGenExitCode.communicate()
+        if kindleGenExitCode.returncode == 0:
             self.kindleGen = True
-            versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
+            versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
             for line in versionCheck.stdout:
                 line = line.decode("utf-8")
                 if 'Amazon kindlegen' in line:
@@ -1002,22 +985,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
             self.addMessage('Since you are a new user of <b>KCC</b> please see few '
                             '<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
                             'info')
-        rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-        rarExitCode = rarExitCode.wait()
-        if rarExitCode == 0 or rarExitCode == 1 or rarExitCode == 7:
-            self.UnRAR = True
-        else:
-            self.UnRAR = False
-            self.addMessage('Cannot find <a href="http://www.rarlab.com/rar_add.htm">UnRAR</a>!'
-                            ' Processing of CBR/RAR files will be disabled.', 'warning')
-        sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-        sevenzaExitCode = sevenzaExitCode.wait()
-        if sevenzaExitCode == 0 or sevenzaExitCode == 7:
-            self.sevenza = True
+        process = Popen('7z', stdout=PIPE, stderr=STDOUT, shell=True)
+        process.communicate()
+        if process.returncode == 0 or process.returncode == 7:
+            self.sevenzip = True
         else:
-            self.sevenza = False
-            self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7za</a>!'
-                            ' Processing of CB7/7Z files will be disabled.', 'warning')
+            self.sevenzip = False
+            self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7z</a>!'
+                            ' Processing of archives will be disabled.', 'warning')
         self.detectKindleGen(True)
 
         APP.messageFromOtherInstance.connect(self.handleMessage)
@@ -1098,7 +1073,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
 class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
     def loadData(self, file):
         self.parser = metadata.MetadataParser(file)
-        if self.parser.compressor == 'rar':
+        if self.parser.format == 'RAR':
             self.editorWidget.setEnabled(False)
             self.okButton.setEnabled(False)
             self.statusLabel.setText('CBR metadata are read-only.')
diff --git a/kindlecomicconverter/cbxarchive.py b/kindlecomicconverter/cbxarchive.py
deleted file mode 100644
index 94545ae..0000000
--- a/kindlecomicconverter/cbxarchive.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
-# Copyright (c) 2013-2018 Pawel Jastrzebski <pawelj@iosphe.re>
-#
-# Permission to use, copy, modify, and/or distribute this software for
-# any purpose with or without fee is hereby granted, provided that the
-# above copyright notice and this permission notice appear in all
-# copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
-# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
-# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
-# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
-# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
-# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-#
-
-import os
-from zipfile import is_zipfile, ZipFile
-from subprocess import STDOUT, PIPE
-from psutil import Popen
-from shutil import move
-from . import rarfile
-from .shared import check7ZFile as is_7zfile
-
-
-class CBxArchive:
-    def __init__(self, fname):
-        self.fname = fname
-        if is_zipfile(fname):
-            self.compressor = 'zip'
-        elif rarfile.is_rarfile(fname):
-            self.compressor = 'rar'
-        elif is_7zfile(fname):
-            self.compressor = '7z'
-        else:
-            self.compressor = None
-
-    def isCbxFile(self):
-        return self.compressor is not None
-
-    def extractCBZ(self, targetdir):
-        cbzFile = ZipFile(self.fname)
-        filelist = []
-        for f in cbzFile.namelist():
-            if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('humbs.db'):
-                pass
-            elif f.endswith('/'):
-                os.makedirs(os.path.join(targetdir, f), exist_ok=True)
-            else:
-                filelist.append(f)
-        cbzFile.extractall(targetdir, filelist)
-
-    def extractCBR(self, targetdir):
-        cbrFile = rarfile.RarFile(self.fname)
-        cbrFile.extractall(targetdir)
-        for root, _, filenames in os.walk(targetdir):
-            for filename in filenames:
-                if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'):
-                    os.remove(os.path.join(root, filename))
-
-    def extractCB7(self, targetdir):
-        output = Popen('7za x "' + self.fname + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' +
-                       targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-        extracted = False
-        for line in output.stdout:
-            if b"Everything is Ok" in line:
-                extracted = True
-        if not extracted:
-            raise OSError
-
-    def extract(self, targetdir):
-        if self.compressor == 'rar':
-            self.extractCBR(targetdir)
-        elif self.compressor == 'zip':
-            self.extractCBZ(targetdir)
-        elif self.compressor == '7z':
-            self.extractCB7(targetdir)
-        adir = os.listdir(targetdir)
-        if 'ComicInfo.xml' in adir:
-            adir.remove('ComicInfo.xml')
-        if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])):
-            for f in os.listdir(os.path.join(targetdir, adir[0])):
-                move(os.path.join(targetdir, adir[0], f), targetdir)
-            os.rmdir(os.path.join(targetdir, adir[0]))
-        return targetdir
diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py
index e118b7e..37e39d7 100755
--- a/kindlecomicconverter/comic2ebook.py
+++ b/kindlecomicconverter/comic2ebook.py
@@ -45,7 +45,7 @@ except ImportError:
 from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace
 from . import comic2panel
 from . import image
-from . import cbxarchive
+from . import comicarchive
 from . import pdfjpgextract
 from . import dualmetafix
 from . import metadata
@@ -597,16 +597,12 @@ def getWorkFolder(afile):
                 raise UserWarning("Failed to extract images from PDF file.")
         else:
             workdir = mkdtemp('', 'KCC-')
-            cbx = cbxarchive.CBxArchive(afile)
-            if cbx.isCbxFile():
-                try:
-                    path = cbx.extract(workdir)
-                except Exception:
-                    rmtree(workdir, True)
-                    raise UserWarning("Failed to extract archive.")
-            else:
+            try:
+                cbx = comicarchive.ComicArchive(afile)
+                path = cbx.extract(workdir)
+            except OSError as e:
                 rmtree(workdir, True)
-                raise UserWarning("Failed to detect archive format.")
+                raise UserWarning(e.strerror)
     else:
         raise UserWarning("Failed to open source file/directory.")
     sanitizePermissions(path)
@@ -1054,21 +1050,17 @@ def checkOptions():
 
 def checkTools(source):
     source = source.upper()
-    if source.endswith('.CBR') or source.endswith('.RAR'):
-        rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-        rarExitCode = rarExitCode.wait()
-        if rarExitCode != 0 and rarExitCode != 1 and rarExitCode != 7:
-            print('ERROR: UnRAR is missing!')
-            exit(1)
-    elif source.endswith('.CB7') or source.endswith('.7Z'):
-        sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-        sevenzaExitCode = sevenzaExitCode.wait()
-        if sevenzaExitCode != 0 and sevenzaExitCode != 7:
-            print('ERROR: 7za is missing!')
+    if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
+            source.endswith('.ZIP') or source.endswith('.CBZ'):
+        process = Popen('7z', stdout=PIPE, stderr=STDOUT, shell=True)
+        process.communicate()
+        if process.returncode != 0 and process.returncode != 7:
+            print('ERROR: 7z is missing!')
             exit(1)
     if options.format == 'MOBI':
-        kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-        if kindleGenExitCode.wait() != 0:
+        kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
+        kindleGenExitCode.communicate()
+        if kindleGenExitCode.returncode != 0:
             print('ERROR: KindleGen is missing!')
             exit(1)
 
@@ -1215,7 +1207,7 @@ def makeMOBIWorker(item):
     try:
         if os.path.getsize(item) < 629145600:
             output = Popen('kindlegen -dont_append_source -locale en "' + item + '"',
-                           stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
+                           stdout=PIPE, stderr=STDOUT, shell=True)
             for line in output.stdout:
                 line = line.decode('utf-8')
                 # ERROR: Generic error
diff --git a/kindlecomicconverter/comicarchive.py b/kindlecomicconverter/comicarchive.py
new file mode 100644
index 0000000..df9029c
--- /dev/null
+++ b/kindlecomicconverter/comicarchive.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
+# Copyright (c) 2013-2018 Pawel Jastrzebski <pawelj@iosphe.re>
+#
+# Permission to use, copy, modify, and/or distribute this software for
+# any purpose with or without fee is hereby granted, provided that the
+# above copyright notice and this permission notice appear in all
+# copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+#
+
+import os
+from psutil import Popen
+from shutil import move
+from subprocess import STDOUT, PIPE
+from xml.dom.minidom import parseString
+from xml.parsers.expat import ExpatError
+
+
+class ComicArchive:
+    def __init__(self, filepath):
+        self.filepath = filepath
+        self.type = None
+        if not os.path.isfile(self.filepath):
+            raise OSError('File not found.')
+        process = Popen('7z l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, shell=True)
+        for line in process.stdout:
+            if b'Type =' in line:
+                self.type = line.rstrip().decode().split(' = ')[1].upper()
+                break
+        process.communicate()
+        if process.returncode != 0:
+            raise OSError('Archive is corrupted or encrypted.')
+        elif self.type not in ['7Z', 'RAR', 'ZIP']:
+            raise OSError('Unsupported archive format.')
+
+    def extract(self, targetdir):
+        if not os.path.isdir(targetdir):
+            raise OSError('Target directory don\'t exist.')
+        process = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' +
+                        self.filepath + '"', stdout=PIPE, stderr=STDOUT, shell=True)
+        process.communicate()
+        if process.returncode != 0:
+            raise OSError('Failed to extract archive.')
+        tdir = os.listdir(targetdir)
+        if 'ComicInfo.xml' in tdir:
+            tdir.remove('ComicInfo.xml')
+        if len(tdir) == 1 and os.path.isdir(os.path.join(targetdir, tdir[0])):
+            for f in os.listdir(os.path.join(targetdir, tdir[0])):
+                move(os.path.join(targetdir, tdir[0], f), targetdir)
+            os.rmdir(os.path.join(targetdir, tdir[0]))
+        return targetdir
+
+    def addFile(self, sourcefile):
+        if self.type == 'RAR':
+            raise NotImplementedError
+        process = Popen('7z a -y "' + self.filepath + '" "' + sourcefile + '"',
+                        stdout=PIPE, stderr=STDOUT, shell=True)
+        process.communicate()
+        if process.returncode != 0:
+            raise OSError('Failed to add the file.')
+
+    def extractMetadata(self):
+        process = Popen('7z x -y -so "' + self.filepath + '" ComicInfo.xml',
+                        stdout=PIPE, stderr=STDOUT, shell=True)
+        xml = process.communicate()
+        if process.returncode != 0:
+            raise OSError('Failed to extract archive.')
+        try:
+            return parseString(xml[0])
+        except ExpatError:
+            return None
diff --git a/kindlecomicconverter/metadata.py b/kindlecomicconverter/metadata.py
index da16718..df87340 100644
--- a/kindlecomicconverter/metadata.py
+++ b/kindlecomicconverter/metadata.py
@@ -19,13 +19,11 @@
 import os
 from xml.dom.minidom import parse, Document
 from re import compile
-from zipfile import is_zipfile, ZipFile, ZIP_DEFLATED
 from subprocess import STDOUT, PIPE
 from psutil import Popen
 from tempfile import mkdtemp
 from shutil import rmtree
-from .shared import removeFromZIP, check7ZFile as is_7zfile
-from . import rarfile
+from . import comicarchive
 
 
 class MetadataParser:
@@ -42,47 +40,19 @@ class MetadataParser:
                      'MUid': '',
                      'Bookmarks': []}
         self.rawdata = None
-        self.compressor = None
+        self.format = None
         if self.source.endswith('.xml') and os.path.exists(self.source):
             self.rawdata = parse(self.source)
             self.parseXML()
         elif not self.source.endswith('.xml'):
-            if is_zipfile(self.source):
-                self.compressor = 'zip'
-                with ZipFile(self.source) as zip_file:
-                    for member in zip_file.namelist():
-                        if member != 'ComicInfo.xml':
-                            continue
-                        with zip_file.open(member) as xml_file:
-                            self.rawdata = parse(xml_file)
-            elif rarfile.is_rarfile(self.source):
-                self.compressor = 'rar'
-                with rarfile.RarFile(self.source) as rar_file:
-                    for member in rar_file.namelist():
-                        if member != 'ComicInfo.xml':
-                            continue
-                        with rar_file.open(member) as xml_file:
-                            self.rawdata = parse(xml_file)
-            elif is_7zfile(self.source):
-                self.compressor = '7z'
-                workdir = mkdtemp('', 'KCC-')
-                tmpXML = os.path.join(workdir, 'ComicInfo.xml')
-                output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"',
-                               stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-                extracted = False
-                for line in output.stdout:
-                    if b"Everything is Ok" in line or b"No files to process" in line:
-                        extracted = True
-                if not extracted:
-                    rmtree(workdir)
-                    raise OSError('Failed to extract 7ZIP file.')
-                if os.path.isfile(tmpXML):
-                    self.rawdata = parse(tmpXML)
-                rmtree(workdir)
-            else:
-                raise OSError('Failed to detect archive format.')
-            if self.rawdata:
-                self.parseXML()
+            try:
+                cbx = comicarchive.ComicArchive(self.source)
+                self.rawdata = cbx.extractMetadata()
+                self.format = cbx.type
+            except OSError as e:
+                raise UserWarning(e.strerror)
+        if self.rawdata:
+            self.parseXML()
 
     def parseXML(self):
         if len(self.rawdata.getElementsByTagName('Series')) != 0:
@@ -154,20 +124,9 @@ class MetadataParser:
             tmpXML = os.path.join(workdir, 'ComicInfo.xml')
             with open(tmpXML, 'w', encoding='utf-8') as f:
                 self.rawdata.writexml(f, encoding='utf-8')
-            if is_zipfile(self.source):
-                removeFromZIP(self.source, 'ComicInfo.xml')
-                with ZipFile(self.source, mode='a', compression=ZIP_DEFLATED) as zip_file:
-                    zip_file.write(tmpXML, arcname=tmpXML.split(os.sep)[-1])
-            elif rarfile.is_rarfile(self.source):
-                raise NotImplementedError
-            elif is_7zfile(self.source):
-                output = Popen('7za a "' + self.source + '" "' + tmpXML + '"',
-                               stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
-                extracted = False
-                for line in output.stdout:
-                    if b"Everything is Ok" in line:
-                        extracted = True
-                if not extracted:
-                    rmtree(workdir)
-                    raise OSError('Failed to modify 7ZIP file.')
+            try:
+                cbx = comicarchive.ComicArchive(self.source)
+                cbx.addFile(tmpXML)
+            except OSError as e:
+                raise UserWarning(e.strerror)
             rmtree(workdir)
diff --git a/kindlecomicconverter/rarfile.py b/kindlecomicconverter/rarfile.py
deleted file mode 100644
index afb19a7..0000000
--- a/kindlecomicconverter/rarfile.py
+++ /dev/null
@@ -1,1990 +0,0 @@
-# rarfile.py
-#
-# Copyright (c) 2005-2014  Marko Kreen <markokr@gmail.com>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-r"""RAR archive reader.
-
-This is Python module for Rar archive reading.  The interface
-is made as :mod:`zipfile`-like as possible.
-
-Basic logic:
- - Parse archive structure with Python.
- - Extract non-compressed files with Python
- - Extract compressed files with unrar.
- - Optionally write compressed data to temp file to speed up unrar,
-   otherwise it needs to scan whole archive on each execution.
-
-Example::
-
-    import rarfile
-
-    rf = rarfile.RarFile('myarchive.rar')
-    for f in rf.infolist():
-        print f.filename, f.file_size
-        if f.filename == 'README':
-            print(rf.read(f))
-
-Archive files can also be accessed via file-like object returned
-by :meth:`RarFile.open`::
-
-    import rarfile
-
-    with rarfile.RarFile('archive.rar') as rf:
-        with rf.open('README') as f:
-            for ln in f:
-                print(ln.strip())
-
-There are few module-level parameters to tune behaviour,
-here they are with defaults, and reason to change it::
-
-    import rarfile
-
-    # Set to full path of unrar.exe if it is not in PATH
-    rarfile.UNRAR_TOOL = "unrar"
-
-    # Set to 0 if you don't look at comments and want to
-    # avoid wasting time for parsing them
-    rarfile.NEED_COMMENTS = 1
-
-    # Set up to 1 if you don't want to deal with decoding comments
-    # from unknown encoding.  rarfile will try couple of common
-    # encodings in sequence.
-    rarfile.UNICODE_COMMENTS = 0
-
-    # Set to 1 if you prefer timestamps to be datetime objects
-    # instead tuples
-    rarfile.USE_DATETIME = 0
-
-    # Set to '/' to be more compatible with zipfile
-    rarfile.PATH_SEP = '\\'
-
-For more details, refer to source.
-
-"""
-
-__version__ = '2.7-kcc'
-
-# export only interesting items
-__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
-
-##
-## Imports and compat - support both Python 2.x and 3.x
-##
-
-import sys, os, struct, errno
-from struct import pack, unpack
-from binascii import crc32
-from tempfile import mkstemp
-from subprocess import Popen, PIPE, STDOUT
-from datetime import datetime
-
-# only needed for encryped headers
-try:
-    from Crypto.Cipher import AES
-    try:
-        from hashlib import sha1
-    except ImportError:
-        from sha import new as sha1
-    _have_crypto = 1
-except ImportError:
-    _have_crypto = 0
-
-# compat with 2.x
-if sys.hexversion < 0x3000000:
-    # prefer 3.x behaviour
-    range = xrange
-    # py2.6 has broken bytes()
-    def bytes(s, enc):
-        return str(s)
-else:
-    unicode = str
-
-# see if compat bytearray() is needed
-try:
-    bytearray
-except NameError:
-    import array
-    class bytearray:
-        def __init__(self, val = ''):
-            self.arr = array.array('B', val)
-            self.append = self.arr.append
-            self.__getitem__ = self.arr.__getitem__
-            self.__len__ = self.arr.__len__
-        def decode(self, *args):
-            return self.arr.tostring().decode(*args)
-
-# Optimized .readinto() requires memoryview
-try:
-    memoryview
-    have_memoryview = 1
-except NameError:
-    have_memoryview = 0
-
-# Struct() for older python
-try:
-    from struct import Struct
-except ImportError:
-    class Struct:
-        def __init__(self, fmt):
-            self.format = fmt
-            self.size = struct.calcsize(fmt)
-        def unpack(self, buf):
-            return unpack(self.format, buf)
-        def unpack_from(self, buf, ofs = 0):
-            return unpack(self.format, buf[ofs : ofs + self.size])
-        def pack(self, *args):
-            return pack(self.format, *args)
-
-# file object superclass
-try:
-    from io import RawIOBase
-except ImportError:
-    class RawIOBase(object):
-        def close(self):
-            pass
-
-
-##
-## Module configuration.  Can be tuned after importing.
-##
-
-#: default fallback charset
-DEFAULT_CHARSET = "windows-1252"
-
-#: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed
-TRY_ENCODINGS = ('utf8', 'utf-16le')
-
-#: 'unrar', 'rar' or full path to either one
-UNRAR_TOOL = "unrar"
-
-#: Command line args to use for opening file for reading.
-OPEN_ARGS = ('p', '-inul')
-
-#: Command line args to use for extracting file to disk.
-EXTRACT_ARGS = ('x', '-y', '-idq')
-
-#: args for testrar()
-TEST_ARGS = ('t', '-idq')
-
-#
-# Allow use of tool that is not compatible with unrar.
-#
-# By default use 'bsdtar' which is 'tar' program that
-# sits on top of libarchive.
-#
-# Problems with libarchive RAR backend:
-# - Does not support solid archives.
-# - Does not support password-protected archives.
-#
-
-ALT_TOOL = 'bsdtar'
-ALT_OPEN_ARGS = ('-x', '--to-stdout', '-f')
-ALT_EXTRACT_ARGS = ('-x', '-f')
-ALT_TEST_ARGS = ('-t', '-f')
-ALT_CHECK_ARGS = ('--help',)
-
-#: whether to speed up decompression by using tmp archive
-USE_EXTRACT_HACK = 0
-
-#: limit the filesize for tmp archive usage
-HACK_SIZE_LIMIT = 20*1024*1024
-
-#: whether to parse file/archive comments.
-NEED_COMMENTS = 1
-
-#: whether to convert comments to unicode strings
-UNICODE_COMMENTS = 0
-
-#: Convert RAR time tuple into datetime() object
-USE_DATETIME = 0
-
-#: Separator for path name components.  RAR internally uses '\\'.
-#: Use '/' to be similar with zipfile.
-PATH_SEP = '\\'
-
-##
-## rar constants
-##
-
-# block types
-RAR_BLOCK_MARK          = 0x72 # r
-RAR_BLOCK_MAIN          = 0x73 # s
-RAR_BLOCK_FILE          = 0x74 # t
-RAR_BLOCK_OLD_COMMENT   = 0x75 # u
-RAR_BLOCK_OLD_EXTRA     = 0x76 # v
-RAR_BLOCK_OLD_SUB       = 0x77 # w
-RAR_BLOCK_OLD_RECOVERY  = 0x78 # x
-RAR_BLOCK_OLD_AUTH      = 0x79 # y
-RAR_BLOCK_SUB           = 0x7a # z
-RAR_BLOCK_ENDARC        = 0x7b # {
-
-# flags for RAR_BLOCK_MAIN
-RAR_MAIN_VOLUME         = 0x0001
-RAR_MAIN_COMMENT        = 0x0002
-RAR_MAIN_LOCK           = 0x0004
-RAR_MAIN_SOLID          = 0x0008
-RAR_MAIN_NEWNUMBERING   = 0x0010
-RAR_MAIN_AUTH           = 0x0020
-RAR_MAIN_RECOVERY       = 0x0040
-RAR_MAIN_PASSWORD       = 0x0080
-RAR_MAIN_FIRSTVOLUME    = 0x0100
-RAR_MAIN_ENCRYPTVER     = 0x0200
-
-# flags for RAR_BLOCK_FILE
-RAR_FILE_SPLIT_BEFORE   = 0x0001
-RAR_FILE_SPLIT_AFTER    = 0x0002
-RAR_FILE_PASSWORD       = 0x0004
-RAR_FILE_COMMENT        = 0x0008
-RAR_FILE_SOLID          = 0x0010
-RAR_FILE_DICTMASK       = 0x00e0
-RAR_FILE_DICT64         = 0x0000
-RAR_FILE_DICT128        = 0x0020
-RAR_FILE_DICT256        = 0x0040
-RAR_FILE_DICT512        = 0x0060
-RAR_FILE_DICT1024       = 0x0080
-RAR_FILE_DICT2048       = 0x00a0
-RAR_FILE_DICT4096       = 0x00c0
-RAR_FILE_DIRECTORY      = 0x00e0
-RAR_FILE_LARGE          = 0x0100
-RAR_FILE_UNICODE        = 0x0200
-RAR_FILE_SALT           = 0x0400
-RAR_FILE_VERSION        = 0x0800
-RAR_FILE_EXTTIME        = 0x1000
-RAR_FILE_EXTFLAGS       = 0x2000
-
-# flags for RAR_BLOCK_ENDARC
-RAR_ENDARC_NEXT_VOLUME  = 0x0001
-RAR_ENDARC_DATACRC      = 0x0002
-RAR_ENDARC_REVSPACE     = 0x0004
-RAR_ENDARC_VOLNR        = 0x0008
-
-# flags common to all blocks
-RAR_SKIP_IF_UNKNOWN     = 0x4000
-RAR_LONG_BLOCK          = 0x8000
-
-# Host OS types
-RAR_OS_MSDOS = 0
-RAR_OS_OS2   = 1
-RAR_OS_WIN32 = 2
-RAR_OS_UNIX  = 3
-RAR_OS_MACOS = 4
-RAR_OS_BEOS  = 5
-
-# Compression methods - '0'..'5'
-RAR_M0 = 0x30
-RAR_M1 = 0x31
-RAR_M2 = 0x32
-RAR_M3 = 0x33
-RAR_M4 = 0x34
-RAR_M5 = 0x35
-
-##
-## internal constants
-##
-
-RAR_ID = bytes("Rar!\x1a\x07\x00", 'ascii')
-RAR5_ID = bytes("Rar!\x1a\x07\x01", 'ascii')
-ZERO = bytes("\0", 'ascii')
-EMPTY = bytes("", 'ascii')
-
-S_BLK_HDR = Struct('<HBHH')
-S_FILE_HDR = Struct('<LLBLLBBHL')
-S_LONG = Struct('<L')
-S_SHORT = Struct('<H')
-S_BYTE = Struct('<B')
-S_COMMENT_HDR = Struct('<HBBH')
-
-##
-## Public interface
-##
-
-class Error(Exception):
-    """Base class for rarfile errors."""
-class BadRarFile(Error):
-    """Incorrect data in archive."""
-class NotRarFile(Error):
-    """The file is not RAR archive."""
-class BadRarName(Error):
-    """Cannot guess multipart name components."""
-class NoRarEntry(Error):
-    """File not found in RAR"""
-class PasswordRequired(Error):
-    """File requires password"""
-class NeedFirstVolume(Error):
-    """Need to start from first volume."""
-class NoCrypto(Error):
-    """Cannot parse encrypted headers - no crypto available."""
-class RarExecError(Error):
-    """Problem reported by unrar/rar."""
-class RarWarning(RarExecError):
-    """Non-fatal error"""
-class RarFatalError(RarExecError):
-    """Fatal error"""
-class RarCRCError(RarExecError):
-    """CRC error during unpacking"""
-class RarLockedArchiveError(RarExecError):
-    """Must not modify locked archive"""
-class RarWriteError(RarExecError):
-    """Write error"""
-class RarOpenError(RarExecError):
-    """Open error"""
-class RarUserError(RarExecError):
-    """User error"""
-class RarMemoryError(RarExecError):
-    """Memory error"""
-class RarCreateError(RarExecError):
-    """Create error"""
-class RarNoFilesError(RarExecError):
-    """No files that match pattern were found"""
-class RarUserBreak(RarExecError):
-    """User stop"""
-class RarUnknownError(RarExecError):
-    """Unknown exit code"""
-class RarSignalExit(RarExecError):
-    """Unrar exited with signal"""
-class RarCannotExec(RarExecError):
-    """Executable not found."""
-
-
-def is_rarfile(xfile):
-    '''Check quickly whether file is rar archive.'''
-    with open(xfile, 'rb') as fh:
-        buf = fh.read(len(RAR_ID))
-    if buf == RAR_ID or buf == RAR5_ID:
-        return True
-    else:
-        return False
-
-
-class RarInfo(object):
-    r'''An entry in rar archive.
-
-    :mod:`zipfile`-compatible fields:
-    
-        filename
-            File name with relative path.
-            Default path separator is '\\', to change set rarfile.PATH_SEP.
-            Always unicode string.
-        date_time
-            Modification time, tuple of (year, month, day, hour, minute, second).
-            Or datetime() object if USE_DATETIME is set.
-        file_size
-            Uncompressed size.
-        compress_size
-            Compressed size.
-        CRC
-            CRC-32 of uncompressed file, unsigned int.
-        comment
-            File comment.  Byte string or None.  Use UNICODE_COMMENTS
-            to get automatic decoding to unicode.
-        volume
-            Volume nr, starting from 0.
-
-    RAR-specific fields:
-
-        compress_type
-            Compression method: 0x30 - 0x35.
-        extract_version
-            Minimal Rar version needed for decompressing.
-        host_os
-            Host OS type, one of RAR_OS_* constants.
-        mode
-            File attributes. May be either dos-style or unix-style, depending on host_os.
-        volume_file
-            Volume file name, where file starts.
-        mtime
-            Optional time field: Modification time, with float seconds.
-            Same as .date_time but with more precision.
-        ctime
-            Optional time field: creation time, with float seconds.
-        atime
-            Optional time field: last access time, with float seconds.
-        arctime
-            Optional time field: archival time, with float seconds.
-
-    Internal fields:
-
-        type
-            One of RAR_BLOCK_* types.  Only entries with type==RAR_BLOCK_FILE are shown in .infolist().
-        flags
-            For files, RAR_FILE_* bits.
-    '''
-
-    __slots__ = (
-        # zipfile-compatible fields
-        'filename',
-        'file_size',
-        'compress_size',
-        'date_time',
-        'comment',
-        'CRC',
-        'volume',
-        'orig_filename', # bytes in unknown encoding
-
-        # rar-specific fields
-        'extract_version',
-        'compress_type',
-        'host_os',
-        'mode',
-        'type',
-        'flags',
-
-        # optional extended time fields
-        # tuple where the sec is float, or datetime().
-        'mtime', # same as .date_time
-        'ctime',
-        'atime',
-        'arctime',
-
-        # RAR internals
-        'name_size',
-        'header_size',
-        'header_crc',
-        'file_offset',
-        'add_size',
-        'header_data',
-        'header_base',
-        'header_offset',
-        'salt',
-        'volume_file',
-    )
-
-    def isdir(self):
-        '''Returns True if the entry is a directory.'''
-        if self.type == RAR_BLOCK_FILE:
-            return (self.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY
-        return False
-
-    def needs_password(self):
-        return self.flags & RAR_FILE_PASSWORD
-
-
-class RarFile(object):
-    '''Parse RAR structure, provide access to files in archive.
-    '''
-
-    #: Archive comment.  Byte string or None.  Use :data:`UNICODE_COMMENTS`
-    #: to get automatic decoding to unicode.
-    comment = None
-
-    def __init__(self, rarfile, mode="r", charset=None, info_callback=None,
-                 crc_check = True, errors = "stop"):
-        """Open and parse a RAR archive.
-        
-        Parameters:
-
-            rarfile
-                archive file name
-            mode
-                only 'r' is supported.
-            charset
-                fallback charset to use, if filenames are not already Unicode-enabled.
-            info_callback
-                debug callback, gets to see all archive entries.
-            crc_check
-                set to False to disable CRC checks
-            errors
-                Either "stop" to quietly stop parsing on errors,
-                or "strict" to raise errors.  Default is "stop".
-        """
-        self.rarfile = rarfile
-        self.comment = None
-        self._charset = charset or DEFAULT_CHARSET
-        self._info_callback = info_callback
-
-        self._info_list = []
-        self._info_map = {}
-        self._needs_password = False
-        self._password = None
-        self._crc_check = crc_check
-        self._vol_list = []
-
-        if errors == "stop":
-            self._strict = False
-        elif errors == "strict":
-            self._strict = True
-        else:
-            raise ValueError("Invalid value for 'errors' parameter.")
-
-        self._main = None
-
-        if mode != "r":
-            raise NotImplementedError("RarFile supports only mode=r")
-
-        self._parse()
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, type, value, traceback):
-        self.close()
-
-    def setpassword(self, password):
-        '''Sets the password to use when extracting.'''
-        self._password = password
-        if not self._main:
-            self._parse()
-
-    def needs_password(self):
-        '''Returns True if any archive entries require password for extraction.'''
-        return self._needs_password
-
-    def namelist(self):
-        '''Return list of filenames in archive.'''
-        return [f.filename for f in self._info_list]
-
-    def infolist(self):
-        '''Return RarInfo objects for all files/directories in archive.'''
-        return self._info_list
-
-    def volumelist(self):
-        '''Returns filenames of archive volumes.
-
-        In case of single-volume archive, the list contains
-        just the name of main archive file.
-        '''
-        return self._vol_list
-
-    def getinfo(self, fname):
-        '''Return RarInfo for file.'''
-
-        if isinstance(fname, RarInfo):
-            return fname
-
-        # accept both ways here
-        if PATH_SEP == '/':
-            fname2 = fname.replace("\\", "/")
-        else:
-            fname2 = fname.replace("/", "\\")
-
-        try:
-            return self._info_map[fname]
-        except KeyError:
-            try:
-                return self._info_map[fname2]
-            except KeyError:
-                raise NoRarEntry("No such file: "+fname)
-
-    def open(self, fname, mode = 'r', psw = None):
-        '''Returns file-like object (:class:`RarExtFile`),
-        from where the data can be read.
-        
-        The object implements :class:`io.RawIOBase` interface, so it can
-        be further wrapped with :class:`io.BufferedReader`
-        and :class:`io.TextIOWrapper`.
-
-        On older Python where io module is not available, it implements
-        only .read(), .seek(), .tell() and .close() methods.
-
-        The object is seekable, although the seeking is fast only on
-        uncompressed files, on compressed files the seeking is implemented
-        by reading ahead and/or restarting the decompression.
-
-        Parameters:
-
-            fname
-                file name or RarInfo instance.
-            mode
-                must be 'r'
-            psw
-                password to use for extracting.
-        '''
-
-        if mode != 'r':
-            raise NotImplementedError("RarFile.open() supports only mode=r")
-
-        # entry lookup
-        inf = self.getinfo(fname)
-        if inf.isdir():
-            raise TypeError("Directory does not have any data: " + inf.filename)
-
-        if inf.flags & RAR_FILE_SPLIT_BEFORE:
-            raise NeedFirstVolume("Partial file, please start from first volume: " + inf.filename)
-
-        # check password
-        if inf.needs_password():
-            psw = psw or self._password
-            if psw is None:
-                raise PasswordRequired("File %s requires password" % inf.filename)
-        else:
-            psw = None
-
-        # is temp write usable?
-        use_hack = 1
-        if not self._main:
-            use_hack = 0
-        elif self._main.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD):
-            use_hack = 0
-        elif inf.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
-            use_hack = 0
-        elif is_filelike(self.rarfile):
-            pass
-        elif inf.file_size > HACK_SIZE_LIMIT:
-            use_hack = 0
-        elif not USE_EXTRACT_HACK:
-            use_hack = 0
-
-        # now extract
-        if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0:
-            return self._open_clear(inf)
-        elif use_hack:
-            return self._open_hack(inf, psw)
-        else:
-            return self._open_unrar(self.rarfile, inf, psw)
-
-    def read(self, fname, psw = None):
-        """Return uncompressed data for archive entry.
-        
-        For longer files using :meth:`RarFile.open` may be better idea.
-
-        Parameters:
-
-            fname
-                filename or RarInfo instance
-            psw
-                password to use for extracting.
-        """
-
-        f = self.open(fname, 'r', psw)
-        try:
-            return f.read()
-        finally:
-            f.close()
-
-    def close(self):
-        """Release open resources."""
-        pass
-
-    def printdir(self):
-        """Print archive file list to stdout."""
-        for f in self._info_list:
-            print(f.filename)
-
-    def extract(self, member, path=None, pwd=None):
-        """Extract single file into current directory.
-        
-        Parameters:
-
-            member
-                filename or :class:`RarInfo` instance
-            path
-                optional destination path
-            pwd
-                optional password to use
-        """
-        if isinstance(member, RarInfo):
-            fname = member.filename
-        else:
-            fname = member
-        self._extract([fname], path, pwd)
-
-    def extractall(self, path=None, members=None, pwd=None):
-        """Extract all files into current directory.
-        
-        Parameters:
-
-            path
-                optional destination path
-            members
-                optional filename or :class:`RarInfo` instance list to extract
-            pwd
-                optional password to use
-        """
-        fnlist = []
-        if members is not None:
-            for m in members:
-                if isinstance(m, RarInfo):
-                    fnlist.append(m.filename)
-                else:
-                    fnlist.append(m)
-        self._extract(fnlist, path, pwd)
-
-    def testrar(self):
-        """Let 'unrar' test the archive.
-        """
-        cmd = [UNRAR_TOOL] + list(TEST_ARGS)
-        add_password_arg(cmd, self._password)
-        cmd.append(self.rarfile)
-        p = custom_popen(cmd)
-        output = p.communicate()[0]
-        check_returncode(p, output)
-
-    def strerror(self):
-        """Return error string if parsing failed,
-        or None if no problems.
-        """
-        return self._parse_error
-
-    ##
-    ## private methods
-    ##
-
-    def _set_error(self, msg, *args):
-        if args:
-            msg = msg % args
-        self._parse_error = msg
-        if self._strict:
-            raise BadRarFile(msg)
-
-    # store entry
-    def _process_entry(self, item):
-        if item.type == RAR_BLOCK_FILE:
-            # use only first part
-            if (item.flags & RAR_FILE_SPLIT_BEFORE) == 0:
-                self._info_map[item.filename] = item
-                self._info_list.append(item)
-                # remember if any items require password
-                if item.needs_password():
-                    self._needs_password = True
-            elif len(self._info_list) > 0:
-                # final crc is in last block
-                old = self._info_list[-1]
-                old.CRC = item.CRC
-                old.compress_size += item.compress_size
-
-        # parse new-style comment
-        if item.type == RAR_BLOCK_SUB and item.filename == 'CMT':
-            if not NEED_COMMENTS:
-                pass
-            elif item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
-                pass
-            elif item.flags & RAR_FILE_SOLID:
-                # file comment
-                cmt = self._read_comment_v3(item, self._password)
-                if len(self._info_list) > 0:
-                    old = self._info_list[-1]
-                    old.comment = cmt
-            else:
-                # archive comment
-                cmt = self._read_comment_v3(item, self._password)
-                self.comment = cmt
-
-        if self._info_callback:
-            self._info_callback(item)
-
-    # read rar
-    def _parse(self):
-        self._fd = None
-        try:
-            self._parse_real()
-        finally:
-            if self._fd:
-                self._fd.close()
-                self._fd = None
-
-    def _parse_real(self):
-        fd = XFile(self.rarfile)
-        self._fd = fd
-        id = fd.read(len(RAR_ID))
-        if id != RAR_ID and id != RAR5_ID:
-            raise NotRarFile("Not a Rar archive: "+self.rarfile)
-
-        volume = 0  # first vol (.rar) is 0
-        more_vols = 0
-        endarc = 0
-        volfile = self.rarfile
-        self._vol_list = [self.rarfile]
-        while 1:
-            if endarc:
-                h = None    # don't read past ENDARC
-            else:
-                h = self._parse_header(fd)
-            if not h:
-                if more_vols:
-                    volume += 1
-                    fd.close()
-                    try:
-                        volfile = self._next_volname(volfile)
-                        fd = XFile(volfile)
-                    except IOError:
-                        self._set_error("Cannot open next volume: %s", volfile)
-                        break
-                    self._fd = fd
-                    more_vols = 0
-                    endarc = 0
-                    self._vol_list.append(volfile)
-                    continue
-                break
-            h.volume = volume
-            h.volume_file = volfile
-
-            if h.type == RAR_BLOCK_MAIN and not self._main:
-                self._main = h
-                if h.flags & RAR_MAIN_NEWNUMBERING:
-                    # RAR 2.x does not set FIRSTVOLUME,
-                    # so check it only if NEWNUMBERING is used
-                    if (h.flags & RAR_MAIN_FIRSTVOLUME) == 0:
-                        raise NeedFirstVolume("Need to start from first volume")
-                if h.flags & RAR_MAIN_PASSWORD:
-                    self._needs_password = True
-                    if not self._password:
-                        self._main = None
-                        break
-            elif h.type == RAR_BLOCK_ENDARC:
-                more_vols = h.flags & RAR_ENDARC_NEXT_VOLUME
-                endarc = 1
-            elif h.type == RAR_BLOCK_FILE:
-                # RAR 2.x does not write RAR_BLOCK_ENDARC
-                if h.flags & RAR_FILE_SPLIT_AFTER:
-                    more_vols = 1
-                # RAR 2.x does not set RAR_MAIN_FIRSTVOLUME
-                if volume == 0 and h.flags & RAR_FILE_SPLIT_BEFORE:
-                    raise NeedFirstVolume("Need to start from first volume")
-
-            # store it
-            self._process_entry(h)
-
-            # go to next header
-            if h.add_size > 0:
-                fd.seek(h.file_offset + h.add_size, 0)
-
-    # AES encrypted headers
-    _last_aes_key = (None, None, None) # (salt, key, iv)
-    def _decrypt_header(self, fd):
-        if not _have_crypto:
-            raise NoCrypto('Cannot parse encrypted headers - no crypto')
-        salt = fd.read(8)
-        if self._last_aes_key[0] == salt:
-            key, iv = self._last_aes_key[1:]
-        else:
-            key, iv = rar3_s2k(self._password, salt)
-            self._last_aes_key = (salt, key, iv)
-        return HeaderDecrypt(fd, key, iv)
-
-    # read single header
-    def _parse_header(self, fd):
-        try:
-            # handle encrypted headers
-            if self._main and self._main.flags & RAR_MAIN_PASSWORD:
-                if not self._password:
-                    return
-                fd = self._decrypt_header(fd)
-
-            # now read actual header
-            return self._parse_block_header(fd)
-        except struct.error:
-            self._set_error('Broken header in RAR file')
-            return None
-
-    # common header
-    def _parse_block_header(self, fd):
-        h = RarInfo()
-        h.header_offset = fd.tell()
-        h.comment = None
-
-        # read and parse base header
-        buf = fd.read(S_BLK_HDR.size)
-        if not buf:
-            return None
-        t = S_BLK_HDR.unpack_from(buf)
-        h.header_crc, h.type, h.flags, h.header_size = t
-        h.header_base = S_BLK_HDR.size
-        pos = S_BLK_HDR.size
-
-        # read full header
-        if h.header_size > S_BLK_HDR.size:
-            h.header_data = buf + fd.read(h.header_size - S_BLK_HDR.size)
-        else:
-            h.header_data = buf
-        h.file_offset = fd.tell()
-
-        # unexpected EOF?
-        if len(h.header_data) != h.header_size:
-            self._set_error('Unexpected EOF when reading header')
-            return None
-
-        # block has data assiciated with it?
-        if h.flags & RAR_LONG_BLOCK:
-            h.add_size = S_LONG.unpack_from(h.header_data, pos)[0]
-        else:
-            h.add_size = 0
-
-        # parse interesting ones, decide header boundaries for crc
-        if h.type == RAR_BLOCK_MARK:
-            return h
-        elif h.type == RAR_BLOCK_MAIN:
-            h.header_base += 6
-            if h.flags & RAR_MAIN_ENCRYPTVER:
-                h.header_base += 1
-            if h.flags & RAR_MAIN_COMMENT:
-                self._parse_subblocks(h, h.header_base)
-                self.comment = h.comment
-        elif h.type == RAR_BLOCK_FILE:
-            self._parse_file_header(h, pos)
-        elif h.type == RAR_BLOCK_SUB:
-            self._parse_file_header(h, pos)
-            h.header_base = h.header_size
-        elif h.type == RAR_BLOCK_OLD_AUTH:
-            h.header_base += 8
-        elif h.type == RAR_BLOCK_OLD_EXTRA:
-            h.header_base += 7
-        else:
-            h.header_base = h.header_size
-
-        # check crc
-        if h.type == RAR_BLOCK_OLD_SUB:
-            crcdat = h.header_data[2:] + fd.read(h.add_size)
-        else:
-            crcdat = h.header_data[2:h.header_base]
-
-        calc_crc = crc32(crcdat) & 0xFFFF
-
-        # return good header
-        if h.header_crc == calc_crc:
-            return h
-
-        # header parsing failed.
-        self._set_error('Header CRC error (%02x): exp=%x got=%x (xlen = %d)',
-                h.type, h.header_crc, calc_crc, len(crcdat))
-
-        # instead panicing, send eof
-        return None
-
-    # read file-specific header
-    def _parse_file_header(self, h, pos):
-        fld = S_FILE_HDR.unpack_from(h.header_data, pos)
-        h.compress_size = fld[0]
-        h.file_size = fld[1]
-        h.host_os = fld[2]
-        h.CRC = fld[3]
-        h.date_time = parse_dos_time(fld[4])
-        h.extract_version = fld[5]
-        h.compress_type = fld[6]
-        h.name_size = fld[7]
-        h.mode = fld[8]
-        pos += S_FILE_HDR.size
-
-        if h.flags & RAR_FILE_LARGE:
-            h1 = S_LONG.unpack_from(h.header_data, pos)[0]
-            h2 = S_LONG.unpack_from(h.header_data, pos + 4)[0]
-            h.compress_size |= h1 << 32
-            h.file_size |= h2 << 32
-            pos += 8
-            h.add_size = h.compress_size
-
-        name = h.header_data[pos : pos + h.name_size ]
-        pos += h.name_size
-        if h.flags & RAR_FILE_UNICODE:
-            nul = name.find(ZERO)
-            h.orig_filename = name[:nul]
-            u = UnicodeFilename(h.orig_filename, name[nul + 1 : ])
-            h.filename = u.decode()
-
-            # if parsing failed fall back to simple name
-            if u.failed:
-                h.filename = self._decode(h.orig_filename)
-        else:
-            h.orig_filename = name
-            h.filename = self._decode(name)
-
-        # change separator, if requested
-        if PATH_SEP != '\\':
-            h.filename = h.filename.replace('\\', PATH_SEP)
-
-        if h.flags & RAR_FILE_SALT:
-            h.salt = h.header_data[pos : pos + 8]
-            pos += 8
-        else:
-            h.salt = None
-
-        # optional extended time stamps
-        if h.flags & RAR_FILE_EXTTIME:
-            pos = self._parse_ext_time(h, pos)
-        else:
-            h.mtime = h.atime = h.ctime = h.arctime = None
-
-        # base header end
-        h.header_base = pos
-
-        if h.flags & RAR_FILE_COMMENT:
-            self._parse_subblocks(h, pos)
-
-        # convert timestamps
-        if USE_DATETIME:
-            h.date_time = to_datetime(h.date_time)
-            h.mtime = to_datetime(h.mtime)
-            h.atime = to_datetime(h.atime)
-            h.ctime = to_datetime(h.ctime)
-            h.arctime = to_datetime(h.arctime)
-
-        # .mtime is .date_time with more precision
-        if h.mtime:
-            if USE_DATETIME:
-                h.date_time = h.mtime
-            else:
-                # keep seconds int
-                h.date_time = h.mtime[:5] + (int(h.mtime[5]),)
-
-        return pos
-
-    # find old-style comment subblock
-    def _parse_subblocks(self, h, pos):
-        hdata = h.header_data
-        while pos < len(hdata):
-            # ordinary block header
-            t = S_BLK_HDR.unpack_from(hdata, pos)
-            scrc, stype, sflags, slen = t
-            pos_next = pos + slen
-            pos += S_BLK_HDR.size
-
-            # corrupt header
-            if pos_next < pos:
-                break
-
-            # followed by block-specific header
-            if stype == RAR_BLOCK_OLD_COMMENT and pos + S_COMMENT_HDR.size <= pos_next:
-                declen, ver, meth, crc = S_COMMENT_HDR.unpack_from(hdata, pos)
-                pos += S_COMMENT_HDR.size
-                data = hdata[pos : pos_next]
-                cmt = rar_decompress(ver, meth, data, declen, sflags,
-                                     crc, self._password)
-                if not self._crc_check:
-                    h.comment = self._decode_comment(cmt)
-                elif crc32(cmt) & 0xFFFF == crc:
-                    h.comment = self._decode_comment(cmt)
-
-            pos = pos_next
-
-    def _parse_ext_time(self, h, pos):
-        data = h.header_data
-
-        # flags and rest of data can be missing
-        flags = 0
-        if pos + 2 <= len(data):
-            flags = S_SHORT.unpack_from(data, pos)[0]
-            pos += 2
-
-        h.mtime, pos = self._parse_xtime(flags >> 3*4, data, pos, h.date_time)
-        h.ctime, pos = self._parse_xtime(flags >> 2*4, data, pos)
-        h.atime, pos = self._parse_xtime(flags >> 1*4, data, pos)
-        h.arctime, pos = self._parse_xtime(flags >> 0*4, data, pos)
-        return pos
-
-    def _parse_xtime(self, flag, data, pos, dostime = None):
-        unit = 10000000.0 # 100 ns units
-        if flag & 8:
-            if not dostime:
-                t = S_LONG.unpack_from(data, pos)[0]
-                dostime = parse_dos_time(t)
-                pos += 4
-            rem = 0
-            cnt = flag & 3
-            for i in range(cnt):
-                b = S_BYTE.unpack_from(data, pos)[0]
-                rem = (b << 16) | (rem >> 8)
-                pos += 1
-            sec = dostime[5] + rem / unit
-            if flag & 4:
-                sec += 1
-            dostime = dostime[:5] + (sec,)
-        return dostime, pos
-
-    # given current vol name, construct next one
-    def _next_volname(self, volfile):
-        if is_filelike(volfile):
-            raise IOError("Working on single FD")
-        if self._main.flags & RAR_MAIN_NEWNUMBERING:
-            return self._next_newvol(volfile)
-        return self._next_oldvol(volfile)
-
-    # new-style next volume
-    def _next_newvol(self, volfile):
-        i = len(volfile) - 1
-        while i >= 0:
-            if volfile[i] >= '0' and volfile[i] <= '9':
-                return self._inc_volname(volfile, i)
-            i -= 1
-        raise BadRarName("Cannot construct volume name: "+volfile)
-
-    # old-style next volume
-    def _next_oldvol(self, volfile):
-        # rar -> r00
-        if volfile[-4:].lower() == '.rar':
-            return volfile[:-2] + '00'
-        return self._inc_volname(volfile, len(volfile) - 1)
-
-    # increase digits with carry, otherwise just increment char
-    def _inc_volname(self, volfile, i):
-        fn = list(volfile)
-        while i >= 0:
-            if fn[i] != '9':
-                fn[i] = chr(ord(fn[i]) + 1)
-                break
-            fn[i] = '0'
-            i -= 1
-        return ''.join(fn)
-
-    def _open_clear(self, inf):
-        return DirectReader(self, inf)
-
-    # put file compressed data into temporary .rar archive, and run
-    # unrar on that, thus avoiding unrar going over whole archive
-    def _open_hack(self, inf, psw = None):
-        BSIZE = 32*1024
-
-        size = inf.compress_size + inf.header_size
-        rf = XFile(inf.volume_file, 0)
-        rf.seek(inf.header_offset)
-
-        tmpfd, tmpname = mkstemp(suffix='.rar')
-        tmpf = os.fdopen(tmpfd, "wb")
-
-        try:
-            # create main header: crc, type, flags, size, res1, res2
-            mh = S_BLK_HDR.pack(0x90CF, 0x73, 0, 13) + ZERO * (2+4)
-            tmpf.write(RAR_ID + mh)
-            while size > 0:
-                if size > BSIZE:
-                    buf = rf.read(BSIZE)
-                else:
-                    buf = rf.read(size)
-                if not buf:
-                    raise BadRarFile('read failed: ' + inf.filename)
-                tmpf.write(buf)
-                size -= len(buf)
-            tmpf.close()
-            rf.close()
-        except:
-            rf.close()
-            tmpf.close()
-            os.unlink(tmpname)
-            raise
-
-        return self._open_unrar(tmpname, inf, psw, tmpname)
-
-    def _read_comment_v3(self, inf, psw=None):
-
-        # read data
-        rf = XFile(inf.volume_file)
-        rf.seek(inf.file_offset)
-        data = rf.read(inf.compress_size)
-        rf.close()
-
-        # decompress
-        cmt = rar_decompress(inf.extract_version, inf.compress_type, data,
-                             inf.file_size, inf.flags, inf.CRC, psw, inf.salt)
-
-        # check crc
-        if self._crc_check:
-            crc = crc32(cmt)
-            if crc < 0:
-                crc += (long(1) << 32)
-            if crc != inf.CRC:
-                return None
-
-        return self._decode_comment(cmt)
-
-    # extract using unrar
-    def _open_unrar(self, rarfile, inf, psw = None, tmpfile = None):
-        if is_filelike(rarfile):
-            raise ValueError("Cannot use unrar directly on memory buffer")
-        cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
-        add_password_arg(cmd, psw)
-        cmd.append(rarfile)
-
-        # not giving filename avoids encoding related problems
-        if not tmpfile:
-            fn = inf.filename
-            if PATH_SEP != os.sep:
-                fn = fn.replace(PATH_SEP, os.sep)
-            cmd.append(fn)
-
-        # read from unrar pipe
-        return PipeReader(self, inf, cmd, tmpfile)
-
-    def _decode(self, val):
-        for c in TRY_ENCODINGS:
-            try:
-                return val.decode(c)
-            except UnicodeError:
-                pass
-        return val.decode(self._charset, 'replace')
-
-    def _decode_comment(self, val):
-        if UNICODE_COMMENTS:
-            return self._decode(val)
-        return val
-
-    # call unrar to extract a file
-    def _extract(self, fnlist, path=None, psw=None):
-        cmd = [UNRAR_TOOL] + list(EXTRACT_ARGS)
-
-        # pasoword
-        psw = psw or self._password
-        add_password_arg(cmd, psw)
-
-        # rar file
-        cmd.append(self.rarfile)
-
-        # file list
-        for fn in fnlist:
-            if os.sep != PATH_SEP:
-                fn = fn.replace(PATH_SEP, os.sep)
-            cmd.append(fn)
-
-        # destination path
-        if path is not None:
-            cmd.append(path + os.sep)
-
-        # call
-        p = custom_popen(cmd)
-        output = p.communicate()[0]
-        check_returncode(p, output)
-
-##
-## Utility classes
-##
-
-class UnicodeFilename:
-    """Handle unicode filename decompression"""
-
-    def __init__(self, name, encdata):
-        self.std_name = bytearray(name)
-        self.encdata = bytearray(encdata)
-        self.pos = self.encpos = 0
-        self.buf = bytearray()
-        self.failed = 0
-
-    def enc_byte(self):
-        try:
-            c = self.encdata[self.encpos]
-            self.encpos += 1
-            return c
-        except IndexError:
-            self.failed = 1
-            return 0
-
-    def std_byte(self):
-        try:
-            return self.std_name[self.pos]
-        except IndexError:
-            self.failed = 1
-            return ord('?')
-
-    def put(self, lo, hi):
-        self.buf.append(lo)
-        self.buf.append(hi)
-        self.pos += 1
-
-    def decode(self):
-        hi = self.enc_byte()
-        flagbits = 0
-        while self.encpos < len(self.encdata):
-            if flagbits == 0:
-                flags = self.enc_byte()
-                flagbits = 8
-            flagbits -= 2
-            t = (flags >> flagbits) & 3
-            if t == 0:
-                self.put(self.enc_byte(), 0)
-            elif t == 1:
-                self.put(self.enc_byte(), hi)
-            elif t == 2:
-                self.put(self.enc_byte(), self.enc_byte())
-            else:
-                n = self.enc_byte()
-                if n & 0x80:
-                    c = self.enc_byte()
-                    for i in range((n & 0x7f) + 2):
-                        lo = (self.std_byte() + c) & 0xFF
-                        self.put(lo, hi)
-                else:
-                    for i in range(n + 2):
-                        self.put(self.std_byte(), 0)
-        return self.buf.decode("utf-16le", "replace")
-
-
-class RarExtFile(RawIOBase):
-    """Base class for file-like object that :meth:`RarFile.open` returns.
-
-    Provides public methods and common crc checking.
-
-    Behaviour:
-     - no short reads - .read() and .readinfo() read as much as requested.
-     - no internal buffer, use io.BufferedReader for that.
-
-    If :mod:`io` module is available (Python 2.6+, 3.x), then this calls
-    will inherit from :class:`io.RawIOBase` class.  This makes line-based
-    access available: :meth:`RarExtFile.readline` and ``for ln in f``.
-    """
-
-    #: Filename of the archive entry
-    name = None
-
-    def __init__(self, rf, inf):
-        RawIOBase.__init__(self)
-
-        # standard io.* properties
-        self.name = inf.filename
-        self.mode = 'rb'
-
-        self.rf = rf
-        self.inf = inf
-        self.crc_check = rf._crc_check
-        self.fd = None
-        self.CRC = 0
-        self.remain = 0
-        self.returncode = 0
-
-        self._open()
-
-    def _open(self):
-        if self.fd:
-            self.fd.close()
-        self.fd = None
-        self.CRC = 0
-        self.remain = self.inf.file_size
-
-    def read(self, cnt = None):
-        """Read all or specified amount of data from archive entry."""
-
-        # sanitize cnt
-        if cnt is None or cnt < 0:
-            cnt = self.remain
-        elif cnt > self.remain:
-            cnt = self.remain
-        if cnt == 0:
-            return EMPTY
-
-        # actual read
-        data = self._read(cnt)
-        if data:
-            self.CRC = crc32(data, self.CRC)
-            self.remain -= len(data)
-        if len(data) != cnt:
-            raise BadRarFile("Failed the read enough data")
-
-        # done?
-        if not data or self.remain == 0:
-            #self.close()
-            self._check()
-        return data
-
-    def _check(self):
-        """Check final CRC."""
-        if not self.crc_check:
-            return
-        if self.returncode:
-            check_returncode(self, '')
-        if self.remain != 0:
-            raise BadRarFile("Failed the read enough data")
-        crc = self.CRC
-        if crc < 0:
-            crc += (long(1) << 32)
-        if crc != self.inf.CRC:
-            raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename)
-
-    def _read(self, cnt):
-        """Actual read that gets sanitized cnt."""
-
-    def close(self):
-        """Close open resources."""
-
-        RawIOBase.close(self)
-
-        if self.fd:
-            self.fd.close()
-            self.fd = None
-
-    def __del__(self):
-        """Hook delete to make sure tempfile is removed."""
-        self.close()
-
-    def readinto(self, buf):
-        """Zero-copy read directly into buffer.
-
-        Returns bytes read.
-        """
-
-        data = self.read(len(buf))
-        n = len(data)
-        try:
-            buf[:n] = data
-        except TypeError:
-            import array
-            if not isinstance(buf, array.array):
-                raise
-            buf[:n] = array.array(buf.typecode, data)
-        return n
-
-    def tell(self):
-        """Return current reading position in uncompressed data."""
-        return self.inf.file_size - self.remain
-
-    def seek(self, ofs, whence = 0):
-        """Seek in data.
-        
-        On uncompressed files, the seeking works by actual
-        seeks so it's fast.  On compresses files its slow
-        - forward seeking happends by reading ahead,
-        backwards by re-opening and decompressing from the start.
-        """
-
-        # disable crc check when seeking
-        self.crc_check = 0
-
-        fsize = self.inf.file_size
-        cur_ofs = self.tell()
-
-        if whence == 0:     # seek from beginning of file
-            new_ofs = ofs
-        elif whence == 1:   # seek from current position
-            new_ofs = cur_ofs + ofs
-        elif whence == 2:   # seek from end of file
-            new_ofs = fsize + ofs
-        else:
-            raise ValueError('Invalid value for whence')
-
-        # sanity check
-        if new_ofs < 0:
-            new_ofs = 0
-        elif new_ofs > fsize:
-            new_ofs = fsize
-
-        # do the actual seek
-        if new_ofs >= cur_ofs:
-            self._skip(new_ofs - cur_ofs)
-        else:
-            # process old data ?
-            #self._skip(fsize - cur_ofs)
-            # reopen and seek
-            self._open()
-            self._skip(new_ofs)
-        return self.tell()
-
-    def _skip(self, cnt):
-        """Read and discard data"""
-        while cnt > 0:
-            if cnt > 8192:
-                buf = self.read(8192)
-            else:
-                buf = self.read(cnt)
-            if not buf:
-                break
-            cnt -= len(buf)
-
-    def readable(self):
-        """Returns True"""
-        return True
-
-    def writable(self):
-        """Returns False.
-        
-        Writing is not supported."""
-        return False
-
-    def seekable(self):
-        """Returns True.
-        
-        Seeking is supported, although it's slow on compressed files.
-        """
-        return True
-
-    def readall(self):
-        """Read all remaining data"""
-        # avoid RawIOBase default impl
-        return self.read()
-
-
-class PipeReader(RarExtFile):
-    """Read data from pipe, handle tempfile cleanup."""
-
-    def __init__(self, rf, inf, cmd, tempfile=None):
-        self.cmd = cmd
-        self.proc = None
-        self.tempfile = tempfile
-        RarExtFile.__init__(self, rf, inf)
-
-    def _close_proc(self):
-        if not self.proc:
-            return
-        if self.proc.stdout:
-            self.proc.stdout.close()
-        if self.proc.stdin:
-            self.proc.stdin.close()
-        if self.proc.stderr:
-            self.proc.stderr.close()
-        self.proc.wait()
-        self.returncode = self.proc.returncode
-        self.proc = None
-
-    def _open(self):
-        RarExtFile._open(self)
-
-        # stop old process
-        self._close_proc()
-
-        # launch new process
-        self.returncode = 0
-        self.proc = custom_popen(self.cmd)
-        self.fd = self.proc.stdout
-
-        # avoid situation where unrar waits on stdin
-        if self.proc.stdin:
-            self.proc.stdin.close()
-
-    def _read(self, cnt):
-        """Read from pipe."""
-
-        # normal read is usually enough
-        data = self.fd.read(cnt)
-        if len(data) == cnt or not data:
-            return data
-
-        # short read, try looping
-        buf = [data]
-        cnt -= len(data)
-        while cnt > 0:
-            data = self.fd.read(cnt)
-            if not data:
-                break
-            cnt -= len(data)
-            buf.append(data)
-        return EMPTY.join(buf)
-
-    def close(self):
-        """Close open resources."""
-
-        self._close_proc()
-        RarExtFile.close(self)
-
-        if self.tempfile:
-            try:
-                os.unlink(self.tempfile)
-            except OSError:
-                pass
-            self.tempfile = None
-
-    if have_memoryview:
-        def readinto(self, buf):
-            """Zero-copy read directly into buffer."""
-            cnt = len(buf)
-            if cnt > self.remain:
-                cnt = self.remain
-            vbuf = memoryview(buf)
-            res = got = 0
-            while got < cnt:
-                res = self.fd.readinto(vbuf[got : cnt])
-                if not res:
-                    break
-                if self.crc_check:
-                    self.CRC = crc32(vbuf[got : got + res], self.CRC)
-                self.remain -= res
-                got += res
-            return got
-
-
-class DirectReader(RarExtFile):
-    """Read uncompressed data directly from archive."""
-
-    def _open(self):
-        RarExtFile._open(self)
-
-        self.volfile = self.inf.volume_file
-        self.fd = XFile(self.volfile, 0)
-        self.fd.seek(self.inf.header_offset, 0)
-        self.cur = self.rf._parse_header(self.fd)
-        self.cur_avail = self.cur.add_size
-
-    def _skip(self, cnt):
-        """RAR Seek, skipping through rar files to get to correct position
-        """
-
-        while cnt > 0:
-            # next vol needed?
-            if self.cur_avail == 0:
-                if not self._open_next():
-                    break
-
-            # fd is in read pos, do the read
-            if cnt > self.cur_avail:
-                cnt -= self.cur_avail
-                self.remain -= self.cur_avail
-                self.cur_avail = 0
-            else:
-                self.fd.seek(cnt, 1)
-                self.cur_avail -= cnt
-                self.remain -= cnt
-                cnt = 0
-
-    def _read(self, cnt):
-        """Read from potentially multi-volume archive."""
-
-        buf = []
-        while cnt > 0:
-            # next vol needed?
-            if self.cur_avail == 0:
-                if not self._open_next():
-                    break
-
-            # fd is in read pos, do the read
-            if cnt > self.cur_avail:
-                data = self.fd.read(self.cur_avail)
-            else:
-                data = self.fd.read(cnt)
-            if not data:
-                break
-
-            # got some data
-            cnt -= len(data)
-            self.cur_avail -= len(data)
-            buf.append(data)
-
-        if len(buf) == 1:
-            return buf[0]
-        return EMPTY.join(buf)
-
-    def _open_next(self):
-        """Proceed to next volume."""
-
-        # is the file split over archives?
-        if (self.cur.flags & RAR_FILE_SPLIT_AFTER) == 0:
-            return False
-
-        if self.fd:
-            self.fd.close()
-            self.fd = None
-
-        # open next part
-        self.volfile = self.rf._next_volname(self.volfile)
-        fd = open(self.volfile, "rb", 0)
-        self.fd = fd
-
-        # loop until first file header
-        while 1:
-            cur = self.rf._parse_header(fd)
-            if not cur:
-                raise BadRarFile("Unexpected EOF")
-            if cur.type in (RAR_BLOCK_MARK, RAR_BLOCK_MAIN):
-                if cur.add_size:
-                    fd.seek(cur.add_size, 1)
-                continue
-            if cur.orig_filename != self.inf.orig_filename:
-                raise BadRarFile("Did not found file entry")
-            self.cur = cur
-            self.cur_avail = cur.add_size
-            return True
-
-    if have_memoryview:
-        def readinto(self, buf):
-            """Zero-copy read directly into buffer."""
-            got = 0
-            vbuf = memoryview(buf)
-            while got < len(buf):
-                # next vol needed?
-                if self.cur_avail == 0:
-                    if not self._open_next():
-                        break
-
-                # lenght for next read
-                cnt = len(buf) - got
-                if cnt > self.cur_avail:
-                    cnt = self.cur_avail
-
-                # read into temp view
-                res = self.fd.readinto(vbuf[got : got + cnt])
-                if not res:
-                    break
-                if self.crc_check:
-                    self.CRC = crc32(vbuf[got : got + res], self.CRC)
-                self.cur_avail -= res
-                self.remain -= res
-                got += res
-            return got
-
-
-class HeaderDecrypt:
-    """File-like object that decrypts from another file"""
-    def __init__(self, f, key, iv):
-        self.f = f
-        self.ciph = AES.new(key, AES.MODE_CBC, iv)
-        self.buf = EMPTY
-
-    def tell(self):
-        return self.f.tell()
-
-    def read(self, cnt=None):
-        if cnt > 8*1024:
-            raise BadRarFile('Bad count to header decrypt - wrong password?')
-
-        # consume old data
-        if cnt <= len(self.buf):
-            res = self.buf[:cnt]
-            self.buf = self.buf[cnt:]
-            return res
-        res = self.buf
-        self.buf = EMPTY
-        cnt -= len(res)
-
-        # decrypt new data
-        BLK = self.ciph.block_size
-        while cnt > 0:
-            enc = self.f.read(BLK)
-            if len(enc) < BLK:
-                break
-            dec = self.ciph.decrypt(enc)
-            if cnt >= len(dec):
-                res += dec
-                cnt -= len(dec)
-            else:
-                res += dec[:cnt]
-                self.buf = dec[cnt:]
-                cnt = 0
-
-        return res
-
-# handle (filename|filelike) object
-class XFile(object):
-    __slots__ = ('_fd', '_need_close')
-    def __init__(self, xfile, bufsize = 1024):
-        if is_filelike(xfile):
-            self._need_close = False
-            self._fd = xfile
-            self._fd.seek(0)
-        else:
-            self._need_close = True
-            self._fd = open(xfile, 'rb', bufsize)
-    def read(self, n=None):
-        return self._fd.read(n)
-    def tell(self):
-        return self._fd.tell()
-    def seek(self, ofs, whence=0):
-        return self._fd.seek(ofs, whence)
-    def readinto(self, dst):
-        return self._fd.readinto(dst)
-    def close(self):
-        if self._need_close:
-            self._fd.close()
-    def __enter__(self):
-        return self
-    def __exit__(self, typ, val, tb):
-        self.close()
-
-##
-## Utility functions
-##
-
-def is_filelike(obj):
-    if isinstance(obj, str) or isinstance(obj, unicode):
-        return False
-    res = True
-    for a in ('read', 'tell', 'seek'):
-        res = res and hasattr(obj, a)
-    if not res:
-        raise ValueError("Invalid object passed as file")
-    return True
-
-def rar3_s2k(psw, salt):
-    """String-to-key hash for RAR3."""
-
-    seed = psw.encode('utf-16le') + salt
-    iv = EMPTY
-    h = sha1()
-    for i in range(16):
-        for j in range(0x4000):
-            cnt = S_LONG.pack(i*0x4000 + j)
-            h.update(seed + cnt[:3])
-            if j == 0:
-                iv += h.digest()[19:20]
-    key_be = h.digest()[:16]
-    key_le = pack("<LLLL", *unpack(">LLLL", key_be))
-    return key_le, iv
-
-def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=None):
-    """Decompress blob of compressed data.
-
-    Used for data with non-standard header - eg. comments.
-    """
-
-    # already uncompressed?
-    if meth == RAR_M0 and (flags & RAR_FILE_PASSWORD) == 0:
-        return data
-
-    # take only necessary flags
-    flags = flags & (RAR_FILE_PASSWORD | RAR_FILE_SALT | RAR_FILE_DICTMASK)
-    flags |= RAR_LONG_BLOCK
-
-    # file header
-    fname = bytes('data', 'ascii')
-    date = 0
-    mode = 0x20
-    fhdr = S_FILE_HDR.pack(len(data), declen, RAR_OS_MSDOS, crc,
-                           date, vers, meth, len(fname), mode)
-    fhdr += fname
-    if flags & RAR_FILE_SALT:
-        if not salt:
-            return EMPTY
-        fhdr += salt
-
-    # full header
-    hlen = S_BLK_HDR.size + len(fhdr)
-    hdr = S_BLK_HDR.pack(0, RAR_BLOCK_FILE, flags, hlen) + fhdr
-    hcrc = crc32(hdr[2:]) & 0xFFFF
-    hdr = S_BLK_HDR.pack(hcrc, RAR_BLOCK_FILE, flags, hlen) + fhdr
-
-    # archive main header
-    mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + ZERO * (2+4)
-
-    # decompress via temp rar
-    tmpfd, tmpname = mkstemp(suffix='.rar')
-    tmpf = os.fdopen(tmpfd, "wb")
-    try:
-        tmpf.write(RAR_ID + mh + hdr + data)
-        tmpf.close()
-
-        cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
-        add_password_arg(cmd, psw, (flags & RAR_FILE_PASSWORD))
-        cmd.append(tmpname)
-
-        p = custom_popen(cmd)
-        return p.communicate()[0]
-    finally:
-        tmpf.close()
-        os.unlink(tmpname)
-
-def to_datetime(t):
-    """Convert 6-part time tuple into datetime object."""
-
-    if t is None:
-        return None
-
-    # extract values
-    year, mon, day, h, m, xs = t
-    s = int(xs)
-    us = int(1000000 * (xs - s))
-
-    # assume the values are valid
-    try:
-        return datetime(year, mon, day, h, m, s, us)
-    except ValueError:
-        pass
-
-    # sanitize invalid values
-    MDAY = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
-    if mon < 1:  mon = 1
-    if mon > 12: mon = 12
-    if day < 1:  day = 1
-    if day > MDAY[mon]: day = MDAY[mon]
-    if h > 23:   h = 23
-    if m > 59:   m = 59
-    if s > 59:   s = 59
-    if mon == 2 and day == 29:
-        try:
-            return datetime(year, mon, day, h, m, s, us)
-        except ValueError:
-            day = 28
-    return datetime(year, mon, day, h, m, s, us)
-
-def parse_dos_time(stamp):
-    """Parse standard 32-bit DOS timestamp."""
-
-    sec = stamp & 0x1F; stamp = stamp >> 5
-    min = stamp & 0x3F; stamp = stamp >> 6
-    hr  = stamp & 0x1F; stamp = stamp >> 5
-    day = stamp & 0x1F; stamp = stamp >> 5
-    mon = stamp & 0x0F; stamp = stamp >> 4
-    yr = (stamp & 0x7F) + 1980
-    return (yr, mon, day, hr, min, sec * 2)
-
-def custom_popen(cmd):
-    """Disconnect cmd from parent fds, read only from stdout."""
-
-    # needed for py2exe
-    creationflags = 0
-    if sys.platform == 'win32':
-        creationflags = 0x08000000 # CREATE_NO_WINDOW
-
-    # run command
-    try:
-        p = Popen(cmd, bufsize = 0,
-                  stdout = PIPE, stdin = PIPE, stderr = STDOUT,
-                  creationflags = creationflags)
-    except OSError:
-        ex = sys.exc_info()[1]
-        if ex.errno == errno.ENOENT:
-            raise RarCannotExec("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
-        raise
-    return p
-
-def custom_check(cmd, ignore_retcode=False):
-    """Run command, collect output, raise error if needed."""
-    p = custom_popen(cmd)
-    out, err = p.communicate()
-    if p.returncode and not ignore_retcode:
-        raise RarExecError("Check-run failed")
-    return out
-
-def add_password_arg(cmd, psw, required=False):
-    """Append password switch to commandline."""
-    if UNRAR_TOOL == ALT_TOOL:
-        return
-    if psw is not None:
-        cmd.append('-p' + psw)
-    else:
-        cmd.append('-p-')
-
-def check_returncode(p, out):
-    """Raise exception according to unrar exit code"""
-
-    code = p.returncode
-    if code == 0:
-        return
-    if code == 9:
-        return
-
-    # map return code to exception class
-    errmap = [None,
-        RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
-        RarWriteError, RarOpenError, RarUserError, RarMemoryError,
-        RarCreateError, RarNoFilesError] # codes from rar.txt
-    if UNRAR_TOOL == ALT_TOOL:
-        errmap = [None]
-    if code > 0 and code < len(errmap):
-        exc = errmap[code]
-    elif code == 255:
-        exc = RarUserBreak
-    elif code < 0:
-        exc = RarSignalExit
-    else:
-        exc = RarUnknownError
-
-    # format message
-    if out:
-        msg = "%s [%d]: %s" % (exc.__doc__, p.returncode, out)
-    else:
-        msg = "%s [%d]" % (exc.__doc__, p.returncode)
-
-    raise exc(msg)
-
-#
-# Check if unrar works
-#
-
-try:
-    # does UNRAR_TOOL work?
-    custom_check([UNRAR_TOOL], True)
-except RarCannotExec:
-    try:
-        # does ALT_TOOL work?
-        custom_check([ALT_TOOL] + list(ALT_CHECK_ARGS), True)
-        # replace config
-        UNRAR_TOOL = ALT_TOOL
-        OPEN_ARGS = ALT_OPEN_ARGS
-        EXTRACT_ARGS = ALT_EXTRACT_ARGS
-        TEST_ARGS = ALT_TEST_ARGS
-    except RarCannotExec:
-        # no usable tool, only uncompressed archives work
-        pass
-
diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py
index bdfd3e7..32f656c 100644
--- a/kindlecomicconverter/shared.py
+++ b/kindlecomicconverter/shared.py
@@ -24,7 +24,6 @@ from html.parser import HTMLParser
 from distutils.version import StrictVersion
 from shutil import rmtree, copy
 from tempfile import mkdtemp
-from zipfile import ZipFile, ZIP_DEFLATED
 from re import split
 from traceback import format_tb
 
@@ -87,26 +86,6 @@ def md5Checksum(fpath):
         return m.hexdigest()
 
 
-def check7ZFile(fpath):
-    with open(fpath, 'rb') as fh:
-        header = fh.read(6)
-    return header == b"7z\xbc\xaf'\x1c"
-
-
-def removeFromZIP(zipfname, *filenames):
-    tempdir = mkdtemp('', 'KCC-')
-    try:
-        tempname = os.path.join(tempdir, 'KCC.zip')
-        with ZipFile(zipfname, 'r') as zipread:
-            with ZipFile(tempname, 'w', compression=ZIP_DEFLATED) as zipwrite:
-                for item in zipread.infolist():
-                    if item.filename not in filenames:
-                        zipwrite.writestr(item, zipread.read(item.filename))
-        copy(tempname, zipfname)
-    finally:
-        rmtree(tempdir, True)
-
-
 def sanitizeTrace(traceback):
     return ''.join(format_tb(traceback))\
         .replace('C:/projects/kcc/', '') \
diff --git a/kindlecomicconverter/startup.py b/kindlecomicconverter/startup.py
index 158f520..4af160e 100644
--- a/kindlecomicconverter/startup.py
+++ b/kindlecomicconverter/startup.py
@@ -30,14 +30,14 @@ def start():
     os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1"
     KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
     if KCCAplication.isRunning():
-        for i in range (1, len(sys.argv)):
+        for i in range(1, len(sys.argv)):
             KCCAplication.sendMessage(sys.argv[i])
         else:
             KCCAplication.sendMessage('ARISE')
     else:
         KCCWindow = KCC_gui.QMainWindowKCC()
         KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
-        for i in range (1, len(sys.argv)):
+        for i in range(1, len(sys.argv)):
             KCCUI.handleMessage(sys.argv[i])
         sys.exit(KCCAplication.exec_())