PyCRCtrl

Viele von euch haben sicher schon einen neuen halben Server entdeckt, der Atlantis heißt und sich mit Befehlen steuern lässt. Dies ist eine laufende Instanz von PyCRCtrl.
PyCRCtrl, welches in Python geschrieben wurde, bietet ähnlich wie crctrl und CRSM die Möglichkeit, die Clonk-Server-Engine per stdin zu steuern. Im Moment wird zusätzlich zu iostream und PyQt5 auch noch Limnoria für die IRC-Funktionalität benötigt, das Ziel ist aber, ohne Limnoria auszukommen.

####Was es kann:
+ Befehle über Clonk annehmen
+ Befehle via IRC annehmen
+ Hostwünsche in einer Queue speichern
+ OC-Support (theoretisch, praktisch spinnt das Parsen (noch))

####Geplant sind:
+ DCC-Support
+ Berechtigungen (Gruppe x darf Befehl y ausführen)
+ Management-Verbindungen: Bestimmte Personen dürfen über eine spezielle Verbindung die Szenarienauswahl anpassen, die config-File (wird noch nicht verwendet) anpassen…

###Technisches
Die API für Befehle sieht momentan noch so aus:
+ Jeder Befehl gibt ein Tupel in der Form (ExitCode, Message) aus. Der Exit-Code ist dabei ein Wert der enum CmdResult, Message hingegen ist ein string, welcher nach stdin vom Clonk-Server geschrieben wird.
+ Befehle werden mittels addCommand(function, name) registriert. function ist hierbei das Funktionsobjekt, name ein String mit dem Namen (ja, noch nicht sehr schlau gelöst.)

###Installation
1. Dependencies (iostream, PyQt5) herunterladen und so installieren, dass der Python-Interpreter sie findet.
2. Code herunterladen. (Lizenz: GPL v3)
3. Datei von pycrctrl.pyp in pycrctrl.py umbennen oder durch pyprep jagen.
4. Eine Steuerungsanwendung schreiben. (Beispiel (für ein Limnoria-Plugin) hier)

Bugs können im Bugtracker gemeldet werden, für Feedback bin ich immer dankbar.

Kannst du für Schritt 1 nicht auch so eine requirements.txt-Datei bereitstellen, damit man die Abhängigkeiten halbautomatisch bekommt? Hm, dafür müsstest du deine iostream-Bibliothek natürlich auch auf PyPi veröffentlichen.

Welchen Vorteil bringt dir ein Präprozessor? Sind Python-ifs nicht für alles ausreichend?

Ich fänds gut wenn theoretisch jeder irgendwie eigene c4s/ocs/c4s/ocd Dateien zum hosten benutzen kann. Vllt über eine Uploadseite oder indem man Atlantis Downloadlink(s) schickt. Wegen Schrott und zu viel Speicherplatzverschwendung kann ja alles nur temporär gespeichert werden? Ich würde einfach nur gerne meine selbstgemachten Maps hosten können. :stuck_out_tongue:

>Kannst du für Schritt 1 nicht auch so eine requirements.txt-Datei bereitstellen, damit man die Abhängigkeiten halbautomatisch bekommt? Hm, dafür müsstest du deine iostream-Bibliothek natürlich auch auf PyPi veröffentlichen.


Habe mich mit PyPi bisher noch nicht auseinandergesetzt, und die iostream-Bibliothek ist alles andere als fertig:wink: für PyQt5 könnte ich es aber machen, ja.
EDIT: Habe den Code jetzt so geändert, dass die iostream-Bibliothek automatisch heruntergeladen und im aktuellen Ordner gespeichert wird, wenn sie nicht vorhanden ist.

>Welchen Vorteil bringt dir ein Präprozessor? Sind Python-ifs nicht für alles ausreichend?


Nicht für alles, und außerdem ziehen sie an der Performance.

>Vllt über eine Uploadseite oder indem man Atlantis Downloadlink(s) schickt.


Eigentlich eine gute Idee, aber:
+ Atlantis läuft im Moment noch auf meinem 2. PC, da PyCRCtrl noch nicht so weit fortgeschritten ist, dass sich ein eigener Server lohnen würde; somit würde im Moment nur Links via IRC schicken gehen.
+ Ich müsste die Dateien durchchecken, denn man könnte ja einen Link zu einem Virus schicken. (Wobei ich das mit OC-c4group machen könnte.)

>außerdem ziehen sie an der Performance


Aber wohl kaum im messbaren Bereich. Ein Zweig, der immer gleich ausgeht, kann sehr gut vorhergesagt werden. Die JIT von Pypy kann ihn bestimmt auch gleich komplett weglassen.

Was ist, wenn jemand ein Szenario mit Endlosschleife schickt?
Oder noch schlimmer, Szenarios mit Exploits.

##Update!
Armins und Luchs’ Vorschläge wurden berücksichtigt:
+ Es gibt jetzt eine Funktion addScenario, der man einen Link übergibt. Dieser wird dann automatisch ausgewertet, das Szenario wird heruntergeladen und in die Szenarienliste eingetragen.
+ Die iostream-Bibliothek wird nun automatisch heruntergeladen und im aktuellen Ordner gespeichert, wenn sie fehlt.

####Weitere Changes:
+ Es wird nun eine lokale config-File, die pycrctrl.conf heißt, verwendet. In der befinden sich ich einer JSON-Struktur die einzelnen Einstellungen. Vorlage gibt es hier, später wird PyCRCtrl eine Default-config-File auch selber erstellen können.

Szenarien mit Endlosschleifen sind blöd, bisher gibt es noch keine Vorgehensweise dagegen. Außerdem ist die Funktion jedenfalls bei Atlantis intern nur als trusted direkt aktiv, ansonsten wird nur der owner (ich) benachrichtigt.

Aso, dann passts.

Nachdem es einige Probleme mit JSON und regulären Ausdrücken gab, werden letztere nun base64-kodiert gespeichert.

Ich find’s gut, was du machst und wie schnell du Vorschläge umsetzt. Und, hey, Python, meine Lieblingssprache! Deshalb dachte ich mir, da kann ich doch mit einem kurzen Code-Review dienen.

Das mit den #ifdefs und Performance überrascht mich. Der einzige Code der hier betroffen ist kommt in einem Exception-Handler vor, d.h. er läuft wahrscheinlich ohnehin nicht zu oft. Vom Bauchgefühl her vermute ich, dass die diversen String- und Regex-Operationen im Schleifenkörper mehr in’s Gewicht fallen. Per Profiler geprüft habe ich das aber nicht.

Präprozessor und #ifdefs finde ich kreativ, allerdings unpythonisch. Kommst du aus der C-Welt? :wink: Aber auch vom Code-Stil her kommt mir das vor, als gäbe es einen deutlichen C-Einfluss. Statt Präprozessor ist Python-typisch eher, performance-kritische Teile auszulagern und in C zu implementieren. Das muss aber schon sehr kritisch sein, damit das lohnt. Stattdessen kann man ifs sparen, zum Beispiel durch Polymorphismus oder mit Funktionsreferenzen als funktionale Alternative.

Vom Code insgesamt denke ich, dass er sich noch besser in Module aufteilen lässt; mit iostream hast du ja schon angefangen. Nutzer-Input parsen beispielsweise würde hinter einer schönen API den Code besser lesbar machen, statt der bool(re.match(self.decodeRegExp(self.config.get(“RegExps”)[“startExp”]), output))-Konstrukte.
Und ich sehe CmdResult als Enum angelegt, gut so! Ich denke das kannst du beim PyCRCtrl.state auch statt der Strings machen. Wird auf den Zustand eigentlich auch von außen zugegriffen? Vielleicht kannst du dir die Property sparen. Selbst wenn nicht würde ich aber zwecks Lesbarkeit Dekoratoren statt state = property(getState, setState) vorschlagen.
In Zeile 201 ist mir self.scenlist[randint(0, len(self.scenlist)-1)] aufgefallen. Das lässt sich zu random.choice(self.scenlist) vereinfachen.

Ich hoffe, das hilft dir. Schönes kleines Projekt, weiter so!

>Präprozessor und #ifdefs finde ich kreativ, allerdings unpythonisch. Kommst du aus der C-Welt? :wink:


Eigentlich fühle ich mich in C nicht so wirklich zu Hause:wink:, aber den Präprozessor finde ich ziemlich praktisch. Der jetzige Code würde auch ohne auskommen. (Tut er auch, meine Laien-Implementierung des Präprozessors ist wieder mal kaputt:cry:)

>Vom Code insgesamt denke ich, dass er sich noch besser in Module aufteilen lässt; mit iostream hast du ja schon angefangen. Nutzer-Input parsen beispielsweise würde hinter einer schönen API den Code besser lesbar machen, statt der bool(re.match(self.decodeRegExp(self.config.get("RegExps")["startExp"]), output))-Konstrukte.


Ja, diese Konstrukte sind nicht sehr optimal, in den Ferien muss ich das Ganze mal ziemlich umgestalten, danke.

> Ich denke das kannst du beim PyCRCtrl.state auch statt der Strings machen.


Strings haben den Vorteil, dass ich sie direkt ins Topic einbauen kann.

>Stattdessen kann man ifs sparen, zum Beispiel durch Polymorphismus oder mit Funktionsreferenzen als funktionale Alternative.


Ich habe ehrlich gesagt nicht verstanden, wie ich das in den jetzigen Code gut integrieren kann.

>Wird auf den Zustand eigentlich auch von außen zugegriffen?


Huh, im Moment eigentlich nicht. Da ist ein Getter drin, wobei ich nicht mehr weiß, wofür der eigentlich war...hab so das Gefühl, der ist eher zur Vollständigkeit da:wink:

>In Zeile 201 ist mir self.scenlist[randint(0, len(self.scenlist)-1)] aufgefallen. Das lässt sich zu random.choice(self.scenlist) vereinfachen.


Gar nicht gekannt, danke!

>Selbst wenn nicht würde ich aber zwecks Lesbarkeit Dekoratoren statt state = property(getState, setState) vorschlagen.


Hast recht, werde ich bald ändern.

Danke für den Review, er hat mir viel geholfen. Noch ein paar Unschönheiten sind dadurch bald draußen.

> Ich habe ehrlich gesagt nicht verstanden, wie ich das in den jetzigen Code gut integrieren kann.


Da hast recht, das war viel zu abstrakt. Ich schaue, dass ich die nächsten Tage Zeit habe um einen richtigen Refactoring-Vorschlag zu machen.

Freut mich auf jeden Fall, dass dir das Review schon weiter geholfen hat. :)

#Update®!
Das Thema dieses Updates sind der Prototyp des Rechtesystems und der Updater.

Das Rechtesystem erlaubt es, verschiedenen Benutzern verschiedene Befehle zugänglich zu machen. Vorhanden sind die Gruppen
+ User
+ Admin
+ Moderator

In einer JSON-strukturierten Datei, capabilities.conf, können die Einträge für die Moderatoren hinterlegt werden.
Achtung: Das Rechtesystem krankt noch an ein paar Bugs, die demnächst gefixt werden.

Zweite große Neuerung: Der Updater. Falls der Name der in der config hinterlegten Engine openclonk-server ist (Hardcode, ich weiß, wird noch verallgemeinert), wird die Updater-Klasse instanziiert, welche
+ den aktuellen OpenClonk-Snapshot und
+ den aktuellen openclonk-server Build herunterlädt und extrahiert.

Nice!

>herunterladet


Rechtschreibflamewar incoming! :wink:

>Rechtschreibflamewar incoming!


Not necessarily :wink:

Nach langer Zeit gab es wieder ein paar Änderungen:
+ Die Konfiguration wurde von JSON auf INI umgestellt.
+ Der Code steht nun unter der ISC-Lizenz, im Zuge dessen wurde PyQt5 rausgeworfen.
+ PyCRCtrl sollte nun unter Windows funktionieren (zumindest funktioniert subprocess)
+ Nachrichten werden nicht mehr nach stdout, sondern in eine Logdatei geschrieben.
+ Szenarienlisten sind nun simple "Listen" à la scenarios.lst, keine gepickelten Objekte mehr.
+ Encodingprobleme in Clonk sind leider weiterhin vorhanden :frowning:
+ Das Berechtigungssystem wurde rausgeworfen, da es mit Vanilla-CR nicht wirklich funktionierte.

Im August soll die erste Version (0.1) releast werden.

####Testrelease
Interessierte können sich jetzt schon Version 0.1 über PyPI installieren (pip install pycrctrl)und mittels eines Limnoria-Plugins in Betrieb nehmen. Konfigurationsdatei sollte er selber anlegen.
Hier meine (uralte) Implementierung eines Plugins.