Catturare output comando in tempo reale in una gui Python (GTK)

Autore: Fabio Di Matteo
Ultima revisione: 21/12/2018 - 10:48

#!/usr/bin/env python
 
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk
 
import sys, subprocess, shlex, os
import threading
 
class MainWindow (Gtk.Builder):
	textView=None
	textbuffer = None
 
	def cmdThread(self,cmd):
		print("Comando: %s" % cmd)
		txtBuffer=""
		out=""
		fileOutErr = open("errors.txt","w")
		#Lo stdout lo leggo dalla pipe, gli errori li salvo in un file che leggero' dopo
		proc = subprocess.Popen(shlex.split(cmd), bufsize=0 ,stdout=subprocess.PIPE, stderr=fileOutErr)
 
		#leggo la pipe finchè non finisce(proc.poll() is None)
		while proc.poll() is None :
			out = proc.stdout.readline()
			txtBuffer=txtBuffer+str(out.decode("utf-8"))
			GLib.idle_add(MainWindow.textbuffer.set_text,txtBuffer)
			print(str(out.decode("utf-8")))
		fileOutErr.close()
 
		#Stampo gli errori dopo la fine della pipe
		if (os.stat("errors.txt").st_size != 0):
			fileOutErr= open("errors.txt","r")
			listErr=fileOutErr.readlines()
			txtBuffer=txtBuffer+"\n\nErrori:\n------------------\n\n"
			for e in listErr:
				txtBuffer=txtBuffer+e
 
			#Aggiungo al text buffer gli errori presi dal file e chiudo il file
			GLib.idle_add(MainWindow.textbuffer.set_text,txtBuffer)
			fileOutErr.close()
 
	def on_button_clicked(self, textView,entry):
 
		cmdt = threading.Thread(target=MainWindow.cmdThread, args=(self,entry.get_text()),)
		cmdt.daemon = True
		cmdt.start()
 
 
 
 
 
	def quit(self,win):
		print ("Exit!")
		Gtk.main_quit(win)		
 
	def __init__(self):
 
		self = Gtk.Builder()
		self.add_from_file("builder.ui")
 
		window = self.get_object("window")
		entry = self.get_object("entry")
		MainWindow.textView = self.get_object("textView")
		button = self.get_object("button")
 
		#Init
		MainWindow.textbuffer = MainWindow.textView.get_buffer()
		MainWindow.textbuffer.set_text("This is some text inside of a Gtk.TextView. "
			+ "Select text and click one of the buttons 'bold', 'italic', "
			+ "or 'underline' to modify the text accordingly.")
		if (sys.platform == "win32"):
			entry.set_text("./rsync-win/rsync.exe -avz  /cygdrive/c/msys64/mingw64/bin /cygdrive/c/users/admin/desktop/tt/")
 
		window.show_all()
 
		window.connect("delete-event", MainWindow.quit)
		button.connect("clicked", MainWindow.on_button_clicked, MainWindow.textView,entry)
 
 
 
 
 
myWindow =  MainWindow()
Gtk.main()

builder.ui

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.18"/>
  <object class="GtkWindow" id="window">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <signal name="delete-event" handler="quit" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkEntry" id="entry">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="text" translatable="yes">rsync -avz /usr/bin/ /home/fabio/Desktop/tt/</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button">
                <property name="label" translatable="yes">button</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="can_default">True</property>
                <property name="has_default">True</property>
                <property name="receives_default">True</property>
                <signal name="clicked" handler="on_button_clicked" object="label" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <placeholder/>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkScrolledWindow">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="shadow_type">in</property>
            <property name="min_content_width">400</property>
            <property name="min_content_height">300</property>
            <property name="propagate_natural_width">True</property>
            <property name="propagate_natural_height">True</property>
            <child>
              <object class="GtkTextView" id="textView">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="wrap_mode">char</property>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Catturare progresso totale di rsync in una GtkProgressbar

Per questo esperimento useremo l'opzione di rsync –info=progress2

get-rsync-progress.py

#!/usr/bin/env python
 
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk
 
import sys, subprocess, shlex, os
import threading
 
class MainWindow (Gtk.Builder):
	textView=None
	textbuffer = None
	progressBar=None
 
 
	def getPercent(line):
		# Example 11,131,595   1%   10.47MB/s    0:00:00 (xfr#16, to-chk=2761/4538)
 
		# Splitto la corrente riga dello stdout su una lista con separatore lo spazio
		line=line.split(" ")
 
		# Se uno degli elementi della lista contiene un simbolo di percentuale (%)
		# allora ritorno solo l'intero (sotto forma di stringa) ,sia esso formato 
		# da 1,2 o tre cifre
		for l in line:
			if ('%' in l):
				if (len(l)==2): return l[0]
				if (len(l)==3): return l[0]+l[1]
				if (len(l)==4): return l[0]+l[1]+l[2]
 
 
	def cmdThread(self,cmd):
		print("Comando: %s" % cmd)
		txtBuffer=""
		out=""
		fileOutErr = open("errors.txt","w")
 
		# Lo stdout lo leggo dalla pipe, gli errori li salvo in un file che leggero' dopo
		proc = subprocess.Popen(shlex.split(cmd), bufsize=0 ,stdout=subprocess.PIPE, stderr=fileOutErr)
 
		# leggo la pipe finchè non finisce(proc.poll() is None)
		while proc.poll() is None :
			out = proc.stdout.readline()
			txtBuffer=txtBuffer+str(out.decode("utf-8"))
			GLib.idle_add(MainWindow.textbuffer.set_text,txtBuffer)
 
			# Faccio il parsing della percentuale di progresso e aggiorno la gtkProgressbar
			# dal metodo MainWindow.getPercent ricevo una stringa del genere "50","10". etc...
			if (MainWindow.getPercent(str(out.decode("utf-8"))) is not None):
				p=float(MainWindow.getPercent(str(out.decode("utf-8"))))/100
				GLib.idle_add(MainWindow.progressBar.set_fraction,p)
 
		fileOutErr.close()
		GLib.idle_add(MainWindow.progressBar.set_fraction,1.0)
 
		#Stampo gli errori dopo la fine della pipe
		if (os.stat("errors.txt").st_size != 0):
			fileOutErr= open("errors.txt","r")
			listErr=fileOutErr.readlines()
			txtBuffer=txtBuffer+"\n\nErrori:\n------------------\n\n"
			for e in listErr:
				txtBuffer=txtBuffer+e
 
			#Aggiungo al text buffer gli errori presi dal file e chiudo il file
			GLib.idle_add(MainWindow.textbuffer.set_text,txtBuffer)
			fileOutErr.close()
 
	def on_button_clicked(self, textView,entry):
 
		cmdt = threading.Thread(target=MainWindow.cmdThread, args=(self,entry.get_text()),)
		cmdt.daemon = True
		cmdt.start()
 
 
 
 
 
	def quit(self,win):
		print ("Exit!")
		Gtk.main_quit(win)		
 
	def __init__(self):
 
		self = Gtk.Builder()
		self.add_from_file("builder.ui")
 
		window = self.get_object("window")
		entry = self.get_object("entry")
		MainWindow.textView = self.get_object("textView")
		button = self.get_object("button")
		MainWindow.progressBar=self.get_object("progressBar")
 
		#Init
		MainWindow.textbuffer = MainWindow.textView.get_buffer()
		MainWindow.textbuffer.set_text("testo libero")
		if (sys.platform == "win32"):
			entry.set_text("./rsync-win/rsync.exe --info=progress2 -avz  /cygdrive/c/msys64/mingw64/bin /cygdrive/c/users/admin/desktop/tt/")
 
		window.show_all()
 
		window.connect("delete-event", MainWindow.quit)
		button.connect("clicked", MainWindow.on_button_clicked, MainWindow.textView,entry)
 
 
 
 
 
myWindow =  MainWindow()
Gtk.main()

builder.ui

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.18"/>
  <object class="GtkWindow" id="window">
    <property name="width_request">700</property>
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <signal name="delete-event" handler="quit" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkEntry" id="entry">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="text" translatable="yes">rsync  --info=progress2 -avz /usr/share/applications/ /home/fabio/Desktop/tt/</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button">
                <property name="label" translatable="yes">button</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="can_default">True</property>
                <property name="has_default">True</property>
                <property name="receives_default">True</property>
                <signal name="clicked" handler="on_button_clicked" object="label" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkProgressBar" id="progressBar">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="show_text">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">2</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkScrolledWindow" id="scrolledWindow">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="shadow_type">in</property>
            <property name="min_content_width">400</property>
            <property name="min_content_height">300</property>
            <property name="propagate_natural_width">True</property>
            <property name="propagate_natural_height">True</property>
            <child>
              <object class="GtkTextView" id="textView">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="wrap_mode">char</property>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>