Python3 and QT5 upgrade (#109)

* Tweaked search string based on new comic vine search behavior
Placated Beaufitul Soup by passing the parser

* First cut at porting to Python 3 and PyQt5

* remove debug print

* tweaked progress dialog handling for issues on ubuntu gui

* Handle bad key more gracefullu

* More integration of unrarlib into settings and rest of app

* Better handling of "personal" unrar lib setting

* PEP 440-compliant version string

* Tuned linux rar help strings

* Got setup working again
* Attempts to build unrar on install
* Some minimal desktop integration on various platforms

* Fix wrong shortfile

* More setup.py enhancements
* Use proper temp file
* Added comment block at top

* Comment out desktop integration attempt for now

* Updated some links and info

* Fixed the html a bit

* Repaired some images that caused libpng to complain

* update readme re:  py3qt5 branch changes

* another note

* #108 feat: try to simplify windows build using only pip and python3

* #108 feat: fix python location on appveyor (try 1)

* #108 feat: use venv (try 2)

* #108 feat: use venv (try 3)

* #108 feat: update to latest pyinstaller develop branch

* #108 feat: update to latest pyinstaller develop branch (again)

* #108: add ssl libraries for windows packaging

* #108: refresh env in win build to pick the right mingw

* #108: change order of win build script operations

* #113: fix subprocess usage in pyinstaller package

* bump version
This commit is contained in:
davide-romanini 2018-09-19 22:05:39 +02:00 committed by GitHub
parent d05133bd57
commit 8918369b05
8 changed files with 97 additions and 86 deletions

View File

@ -19,8 +19,8 @@ import xml.etree.ElementTree as ET
#from pprint import pprint #from pprint import pprint
#import zipfile #import zipfile
from genericmetadata import GenericMetadata from .genericmetadata import GenericMetadata
import utils from . import utils
class CoMet: class CoMet:
@ -76,7 +76,7 @@ class CoMet:
# helper func # helper func
def assign(comet_entry, md_entry): def assign(comet_entry, md_entry):
if md_entry is not None: if md_entry is not None:
ET.SubElement(root, comet_entry).text = u"{0}".format(md_entry) ET.SubElement(root, comet_entry).text = "{0}".format(md_entry)
# title is manditory # title is manditory
if md.title is None: if md.title is None:
@ -131,43 +131,43 @@ class CoMet:
if credit['role'].lower() in set(self.writer_synonyms): if credit['role'].lower() in set(self.writer_synonyms):
ET.SubElement( ET.SubElement(
root, root,
'writer').text = u"{0}".format( 'writer').text = "{0}".format(
credit['person']) credit['person'])
if credit['role'].lower() in set(self.penciller_synonyms): if credit['role'].lower() in set(self.penciller_synonyms):
ET.SubElement( ET.SubElement(
root, root,
'penciller').text = u"{0}".format( 'penciller').text = "{0}".format(
credit['person']) credit['person'])
if credit['role'].lower() in set(self.inker_synonyms): if credit['role'].lower() in set(self.inker_synonyms):
ET.SubElement( ET.SubElement(
root, root,
'inker').text = u"{0}".format( 'inker').text = "{0}".format(
credit['person']) credit['person'])
if credit['role'].lower() in set(self.colorist_synonyms): if credit['role'].lower() in set(self.colorist_synonyms):
ET.SubElement( ET.SubElement(
root, root,
'colorist').text = u"{0}".format( 'colorist').text = "{0}".format(
credit['person']) credit['person'])
if credit['role'].lower() in set(self.letterer_synonyms): if credit['role'].lower() in set(self.letterer_synonyms):
ET.SubElement( ET.SubElement(
root, root,
'letterer').text = u"{0}".format( 'letterer').text = "{0}".format(
credit['person']) credit['person'])
if credit['role'].lower() in set(self.cover_synonyms): if credit['role'].lower() in set(self.cover_synonyms):
ET.SubElement( ET.SubElement(
root, root,
'coverDesigner').text = u"{0}".format( 'coverDesigner').text = "{0}".format(
credit['person']) credit['person'])
if credit['role'].lower() in set(self.editor_synonyms): if credit['role'].lower() in set(self.editor_synonyms):
ET.SubElement( ET.SubElement(
root, root,
'editor').text = u"{0}".format( 'editor').text = "{0}".format(
credit['person']) credit['person'])
# self pretty-print # self pretty-print

View File

@ -23,7 +23,7 @@ import subprocess
import platform import platform
import ctypes import ctypes
import time import time
import StringIO import io
#import io #import io
#import locale #import locale
#import shutil #import shutil
@ -65,12 +65,13 @@ try:
self._data += chunk self._data += chunk
return 1 return 1
rarfile._ReadIntoMemory._callback = _rar_cb rarfile._ReadIntoMemory._callback = _rar_cb
except: except Exception as e:
print "WARNING: cannot find libunrar, rar support is disabled" print(e)
print("WARNING: cannot find libunrar, rar support is disabled")
pass pass
if platform.system() == "Windows": #if platform.system() == "Windows":
import _subprocess # import _subprocess
try: try:
import Image import Image
@ -78,11 +79,11 @@ try:
except ImportError: except ImportError:
pil_available = False pil_available = False
from comicinfoxml import ComicInfoXml from .comicinfoxml import ComicInfoXml
from comicbookinfo import ComicBookInfo from .comicbookinfo import ComicBookInfo
from comet import CoMet from .comet import CoMet
from genericmetadata import GenericMetadata, PageType from .genericmetadata import GenericMetadata, PageType
from filenameparser import FileNameParser from .filenameparser import FileNameParser
#from settings import ComicTaggerSettings #from settings import ComicTaggerSettings
@ -109,7 +110,10 @@ class ZipArchiver:
return comment return comment
def setArchiveComment(self, comment): def setArchiveComment(self, comment):
return self.writeZipComment(self.path, comment) zf = zipfile.ZipFile(self.path, 'a')
zf.comment = bytes(comment, 'utf-8')
zf.close()
return True
def readArchiveFile(self, archive_file): def readArchiveFile(self, archive_file):
data = "" data = ""
@ -118,14 +122,13 @@ class ZipArchiver:
try: try:
data = zf.read(archive_file) data = zf.read(archive_file)
except zipfile.BadZipfile as e: except zipfile.BadZipfile as e:
print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format( print("bad zipfile [{0}]: {1} :: {2}".format(e, self.path, archive_file), file=sys.stderr)
e, self.path, archive_file)
zf.close() zf.close()
raise IOError raise IOError
except Exception as e: except Exception as e:
zf.close() zf.close()
print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format( print("bad zipfile [{0}]: {1} :: {2}".format(
e, self.path, archive_file) e, self.path, archive_file), file=sys.stderr)
raise IOError raise IOError
finally: finally:
zf.close() zf.close()
@ -164,8 +167,8 @@ class ZipArchiver:
zf.close() zf.close()
return namelist return namelist
except Exception as e: except Exception as e:
print >> sys.stderr, u"Unable to get zipfile list [{0}]: {1}".format( print("Unable to get zipfile list [{0}]: {1}".format(
e, self.path) e, self.path), file=sys.stderr)
return [] return []
def rebuildZipFile(self, exclude_list): def rebuildZipFile(self, exclude_list):
@ -253,12 +256,12 @@ class ZipArchiver:
fo.seek(pos + 2, 2) fo.seek(pos + 2, 2)
# write out the comment itself # write out the comment itself
fo.write(comment) fo.write(bytes(comment))
fo.truncate() fo.truncate()
fo.close() fo.close()
else: else:
raise Exception('Failed to write comment to zip file!') raise Exception('Failed to write comment to zip file!')
except: except Exception as e:
return False return False
else: else:
return True return True
@ -280,8 +283,8 @@ class ZipArchiver:
if not self.writeZipComment(self.path, comment): if not self.writeZipComment(self.path, comment):
return False return False
except Exception as e: except Exception as e:
print >> sys.stderr, u"Error while copying to {0}: {1}".format( print("Error while copying to {0}: {1}".format(
self.path, e) self.path, e), file=sys.stderr)
return False return False
else: else:
return True return True
@ -305,7 +308,7 @@ class RarArchiver:
# windows only, keeps the cmd.exe from popping up # windows only, keeps the cmd.exe from popping up
if platform.system() == "Windows": if platform.system() == "Windows":
self.startupinfo = subprocess.STARTUPINFO() self.startupinfo = subprocess.STARTUPINFO()
self.startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
else: else:
self.startupinfo = None self.startupinfo = None
@ -314,37 +317,38 @@ class RarArchiver:
pass pass
def getArchiveComment(self): def getArchiveComment(self):
rarc = self.getRARObj() rarc = self.getRARObj()
return rarc.comment return rarc.comment
def setArchiveComment(self, comment): def setArchiveComment(self, comment):
if self.rar_exe_path is not None: if self.rar_exe_path is not None:
try: try:
# write comment to temp file # write comment to temp file
tmp_fd, tmp_name = tempfile.mkstemp() tmp_fd, tmp_name = tempfile.mkstemp()
f = os.fdopen(tmp_fd, 'w+b') f = os.fdopen(tmp_fd, 'w+')
f.write(comment) f.write(comment)
f.close() f.close()
working_dir = os.path.dirname(os.path.abspath(self.path)) working_dir = os.path.dirname(os.path.abspath(self.path))
# use external program to write comment to Rar archive # use external program to write comment to Rar archive
subprocess.call([self.rar_exe_path, proc_args = [self.rar_exe_path,
'c', 'c',
'-w' + working_dir, '-w' + working_dir,
'-c-', '-c-',
'-z' + tmp_name, '-z' + tmp_name,
self.path], self.path]
subprocess.call(proc_args,
startupinfo=self.startupinfo, startupinfo=self.startupinfo,
stdout=RarArchiver.devnull) stdout=RarArchiver.devnull,
stdin=RarArchiver.devnull,
stderr=RarArchiver.devnull)
if platform.system() == "Darwin": if platform.system() == "Darwin":
time.sleep(1) time.sleep(1)
os.remove(tmp_name) os.remove(tmp_name)
except: except Exception as e:
print(e)
return False return False
else: else:
return True return True
@ -376,26 +380,26 @@ class RarArchiver:
#entries = rarc.read_files( archive_file ) #entries = rarc.read_files( archive_file )
if entries[0][0].file_size != len(entries[0][1]): if entries[0][0].file_size != len(entries[0][1]):
print >> sys.stderr, u"readArchiveFile(): [file is not expected size: {0} vs {1}] {2}:{3} [attempt # {4}]".format( print("readArchiveFile(): [file is not expected size: {0} vs {1}] {2}:{3} [attempt # {4}]".format(
entries[0][0].file_size, len( entries[0][0].file_size, len(
entries[0][1]), self.path, archive_file, tries) entries[0][1]), self.path, archive_file, tries), file=sys.stderr)
continue continue
except (OSError, IOError) as e: except (OSError, IOError) as e:
print >> sys.stderr, u"readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format( print("readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(
str(e), self.path, archive_file, tries) str(e), self.path, archive_file, tries), file=sys.stderr)
time.sleep(1) time.sleep(1)
except Exception as e: except Exception as e:
print >> sys.stderr, u"Unexpected exception in readArchiveFile(): [{0}] for {1}:{2} attempt#{3}".format( print("Unexpected exception in readArchiveFile(): [{0}] for {1}:{2} attempt#{3}".format(
str(e), self.path, archive_file, tries) str(e), self.path, archive_file, tries), file=sys.stderr)
break break
else: else:
# Success" # Success"
# entries is a list of of tuples: ( rarinfo, filedata) # entries is a list of of tuples: ( rarinfo, filedata)
if tries > 1: if tries > 1:
print >> sys.stderr, u"Attempted read_files() {0} times".format( print("Attempted read_files() {0} times".format(
tries) tries), file=sys.stderr)
if (len(entries) == 1): if (len(entries) == 1):
return entries[0][1] return entries[0][1]
else: else:
@ -428,7 +432,9 @@ class RarArchiver:
self.path, self.path,
tmp_file], tmp_file],
startupinfo=self.startupinfo, startupinfo=self.startupinfo,
stdout=RarArchiver.devnull) stdout=RarArchiver.devnull,
stdin=RarArchiver.devnull,
stderr=RarArchiver.devnull)
if platform.system() == "Darwin": if platform.system() == "Darwin":
time.sleep(1) time.sleep(1)
@ -451,7 +457,9 @@ class RarArchiver:
self.path, self.path,
archive_file], archive_file],
startupinfo=self.startupinfo, startupinfo=self.startupinfo,
stdout=RarArchiver.devnull) stdout=RarArchiver.devnull,
stdin=RarArchiver.devnull,
stderr=RarArchiver.devnull)
if platform.system() == "Darwin": if platform.system() == "Darwin":
time.sleep(1) time.sleep(1)
@ -479,8 +487,8 @@ class RarArchiver:
namelist.append(item.filename) namelist.append(item.filename)
except (OSError, IOError) as e: except (OSError, IOError) as e:
print >> sys.stderr, u"getArchiveFilenameList(): [{0}] {1} attempt#{2}".format( print("getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(
str(e), self.path, tries) str(e), self.path, tries), file=sys.stderr)
time.sleep(1) time.sleep(1)
else: else:
@ -497,8 +505,8 @@ class RarArchiver:
rarc = rarfile.RarFile( self.path ) rarc = rarfile.RarFile( self.path )
except (OSError, IOError) as e: except (OSError, IOError) as e:
print >> sys.stderr, u"getRARObj(): [{0}] {1} attempt#{2}".format( print("getRARObj(): [{0}] {1} attempt#{2}".format(
str(e), self.path, tries) str(e), self.path, tries), file=sys.stderr)
time.sleep(1) time.sleep(1)
else: else:
@ -634,7 +642,7 @@ class ComicArchive:
logo_data = None logo_data = None
class ArchiveType: class ArchiveType:
Zip, Rar, Folder, Pdf, Unknown = range(5) Zip, Rar, Folder, Pdf, Unknown = list(range(5))
def __init__(self, path, rar_exe_path=None, default_image_path=None): def __init__(self, path, rar_exe_path=None, default_image_path=None):
self.path = path self.path = path
@ -729,7 +737,7 @@ class ComicArchive:
if self.archive_type == self.ArchiveType.Unknown: if self.archive_type == self.ArchiveType.Unknown:
return False return False
elif check_rar_status and self.isRar() and self.rar_exe_path is None: elif check_rar_status and self.isRar() and not self.rar_exe_path:
return False return False
elif not os.access(self.path, os.W_OK): elif not os.access(self.path, os.W_OK):
@ -817,7 +825,7 @@ class ComicArchive:
try: try:
image_data = self.archiver.readArchiveFile(filename) image_data = self.archiver.readArchiveFile(filename)
except IOError: except IOError:
print >> sys.stderr, u"Error reading in page. Substituting logo page." print("Error reading in page. Substituting logo page.", file=sys.stderr)
image_data = ComicArchive.logo_data image_data = ComicArchive.logo_data
return image_data return image_data
@ -859,7 +867,7 @@ class ComicArchive:
# sort by most common # sort by most common
sorted_buckets = sorted( sorted_buckets = sorted(
length_buckets.iteritems(), iter(length_buckets.items()),
key=lambda k_v: ( key=lambda k_v: (
k_v[1], k_v[1],
k_v[0]), k_v[0]),
@ -1006,7 +1014,7 @@ class ComicArchive:
try: try:
raw_cix = self.archiver.readArchiveFile(self.ci_xml_filename) raw_cix = self.archiver.readArchiveFile(self.ci_xml_filename)
except IOError: except IOError:
print "Error reading in raw CIX!" print("Error reading in raw CIX!")
raw_cix = "" raw_cix = ""
return raw_cix return raw_cix
@ -1075,13 +1083,13 @@ class ComicArchive:
def readRawCoMet(self): def readRawCoMet(self):
if not self.hasCoMet(): if not self.hasCoMet():
print >> sys.stderr, self.path, "doesn't have CoMet data!" print(self.path, "doesn't have CoMet data!", file=sys.stderr)
return None return None
try: try:
raw_comet = self.archiver.readArchiveFile(self.comet_filename) raw_comet = self.archiver.readArchiveFile(self.comet_filename)
except IOError: except IOError:
print >> sys.stderr, u"Error reading in raw CoMet!" print("Error reading in raw CoMet!", file=sys.stderr)
raw_comet = "" raw_comet = ""
return raw_comet return raw_comet
@ -1136,7 +1144,7 @@ class ComicArchive:
data = self.archiver.readArchiveFile(n) data = self.archiver.readArchiveFile(n)
except: except:
data = "" data = ""
print >> sys.stderr, u"Error reading in Comet XML for validation!" print("Error reading in Comet XML for validation!", file=sys.stderr)
if CoMet().validateString(data): if CoMet().validateString(data):
# since we found it, save it! # since we found it, save it!
self.comet_filename = n self.comet_filename = n
@ -1156,7 +1164,7 @@ class ComicArchive:
data = self.getPage(idx) data = self.getPage(idx)
if data is not None: if data is not None:
try: try:
im = Image.open(StringIO.StringIO(data)) im = Image.open(io.StringIO(data))
w, h = im.size w, h = im.size
p['ImageSize'] = str(len(data)) p['ImageSize'] = str(len(data))

View File

@ -18,8 +18,8 @@ import json
from datetime import datetime from datetime import datetime
#import zipfile #import zipfile
from genericmetadata import GenericMetadata from .genericmetadata import GenericMetadata
import utils from . import utils
#import ctversion #import ctversion
@ -27,7 +27,7 @@ class ComicBookInfo:
def metadataFromString(self, string): def metadataFromString(self, string):
cbi_container = json.loads(unicode(string, 'utf-8')) cbi_container = json.loads(str(string, 'utf-8'))
metadata = GenericMetadata() metadata = GenericMetadata()
@ -109,7 +109,7 @@ class ComicBookInfo:
# helper func # helper func
def toInt(s): def toInt(s):
i = None i = None
if type(s) in [str, unicode, int]: if type(s) in [str, str, int]:
try: try:
i = int(s) i = int(s)
except ValueError: except ValueError:

View File

@ -19,8 +19,8 @@ import xml.etree.ElementTree as ET
#from pprint import pprint #from pprint import pprint
#import zipfile #import zipfile
from genericmetadata import GenericMetadata from .genericmetadata import GenericMetadata
import utils from . import utils
class ComicInfoXml: class ComicInfoXml:
@ -54,7 +54,8 @@ class ComicInfoXml:
header = '<?xml version="1.0"?>\n' header = '<?xml version="1.0"?>\n'
tree = self.convertMetadataToXML(self, metadata) tree = self.convertMetadataToXML(self, metadata)
return header + ET.tostring(tree.getroot()) tree_str = ET.tostring(tree.getroot()).decode()
return header + tree_str
def indent(self, elem, level=0): def indent(self, elem, level=0):
# for making the XML output readable # for making the XML output readable
@ -85,7 +86,7 @@ class ComicInfoXml:
def assign(cix_entry, md_entry): def assign(cix_entry, md_entry):
if md_entry is not None: if md_entry is not None:
ET.SubElement(root, cix_entry).text = u"{0}".format(md_entry) ET.SubElement(root, cix_entry).text = "{0}".format(md_entry)
assign('Title', md.title) assign('Title', md.title)
assign('Series', md.series) assign('Series', md.series)

View File

@ -22,7 +22,7 @@ This should probably be re-written, but, well, it mostly works!
import re import re
import os import os
from urllib import unquote from urllib.parse import unquote
class FileNameParser: class FileNameParser:

View File

@ -20,7 +20,7 @@ possible, however lossy it might be
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import utils from . import utils
class PageType: class PageType:
@ -251,7 +251,7 @@ class GenericMetadata:
return "No metadata" return "No metadata"
def add_string(tag, val): def add_string(tag, val):
if val is not None and u"{0}".format(val) != "": if val is not None and "{0}".format(val) != "":
vals.append((tag, val)) vals.append((tag, val))
def add_attr_string(tag): def add_attr_string(tag):
@ -314,7 +314,7 @@ class GenericMetadata:
# format the data nicely # format the data nicely
outstr = "" outstr = ""
fmt_str = u"{0: <" + str(flen) + "} {1}\n" fmt_str = "{0: <" + str(flen) + "} {1}\n"
for i in vals: for i in vals:
outstr += fmt_str.format(i[0] + ":", i[1]) outstr += fmt_str.format(i[0] + ":", i[1])

View File

@ -44,7 +44,7 @@ class IssueString:
if len(text) == 0: if len(text) == 0:
return return
text = unicode(text) text = str(text)
# skip the minus sign if it's first # skip the minus sign if it's first
if text[0] == '-': if text[0] == '-':
@ -119,7 +119,7 @@ class IssueString:
def asFloat(self): def asFloat(self):
# return the float, with no suffix # return the float, with no suffix
if self.suffix == u"½": if self.suffix == "½":
if self.num is not None: if self.num is not None:
return self.num + .5 return self.num + .5
else: else:

View File

@ -55,20 +55,22 @@ def get_recursive_filelist(pathlist):
# if path is a folder, walk it recursively, and all files underneath # if path is a folder, walk it recursively, and all files underneath
if isinstance(p, str): if isinstance(p, str):
# make sure string is unicode # make sure string is unicode
p = p.decode(filename_encoding) # , 'replace') #p = p.decode(filename_encoding) # , 'replace')
elif not isinstance(p, unicode): pass
elif not isinstance(p, str):
# it's probably a QString # it's probably a QString
p = unicode(p) p = str(p)
if os.path.isdir(p): if os.path.isdir(p):
for root, dirs, files in os.walk(p): for root, dirs, files in os.walk(p):
for f in files: for f in files:
if isinstance(f, str): if isinstance(f, str):
# make sure string is unicode # make sure string is unicode
f = f.decode(filename_encoding, 'replace') #f = f.decode(filename_encoding, 'replace')
elif not isinstance(f, unicode): pass
elif not isinstance(f, str):
# it's probably a QString # it's probably a QString
f = unicode(f) f = str(f)
filelist.append(os.path.join(root, f)) filelist.append(os.path.join(root, f))
else: else:
filelist.append(p) filelist.append(p)
@ -121,7 +123,7 @@ def which(program):
def removearticles(text): def removearticles(text):
text = text.lower() text = text.lower()
articles = ['and', 'the', 'a', '&', 'issue'] articles = ['and', 'a', '&', 'issue']
newText = '' newText = ''
for word in text.split(' '): for word in text.split(' '):
if word not in articles: if word not in articles: