Work on multi-file processing

git-svn-id: http://comictagger.googlecode.com/svn/trunk@300 6c5673fe-1810-88d6-992b-cd32ca31540c
This commit is contained in:
beville@gmail.com 2013-01-16 22:46:22 +00:00
parent 8f45994b9a
commit 9e68516dac
7 changed files with 1421 additions and 961 deletions

View File

@ -419,8 +419,8 @@ class ComicArchive:
self.path = path
self.ci_xml_filename = 'ComicInfo.xml'
self.comet_default_filename = 'CoMet.xml'
self.comet_filename = None
self.resetCache()
if self.zipTest():
self.archive_type = self.ArchiveType.Zip
self.archiver = ZipArchiver( self.path )
@ -436,6 +436,14 @@ class ComicArchive:
self.archive_type = self.ArchiveType.Unknown
self.archiver = UnknownArchiver( self.path )
# Clears the cached data
def resetCache( self ):
self.has_cix = None
self.has_cbi = None
self.comet_filename = None
self.page_count = None
self.page_list = None
def setExternalRarProgram( self, rar_exe_path ):
if self.isRar():
self.archiver.rar_exe_path = rar_exe_path
@ -512,12 +520,16 @@ class ComicArchive:
def writeMetadata( self, metadata, style ):
retcode = None
if style == MetaDataStyle.CIX:
return self.writeCIX( metadata )
retcode = self.writeCIX( metadata )
elif style == MetaDataStyle.CBI:
return self.writeCBI( metadata )
retcode = self.writeCBI( metadata )
elif style == MetaDataStyle.COMET:
return self.writeCoMet( metadata )
retcode = self.writeCoMet( metadata )
self.resetCache()
return retcode
def hasMetadata( self, style ):
@ -561,29 +573,32 @@ class ComicArchive:
def getPageNameList( self , sort_list=True):
# get the list file names in the archive, and sort
files = self.archiver.getArchiveFilenameList()
# seems like some archive creators are on Windows, and don't know about case-sensitivity!
if sort_list:
files.sort(key=lambda x: x.lower())
# make a sub-list of image files
page_list = []
for name in files:
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] and os.path.basename(name)[0] != "." ):
page_list.append(name)
if self.page_list is None:
# get the list file names in the archive, and sort
files = self.archiver.getArchiveFilenameList()
# seems like some archive creators are on Windows, and don't know about case-sensitivity!
if sort_list:
files.sort(key=lambda x: x.lower())
# make a sub-list of image files
self.page_list = []
for name in files:
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] and os.path.basename(name)[0] != "." ):
self.page_list.append(name)
return page_list
return self.page_list
def getNumberOfPages( self ):
return len( self.getPageNameList( sort_list=False ) )
if self.page_count is None:
self.page_count = len( self.getPageNameList( ) )
return self.page_count
def readCBI( self ):
raw_cbi = self.readRawCBI()
if raw_cbi is None:
md =GenericMetadata()
md = GenericMetadata()
else:
md = ComicBookInfo().metadataFromString( raw_cbi )
@ -598,12 +613,16 @@ class ComicArchive:
return self.archiver.getArchiveComment()
def hasCBI(self):
#if ( not ( self.isZip() or self.isRar()) or not self.seemsToBeAComicArchive() ):
if not self.seemsToBeAComicArchive():
return False
if self.has_cbi is None:
comment = self.archiver.getArchiveComment()
return ComicBookInfo().validateString( comment )
#if ( not ( self.isZip() or self.isRar()) or not self.seemsToBeAComicArchive() ):
if not self.seemsToBeAComicArchive():
self.has_cbi = False
else:
comment = self.archiver.getArchiveComment()
self.has_cbi = ComicBookInfo().validateString( comment )
return self.has_cbi
def writeCBI( self, metadata ):
self.applyArchiveInfoToMetadata( metadata )
@ -633,7 +652,6 @@ class ComicArchive:
def readRawCIX( self ):
if not self.hasCIX():
print self.path, "doesn't have ComicInfo.xml data!"
return None
return self.archiver.readArchiveFile( self.ci_xml_filename )
@ -652,12 +670,15 @@ class ComicArchive:
return self.archiver.removeArchiveFile( self.ci_xml_filename )
def hasCIX(self):
if not self.seemsToBeAComicArchive():
return False
elif self.ci_xml_filename in self.archiver.getArchiveFilenameList():
return True
else:
return False
if self.has_cix is None:
if not self.seemsToBeAComicArchive():
self.has_cix = False
elif self.ci_xml_filename in self.archiver.getArchiveFilenameList():
self.has_cix = True
else:
self.has_cix = False
return self.has_cix
def readCoMet( self ):

217
fileselectionlist.py Normal file
View File

@ -0,0 +1,217 @@
# coding=utf-8
"""
A PyQt4 widget for managing list of files
"""
"""
Copyright 2012 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic
from PyQt4.QtCore import pyqtSignal
from settings import ComicTaggerSettings
from comicarchive import ComicArchive
from genericmetadata import GenericMetadata, PageType
from options import MetaDataStyle
class FileTableWidget( QTableWidget ):
def __init__(self, parent ):
super(FileTableWidget, self).__init__(parent)
self.setColumnCount(5)
self.setHorizontalHeaderLabels (["File", "Folder", "CR", "CBL", ""])
self.horizontalHeader().setStretchLastSection( True )
class FileTableWidgetItem(QTableWidgetItem):
def __lt__(self, other):
return (self.data(Qt.UserRole).toBool() <
other.data(Qt.UserRole).toBool())
class FileInfo( ):
def __init__(self, path, ca, cix_md, cbi_md ):
self.path = path
self.cix_md = cix_md
self.cbi_md = cbi_md
self.ca = ca
class FileSelectionList(QWidget):
selectionChanged = pyqtSignal(QVariant)
def __init__(self, parent , settings ):
super(FileSelectionList, self).__init__(parent)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'fileselectionlist.ui' ), self)
self.settings = settings
#self.twList = FileTableWidget( self )
#gridlayout = QGridLayout( self )
#gridlayout.addWidget( self.twList )
self.setAcceptDrops(True)
self.twList.itemSelectionChanged.connect( self.itemSelectionChangedCB )
def dragEnterEvent(self, event):
self.droppedFiles = None
if event.mimeData().hasUrls():
# walk through the URL list and build a file list
for url in event.mimeData().urls():
if url.isValid() and url.scheme() == "file":
if self.droppedFiles is None:
self.droppedFiles = []
self.droppedFiles.append(url.toLocalFile())
if self.droppedFiles is not None:
event.accept()
def dropEvent(self, event):
self.addPathList( self.droppedFiles)
event.accept()
def addPathList( self, pathlist ):
filelist = []
for p in pathlist:
# if path is a folder, walk it recursivly, and all files underneath
if os.path.isdir( unicode(p)):
for root,dirs,files in os.walk( unicode(p) ):
for f in files:
filelist.append(os.path.join(root,unicode(f)))
else:
filelist.append(unicode(p))
# we now have a list of files to add
progdialog = QProgressDialog("", "Cancel", 0, len(filelist), self)
progdialog.setWindowTitle( "Adding Files" )
progdialog.setWindowModality(Qt.WindowModal)
self.twList.setSortingEnabled(False)
for idx,f in enumerate(filelist):
QCoreApplication.processEvents()
if progdialog.wasCanceled():
break
progdialog.setValue(idx)
self.addPathItem( f )
progdialog.close()
self.twList.setSortingEnabled(True)
#Maybe set a max size??
self.twList.resizeColumnsToContents()
def isListDupe( self, path ):
r = 0
while r < self.twList.rowCount():
fi = self.twList.item(r, 0).data( Qt.UserRole ).toPyObject()
if fi.path == path:
return True
r = r + 1
return False
def addPathItem( self, path):
path = unicode( path )
#print "processing", path
if self.isListDupe(path):
return
ca = ComicArchive( path )
if self.settings.rar_exe_path != "":
ca.setExternalRarProgram( self.settings.rar_exe_path )
if ca.seemsToBeAComicArchive() :
row = self.twList.rowCount()
self.twList.insertRow( row )
cix_md = None
cbi_md = None
has_cix = ca.hasCIX()
if has_cix:
cix_md = ca.readCIX()
has_cbi = ca.hasCBI()
if has_cbi:
cbi_md = ca.readCBI()
fi = FileInfo( path, ca, cix_md, cbi_md )
item_text = os.path.split(path)[1]
item = QTableWidgetItem(item_text)
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setData( Qt.UserRole , fi )
item.setData( Qt.ToolTipRole ,item_text)
self.twList.setItem(row, 0, item)
item_text = os.path.split(path)[0]
item = QTableWidgetItem(item_text)
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setData( Qt.ToolTipRole ,item_text)
self.twList.setItem(row, 1, item)
# Attempt to use a special checkbox widget in the cell.
# Couldn't figure out how to disable it with "enabled" colors
#w = QWidget()
#cb = QCheckBox(w)
#cb.setCheckState(Qt.Checked)
#layout = QHBoxLayout()
#layout.addWidget( cb )
#layout.setAlignment(Qt.AlignHCenter)
#layout.setMargin(2)
#w.setLayout(layout)
#self.twList.setCellWidget( row, 2, w )
item = FileTableWidgetItem()
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setTextAlignment(Qt.AlignHCenter)
if has_cix:
item.setCheckState(Qt.Checked)
item.setData(Qt.UserRole, True)
else:
item.setData(Qt.UserRole, False)
self.twList.setItem(row, 2, item)
item = FileTableWidgetItem()
item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
item.setTextAlignment(Qt.AlignHCenter)
if has_cbi:
item.setCheckState(Qt.Checked)
item.setData(Qt.UserRole, True)
else:
item.setData(Qt.UserRole, False)
self.twList.setItem(row, 3, item)
def itemSelectionChangedCB( self ):
idx = self.twList.currentRow()
fi = self.twList.item(idx, 0).data( Qt.UserRole ).toPyObject()
#if fi.cix_md is not None:
# print u"{0}".format(fi.cix_md)
self.selectionChanged.emit( QVariant(fi))

69
fileselectionlist.ui Normal file
View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>pageListEditor</class>
<widget class="QWidget" name="pageListEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>527</width>
<height>323</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableWidget" name="twList">
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>61</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>36</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>File</string>
</property>
</column>
<column>
<property name="text">
<string>Path</string>
</property>
</column>
<column>
<property name="text">
<string>CR</string>
</property>
<property name="toolTip">
<string/>
</property>
<property name="textAlignment">
<set>AlignHCenter|AlignVCenter|AlignCenter</set>
</property>
</column>
<column>
<property name="text">
<string>CBL</string>
</property>
<property name="textAlignment">
<set>AlignHCenter|AlignVCenter|AlignCenter</set>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -27,6 +27,7 @@ from PyQt4 import uic
from settings import ComicTaggerSettings
from genericmetadata import GenericMetadata, PageType
from options import MetaDataStyle
from pageloader import PageLoader
def itemMoveEvents( widget ):
@ -79,6 +80,7 @@ class PageListEditor(QWidget):
self.comic_archive = None
self.pages_list = None
self.page_loader = None
self.current_pixmap = QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' ))
self.setDisplayPixmap( 0, 0)
@ -151,17 +153,19 @@ class PageListEditor(QWidget):
#idx = int(str (self.listWidget.item( row ).text()))
idx = int(self.listWidget.item( row ).data(Qt.UserRole).toPyObject()[0]['Image'])
if self.page_loader is not None:
self.page_loader.abandoned = True
if self.comic_archive is not None:
image_data = self.comic_archive.getPage( idx )
else:
image_data = None
if image_data is not None:
img = QImage()
img.loadFromData( image_data )
self.current_pixmap = QPixmap(QPixmap(img))
self.setDisplayPixmap( 0, 0)
self.page_loader = PageLoader( self.comic_archive, idx )
self.page_loader.loadComplete.connect( self.actualChangePageImage )
self.page_loader.start()
def actualChangePageImage( self, img ):
self.page_loader = None
self.current_pixmap = QPixmap(img)
self.setDisplayPixmap( 0, 0)
def getFirstFrontCover( self ):
frontCover = 0

77
pageloader.py Normal file
View File

@ -0,0 +1,77 @@
"""
A PyQT4 class to load a page image from a ComicArchive in a background thread
"""
"""
Copyright 2012 Anthony Beville
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import pyqtSignal
from comicarchive import ComicArchive
"""
This class holds onto a reference of each instance in a list
since problems occur if the ref count goes to zero and the GC
tries to reap the object while the thread is going.
If the client class wants to stop the thread, they should mark
it as "abandoned", and no signals will be issued
"""
class PageLoader( QtCore.QThread ):
loadComplete = pyqtSignal( QtGui.QImage )
instanceList = []
mutex = QtCore.QMutex()
"""
Remove all finished threads from the list
"""
@staticmethod
def reapInstances():
for obj in reversed(PageLoader.instanceList ):
if obj.isFinished():
PageLoader.instanceList.remove(obj)
def __init__(self, ca, page_num ):
QtCore.QThread.__init__(self)
self.ca = ca
self.page_num = page_num
self.abandoned = False
# remove any old instances, and then add ourself
PageLoader.mutex.lock()
PageLoader.reapInstances()
PageLoader.instanceList.append( self )
PageLoader.mutex.unlock()
def run(self):
image_data = self.ca.getPage( self.page_num )
if self.abandoned:
return
if image_data is not None:
img = QtGui.QImage()
img.loadFromData( image_data )
if self.abandoned:
return
self.loadComplete.emit( img )

View File

@ -44,8 +44,10 @@ from filenameparser import FileNameParser
from logwindow import LogWindow
from optionalmsgdialog import OptionalMessageDialog
from pagelisteditor import PageListEditor
from fileselectionlist import FileSelectionList
from cbltransformer import CBLTransformer
from renamewindow import RenameWindow
from pageloader import PageLoader
import utils
import ctversion
@ -91,18 +93,32 @@ class TaggerWindow( QtGui.QMainWindow):
#signal.signal(signal.SIGINT, self.sigint_handler)
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'taggerwindow.ui' ), self)
self.settings = settings
self.pageListEditor = PageListEditor( self.tabPages )
gridlayout = QtGui.QGridLayout( self.tabPages )
gridlayout.addWidget( self.pageListEditor )
#---------------------------
self.fileSelectionList = FileSelectionList( self.widgetListHolder, self.settings )
gridlayout = QtGui.QGridLayout( self.widgetListHolder )
gridlayout.addWidget( self.fileSelectionList )
self.fileSelectionList.selectionChanged.connect( self.fileListSelectionChanged )
# ATB: Disable the list for now...
self.splitter.setSizes([100,0])
self.splitter.setHandleWidth(0)
self.splitter.handle(1).setDisabled(True)
#---------------------------
self.setWindowIcon(QtGui.QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/app.png' )))
self.lblCover.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )))
#print platform.system(), platform.release()
self.dirtyFlag = False
self.settings = settings
self.data_style = settings.last_selected_data_style
#set up a default metadata object
@ -117,6 +133,7 @@ class TaggerWindow( QtGui.QMainWindow):
self.droppedFile = None
self.page_browser = None
self.page_loader = None
self.populateComboBoxes()
@ -362,7 +379,6 @@ class TaggerWindow( QtGui.QMainWindow):
msgBox.setStandardButtons( QtGui.QMessageBox.Ok )
msgBox.exec_()
def dragEnterEvent(self, event):
self.droppedFile=None
if event.mimeData().hasUrls():
@ -371,12 +387,12 @@ class TaggerWindow( QtGui.QMainWindow):
if url.scheme()=="file":
self.droppedFile=url.toLocalFile()
event.accept()
def dropEvent(self, event):
if self.dirtyFlagVerification( "Open Archive",
"If you open a new archive now, data in the form will be lost. Are you sure?"):
self.openArchive( unicode(self.droppedFile))
def openArchive( self, path, explicit_style=None, clear_form=True ):
if path is None or path == "":
@ -431,35 +447,44 @@ class TaggerWindow( QtGui.QMainWindow):
else:
return
if self.metadata.isEmpty:
self.metadata = self.comic_archive.metadataFromFilename( )
self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() )
self.updateCoverImage()
if self.page_browser is not None:
self.page_browser.setComicArchive( self.comic_archive )
self.page_browser.metadata = self.metadata
self.metadataToForm()
self.pageListEditor.setData( self.comic_archive, self.metadata.pages )
self.clearDirtyFlag() # also updates the app title
self.updateInfoBox()
self.updateMenus()
#self.updatePagesInfo()
self.loadCurrentArchive()
else:
QtGui.QMessageBox.information(self, self.tr("Whoops!"), self.tr("That file doesn't appear to be a comic archive!"))
def updateCoverImage( self ):
cover_idx = self.metadata.getCoverPageIndexList()[0]
image_data = self.comic_archive.getPage( cover_idx )
if not image_data is None:
img = QtGui.QImage()
img.loadFromData( image_data )
self.lblCover.setPixmap(QtGui.QPixmap(img))
self.lblCover.setScaledContents(True)
def loadCurrentArchive( self ):
if self.metadata.isEmpty:
self.metadata = self.comic_archive.metadataFromFilename( )
self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() )
self.updateCoverImage()
if self.page_browser is not None:
self.page_browser.setComicArchive( self.comic_archive )
self.page_browser.metadata = self.metadata
self.metadataToForm()
self.pageListEditor.setData( self.comic_archive, self.metadata.pages )
self.clearDirtyFlag() # also updates the app title
self.updateInfoBox()
self.updateMenus()
def updateCoverImage( self ):
if self.page_loader is not None:
self.page_loader.abandoned = True
cover_idx = self.metadata.getCoverPageIndexList()[0]
self.page_loader = PageLoader( self.comic_archive, cover_idx )
self.page_loader.loadComplete.connect( self.actualUpdateCoverImage )
self.page_loader.start()
def actualUpdateCoverImage( self, img ):
self.page_loader = None
self.lblCover.setPixmap(QtGui.QPixmap(img))
self.lblCover.setScaledContents(True)
def updateMenus( self ):
# First just disable all the questionable items
@ -1389,5 +1414,19 @@ class TaggerWindow( QtGui.QMainWindow):
self.comic_archive = None
self.openArchive( dlg.new_name )
def fileListSelectionChanged( self, qvarFI ):
fi = qvarFI.toPyObject()
#if fi.cix_md is not None:
# print u"{0}".format(fi.cix_md)
self.comic_archive = None
self.clearForm()
self.comic_archive = fi.ca
if self.data_style == MetaDataStyle.CIX:
self.metadata = fi.cix_md
else:
self.metadata = fi.cbi_md
if self.metadata is None:
self.metadata = GenericMetadata()
self.loadCurrentArchive()

File diff suppressed because it is too large Load Diff