Midnight Commander in Python
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Dateiübertragung auf das Handy war bisher für mich nicht zufriedenstellend. Über USB ging es unter Ubuntu manchmal aber auch oft nicht.
Über Samba Server am WLAN Router und dem ES Datei Explorer ging es, wenn auch nicht besonders schnell.
Mir würde so etwas wie der Midnight Commander am PC vorschweben:
https://de.wikipedia.org/wiki/Midnight_ ... 4.7-de.png
Das ließe sich doch mit einer Listbox links und einer Listbox rechts leicht machen, etwa tkinter Listbox im extended Mode.
Außerdem könnte man da auch eine integrierte Umgebung für QPython machen, etwa .py Datei auf dem Handy mit dem Commander auf dem PC anklicken, dann auf dem PC bearbeiten und wieder auf dem Handy speichern.
Über Samba Server am WLAN Router und dem ES Datei Explorer ging es, wenn auch nicht besonders schnell.
Mir würde so etwas wie der Midnight Commander am PC vorschweben:
https://de.wikipedia.org/wiki/Midnight_ ... 4.7-de.png
Das ließe sich doch mit einer Listbox links und einer Listbox rechts leicht machen, etwa tkinter Listbox im extended Mode.
Außerdem könnte man da auch eine integrierte Umgebung für QPython machen, etwa .py Datei auf dem Handy mit dem Commander auf dem PC anklicken, dann auf dem PC bearbeiten und wieder auf dem Handy speichern.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Ich denke mal, das Konzept sollte man ändern, nämlich in beliebig viele Fenster und Geräte. Soviel, wie auf den Bildschirm passen und wenn man die Fenster noch dazu minimieren kann, können es auch mehr sein. Man würde dannn ein Pack Left Layout nehmen - oder auch Grid - und für die Fenster und Geräte eine eigene Instanz, so dass eh alles gleich ist.
Pro Instanz braucht man dann einen Gerätedeskriptor und einen Pfaddeskriptor. Letzterer deshalb, weil man nicht den OS Pfad verändern sollte und auch mehrere Fenster und Pfade ermöglichen sollte.
Und wenn beim Kopieren oder Moven beide Gerätedeskriptoren übereinstimmen, ist es lokales Kopieren oder Moven auf dem entsprechenden Gerät. Ansonsten geht es über Netzwerk.
Also man braucht getrennte Instanzen für Geschäftslogik und GUI, weil man das möglichst unabhängig von der GUI schreiben sollte.
Aber ganz unabhängig sind die Instanzen doch nicht. WEnn in der GUI etwas ausgewählt wurde zum Löschen - aber zwei Fenster auf dasselbe Verzeichnis offen sind, dann darf der Löschbefehl nur an eine Instanz der Geschäftslogik gehen. Das GUI update danach aber an beide GUI Instanzen. Für gewisse Kommandos braucht man also auch ein Verzeichnis der Instanzen mit Pad und Gerätedeskriptor und wenn die gleich sind, dann GUI Update für beide. Bei Verzeichniswechsel allerdings nicht.
Pro Instanz braucht man dann einen Gerätedeskriptor und einen Pfaddeskriptor. Letzterer deshalb, weil man nicht den OS Pfad verändern sollte und auch mehrere Fenster und Pfade ermöglichen sollte.
Und wenn beim Kopieren oder Moven beide Gerätedeskriptoren übereinstimmen, ist es lokales Kopieren oder Moven auf dem entsprechenden Gerät. Ansonsten geht es über Netzwerk.
Also man braucht getrennte Instanzen für Geschäftslogik und GUI, weil man das möglichst unabhängig von der GUI schreiben sollte.
Aber ganz unabhängig sind die Instanzen doch nicht. WEnn in der GUI etwas ausgewählt wurde zum Löschen - aber zwei Fenster auf dasselbe Verzeichnis offen sind, dann darf der Löschbefehl nur an eine Instanz der Geschäftslogik gehen. Das GUI update danach aber an beide GUI Instanzen. Für gewisse Kommandos braucht man also auch ein Verzeichnis der Instanzen mit Pad und Gerätedeskriptor und wenn die gleich sind, dann GUI Update für beide. Bei Verzeichniswechsel allerdings nicht.
Zuletzt geändert von Alfons Mittelmeyer am Samstag 19. September 2015, 16:32, insgesamt 1-mal geändert.
- cofi
- Python-Forum Veteran
- Beiträge: 4432
- Registriert: Sonntag 30. März 2008, 04:16
- Wohnort: RGFybXN0YWR0
Ranger: http://nongnu.org/ranger/
Michael Markert ❖ PEP 8 Übersetzung ❖ Tutorial Übersetzung (3.x) ⇒ Online-Version (Python 3.3) ❖ Deutscher Python-Insider ❖ Projekte
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
@cofi Kann ich da auch auf das Handy übertragen? Und wie lange braucht eine 60 MB Datei? Bei meinem WLAN mit meinem Messagebroker waren es 20 Sekunden.
Und dieser ranger ist doch ziemlich eingeschränkt. Ich stelle mir auswählen und dann control-c control-v vor und das auch an alle teilnehmenden Geräte, etwa PDF Dateien auf die Computer aller Schüler in einer Klasse kopieren und natürlich auch auf Handies, Tablets und dergleichen.
Und dieser ranger ist doch ziemlich eingeschränkt. Ich stelle mir auswählen und dann control-c control-v vor und das auch an alle teilnehmenden Geräte, etwa PDF Dateien auf die Computer aller Schüler in einer Klasse kopieren und natürlich auch auf Handies, Tablets und dergleichen.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Man sollte sich eher Gedanken machen was die GUI, etwa eine Listbox der Geschäftslogik meldet und was die GUI verwaltet.
Ich komme da auf den Namen des Eintrags und den Typ. Und zu unterscheiden wären da drei Typen:
- directory
- file
- parent_directory (etwa '..')
Dann könnten das diese Methoden sein:
- select, etwa bei Doppelklick auf einen Eintrag.
- - bei Directory dann Wechsel in das Sub Directory
- - bei Parent dann Wechsel in das übergeordnete Verzeichnis
- - bei File je nach Filetyp, anzeigen, öffnen zum Editieren, aber vorerst einmal nichts
- delete etwa bei control-y gedrückt. Die Geschäftslogik erhält dabei die ausgewählten Einträge. Typ Parent zählt dabei nicht.
- rename Bedienung noch festzulegen. Parameter alter Name und Typ und neuer Name. Typ Parent zählt dabei nicht.
- selected_for_copy etwa bei control-c. Die Geschäftslogik erhält dabei die ausgewählten Einträge. Typ Parent zählt dabei nicht.
- selected_for_move etwa bei control-x. Die Geschäftslogik erhält dabei die ausgewählten Einträge. Typ Parent zählt dabei nicht.
- selected_for_insert etwa bei control-v. Die Geschäftslogik erhält dabei lediglich, dass das ausgewählt wurde und kümmert sich dann um das Weitere
Und für die GUI gibt es dann nur eine update Funktion mit einer Liste der Verzeichniseinträge. Für rename braucht man vielleicht keine, und wenn dann sollte die alte Auswahl angezeigt werden.
Für Anderes - das wäre nach 'delete' und 'selected_for_insert' - sollte der Auswahlbalken, wenn überhaupt, dann auf '..' stehen
Ich komme da auf den Namen des Eintrags und den Typ. Und zu unterscheiden wären da drei Typen:
- directory
- file
- parent_directory (etwa '..')
Dann könnten das diese Methoden sein:
- select, etwa bei Doppelklick auf einen Eintrag.
- - bei Directory dann Wechsel in das Sub Directory
- - bei Parent dann Wechsel in das übergeordnete Verzeichnis
- - bei File je nach Filetyp, anzeigen, öffnen zum Editieren, aber vorerst einmal nichts
- delete etwa bei control-y gedrückt. Die Geschäftslogik erhält dabei die ausgewählten Einträge. Typ Parent zählt dabei nicht.
- rename Bedienung noch festzulegen. Parameter alter Name und Typ und neuer Name. Typ Parent zählt dabei nicht.
- selected_for_copy etwa bei control-c. Die Geschäftslogik erhält dabei die ausgewählten Einträge. Typ Parent zählt dabei nicht.
- selected_for_move etwa bei control-x. Die Geschäftslogik erhält dabei die ausgewählten Einträge. Typ Parent zählt dabei nicht.
- selected_for_insert etwa bei control-v. Die Geschäftslogik erhält dabei lediglich, dass das ausgewählt wurde und kümmert sich dann um das Weitere
Und für die GUI gibt es dann nur eine update Funktion mit einer Liste der Verzeichniseinträge. Für rename braucht man vielleicht keine, und wenn dann sollte die alte Auswahl angezeigt werden.
Für Anderes - das wäre nach 'delete' und 'selected_for_insert' - sollte der Auswahlbalken, wenn überhaupt, dann auf '..' stehen
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Zweite Idee, die Anmeldung.
Es gibt eine Masterapplikation mit einer GUI, mit der man Verzeichnisse bearbeitet. Und es gibt Slave-Applikationen - über LAN oder WLAN verbunden, deren Verzeichnisse von der Masterapplikation bearbeitet werden. Die Masterapplikation kann auch ihre eigenen Verzeichnisse bearbeiten.
Wenn eine Slave Applikation hochfährt, meldet sie sich bei der Masterapplikation an. Das nützt natürlich nichts, wenn die Masterapplikation noch nicht hochgefahren ist. Aber wenn die Masterapplikation hochfährt, schickt sie eine Aufforderung zur Anmeldung an die Slaves. Dadurch ist die Anmeldung der Slaves gewährleistet.
Die Auswirkung der Anmeldung.
Die linke Spalte der GUI Applikation enthält die angemeldeten Slaves und den Master. Der Master steht ganz oben (pack top) darunter die Slaves (pack bottom). Da gibt es pro Applikation (Master und Slaves) einen Button, um ein neues Fenster zu öffnen. Darunter werden die Pfade bereits geöffneter Fenster für die betreffende Applikation angezeigt - eventuell auch Buttons, um minimierte Fenster wieder groß zu machen.
Bei Drücken des Buttons für ein neues Fenster wird die betreffende Applikation aufgefordert, ihr Root Verzeichnis zur Anzeige zur senden. Beim Root Verzeichnis handelt es sich um eine virtuelle Root, welche die für diese Applikation freigegebenen Verzeichnisse enthält. Das könnten etwa virtuelle Laufwerke sein A:,B:,C:,D: etc. In der virtuellen Root dürfen keine Änderungen ausgeführt werden, sondern nur in den freigegebenen Verzeichnissen. Die Pfadangaben bestehen daher aus einem Laufwerksbuchstaben und einem Pfad. Damit ist dann die Bearbeitung anderer Verzeichnisse ausgeschlossen.
Ein neues Fenster aufzumachen soll auch möglich sein von einem bereits geöffneten Fester aus. Da wird dann der Pfad des bereits geöffneten Fensters übernommen. Gut um in ein Unterverzeichnis zu moven oder in das übergeordnete Verzeichnis.
Es gibt eine Masterapplikation mit einer GUI, mit der man Verzeichnisse bearbeitet. Und es gibt Slave-Applikationen - über LAN oder WLAN verbunden, deren Verzeichnisse von der Masterapplikation bearbeitet werden. Die Masterapplikation kann auch ihre eigenen Verzeichnisse bearbeiten.
Wenn eine Slave Applikation hochfährt, meldet sie sich bei der Masterapplikation an. Das nützt natürlich nichts, wenn die Masterapplikation noch nicht hochgefahren ist. Aber wenn die Masterapplikation hochfährt, schickt sie eine Aufforderung zur Anmeldung an die Slaves. Dadurch ist die Anmeldung der Slaves gewährleistet.
Die Auswirkung der Anmeldung.
Die linke Spalte der GUI Applikation enthält die angemeldeten Slaves und den Master. Der Master steht ganz oben (pack top) darunter die Slaves (pack bottom). Da gibt es pro Applikation (Master und Slaves) einen Button, um ein neues Fenster zu öffnen. Darunter werden die Pfade bereits geöffneter Fenster für die betreffende Applikation angezeigt - eventuell auch Buttons, um minimierte Fenster wieder groß zu machen.
Bei Drücken des Buttons für ein neues Fenster wird die betreffende Applikation aufgefordert, ihr Root Verzeichnis zur Anzeige zur senden. Beim Root Verzeichnis handelt es sich um eine virtuelle Root, welche die für diese Applikation freigegebenen Verzeichnisse enthält. Das könnten etwa virtuelle Laufwerke sein A:,B:,C:,D: etc. In der virtuellen Root dürfen keine Änderungen ausgeführt werden, sondern nur in den freigegebenen Verzeichnissen. Die Pfadangaben bestehen daher aus einem Laufwerksbuchstaben und einem Pfad. Damit ist dann die Bearbeitung anderer Verzeichnisse ausgeschlossen.
Ein neues Fenster aufzumachen soll auch möglich sein von einem bereits geöffneten Fester aus. Da wird dann der Pfad des bereits geöffneten Fensters übernommen. Gut um in ein Unterverzeichnis zu moven oder in das übergeordnete Verzeichnis.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Hat keiner eine Idee, wie man beginnen könnte? Ich würde bei einem Button anfangen. Wenn man den drückt, soll für das betreffende Gerät ein Fenster aufgehen und das Root-Directory (virtuelle Root ) dieses Gerätes angezeigt werden.
Also der Startpunkt wäre:Und wie könnte oder sollte das aussehen?
Also der Startpunkt wäre:
Code: Alles auswählen
class NewWindowButton:
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Noch ein kleiner Tipp. Wir setzen dabei einen Event Broker ein. Das heißt, ein Subscriber soll sich darum kümmern.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Keiner einen Tipp? Aber da ist die Lösung:Und das ist die entscheidende Zeile:Und zwar die lambda expression:Wenn jemand diesen Button drückt, dann geht die Nachricht an das Gerät self.decice, dass es den Directory Eintrag schickt ("GetDirEntry") für die virtuelle Root (ROOTVOLUME) - der Pfad (NOPATH) zählt dabei nicht. Und zurücksenden soll der Device das mit der Window ID NEWWINDOW, denn dann geht ein neues GUI Fenster auf.
Und hier haben wir die Konfiguration config.py:Und hier der ganz entscheidende Code des event brokers event_broker.py:Leider noch nicht in weit fortgeschrittenem Zustand.
Code: Alles auswählen
import tkinter as tk
import event_broker
import config
root = tk.Tk()
eventbroker = event_broker.EventBroker()
class NewButton(tk.Button):
def __init__(self,parent,device):
self.device = device
self.master = parent
tk.Button.__init__(self,parent,text=device,command=lambda: eventbroker.publish(self.device,("GetDirEntry",config.ROOTVOLUME,config.NOPATH,config.NEWWINDOW)))
mybutton = NewButton(root,config.MASTER_DEVICE)
mybutton.pack()
root.mainloop()
Code: Alles auswählen
tk.Button.__init__(self,parent,text=device,command=lambda: eventbroker.publish(self.device,("GetDirEntry",config.ROOTVOLUME,config.NOPATH,config.NEWWINDOW)))
Code: Alles auswählen
command=lambda: eventbroker.publish(self.device,("GetDirEntry",config.ROOTVOLUME,config.NOPATH,config.NEWWINDOW)
Und hier haben wir die Konfiguration config.py:
Code: Alles auswählen
MASTER_DEVICE="MASTER PC"
ROOTVOLUME =""
NOPATH = ""
NEWWINDOW = 0
Code: Alles auswählen
class EventBroker:
def __init__(self): pass
# publish within same thread
def publish(self,msgid,msgdata=None): pass
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Eine kleine Designänderung: Wir unterscheiden nicht zwischen Volume und Path und ändern entsprechend die config.py:Und dann können wir die fertige Lösung für unseren NewButton in der GUI Task präsentieren:Die Lösung ist: die Lösung liegt gar nicht in der GUI Task sondern irgendwo anders im System. Daher nehmen wir den public_eventbroker. Der arbeitet in einem anderen Thread und routet die Aufgabe weiter.
Und ganz stolz sind wir auf die fertige Lösung für den public event broker.
public_evenbroker.py:Diese Lösung ist doch einfach tutto perfetto - vollkommen fertig und perfekt oder?
Das Verhalten hängt natürlich vom event_broker ab. Und der ist keineswegs fertig.
event_broker.py:
Code: Alles auswählen
MASTER_DEVICE="MASTER PC"
ROOTPATH ="/"
NEWWINDOW = 0
Code: Alles auswählen
import tkinter as tk
import config
import public_eventbroker
root = tk.Tk()
class NewButton(tk.Button):
def __init__(self,parent,device):
self.device = device
self.master = parent
tk.Button.__init__(self,parent,text=device,command=lambda: public_eventbroker.eventbroker.publish_extern(self.device,("GetDirEntry",config.ROOTPATH,config.NEWWINDOW)))
mybutton = NewButton(root,config.MASTER_DEVICE)
mybutton.pack()
root.mainloop()
Und ganz stolz sind wir auf die fertige Lösung für den public event broker.
public_evenbroker.py:
Code: Alles auswählen
# This thread contains the public eventbroker, which other threads - which don't know each other - use for communication between them.
# - For events, that other threads want to receive, the method 'public_subscribtions' of their event broker shall be used.
# This method redirects events in this public event broker to that threads event broker.
# - For publishing events for other threads to this public event broker the method 'publish_extern' of this public event broker may be used directly
# - or other threads may use the method 'public_publications' of their event broker for redirecting events of their event broker to this public event broker
import threading
import event_broker
eventbroker = None
class MyThread(threading.Thread):
def run(self):
global eventbroker
eventbroker = event_broker.EventBroker()
eventbroker.loop()
mythread = MyThread()
mythread.daemon = True
mythread.start()
Das Verhalten hängt natürlich vom event_broker ab. Und der ist keineswegs fertig.
event_broker.py:
Code: Alles auswählen
import threading
class EventBroker:
def __init__(self): pass
# publish within same thread
def publish(self,msgid,msgdata=None): pass
# publish function called by another thread - use it, if you directly access the event broker of another thread
def publish_extern(self,msgid,msgdata=None): pass
# loop for event driven threads - don't use this in a GUI event loop (mainloop) - it will block the GUI
def loop(self):
self.event = threading.Event()
self.event.set()
self._looping = True
while self._looping:
self.event.wait()
self.event.clear()
#self.do_work()
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Der public event broker war doch noch nicht ganz perfekt. War erst nach Verzögerungszeit einsetzbar. Aber jetzt ist er vollkommen.
public_eventbroker.py:Und auch der event broker hat jetzt alle erforderlichen Schnittstellen. Unsubscribe Funktionen für dynamische Applikationen, die sich verändern und dynamisch nachladen, habe ich mal weggelassen.
event_broker.py:Die Schnittstellen sind da, wenngleich noch keine Funktionalität. Das heißt aber, man kann schon mal implementieren, sodaß nichts abstürzt, auch wenn dann noch nichts geht.
public_eventbroker.py:
Code: Alles auswählen
# This thread contains the public eventbroker, which other threads - which don't know each other - use for communication between them.
# - For events, that other threads want to receive, the method 'public_subscriptions' of their event broker shall be used.
# This method redirects events in this public event broker to that threads event broker.
# - For publishing events for other threads to this public event broker the method 'publish_extern' of this public event broker may be used directly
# - or other threads may use the method 'public_publications' of their event broker for redirecting events of their event broker to this public event broker
import threading
import event_broker
eventbroker = event_broker.EventBroker()
_worker_thread = threading.Thread(target=eventbroker.loop)
_worker_thread.daemon = True
_worker_thread.start()
event_broker.py:
Code: Alles auswählen
import threading
class EventBroker:
def __init__(self,public_eventbroker=None):
if public_eventbroker==None: self.public_eventbroker = self
else: self.public_eventbroker = public_eventbroker
# Configuration for triggers at start, which may be changed by the user
self.trigger = self.do_work # intern trigger at start is direct call of 'do_work'.
self.extern_trigger = self._noop # extern trigger at start is set to do nothing. feel free to change this in your GUI application, if the GUI framework has a safe trigger, which may be called by another thread
# otherwise polling via 'do_work' has to be used
self._initialize() # initialize the event broker
# publish within same thread
def publish(self,msgid,msgdata=None): pass
# publish function called by another thread - use it, if you directly access the event broker of another thread
def publish_extern(self,msgid,msgdata=None): pass
# transmit an event to another thread. Similar to publish_extern, but parameters are different: transmit_extern((msgid,message)) , publish_extern(msgid,message)
def transmit_extern(self,msgid_message): pass
# subscribe a callback for an event
def subscribe(self,msgid,callback,optional_parameter=False): pass
# subscribe function called by another thread - use it, if you access the event broker of another thread
def subscribe_extern(self,msgid,callback,optional_parameter=False): pass
# subscribe a list of events to be received from the public event broker
def public_subscriptions(self,msgid_list):
for msgid in msgid_list: self.public_eventbroker.subscribe_extern(msgid,self.transmit_extern,True)
# subscribe a list of events to be sent to the public event broker
def public_publications(self,msgid_list):
for msgid in msgid_list: self.subscribe(msgid,self.public_eventbroker.transmit_extern,True)
# loop for event driven threads - don't use this in a GUI event loop (mainloop) - it will block the GUI
def loop(self,execute_at_loop_start = None):
self.event = threading.Event()
self.trigger = self.event.set
self.extern_trigger = self.event.set
self.event.set()
self._looping = True
if execute_at_loop_start != None: execute_at_loop_start()
while self._looping:
self.event.wait()
self.event.clear()
self.do_work()
# exit event loop
def exit_loop(self,*args):
self._looping = False
self.trigger()
# start function for extern_trigger
def _noop(self): pass
# process all events in the queues and block other calls
def do_work(self,*args):
if self._running: return
self._running = True
while self.work(): pass
self._running = False
# process one event in the queues
def work(self,*args): return False
def _initialize(self):
self._running = False # regulates calls of method 'do_work'- start value False enables call of method 'work'
self._looping = False # regulates ending event loop. After 'loop' is called, the loop may be ended by calling 'exit_loop'
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Und jetzt können wir die erste Implementierung machen mit Antwort des betreffenden Devices.
Wir haben den Button. Wenn wir diesen drücken, dann wird zum betreffenden Device gesendet, dass die Masterapplikation dessen Rootdirectory haben will.
Der Device antwortet dann, indem er sein Rootdirectory an die Masterapplikation schickt und wir lassen das zur Probe printen.
newbutton.py:Das Event wird zum betreffenden Device geschickt und landet dort im public_eventbroker. Ein Thread holt es sich von da ab, bearbeitet es und schickt das Directory zur Masterapplikation zurück.
Dieser Thread benutzt den public_eventbroker und einen eigenen. Der eigene wird so konfiguriert, dass dieser auf den public_eventbroker zugreifen kann:Dieser Thread holt genau die Events ab, die für das betreffende Gerät hereinkommen. Das sind die Events, die als Event ID die Device ID des betreffenden Gerätes haben. Und diese Events werden dann an den job_distributor dieses Threads geleitet. Dieser verteilt dann die Aufgaben und das haben wir hier:Und message[0] war in unserem Fall "GetDirEntry". Für diese Message ist ein job worker zu installieren. Das wird erreicht durch:Und diese Funktion tut es dann, vorerst nur einmal für das Rootdirectory implementiert:Die Directory Information wird an die hereingekommene Message gehängt. Der erste Eintrag wird auf "DirEntry" gesetzt, damit diese Message dann richtig behandelt wird, wenn sie beim Master Device ankommt. Dann wird das Event mit der MASTER_DEVICE ID auf die Reise zum Master Device geschickt.
Bei Master Device angekommen, landet das Event dann dort bei dessen Job Workers. Der Job Distributor holt es sich auch dort wieder ab und gibt dann die Aufgabe "DirEntry" weiter. In diesem Falle soll es allerdings kein Job Worker dieses Moduls tun, sondern das Event soll zur GUI Applikation gehen. Also wird dieses Event nun als ausgepacktes "DirEntry" wieder dem public_eventbroker übergeben. Und das wird dadurch ereicht, indem es in die public_publications Liste aufgenommen wird:Und das ist dann das Modul.
job_workers.py:Und die config Datei dazu sieht so aus:
config.py:Und wenn der event broker funktionsfähig wäre, sollte es das tun.
Wir haben den Button. Wenn wir diesen drücken, dann wird zum betreffenden Device gesendet, dass die Masterapplikation dessen Rootdirectory haben will.
Der Device antwortet dann, indem er sein Rootdirectory an die Masterapplikation schickt und wir lassen das zur Probe printen.
newbutton.py:
Code: Alles auswählen
import tkinter as tk
import config
import public_eventbroker
import job_workers
public_eventbroker.eventbroker.subscribe_extern("DirEntry",lambda message: print(message[4]))
root = tk.Tk()
class NewButton(tk.Button):
def __init__(self,parent,device):
self.device = device
self.master = parent
tk.Button.__init__(self,parent,text=device,command=lambda: public_eventbroker.eventbroker.publish_extern(self.device,["GetDirEntry",self.device,config.ROOTPATH,config.NEWWINDOW]))
mybutton = NewButton(root,config.MASTER_DEVICE)
mybutton.pack()
root.mainloop()
Dieser Thread benutzt den public_eventbroker und einen eigenen. Der eigene wird so konfiguriert, dass dieser auf den public_eventbroker zugreifen kann:
Code: Alles auswählen
self.public_eventbroker = public_eventbroker.eventbroker
self.eventbroker = event_broker.EventBroker(self.public_eventbroker)
Code: Alles auswählen
self.public_eventbroker.subscribe_extern(config.THIS_DEVICE,self.job_distributor)
# called by the public event broker, so self.eventbroker is the extern event broker
def job_distributor(message): self.eventbroker.publish_extern(message[0],message)
Code: Alles auswählen
self.eventbroker.subscribe("GetDirEntry",self.get_dir_entry)
Code: Alles auswählen
def get_dir_entry(message):
if message[2] == config.ROOTPATH: message.append(config.DEVICE_ROOT)
else: message.append(config.DEVICE_ROOT) # to do later
message[0] = "DirEntry"
self.public_eventbroker.publish_extern(MASTER_DEVICE,message)
Bei Master Device angekommen, landet das Event dann dort bei dessen Job Workers. Der Job Distributor holt es sich auch dort wieder ab und gibt dann die Aufgabe "DirEntry" weiter. In diesem Falle soll es allerdings kein Job Worker dieses Moduls tun, sondern das Event soll zur GUI Applikation gehen. Also wird dieses Event nun als ausgepacktes "DirEntry" wieder dem public_eventbroker übergeben. Und das wird dadurch ereicht, indem es in die public_publications Liste aufgenommen wird:
Code: Alles auswählen
# neccessary if this device is the master device
self.eventbroker.public_publications(("DirEntry",))
job_workers.py:
Code: Alles auswählen
import config
import threading
import event_broker
import public_eventbroker
class Main:
def __init__(self):
self.public_eventbroker = public_eventbroker.eventbroker
self.eventbroker = event_broker.EventBroker(self.public_eventbroker)
self.public_eventbroker.subscribe_extern(config.THIS_DEVICE,self.job_distributor)
self.eventbroker.subscribe("GetDirEntry",self.get_dir_entry)
# neccessary if this device is the master device
self.eventbroker.public_publications(("DirEntry",))
worker_thread = threading.Thread(target=self.eventbroker.loop)
worker_thread.daemon = True
worker_thread.start()
# called by the public event broker, so self.eventbroker is the extern event broker
def job_distributor(message): self.eventbroker.publish_extern(message[0],message)
def get_dir_entry(message):
if message[2] == config.ROOTPATH: message.append(config.DEVICE_ROOT)
else: message.append(config.DEVICE_ROOT) # to do later
message[0] = "DirEntry"
self.public_eventbroker.publish_extern(MASTER_DEVICE,message)
Main()
config.py:
Code: Alles auswählen
MASTER_DEVICE="MASTER PC"
THIS_DEVICE = "MASTER_PC"
DIR_TYPE = 0
ROOTPATH ="/"
DEVICE_ROOT = (("Documents",DIR_TYPE),("Music",DIR_TYPE),("Pictures",DIR_TYPE),("Videos",DIR_TYPE))
NEWWINDOW = 0
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Den Event Broker zum Laufen zu bringen, ist relativ einfach. Das sind lediglich:
- die publish Funktionen
- die subscribe Funktion
- und work
und für die publish Funktionen nehmen wir eine Queue:
- die publish Funktionen
- die subscribe Funktion
- und work
und für die publish Funktionen nehmen wir eine Queue:
Code: Alles auswählen
import queue
# publish within same thread
def publish(self,msgid,msgdata=None):
self._Queue.put((msgid,msgdata))
self.trigger()
# publish function called by another thread - use it, if you directly access the event broker of another thread
def publish_extern(self,msgid,msgdata=None):
self._Queue.put((msgid,msgdata))
self.extern_trigger()
# transmit an event to another thread. Similar to publish_extern, but parameters are different: transmit_extern((msgid,message)) , publish_extern(msgid,message)
def transmit_extern(self,msgid_message):
self._Queue.put(msgid_message)
self.extern_trigger()
def _initialize(self):
self._running = False # regulates calls of method 'do_work'- start value False enables call of method 'work'
self._looping = False # regulates ending event loop. After 'loop' is called, the loop may be ended by calling 'exit_loop'
self._Queue = queue.Queue() # Queue for publishing
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Für Subscribe benutzen wir ein Dictionary:Für subscribe_extern müssen wir uns allerdings etwas einfallen lassen, denn es darf ein externer Thread das Dictionary nicht verändern, während es benutzt wird.
Code: Alles auswählen
# subscribe a callback for an event
def subscribe(self,msgid,callback,optional_parameter=False):
if msgid not in self._Dictionary: self._Dictionary[msgid] = {}
self._Dictionary[msgid][callback] = optional_parameter
def _initialize(self):
self._running = False # regulates calls of method 'do_work'- start value False enables call of method 'work'
self._looping = False # regulates ending event loop. After 'loop' is called, the loop may be ended by calling 'exit_loop'
self._Queue = queue.Queue() # Queue for publishing
self._Dictionary = {} # contains registered message ids and registered callback functions for these message ids
Ich glaube nicht, dass jemand deiner Art der Codepräsentation auf diese Weise folgen kann/will.
Klick dir einen Account github oder bitbucket und lad den funktionierenden Code dort hoch.
Hier müsste man, wenn man deinem Monolog folgen will, anfangen Codeschnippsel ineinander zu kopieren. Ich befürchte, das tut niemand.
Klick dir einen Account github oder bitbucket und lad den funktionierenden Code dort hoch.
Hier müsste man, wenn man deinem Monolog folgen will, anfangen Codeschnippsel ineinander zu kopieren. Ich befürchte, das tut niemand.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Der Trick für subscribe_extern besteht aus zwei Komponenten:
- einer Callback Funktion für die event ID "execute_function"
- und einer HighPrio Queue
- einer Callback Funktion für die event ID "execute_function"
- und einer HighPrio Queue
Code: Alles auswählen
# subscribe function called by another thread - use it, if you access the event broker of another thread
def subscribe_extern(self,msgid,callback,optional_parameter=False):
self._Queue_HighPrio.put(("execute_function",lambda: self.subscribe(msgid,callback,optional_parameter)))
self.extern_trigger()
def _initialize(self):
self._running = False # regulates calls of method 'do_work'- start value False enables call of method 'work'
self._looping = False # regulates ending event loop. After 'loop' is called, the loop may be ended by calling 'exit_loop'
self._Queue = queue.Queue() # Queue for publishing
self._Dictionary = {} # contains registered message ids and registered callback functions for these message ids
self.subscribe("execute_function",lambda message: message()) # very useful callback function: executes messages, which are lambda expressions
self._Queue_HighPrio = queue.Queue() # Queue for register extern callback functions
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Der Event Broker ist fertig. Und er hatte auf Anhieb keinen Fehler. Doch in config.py einen Unterstrich vergessen und in job_workers.py zweimal self und einmal config.
Damit haben wir es.
newbutton.py:
job_workers.py:
public_eventbroker.py:
event_broker.py:
Natürlich können wir die Events via TCP auch zu anderen Geräten schicken, aber zum Entwickeln reicht das vorerst.
Damit haben wir es.
newbutton.py:
Code: Alles auswählen
import tkinter as tk
import config
import public_eventbroker
import job_workers
public_eventbroker.eventbroker.subscribe_extern("DirEntry",lambda message: print(message[4]))
root = tk.Tk()
class NewButton(tk.Button):
def __init__(self,parent,device):
self.device = device
self.master = parent
tk.Button.__init__(self,parent,text=device,command=lambda: public_eventbroker.eventbroker.publish_extern(self.device,["GetDirEntry",self.device,config.ROOTPATH,config.NEWWINDOW]))
mybutton = NewButton(root,config.MASTER_DEVICE)
mybutton.pack()
root.mainloop()
Code: Alles auswählen
import config
import threading
import event_broker
import public_eventbroker
class Main:
def __init__(self):
self.public_eventbroker = public_eventbroker.eventbroker
self.eventbroker = event_broker.EventBroker(self.public_eventbroker)
self.public_eventbroker.subscribe_extern(config.THIS_DEVICE,self.job_distributor)
self.eventbroker.subscribe("GetDirEntry",self.get_dir_entry)
# neccessary if this device is the master device
self.eventbroker.public_publications(("DirEntry",))
worker_thread = threading.Thread(target=self.eventbroker.loop)
worker_thread.daemon = True
worker_thread.start()
# called by the public event broker, so self.eventbroker is the extern event broker
def job_distributor(self,message): self.eventbroker.publish_extern(message[0],message)
def get_dir_entry(self,message):
if message[2] == config.ROOTPATH: message.append(config.DEVICE_ROOT)
else: message.append(config.DEVICE_ROOT) # to do later
message[0] = "DirEntry"
self.public_eventbroker.publish_extern(config.MASTER_DEVICE,message)
Main()
Code: Alles auswählen
# This thread contains the public eventbroker, which other threads - which don't know each other - use for communication between them.
# - For events, that other threads want to receive, the method 'public_subscriptions' of their event broker shall be used.
# This method redirects events in this public event broker to that threads event broker.
# - For publishing events for other threads to this public event broker the method 'publish_extern' of this public event broker may be used directly
# - or other threads may use the method 'public_publications' of their event broker for redirecting events of their event broker to this public event broker
import threading
import event_broker
eventbroker = event_broker.EventBroker()
_worker_thread = threading.Thread(target=eventbroker.loop)
_worker_thread.daemon = True
_worker_thread.start()
Code: Alles auswählen
import threading
import queue
class EventBroker:
def __init__(self,public_eventbroker=None):
if public_eventbroker==None: self.public_eventbroker = self
else: self.public_eventbroker = public_eventbroker
# Configuration for triggers at start, which may be changed by the user
self.trigger = self.do_work # intern trigger at start is direct call of 'do_work'.
self.extern_trigger = self._noop # extern trigger at start is set to do nothing. feel free to change this in your GUI application, if the GUI framework has a safe trigger, which may be called by another thread
# otherwise polling via 'do_work' has to be used
self._initialize() # initialize the event broker
# publish within same thread
def publish(self,msgid,msgdata=None):
self._Queue.put((msgid,msgdata))
self.trigger()
# publish function called by another thread - use it, if you directly access the event broker of another thread
def publish_extern(self,msgid,msgdata=None):
self._Queue.put((msgid,msgdata))
self.extern_trigger()
# transmit an event to another thread. Similar to publish_extern, but parameters are different: transmit_extern((msgid,message)) , publish_extern(msgid,message)
def transmit_extern(self,msgid_message):
self._Queue.put(msgid_message)
self.extern_trigger()
# subscribe a callback for an event
def subscribe(self,msgid,callback,optional_parameter=False):
if msgid not in self._Dictionary: self._Dictionary[msgid] = {}
self._Dictionary[msgid][callback] = optional_parameter
# subscribe function called by another thread - use it, if you access the event broker of another thread
def subscribe_extern(self,msgid,callback,optional_parameter=False):
self._Queue_HighPrio.put(("execute_function",lambda msgid=msgid, callback=callback,optional_parameter=optional_parameter: self.subscribe(msgid,callback,optional_parameter)))
self.extern_trigger()
# subscribe a list of events to be received from the public event broker
def public_subscriptions(self,msgid_list):
for msgid in msgid_list: self.public_eventbroker.subscribe_extern(msgid,self.transmit_extern,True)
# subscribe a list of events to be sent to the public event broker
def public_publications(self,msgid_list):
for msgid in msgid_list: self.subscribe(msgid,self.public_eventbroker.transmit_extern,True)
# loop for event driven threads - don't use this in a GUI event loop (mainloop) - it will block the GUI
def loop(self,execute_at_loop_start = None):
self.event = threading.Event()
self.trigger = self.event.set
self.extern_trigger = self.event.set
self.event.set()
self._looping = True
if execute_at_loop_start != None: execute_at_loop_start()
while self._looping:
self.event.wait()
self.event.clear()
self.do_work()
# exit event loop
def exit_loop(self,*args):
self._looping = False
self.trigger()
# start function for extern_trigger
def _noop(self): pass
# process all events in the queues and block other calls
def do_work(self,*args):
if self._running: return
self._running = True
while self.work(): pass
self._running = False
# process one event in the queues
def work(self,*args):
# get message from Queue
if not self._Queue_HighPrio.empty(): data = self._Queue_HighPrio.get()
elif not self._Queue.empty(): data = self._Queue.get()
else: return False
# look up message id and registered callbacks for it and call callback
msgid = data[0]
msgdata = data[1]
if msgid in self._Dictionary:
receivers = dict(self._Dictionary[msgid]).items() # # items contain callback function and optional_parameter and we need a copy (via dict), because the entry could change via subscribe
for callback,optional_parameter in receivers:
if type(optional_parameter) is bool:
if optional_parameter: callback((msgid,msgdata))
else: callback(msgdata)
else: callback((optional_parameter,(msgid,msgdata)))
return True
def _initialize(self):
self._running = False # regulates calls of method 'do_work'- start value False enables call of method 'work'
self._looping = False # regulates ending event loop. After 'loop' is called, the loop may be ended by calling 'exit_loop'
self._Queue = queue.Queue() # Queue for publishing
self._Dictionary = {} # contains registered message ids and registered callback functions for these message ids
self.subscribe("execute_function",lambda message: message()) # very useful callback function: executes messages, which are lambda expressions
self._Queue_HighPrio = queue.Queue() # Queue for register extern callback functions
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Der GUI Window Manager ist für die ersten zwei Spalten der Applikation zuständig.
Die erste Spalte würde bei zwei Geräten so aussehen, wenn noch keine Fenster geöffnet sind:Ich würde vorschlagen, wir implementieren den Start des GUI Managers und lassen dann etwas später ein Handy dazu kommen.
Danach lassen wir das Handy zuerst hochfahren und dann die Master Applikation.
Was meint Ihr dazu?
Die erste Spalte würde bei zwei Geräten so aussehen, wenn noch keine Fenster geöffnet sind:
Code: Alles auswählen
import tkinter as tk
root = tk.Tk()
a = tk.LabelFrame(root,text="Devices")
a.grid(row='0')
def sub(parent):
a = tk.Frame(parent,bd=1,relief='solid')
a.pack(pady='1',fill='x')
tk.Button(a,text="MASTER PC").pack(fill='x')
sub(a)
def sub(parent):
a = tk.Frame(parent,bd=1,relief='solid')
a.pack(pady='1',fill='x')
tk.Button(a,text="Mein Handy").pack(fill='x')
sub(a)
root.mainloop()
Danach lassen wir das Handy zuerst hochfahren und dann die Master Applikation.
Was meint Ihr dazu?
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Hab mal den NewButton abgespeckt - ohne event broker.
Und mit dieser Gliederung kann man schon einmal Geräte konnekten und un-konnekten:
Und mit dieser Gliederung kann man schon einmal Geräte konnekten und un-konnekten:
Code: Alles auswählen
import tkinter as tk
import config
class NewButton(tk.Button):
def __init__(self,parent,device_name):
self.device_name = device_name
tk.Button.__init__(self,parent,text=device_name)
class DeviceManager(tk.Frame):
def __init__(self,parent,device_name):
tk.Frame.__init__(self,parent,bd=1,relief='solid')
self.pack(side='bottom',pady='1',fill='x')
NewButton(self,device_name).pack(fill='x')
class GuiManager:
def __init__(self,parent):
self.device_frame = tk.LabelFrame(parent,text="Devices")
self.device_frame.grid(row='0')
self.device_dict = {}
master = self.connect_device(config.MASTER_DEVICE)
master.pack(side='top',pady='1',fill='x') # the master shall be on top
def connect_device(self,name):
manager = DeviceManager(self.device_frame,name)
self.device_dict[name] = manager
return manager
def unconnect_device(self,name):
try: self.device_dict.pop(name).destroy()
except: pass
root = tk.Tk()
gui_manager=GuiManager(root)
gui_manager.connect_device("Mein kaputtes Handy")
gui_manager.connect_device("Mein Tablet")
gui_manager.connect_device("Mein neues Handy")
gui_manager.unconnect_device("Mein kaputtes Handy")
root.mainloop()
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
DirEntry Messages für neues Window und Pfad update:Damit sollte Spalte eins vorerst einmal fertig sein. In Verbindung mit Spalte zwei gibt es dann freilich noch ein paar kleine Features.
Code: Alles auswählen
import tkinter as tk
import config
import time
testmessage = (["DirEntry","Mein Tablet",config.ROOTPATH,config.NEWWINDOW,config.DEVICE_ROOT],
["DirEntry","Mein Tablet","/Music",config.NEWWINDOW,config.DEVICE_ROOT],
["DirEntry","Mein Tablet","/Video",100,config.DEVICE_ROOT],
["DirEntry","Mein Tablet","/Foto",101,config.DEVICE_ROOT])
class NewButton(tk.Button):
def __init__(self,parent,device_name):
self.device_name = device_name
tk.Button.__init__(self,parent,text=device_name)
class DeviceManager(tk.Frame):
def __init__(self,parent,device_name):
tk.Frame.__init__(self,parent,bd=1,relief='solid')
self.pack(side='bottom',pady='1',fill='x')
NewButton(self,device_name).pack(fill='x')
self.win_dict={}
def new_window(self,win_id,message):
b = tk.Button(self,text=message[2],pady='0',bg='#fff9e9',relief='flat',anchor='w')
b.pack(side='bottom',fill='x')
self.win_dict[win_id] = [b,]
def update_window(self,win_id,message):
try: self.win_dict[win_id][0]['text'] = message[2]
except: pass
class GuiManager:
def __init__(self,parent):
self.device_frame = tk.LabelFrame(parent,text="Devices")
self.device_frame.grid(row='0')
self.device_dict = {}
self.START_WINDOW_ID = 100
self.next_window_id = self.START_WINDOW_ID
master = self.connect_device(config.MASTER_DEVICE)
master.pack(side='top',pady='1',fill='x') # the master shall be on top
def connect_device(self,name):
manager = DeviceManager(self.device_frame,name)
self.device_dict[name] = manager
return manager
def unconnect_device(self,name):
try: self.device_dict.pop(name).destroy()
except: pass
def dir_entry(self,message):
if message[1] in self.device_dict:
if message[3] == config.NEWWINDOW:
win_id = self.next_window_id
self.next_window_id += 1
self.device_dict[message[1]].new_window(win_id,message)
elif message[3] >= self.START_WINDOW_ID:
self.device_dict[message[1]].update_window(message[3],message)
root = tk.Tk()
gui_manager=GuiManager(root)
gui_manager.connect_device("Mein kaputtes Handy")
gui_manager.connect_device("Mein Tablet")
gui_manager.connect_device("Mein neues Handy")
gui_manager.unconnect_device("Mein kaputtes Handy")
index = 0
def loop():
global index
gui_manager.dir_entry(testmessage[index])
index += 1
if index < 4: root.after(2000,loop)
root.after(2000,loop)
root.mainloop()