1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
| #!/usr/bin/python3
# -*- coding: utf-8 -*-
# Python 3, PyQt4
"""
Exemple de code pour dézipper un fichier zip avec barre de progression
et exécution dans un thread pour éviter le gel du graphique
"""
import sys
import zipfile
from PyQt4 import QtCore, QtGui
#############################################################################
class Arretanticipe(Exception):
"""création d'une exception spécifique
"""
pass
#############################################################################
class Monzip(zipfile.ZipFile):
"""sous classement de ZipFile pour ajouter la fonctionnalité de callback
permettant de visualiser la progression du traitement
"""
#========================================================================
def __init__(self, *args, **kwargs):
super(Monzip, self).__init__(*args, **kwargs)
#========================================================================
def extractall(self, path=None, members=None, pwd=None, callback=None):
"""simple recopie de la méthode de ZipFile du module zipfile
avec ajout du callback pour renvoyer le % d'avancement
"""
if members is None:
members = self.namelist()
n = len(members) # <== ajout: nombre total de fichiers à extraire
for i, zipinfo in enumerate(members):
self.extract(zipinfo, path, pwd)
if callback != None: # <== ajout
callback((i + 1) / n) # <== ajout: pourcentage d'avancement
#############################################################################
class Monthread(QtCore.QThread):
# création d'un nouveau signal pour info du % d'avancement
progduthread = QtCore.pyqtSignal(float)
# création d'un nouveau signal pour info de la fin du thread
finduthread = QtCore.pyqtSignal(str)
#========================================================================
def __init__(self, fichierzip, repextract, parent=None):
QtCore.QThread.__init__(self, parent)
self.fichierzip = fichierzip # fichier zip à dézipper
self.repextract = repextract # répertoire pour placer l'extraction
self.stopthread = False # pour gérer l'arrêt anticipé
#========================================================================
def run(self):
"""partie de la classe de QThread qui s'exécute en tâche de fond
"""
try:
# lancement de l'extraction zip avec callback
with Monzip(self.fichierzip, compression=zipfile.ZIP_DEFLATED) as fzip:
fzip.extractall(self.repextract, callback=self.avancement)
# fin normale du thread
self.finduthread.emit("Fin de l'action")
except Arretanticipe:
# exception déclenchée par une demande d'arrêt anticipée
pass
#========================================================================
def avancement(self, pourc):
"""méthode lancée pendant l'action avec callback
"""
if self.stopthread:
# une demande d'arrêt anticipé a été faite
raise Arretanticipe
else:
# envoi du % d'avancement pour mise à jour de la barre de progression
self.progduthread.emit(pourc)
#========================================================================
def stop(self):
"""appelé pour demander l'arrêt anticipé du thread
"""
self.stopthread = True
#############################################################################
class Fenetre(QtGui.QWidget):
#========================================================================
def __init__(self, parent=None):
super(Fenetre, self).__init__(parent)
self.resize(300, 200)
# bouton qui exécute la méthode action
self.bouton = QtGui.QPushButton(u"début", self)
self.bouton.clicked.connect(self.action)
# positionn du bouton dans la fenêtre
posit = QtGui.QGridLayout()
posit.addWidget(self.bouton, 0, 0)
self.setLayout(posit)
# création des variables définies plus tard
self.monthread = None
self.prog = None
#========================================================================
def action(self):
"""méthode lancée par le bouton pour démarrer ou stopper une
opération longue dans un thread
"""
if self.monthread == None or not self.monthread.isRunning():
# aucun thread en cours de travail:
# => démarrage de l'action dans le thread avec progression
# création du QProgressDialog avec une plage de 0% à 100%
self.prog = QtGui.QProgressDialog("En cours...", "Annuler", 0, 100, self)
self.prog.setWindowTitle("Action")
# branchement du bouton "annuler" à la méthode 'stopaction'
self.prog.canceled.connect(self.stopaction)
# affichage de la fenêtre de progression
self.prog.show()
# fichier zip à dézipper
fichierzip = "Lib.zip" # <== à adapter
# répertoire où mettre les fichiers extraits
repextract = "temp" # <== à adapter
# lancement du thread avec son opération
self.monthread = Monthread(fichierzip, repextract)
# se préparer à recevoir le % d'avancement
self.monthread.progduthread.connect(self.progression)
# se préparer à recevoir le signal de fin du thread
self.monthread.finduthread.connect(self.finaction)
# démarrer le thread
self.monthread.start()
# changement du texte du bouton
self.bouton.setText("Stop")
else:
# il y a déjà un thread en cours de travail
# => arrêt du thread avant la fin, demandé par le bouton "Stop"
self.stopaction()
#========================================================================
def progression(self, pourc):
"""met à jour la barre de progression à chaque fois que le thread
renvoie le % d'avancement grâce au callback
"""
# met à jour la barre de progression pour affichage du % d'avancement
self.prog.setValue(pourc * 100)
# force la mise à jour en temps réel de l'affichage
QtCore.QCoreApplication.processEvents()
#========================================================================
def stopaction(self):
"""lancé par le bouton "annuler" de la fenêtre de progression
pour arrêt anticipé du traitement
"""
# déconnecter les liens avec les signaux du thread
self.monthread.progduthread.disconnect()
self.monthread.finduthread.disconnect()
# arrêt anticipée du thread
self.monthread.stop()
# arrêt de la fenêtre de progression
self.prog.reset()
# retour au texte initial du bouton
self.bouton.setText("Début")
#========================================================================
def finaction(self, msg):
"""méthode lancée à la fin normale du thread
"""
# arrêt de la fenêtre de progression
self.prog.reset()
# retour au texte initial du bouton
self.bouton.setText("Début")
# affichage du message de fin pour la fin normale
QtGui.QMessageBox.information(self,
"Information",
msg)
#############################################################################
def main():
app = QtGui.QApplication(sys.argv)
fen = Fenetre()
fen.show()
sys.exit(app.exec_())
#############################################################################
if __name__ == "__main__":
main() |
Partager