Browse Source

Inital commit

digital 7 years ago
commit
bb309ff99b

+ 367 - 0
gui/__init__.py

@@ -0,0 +1,367 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of DigiLib.
+#
+#    DigiLib is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    DigiLib is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with DigiLib.  If not, see <http://www.gnu.org/licenses/>.
+#
+## DOC
+# DigiLib provides widgets based on Gtk3. It doesn't create own widgets, it
+# uses Gtk3 widgets.
+# This Module defines following classes:
+# |-LabelName
+# | |-displas a name/label for something
+# |-LabelValue
+# | |-to display the value of something
+# |-InfoFrame
+# |     lists informations in the format 'Label:Value [Unit] [Widget]', where
+# |     Widget is a Gtk Widget to visualize the Value. A widget_update_func can
+# |     be passed to add_line for easy updating.
+# |-ChildConsoleBase
+# TODO
+#
+## Imports
+# Python Libraries
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+import logging
+import logging.config
+import os
+import pprint
+import random
+import string
+import sys
+import time
+import yaml
+# My libraries
+import digilib.misc
+## Logging
+log = logging.getLogger(__name__+".gui")
+## Global Variables
+## Classes
+
+class LabelName(Gtk.Label):
+    def __init__(self, *args,**kwargs):
+        super(LabelName, self).__init__(*args,**kwargs)
+        self.set_css_name("namelabel")
+        self.set_halign(Gtk.Align(2))
+
+class LabelValue(Gtk.Label):
+    """docstring for LabelValue"""
+
+    def __init__(self, *args,**kwargs):
+        super(LabelValue, self).__init__(*args,**kwargs)
+        self.set_css_name("valuelabel")
+        self.set_halign(Gtk.Align(1))
+
+class InfoFrame(Gtk.Frame):
+    """docstring for InfoFrame."""
+    def __init__(self,**kwargs):
+        super(InfoFrame, self).__init__(**kwargs)
+        self.lines = {
+            # id:{
+            #     "layout": layout,
+            #     "widgets": [label_name,label_value,widget],
+            #     "update_func":widget_update_func
+            # }
+        }
+        self.current_line_no = 0
+        # self.layout = Gtk.Grid(halign=Gtk.Align(1))
+        self.layout = Gtk.Grid()
+        self.layout.set_row_spacing(10)
+        self.layout.set_column_spacing(5)
+        self.layout.set_column_homogeneous(False)
+        self.layout.set_hexpand(True)
+        self.add(self.layout)
+    def add_line(
+            self,
+            identifier,
+            name,
+            value,
+            unit=None,
+            widget=None,
+            widget_update_func=None
+        ):
+        if id in self.lines.keys():
+            raise ValueError(
+                "Identifiers must be uniqe, but '"
+                +identifier
+                +"' already exists!"
+            )
+        self.lines[identifier] = {
+        "layout":None,
+        "wname":None,
+        "wvalue":None,
+        "wunit":None,
+        "wextra":None,
+        "update_func":None
+        }
+        self.lines[identifier]["update_func"] = widget_update_func
+        # if there is a widget and an update function was passed then update it
+
+        if widget_update_func == True:
+            self.lines[identifier]["update_func"] = widget.set_value
+        if self.lines[identifier]["update_func"]:
+            self.lines[identifier]["update_func"](value)
+        layout = Gtk.Box()
+        layout.set_spacing(5)
+        layout.set_homogeneous(False)
+        self.lines[identifier]["layout"] = layout
+        # create the labels for the name and value field and also for the unit
+        # field if an unit was specified
+        label_name = LabelName(name)
+        self.lines[identifier]["wname"] = label_name
+        self.layout.attach(label_name,0,self.current_line_no,1,1)
+        label_value = LabelValue(value)
+        self.lines[identifier]["wvalue"] = label_value
+        layout.add(label_value)
+        if unit:
+            label_unit = LabelValue(unit)
+            self.lines[identifier]["wunit"] = label_unit
+            layout.add(label_unit)
+        if widget:
+            self.lines[identifier]["wextra"] = widget
+            layout.add(widget)
+        self.layout.attach(layout,1,self.current_line_no,1,1)
+        self.current_line_no += 1
+    def update_name(self,identifier,name):
+        self.lines[identifier]["wname"].set_label(name)
+    def update_unit(self,identifier,unit):
+        self.lines[identifier]["wunit"].set_label(unit)
+    def update_value(self,identifier,value):
+        self.lines[identifier]["wvalue"].set_label(str(value))
+        if self.lines[identifier]["update_func"]:
+            self.lines[identifier]["update_func"](value)
+        print(1)
+
+# class ChildGrid(Gtk.Grid):
+#     def __init__(self):
+#         super(ChildGrid,self).__init__()
+#         self.prepare()
+#     def prepare(self):
+#         pass
+#
+# class ChildScrollBase(Gtk.ScrolledWindow):
+#     def __init__(self):
+#         super(ChildScroll,self).__init__()
+#         self.prepare()
+#     def prepare(self):
+#         pass
+
+class ChildConsole(Gtk.Grid):
+    scroll = True
+    history_list = []
+    history_position = -1
+    def __init__(self,get_client):
+        super(ChildConsole,self).__init__()
+        self.get_client = get_client
+        self.set_css_name("tab_console")
+        self.prepare()
+        self.button_send.connect("clicked",self.on_entry_activated)
+        self.entry_cmd.connect("activate",self.on_entry_activated)
+        self.entry_cmd.connect("key-press-event",self.on_keypress)
+    def prepare(self):
+        self.scroll_mark = Gtk.TextMark(name="scroll_mark")
+        self.text_buffer_log = Gtk.TextBuffer()
+        self.text_view_log = Gtk.TextView(hexpand=True,vexpand=True)
+        self.text_view_log.set_buffer(self.text_buffer_log)
+        self.text_view_log.set_editable(False)
+        self.text_view_log.set_cursor_visible(False)
+        self.text_view_log.props.pixels_above_lines = 2
+        self.text_view_log.props.pixels_below_lines = 2
+        self.text_view_log.props.right_margin = 2
+        self.text_view_log.props.left_margin = 6
+        # self.text_view_log.set_hexpand(True)
+        # self.text_view_log.set_vexpand(True)
+        self.scrolled_window = Gtk.ScrolledWindow()
+        self.entry_cmd = Gtk.Entry(hexpand=True)
+        # self.entry_cmd.set_cursor_hadjustment(Gtk.Align(2))
+        # self.entry_cmd.set_hexpand(True)
+        self.button_send = Gtk.Button(label="send")
+        # self.button_send.set_label("send")
+        self.scrolled_window.add(self.text_view_log)
+        self.attach(self.scrolled_window,0,1,2,1)
+        self.attach(self.entry_cmd,0,2,1,1)
+        self.attach(self.button_send,1,2,1,1)
+    def add_msg(self,text,sender_name="Server"):
+        for line in text.splitlines():
+            if sender_name:
+                line = sender_name+":"+line
+            self.text_buffer_log.insert(
+                self.text_buffer_log.get_end_iter(),
+                "\n"+line.rstrip()
+            )
+            self.scroll_down(None)
+        if self.scroll:
+            self.scroll_down(None)
+    def add_msg_threadsafe(self,*args,**kwargs):
+        Gdk.threads_enter()
+        self.add_msg(*args,**kwargs)
+        Gdk.threads_leave()
+    def send(self,text):
+        client = self.get_client()
+        if client:
+            if client.is_connected:
+                client.send(text)
+    def scroll_down(self,*args):
+        self.scroll = True
+        self.text_buffer_log.add_mark(
+            self.scroll_mark,
+            self.text_buffer_log.get_end_iter()
+        )
+        self.text_view_log.scroll_to_mark(
+            mark=self.scroll_mark,
+            # No idea what these arguments do, but they work.
+            within_margin=0.0,
+            use_align=True,
+            xalign=0.5,
+            yalign=0.5
+        )
+        self.text_buffer_log.delete_mark(self.scroll_mark)
+    def toggle_scroll(self,*args):
+        self.scroll = not self.scroll
+    def get_entry_text(self):
+        text = self.entry_cmd.get_text()
+        self.entry_cmd.set_text("")
+        return text
+    def on_entry_activated(self,widget):
+        text = self.get_entry_text()
+        if text.strip():
+            self.history_position = -1
+            if len(self.history_list) == 0:
+                self.history_list.insert(0,text)
+            else:
+                if self.history_list[0] != text:
+                    self.history_list.insert(0,text)
+            self.add_msg(text,"you")
+            self.send(text)
+    def on_keypress(self,widget,event):
+        if not widget == self.entry_cmd:
+            lgui.debug("exiting keypress event function because the widget was \
+                not self.entry_cmd")
+            return True
+        if event.keyval == Gdk.KEY_Up: # Up
+            if self.history_position < len(self.history_list)-1:
+                self.history_position += 1
+                text = self.history_list[self.history_position]
+                self.entry_cmd.set_text(text)
+                self.entry_cmd.set_position(len(text))
+            return True
+        elif event.keyval == Gdk.KEY_Down: # Down
+            text = self.entry_cmd.get_text()
+            if self.history_position == -1 and text != "":
+                self.entry_cmd.set_text("")
+            elif self.history_position == 0:
+                self.history_position = -1
+                self.entry_cmd.set_text("")
+            elif  self.history_position > 0:
+                self.history_position -= 1
+                text = self.history_list[self.history_position]
+                self.entry_cmd.set_text(text)
+                self.entry_cmd.set_position(len(text))
+            return True
+
+class ChildOverview(Gtk.ScrolledWindow):
+    def __init__(self):
+        super(ChildOverview,self).__init__()
+        self.set_css_name("tab_overview")
+        self.layout = Gtk.Grid()
+        self.layout.set_column_homogeneous(True)
+        self.add(self.layout)
+
+class ChildControl(Gtk.Grid):
+    def __init__(self):
+        super(ChildControl,self).__init__()
+        self.label = Gtk.Label()
+        self.label.set_markup("<big>Another fancy self.label</big>")
+        self.add(self.label)
+
+class ChildSettings(Gtk.Grid):
+    def __init__(self):
+        super(ChildSettings,self).__init__()
+        self.label = Gtk.Label()
+        self.label.set_markup("<big>Another fancy self.label</big>")
+        self.add(self.label)
+
+class WindowBase(Gtk.Window):
+    def __init__(self,title="Don't Panic!"):
+        super(Gtk.Window,self).__init__(title=title)
+        self.pressed_keys = []
+        self.key_shortcuts = []
+        self.connect("delete-event",self.on_delete_event)
+        self.connect("key-press-event",self.on_key_press_event)
+        self.connect("key-release-event",self.on_key_release_event)
+    def keystring_to_gkd(self, keys):
+        key_list = keys.split(" ")
+        known_keys = []
+        unknown_keys = []
+        for k in key_list:
+            if "KEY_"+k in dir(Gdk):
+                known_keys.append(eval("Gdk.KEY_"+k))
+            else:
+                unknown_keys.append(k)
+        if unknown_keys:
+            raise ValueError("Unknown Keys: "+", ".join(unknown_keys))
+        else:
+            return known_keys
+    def add_key_shortcut(self,keys,func=None,args=[],kwargs={}):
+        key_list = self.keystring_to_gkd(keys)
+        for ks in self.key_shortcuts:
+            if key_list == ks[0]:
+                self.key_shortcuts.remove(ks)
+                break
+        self.key_shortcuts.append([key_list,func,args,kwargs])
+    def remove_key_shortcut(self,keys):
+        key_list = self.keystring_to_gkd(keys)
+        for ks in self.key_shortcuts:
+            if key_list == ks[0]:
+                self.key_shortcuts.remove(ks)
+                break
+    def on_delete_event(self,*args):
+        print(args)
+        Gtk.main_quit()
+    def on_key_release_event(self,widget,event):
+        if event.keyval in self.pressed_keys:
+            self.pressed_keys.remove(event.keyval)
+    def on_key_press_event(self,widget,event):
+        self.pressed_keys.append(event.keyval)
+        for keys,func,args,kwargs in self.key_shortcuts:
+            if keys == self.pressed_keys:
+                func(*args,**kwargs)
+                break
+    def hello_world(self,*args):
+        print("hello world")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#

+ 425 - 0
gui/__init__.py.old

@@ -0,0 +1,425 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of MyLibGui.
+#
+#    MyLibGui is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    MyLibGui is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with MyLibGui.  If not, see <http://www.gnu.org/licenses/>.
+#
+## Imports
+# Python Libraries
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+import logging
+import logging.config
+import os
+import pprint
+import random
+import string
+import sys
+import time
+import yaml
+# My libraries
+import mylibmisc
+## Logging
+log = logging.getLogger(__name__+".gui")
+## Global Variables
+LOREM_IPSUM = """Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+Pellentesque vel mauris non odio pharetra ultricies quis eu turpis.
+Proin et tortor eu magna ultricies facilisis tincidunt vehicula nisi.
+
+Donec hendrerit massa id viverra lobortis.
+In sed nisl a metus gravida faucibus.
+Praesent bibendum mi semper, sagittis mi sit amet, auctor dui.
+
+Sed ac dui sagittis quam ultricies dapibus.
+In non elit non felis convallis vestibulum.
+
+Vivamus ornare ante suscipit faucibus pulvinar.
+Cras consequat nulla quis quam faucibus mollis.
+Nulla dictum sapien in justo sagittis malesuada.
+Sed ornare orci quis laoreet elementum.
+
+Sed ornare lacus ac ipsum vulputate vulputate.
+Sed accumsan ante nec magna sollicitudin maximus.
+Aliquam condimentum magna nec convallis efficitur.
+Nam posuere mauris et dui pulvinar, quis iaculis leo aliquam.
+Nunc cursus arcu et leo vehicula, quis feugiat mi semper.
+"""
+## Classes
+class LabelValue(Gtk.Label):
+    def __init__(self, *args,**kwargs):
+        super(LabelValue, self).__init__(*args,**kwargs)
+        self.set_css_name("valuelabel")
+        self.set_halign(Gtk.Align(1))
+
+class LabelName(Gtk.Label):
+    def __init__(self, *args,**kwargs):
+        super(LabelName, self).__init__(*args,**kwargs)
+        self.set_css_name("namelabel")
+        self.set_halign(Gtk.Align(2))
+
+class InfoFrame(Gtk.Frame):
+    """docstring for InfoFrame."""
+    def __init__(self,**kwargs):
+        super(InfoFrame, self).__init__(**kwargs)
+        self.lines = {
+            # id:{
+            #     "layout": layout,
+            #     "widgets": [label_name,label_value,widget],
+            #     "update_func":widget_update_func
+            # }
+        }
+        self.current_line_no = 0
+        # self.layout = Gtk.Grid(halign=Gtk.Align(1))
+        self.layout = Gtk.Grid()
+        self.layout.set_row_spacing(10)
+        self.layout.set_column_spacing(5)
+        self.layout.set_column_homogeneous(False)
+        self.layout.set_hexpand(True)
+        self.add(self.layout)
+    def add_line(
+            self,
+            identifier,
+            name,
+            value,
+            unit=None,
+            widget=None,
+            widget_update_func=None
+        ):
+        if id in self.lines.keys():
+            raise ValueError(
+                "Identifiers must be uniqe, but '"
+                +identifier
+                +"' already exists!"
+            )
+        self.lines[identifier] = {
+        "layout":None,
+        "wname":None,
+        "wvalue":None,
+        "wunit":None,
+        "wextra":None,
+        "update_func":None
+        }
+        self.lines[identifier]["update_func"] = widget_update_func
+        # if there is a widget and an update function was passed then update it
+
+        if widget_update_func == True:
+            self.lines[identifier]["update_func"] = widget.set_value
+        if self.lines[identifier]["update_func"]:
+            self.lines[identifier]["update_func"](value)
+        layout = Gtk.Box()
+        layout.set_spacing(5)
+        layout.set_homogeneous(False)
+        self.lines[identifier]["layout"] = layout
+        # create the labels for the name and value field and also for the unit
+        # field if an unit was specified
+        label_name = LabelName(name)
+        self.lines[identifier]["wname"] = label_name
+        self.layout.attach(label_name,0,self.current_line_no,1,1)
+        label_value = LabelValue(value)
+        self.lines[identifier]["wvalue"] = label_value
+        layout.add(label_value)
+        if unit:
+            label_unit = LabelValue(unit)
+            self.lines[identifier]["wunit"] = label_unit
+            layout.add(label_unit)
+        if widget:
+            self.lines[identifier]["wextra"] = widget
+            layout.add(widget)
+        self.layout.attach(layout,1,self.current_line_no,1,1)
+        self.current_line_no += 1
+    def update_name(self,identifier,name):
+        self.lines[identifier]["wname"].set_label(name)
+    def update_unit(self,identifier,unit):
+        self.lines[identifier]["wunit"].set_label(unit)
+    def update_value(self,identifier,value):
+        self.lines[identifier]["wvalue"].set_label(str(value))
+        if self.lines[identifier]["update_func"]:
+            self.lines[identifier]["update_func"](value)
+        print(1)
+
+class MyGtkWindow(Gtk.Window):
+    def __init__(self,title="Don't Panic!"):
+        super(Gtk.Window,self).__init__(title=title)
+        self.pressed_keys = []
+        self.key_shortcuts = []
+        self.connect("delete-event",self.on_delete_event)
+        self.connect("key-press-event",self.on_key_press_event)
+        self.connect("key-release-event",self.on_key_release_event)
+    def keystring_to_gkd(self, keys):
+        key_list = keys.split(" ")
+        known_keys = []
+        unknown_keys = []
+        for k in key_list:
+            if "KEY_"+k in dir(Gdk):
+                known_keys.append(eval("Gdk.KEY_"+k))
+            else:
+                unknown_keys.append(k)
+        if unknown_keys:
+            raise ValueError("Unknown Keys: "+", ".join(unknown_keys))
+        else:
+            return known_keys
+    def add_key_shortcut(self,keys,func=None,args=[],kwargs={}):
+        key_list = self.keystring_to_gkd(keys)
+        for ks in self.key_shortcuts:
+            if key_list == ks[0]:
+                self.key_shortcuts.remove(ks)
+                break
+        self.key_shortcuts.append([key_list,func,args,kwargs])
+    def remove_key_shortcut(self,keys):
+        key_list = self.keystring_to_gkd(keys)
+        for ks in self.key_shortcuts:
+            if key_list == ks[0]:
+                self.key_shortcuts.remove(ks)
+                break
+    def on_delete_event(self,*args):
+        print(args)
+        Gtk.main_quit()
+    def on_key_release_event(self,widget,event):
+        self.pressed_keys.remove(event.keyval)
+    def on_key_press_event(self,widget,event):
+        self.pressed_keys.append(event.keyval)
+        for keys,func,args,kwargs in self.key_shortcuts:
+            if keys == self.pressed_keys:
+                func(*args,**kwargs)
+                break
+    def hello_world(self,*args):
+        print("hello world")
+
+
+# class ChildConsole(Gtk.Grid):
+#     def __init__(self,parent=None):
+#         super(ChildConsole,self).__init__()
+#         self.parent = parent
+#         self.history_list = []
+#         self.history_position = -1
+#         self.scroll = True
+#         self.prepare()
+#         self.button_send.connect("clicked",self.on_send_message_request)
+#         self.entry_cmd.connect("activate",self.on_send_message_request)
+#         self.entry_cmd.connect(
+#             "key-press-event",
+#             self.on_keypress
+#         )
+#         self.add_msg(LOREM_IPSUM,"code")
+#         self.add_msg(LOREM_IPSUM,"code")
+#         self.add_msg(LOREM_IPSUM,"code")
+#     def prepare(self):
+#         self.scroll_mark = Gtk.TextMark(name="scroll_mark")
+#         self.text_buffer_log = Gtk.TextBuffer()
+#         self.text_buffer_log.set_text("program:HAIIII")
+#         self.text_view_log = Gtk.TextView()
+#         self.text_view_log.set_buffer(self.text_buffer_log)
+#         self.text_view_log.set_editable(False)
+#         self.text_view_log.set_cursor_visible(False)
+#         self.text_view_log.props.pixels_above_lines = 2
+#         self.text_view_log.props.pixels_below_lines = 2
+#         self.text_view_log.props.right_margin = 2
+#         self.text_view_log.props.left_margin = 6
+#         self.text_view_log.set_hexpand(True)
+#         self.text_view_log.set_vexpand(True)
+#         self.scrolled_window = Gtk.ScrolledWindow()
+#         self.entry_cmd = Gtk.Entry()
+#         self.entry_cmd.set_hexpand(True)
+#         self.button_send = Gtk.Button()
+#         self.button_send.set_label("send")
+#         self.scrolled_window.add(self.text_view_log)
+#         self.attach(self.scrolled_window,0,1,2,1)
+#         self.attach(self.entry_cmd,0,2,1,1)
+#         self.attach(self.button_send,1,2,1,1)
+#     def add_msg(self,text,sender_name="thin air"):
+#         for line in text.splitlines():
+#             # if not line.strip():
+#             #     continue
+#             self.text_buffer_log.insert(
+#                 self.text_buffer_log.get_end_iter(),
+#                 "\n{}:{}".format(sender_name,line.rstrip())
+#                 )
+#             self.scroll_down(None)
+#         if self.scroll:
+#             self.scroll_down(None)
+#     def add_msg_threadsafe(self,*args,**kwargs):
+#         Gdk.threads_enter()
+#         self.add_msg(*args,**kwargs)
+#         Gdk.threads_leave()
+#     def send(self,text):
+#         if self.parent.client:
+#             if self.parent.client.is_connected:
+#                 self.parent.client.send(text)
+#     def toggle_scroll(self,*args):
+#         self.scroll = not self.scroll
+#     def scroll_down(self,*args):
+#         self.scroll = True
+#         self.text_buffer_log.add_mark(
+#             self.scroll_mark,
+#             self.text_buffer_log.get_end_iter()
+#         )
+#         self.text_view_log.scroll_to_mark(
+#             mark=self.scroll_mark,
+#             # mark=self.text_buffer_log.get_insert(),
+#             within_margin=0.0,
+#             use_align=True,
+#             xalign=0.5,
+#             yalign=0.5
+#         )
+#         self.text_buffer_log.delete_mark(self.scroll_mark)
+#     def get_entry_text(self):
+#         text = self.entry_cmd.get_text()
+#         self.entry_cmd.set_text("")
+#         return text
+#     def on_send_message_request(self,widget):
+#         if widget != self.entry_cmd and widget != self.button_send:
+#             print("what tried to send the message?")
+#         text = self.get_entry_text()
+#         if text.strip():
+#             self.history_position = -1
+#             if len(self.history_list) == 0:
+#                 self.history_list.insert(0,text)
+#             else:
+#                 if self.history_list[0] != text:
+#                     self.history_list.insert(0,text)
+#             self.add_msg(text,"you")
+#             self.send(text)
+#     def on_keypress(self,widget,event):
+#         if not widget == self.entry_cmd:
+#             print("exiting keypress event function because the widget was \n",
+#                 "not self.entry_cmd")
+#             return
+#         if event.keyval == Gdk.KEY_Up: # Up
+#             if self.history_position < len(self.history_list)-1:
+#                 self.history_position += 1
+#                 text = self.history_list[self.history_position]
+#                 self.entry_cmd.set_text(text)
+#             return True
+#         elif event.keyval == Gdk.KEY_Down: # Down
+#             text = self.entry_cmd.get_text()
+#             if self.history_position == -1 and text != "":
+#                 self.entry_cmd.set_text("")
+#             elif self.history_position == 0:
+#                 self.history_position = -1
+#                 self.entry_cmd.set_text("")
+#             elif  self.history_position > 0:
+#                 self.history_position -= 1
+#                 text = self.history_list[self.history_position]
+#                 self.entry_cmd.set_text(text)
+#             return True
+#
+# # class ChildOverview(Gtk.FlowBox):
+# class ChildOverview(Gtk.ScrolledWindow):
+#     def __init__(self):
+#         super(ChildOverview,self).__init__()
+#         self.set_name("child_overview")
+#         # self.set_column_homogeneous(True)
+#         self.prepare()
+#     def prepare(self):
+#         self.infobox1 = InfoFrame()
+#         self.infobox2 = InfoFrame()
+#         self.make_infobox_controller()
+#         self.make_infobox_weight()
+#         self.make_infobox_test(self.infobox1)
+#         self.make_infobox_test(self.infobox2)
+#         self.layout = Gtk.Grid()
+#         self.layout.set_column_homogeneous(True)
+#         # self.layout.set_row_homogeneous(True)
+#         # self.layout.add(self.infbox_controller.frame)
+#         # self.layout.add(self.infbox_weight.frame)
+#         # self.layout.add(self.infbox1.frame)
+#         # self.layout.add(self.infbox2.frame)
+#         self.layout.attach(self.infobox_controller,0,0,1,1)
+#         self.layout.attach(self.infobox_weight,    1,0,1,1)
+#         self.layout.attach(self.infobox1,          0,1,1,1)
+#         self.layout.attach(self.infobox2,          1,1,1,1)
+#         self.add(self.layout)
+#     def make_infobox_controller(self):
+#         levelbar_cpu = Gtk.LevelBar()
+#         levelbar_cpu.set_hexpand(True)
+#         levelbar_mem = Gtk.LevelBar()
+#         levelbar_mem.set_hexpand(True)
+#         # levelbar_mem.set_value(0.4)
+#         self.infobox_controller = InfoFrame(label="Weight")
+#         self.infobox_controller.add_line("uptime","Uptime:","99:59:592017-03-06 12:56")
+#         self.infobox_controller.add_line("ip","IP:","30.47.10.O9",None)
+#         self.infobox_controller.add_line("cpu","CPU:",30,"%",
+#             # levelbar_cpu,levelbar_cpu.set_value)
+#             levelbar_cpu,True)
+#         self.infobox_controller.add_line("mem","Memory:",0.40,"%",
+#             levelbar_mem,levelbar_mem.set_value)
+#         # self.infobox_controller.add_line("mem_temp","Memory temp:","43 C")
+#         # self.infobox_controller.add_line("",":","")
+#     def make_infobox_weight(self):
+#         self.infobox_weight = InfoFrame(label="Weight")
+#         self.infobox_weight.add_line("total","Total:","50 kg")
+#         self.infobox_weight.add_line("bees","Bees:","20 kg")
+#         self.infobox_weight.add_line("honey","Honey:","42 kg")
+#         self.infobox_weight.add_line("time","Time:","2017-03-06 12:56")
+#         # self.infobox_weight.update_name("honey","Honi:")
+#         # self.infobox_weight.update_value("honey","43 kg")
+#     def make_infobox_test(self,infobox):
+#         infobox.add_line("1","Label:","Value")
+#         infobox.add_line("2","Baum:","Haus")
+#         infobox.add_line("3","Weight:","40 kg")
+#         infobox.add_line("4","Wiff","Woff")
+#
+# class ChildControl(Gtk.Grid):
+#     def __init__(self):
+#         super(ChildControl,self).__init__()
+#         self.prepare()
+#     def prepare(self):
+#         self.label = Gtk.Label()
+#         self.label.set_markup("<big>Another fancy self.label</big>")
+#         self.add(self.label)
+#
+# class ChildSettings(Gtk.Grid):
+#     def __init__(self):
+#         super(ChildSettings,self).__init__()
+#         self.prepare()
+#     def prepare(self):
+#         self.label = Gtk.Label()
+#         self.label.set_markup("<big>Another fancy self.label</big>")
+#         self.add(self.label)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#

+ 53 - 0
misc/__init__.py

@@ -0,0 +1,53 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of DigiLib.
+#
+#    DigiLib is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    DigiLib is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with DigiLib.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import logging
+import random
+import string
+import sys
+import time
+
+log = logging.getLogger(__name__+"")
+
+LOREM_IPSUM = """Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+Pellentesque vel mauris non odio pharetra ultricies quis eu turpis.
+Proin et tortor eu magna ultricies facilisis tincidunt vehicula nisi.
+
+Donec hendrerit massa id viverra lobortis.
+In sed nisl a metus gravida faucibus.
+Praesent bibendum mi semper, sagittis mi sit amet, auctor dui.
+
+Sed ac dui sagittis quam ultricies dapibus.
+In non elit non felis convallis vestibulum.
+
+Vivamus ornare ante suscipit faucibus pulvinar.
+Cras consequat nulla quis quam faucibus mollis.
+Nulla dictum sapien in justo sagittis malesuada.
+Sed ornare orci quis laoreet elementum.
+
+Sed ornare lacus ac ipsum vulputate vulputate.
+Sed accumsan ante nec magna sollicitudin maximus.
+Aliquam condimentum magna nec convallis efficitur.
+Nam posuere mauris et dui pulvinar, quis iaculis leo aliquam.
+Nunc cursus arcu et leo vehicula, quis feugiat mi semper.
+"""
+
+class Container():
+    # This class should only be used as a storage space for variables.
+    pass

+ 360 - 0
network/IRC/__init__.py

@@ -0,0 +1,360 @@
+#! /usr/bin/python3
+
+import json
+import threading
+import queue
+import time
+import os
+import pdb
+import re
+import select
+import signal
+import socket
+import sys
+import traceback
+import logging, logging.handlers
+
+import myLogger
+import unixsocket
+
+def signal_handler(signum,frame):
+    if signum == signal.SIGUSR1:
+        pdb.set_trace()
+
+signal.signal(signal.SIGUSR1,signal_handler)
+
+PDB = False
+# PDB = True
+
+# from .irc_numeric_name import *
+
+
+# https://tools.ietf.org/html/rfc2812#section-2.3.1
+p_chanstring    = r"[^\x00\x07\r\n ,:]"
+p_channelid     = r"[a-z0-9]{5}"
+p_user          = r"[^\x00\r\n @]+"
+p_key           = r"[\x01-05\x07-08\x0C\x0E-F1\x21-7F]{1,23}"
+p_letter        = r"[a-zA-Z0-9]"
+p_digit         = r"[0-9]"
+p_hexdigit      = r"("+p_digit+r")|[A-F]"
+# p_special       = r"[\x5B\x60]"#\x7B-7D]"
+# p_special       = r"[\[\],{}`\\_^{|}]"
+p_special       = r"[]{}|,`_^\\]|\["
+
+p_nospcrlfcl    = r"[^\x00\x07\r\n :]"
+# p_nospcrlfcl    = r"[^\x00\x07\r\n]"
+p_wildone       = r"\x3F"
+p_wildmany      = r"\x2A"
+p_nowild        = r"[\x01-29\x2B-3E\x40-FF]"
+p_noesc         = r"[\x01-5B\x5D-FF]"
+
+p_nowild        = r"[^\x3F\x2A]"
+p_noesc         = r"[^\x00\\]"
+
+p_mask          = r"(("+p_nowild+r")|("+p_noesc+p_wildone+r")|("+p_noesc+p_wildmany+r"))*"
+p_targetmask    = r"\$|#"+p_mask
+p_channel       = r"(#|\+|(!"+p_channelid+r")|&)("+p_chanstring+r")+(:("+p_chanstring+r")+)?"
+# p_nickname      = r"("+p_letter+r")|("+p_special+r")(("+p_letter+r")|("+p_digit+r")|("+p_special+r")){0,8}"
+# p_nickname      = r"(("+p_letter+r")|("+p_special+r"))(("+p_letter+r")|("+p_digit+r")|("+p_special+r")){1,8}"
+p_nickname      = r"(("+p_letter+r")|("+p_special+r"))(("+p_letter+r")|("+p_digit+r")|("+p_special+r"))+"
+
+p_shortname     = r"(("+p_letter+r")|("+p_digit+r"))"\
+                + r"(("+p_letter+r")|("+p_digit+r")|(-))*"\
+                + r"(("+p_letter+r")|("+p_digit+r"))*"
+p_shortname     = r"("+p_letter+r"|"+p_digit+r")"\
+                + r"("+p_letter+r"|"+p_digit+r"|-)*"\
+                + r"("+p_letter+r"|"+p_digit+r")*"
+p_shortname     = r"("+p_letter+r"|"+p_digit+r")"\
+                + r"(("+p_letter+r")|("+p_digit+r")|(-))*"\
+                + r"(("+p_letter+r")|("+p_digit+r"))*"
+
+p_hostname      = r"("+p_shortname+r")(\."+p_shortname+r")*" # dot
+p_servername    = p_hostname
+p_ip4addr       = r"("+p_digit+r"){1,3}\."+r"("+p_digit+r"){1,3}\."+r"("+p_digit+r"){1,3}\."+r"("+p_digit+r"){1,3}"
+p_ip6addr       = r"(("+p_hexdigit+r")+(:("+p_hexdigit+r")+){7})"\
+                + r"|(0:0:0:0:0:(0|(FFFF)):"+p_ip4addr+r")"
+p_hostaddr      = r"("+p_ip4addr+r")|("+p_ip6addr+r")"
+p_host          = r"("+p_hostname+r")|("+p_hostaddr+r")"
+p_middle        = p_nospcrlfcl+r"((:)|("+p_nospcrlfcl+r"))*"
+p_trailing      = r"((:)|( )|("+p_nospcrlfcl+r"))*"
+# p_trailing      = r"((:)|( )|("+p_nospcrlfcl+r"))+"
+p_params        = r"(( "+p_middle+r"){0,13}( :"+p_trailing+r"))|(( "+p_middle+r"){14}( :?"+p_trailing+r"))"
+# p_params        = r"(( "+p_middle+r"){0,13}( :"+p_trailing+r")?)|(( "+p_middle+r"){14}( :?"+p_trailing+r")?)"
+
+p_command       = r"("+p_letter+r")+|(\d){3}"
+p_prefix_client = p_nickname+r"((!"+p_user+r")?@"+p_host+r")?"
+p_prefix        = r"("+p_servername+r")|("+p_prefix_client+r")"
+p_message       = r"(:"+p_prefix+r" )?"+p_command+r"("+p_params+r")?"
+p_target        = r"("+p_nickname+r")|("+p_servername+r")" #p_server
+p_msgto         = r"("+p_channel+r")"\
+                +r"|("+p_user+r"(%"+p_host+r")?@"+p_servername+r")"\
+                +r"|("+p_user+r"%"+p_host+r")"\
+                +r"|("+p_targetmask+r")"\
+                +r"|("+p_nickname+r")"\
+                +r"|("+p_nickname+r"!"+p_user+r"@"+p_host+r")"
+p_msgtarget     = p_msgto+r"(,"+p_msgto+r")*"
+
+            # "server":re.compile(
+            #     r":(?P<servername>"+p_servername+r")"+
+            #     r" "+
+            #     r"(?P<command>"+p_command+r")"+
+            #     r"(?P<params>"+p_params+r")"
+            #     ),
+            # "client":re.compile(
+            #     r":(?P<identity>"+
+            #         r"(?P<nick>"+p_nickname+r")"+
+            #         r"!(?P<user>"+p_user+r")"+
+            #         r"@(?P<host>"+p_host+r")"+
+            #         r")"+
+            #     r" "+
+            #     r"(?P<command>"+p_command+r")"+
+            #     # r"(\s?:?)"+
+            #     r"(?P<params>"+p_params+r")"
+            #     ),
+
+
+class IRCClient(unixsocket.Client):
+    def __init__(self, *args, **kwargs):
+        self.super_object = super(IRCClient, self)
+        self.super_object.__init__(*args, block_size = 2048, **kwargs)
+        # all item in this dict are searched for in every message
+        # until the item is found the value defaults to None. when found, the message is the value
+        self.search_items = {}
+        self.pattern = {
+            "full_message":re.compile(
+                r"(:"+
+                    r"(?P<identity>"+
+                        r"(?P<nick>"+p_nickname+r")"+
+                        r"("+
+                            r"(!(?P<user>"+p_user+r"))?"+
+                            r"@(?P<host>"+p_host+r") ?"+
+                            r")?"+
+                        r")"+
+                    r"|(?P<servername>"+p_servername+r")"+
+                    r" )?"+
+                r"(?P<command>"+p_command+r")"+
+                r"(?P<params>"+p_params+r")?"
+                ),
+            "chat":re.compile(
+                # r" ("+p_nickname+r")|("+p_channel+r") :("+p_nickname+r": )?("+p_trailing+r")"
+                r"(?P<target>("+p_channel+r")|("+p_nickname+r")) :"+
+                r"("+
+                    r"(?P<highlight>"+p_nickname+r"): ?"+
+                    r"(?P<msg_command>[:,][a-zA-Z0-9_-]+)? ?"
+                    r")?"+
+                r"(?P<rest>.*)"
+                ),
+        }
+        # this dict holds client information
+        self.client_info = {
+            "user":None,
+            "hostname":None, #!!!
+            "servername":None, #!!!
+            "realname":"irc bot",
+            "identity":None,
+            "pass_auth":False,
+            "nickserv_auth":False,
+            "current_nick":None,
+            "last_nick":None,
+            "server_name":None, #!!
+            "connection_address":None,
+        }
+        self.client_info.update({
+            "user":"baum",
+            "hostname":"digital",
+            "servername":"omnimaga",
+            "realname":"irc bot",
+            "pass_auth":False,
+            "nickserv_auth":False,
+            "startup_chans":["#thebot", "#digital"],
+            "chan_hello":"heyho, I'm a bot",
+            "server_name":self.host,
+            "connection_address":self.host
+        })
+        self.config = {
+            "shown_message_types":["numeric","private","client","PING","unknown"],
+            "nicks":["kevin","clemens","kevin_","kevin__"],
+        }
+        self.__private_config = {
+            "pass_password":"pass_pwd",
+            "nickserv_password":"nickserv_pwd"
+        }
+        self.server_info = {
+            "names":{#"#chan":[".user1",".user2"]
+            },
+        }
+        ## Events
+        self.registered_event = threading.Event()
+
+    def handshake(self):
+        nickname = self.config["nicks"][0]
+        nick_not_accepted_msg = " 433 * "+nickname+" :Nickname is already in use."
+        # welcome message, no MOTD, MOTD end, nickname already taken
+        self.add_search_item(" 001 "," 422 "," 376 ", nick_not_accepted_msg)
+        time.sleep(0.5)
+        if self.client_info["pass_auth"]:
+            auth_pass(self)
+        self.send("NICK "+nickname)
+        self.send("USER {identity} {hostname} {servername} {realname}\r\n".format(**self.client_info))
+        self.add_search_item(nick_not_accepted_msg," 001 ")
+        nicks = iter(self.config["nicks"][1:])
+        while True:
+            if self.search_items[nick_not_accepted_msg]:
+                del self.search_items[nick_not_accepted_msg]
+                try:
+                    nickname = next(nicks)
+                except StopIteration:
+                    self.stop()
+                nick_not_accepted_msg = " 433 * "+nickname+" :Nickname is already in use."
+                self.search_items[nick_not_accepted_msg] = None
+                self.send("NICK "+nickname)
+            elif self.search_items[" 001 "]:
+                self.client_info["current_nick"] = nickname
+                self.client_info["server_name"] = self.search_items[" 001 "].split()[0].lstrip(":")
+                break
+        self.logger.debug(self.client_info["server_name"])
+        self.registered_event.set()
+        # self.wait_for_item(" 422 "," 376 ")
+        if self.client_info["nickserv_auth"]:
+            auth_nickserv(self)
+        self.join_chans(*self.client_info["startup_chans"],message=self.client_info["chan_hello"])
+        time.sleep(0.1)
+        self.send("MODE kevin +R")
+
+    def connect(self):
+        handshake_thread = threading.Thread(target=self.handshake, name="handshake", daemon=True)
+        # calling connect function of the superclass
+        self.super_object.connect()
+        # start handshake thread
+        handshake_thread.start()
+
+    def handle_message(self, message, message_data, *args, **kwargs):
+        pass
+
+    def handle_data(self, data):
+        for message in data.split("\r\n"):
+            # skip the message if it is empty
+            message = message.strip()
+            if not message: 
+                continue
+            # search for every item in self.search_items
+            self.search_in(message)
+            # extract information
+            message_data = self.extract_message(message)
+            # log message
+            if message_data["known"]:
+                self.logger.info(message)
+                print(json.dumps(message_data,indent=4))
+            else:
+                self.logger.info(message)
+                print("\033[31;1m"+json.dumps(message_data,indent=4)+"\033[0m")
+            # answer PINGs
+            if message.find("PING ") == 0:
+                self.send("PONG :"+"".join(message.split(":")[1:]))
+
+            self.handle_message(message,message_data)
+            # if self.registered_event.is_set():
+            #     pass
+
+    def extract_message(self, message):
+        message_data = {}
+        # message_data["type"]="\033[31;1m"+"unknown"+"\033[0m"
+        try:
+            full_msg_match = self.pattern["full_message"].search(message)
+            message_data["full_msg_match"] = str(full_msg_match)
+            if not full_msg_match:
+                message_data["known"]=False
+                return message_data
+            message_data.update(full_msg_match.groupdict())
+            message_data["known"] = True
+            # message_data["split"] = message.split()
+            message_data["params"] = message_data["params"].lstrip()
+            # command specific information
+            # if message_data["command"] in ["PRIVMSG", "NOTICE"]:
+            #     chat_match=self.pattern["chat"].search(message_data["params"])
+            #     message_data.update(chat_match.groupdict())
+            #     message_data["private_msg"] = message_data["target"] == self.client_info["current_nick"]
+            # elif message_data["command"] in ["PART","QUIT"]:
+            #     message_data["target"],message_data["msg"] = message_data["split"]
+            #     chat_match=self.pattern["chat"].search(message_data["params"])
+            #     self.logger.info(chat_match.groups())
+            #     message_data.update(chat_match.groupdict())
+        except Exception as e:
+            self.logger.error(message)
+            self.logger.error(e, exc_info=True)
+        finally:
+            return message_data
+
+    # search functions:
+    def search_in(self, text):
+        for item in self.search_items:
+            if text.find(item) != -1:
+                self.search_items[item] = text
+
+
+    def add_search_item(self, *args):
+        for item in args:
+            self.search_items[item] = self.search_items.get(item, None)
+
+    def remove_search_item(self, *args):
+        for item in args:
+            del self.search_items[item]
+
+    def wait_for_item(self, *args,timeout=0):
+        args = list(args)
+        new_items = set(args) - set(self.search_items)
+        self.add_search_item(*new_items)
+        end_time = time.time() + timeout
+        while not self.exit_event.is_set():
+            for item in args:
+                if self.search_items[item] != None:
+                    self.logger.debug("item found: " + self.search_items[item])
+                    return item
+            time.sleep(0.1)
+            if timeout:
+                if time.time() >= end_time:
+                    return False
+
+    # irc functions
+    def change_nick(self, nickname):
+        self.send("NICK "+nickname)
+        # self.client_info["last_nick"] = self.client_info["current_nick"]
+        # self.client_info["current_nick"] = nickname
+
+    def join_chans(self, *chans, message=None):
+        for c in chans:
+            self.send("JOIN " + c)
+            if message:
+                self.chat(c, message)
+
+    def chat(self, target, message):
+        self.send("PRIVMSG {target} {message}".format(target=target,message=message))
+
+
+class PluginHandler(object):
+    """docstring for IRCPluginHandler"""
+    def __init__(self, client_object, config_str=None, config_file=None):
+        self.super_object = super(PluginHandler,self)
+        self.super_object.__init__(client_object)
+        self.client_object = client_object
+        self.plugin_objects = {}
+        self.config_dict = {}
+
+    def handle_message(self,message,message_data):
+        print(message)
+
+class PluginBase(object):
+    def __init__(self):
+        self.super_object = super(PluginBase, self)
+        self.super_object.__init()
+
+    def handle():
+        pass
+
+class IRCPluginHandler(PluginHandler):
+    def __init__(self,irc_client):
+        self.super_object = super(IRCPluginHandler,self)
+        self.super_object.__init__(irc_client)
+        self.irc_client = irc_client
+
+

+ 860 - 0
network/IRC/irc_numeric_name.py

@@ -0,0 +1,860 @@
+numeric_to_name: {
+    1: 'RPL_WELCOME',
+    2: 'RPL_YOURHOST',
+    3: 'RPL_CREATED',
+    4: 'RPL_MYINFO',
+    5: 'RPL_ISUPPORT',
+    6: 'RPL_MAP',
+    7: 'RPL_MAPEND',
+    8: 'RPL_SNOMASK',
+    9: 'RPL_STATMEMTOT',
+    10: 'RPL_STATMEM',
+    14: 'RPL_YOURCOOKIE',
+    15: 'RPL_MAP',
+    16: 'RPL_MAPMORE',
+    17: 'RPL_MAPEND',
+    42: 'RPL_YOURID',
+    43: 'RPL_SAVENICK',
+    50: 'RPL_ATTEMPTINGJUNC',
+    51: 'RPL_ATTEMPTINGREROUTE',
+    200: 'RPL_TRACELINK',
+    201: 'RPL_TRACECONNECTING',
+    202: 'RPL_TRACEHANDSHAKE',
+    203: 'RPL_TRACEUNKNOWN',
+    204: 'RPL_TRACEOPERATOR',
+    205: 'RPL_TRACEUSER',
+    206: 'RPL_TRACESERVER',
+    207: 'RPL_TRACESERVICE',
+    208: 'RPL_TRACENEWTYPE',
+    209: 'RPL_TRACECLASS',
+    210: 'RPL_STATS',
+    211: 'RPL_STATSLINKINFO',
+    212: 'RPL_STATSCOMMANDS',
+    213: 'RPL_STATSCLINE',
+    214: 'RPL_STATSNLINE',
+    215: 'RPL_STATSILINE',
+    216: 'RPL_STATSKLINE',
+    217: 'RPL_STATSPLINE',
+    218: 'RPL_STATSYLINE',
+    219: 'RPL_ENDOFSTATS',
+    220: 'RPL_STATSBLINE',
+    221: 'RPL_UMODEIS',
+    222: 'RPL_STATSBLINE',
+    223: 'RPL_STATSGLINE',
+    224: 'RPL_STATSTLINE',
+    225: 'RPL_STATSELINE',
+    226: 'RPL_STATSNLINE',
+    227: 'RPL_STATSVLINE',
+    228: 'RPL_STATSQLINE',
+    231: 'RPL_SERVICEINFO',
+    232: 'RPL_RULES',
+    233: 'RPL_SERVICE',
+    234: 'RPL_SERVLIST',
+    235: 'RPL_SERVLISTEND',
+    236: 'RPL_STATSVERBOSE',
+    237: 'RPL_STATSENGINE',
+    238: 'RPL_STATSFLINE',
+    239: 'RPL_STATSIAUTH',
+    240: 'RPL_STATSXLINE',
+    241: 'RPL_STATSLLINE',
+    242: 'RPL_STATSUPTIME',
+    243: 'RPL_STATSOLINE',
+    244: 'RPL_STATSHLINE',
+    245: 'RPL_STATSSLINE',
+    246: 'RPL_STATSULINE',
+    247: 'RPL_STATSGLINE',
+    248: 'RPL_STATSDEFINE',
+    249: 'RPL_STATSDEBUG',
+    250: 'RPL_STATSCONN',
+    251: 'RPL_LUSERCLIENT',
+    252: 'RPL_LUSEROP',
+    253: 'RPL_LUSERUNKNOWN',
+    254: 'RPL_LUSERCHANNELS',
+    255: 'RPL_LUSERME',
+    256: 'RPL_ADMINME',
+    257: 'RPL_ADMINLOC1',
+    258: 'RPL_ADMINLOC2',
+    259: 'RPL_ADMINEMAIL',
+    261: 'RPL_TRACELOG',
+    262: 'RPL_TRACEEND',
+    263: 'RPL_TRYAGAIN',
+    265: 'RPL_LOCALUSERS',
+    266: 'RPL_GLOBALUSERS',
+    267: 'RPL_START_NETSTAT',
+    268: 'RPL_NETSTAT',
+    269: 'RPL_END_NETSTAT',
+    270: 'RPL_PRIVS',
+    271: 'RPL_SILELIST',
+    272: 'RPL_ENDOFSILELIST',
+    273: 'RPL_NOTIFY',
+    274: 'RPL_STATSDELTA',
+    275: 'RPL_STATSDLINE',
+    276: 'RPL_VCHANEXIST',
+    277: 'RPL_VCHANLIST',
+    278: 'RPL_VCHANHELP',
+    280: 'RPL_GLIST',
+    281: 'RPL_ACCEPTLIST',
+    282: 'RPL_JUPELIST',
+    283: 'RPL_ENDOFJUPELIST',
+    284: 'RPL_FEATURE',
+    285: 'RPL_NEWHOSTIS',
+    286: 'RPL_CHKHEAD',
+    287: 'RPL_CHANUSER',
+    288: 'RPL_PATCHHEAD',
+    289: 'RPL_PATCHCON',
+    290: 'RPL_DATASTR',
+    291: 'RPL_ENDOFCHECK',
+    292: 'RPL_HELPTLR',
+    293: 'RPL_HELPHLP',
+    294: 'RPL_HELPFWD',
+    295: 'RPL_HELPIGN',
+    296: 'RPL_CHANINFO_KICKS',
+    299: 'RPL_END_CHANINFO',
+    300: 'RPL_NONE',
+    301: 'RPL_AWAY',
+    302: 'RPL_USERHOST',
+    303: 'RPL_ISON',
+    304: 'RPL_TEXT',
+    305: 'RPL_UNAWAY',
+    306: 'RPL_NOWAWAY',
+    307: 'RPL_SUSERHOST',
+    308: 'RPL_RULESSTART',
+    309: 'RPL_WHOISHELPER',
+    310: 'RPL_WHOISSERVICE',
+    311: 'RPL_WHOISUSER',
+    312: 'RPL_WHOISSERVER',
+    313: 'RPL_WHOISOPERATOR',
+    314: 'RPL_WHOWASUSER',
+    315: 'RPL_ENDOFWHO',
+    316: 'RPL_WHOISCHANOP',
+    317: 'RPL_WHOISIDLE',
+    318: 'RPL_ENDOFWHOIS',
+    319: 'RPL_WHOISCHANNELS',
+    320: 'RPL_WHOISSPECIAL',
+    321: 'RPL_LISTSTART',
+    322: 'RPL_LIST',
+    323: 'RPL_LISTEND',
+    324: 'RPL_CHANNELMODEIS',
+    325: 'RPL_CHANNELPASSIS',
+    326: 'RPL_NOCHANPASS',
+    327: 'RPL_CHPASSUNKNOWN',
+    328: 'RPL_CHANNEL_URL',
+    329: 'RPL_CREATIONTIME',
+    330: 'RPL_WHOISACCOUNT',
+    331: 'RPL_NOTOPIC',
+    332: 'RPL_TOPIC',
+    333: 'RPL_TOPICWHOTIME',
+    334: 'RPL_LISTSYNTAX',
+    335: 'RPL_WHOISBOT',
+    338: 'RPL_WHOISACTUALLY',
+    339: 'RPL_BADCHANPASS',
+    340: 'RPL_USERIP',
+    341: 'RPL_INVITING',
+    342: 'RPL_SUMMONING',
+    345: 'RPL_INVITED',
+    346: 'RPL_INVITELIST',
+    347: 'RPL_ENDOFINVITELIST',
+    348: 'RPL_EXCEPTLIST',
+    349: 'RPL_ENDOFEXCEPTLIST',
+    351: 'RPL_VERSION',
+    352: 'RPL_WHOREPLY',
+    353: 'RPL_NAMREPLY',
+    354: 'RPL_WHOSPCRPL',
+    355: 'RPL_NAMREPLY_',
+    357: 'RPL_MAP',
+    358: 'RPL_MAPMORE',
+    359: 'RPL_MAPEND',
+    361: 'RPL_KILLDONE',
+    362: 'RPL_CLOSING',
+    363: 'RPL_CLOSEEND',
+    364: 'RPL_LINKS',
+    365: 'RPL_ENDOFLINKS',
+    366: 'RPL_ENDOFNAMES',
+    367: 'RPL_BANLIST',
+    368: 'RPL_ENDOFBANLIST',
+    369: 'RPL_ENDOFWHOWAS',
+    371: 'RPL_INFO',
+    372: 'RPL_MOTD',
+    373: 'RPL_INFOSTART',
+    374: 'RPL_ENDOFINFO',
+    375: 'RPL_MOTDSTART',
+    376: 'RPL_ENDOFMOTD',
+    377: 'RPL_SPAM',
+    378: 'RPL_MOTD',
+    379: 'RPL_WHOISMODES',
+    380: 'RPL_YOURHELPER',
+    381: 'RPL_YOUREOPER',
+    382: 'RPL_REHASHING',
+    383: 'RPL_YOURESERVICE',
+    384: 'RPL_MYPORTIS',
+    385: 'RPL_NOTOPERANYMORE',
+    386: 'RPL_IRCOPS',
+    387: 'RPL_ENDOFIRCOPS',
+    388: 'RPL_ALIST',
+    389: 'RPL_ENDOFALIST',
+    391: 'RPL_TIME',
+    392: 'RPL_USERSSTART',
+    393: 'RPL_USERS',
+    394: 'RPL_ENDOFUSERS',
+    395: 'RPL_NOUSERS',
+    396: 'RPL_HOSTHIDDEN',
+    400: 'ERR_UNKNOWNERROR',
+    401: 'ERR_NOSUCHNICK',
+    402: 'ERR_NOSUCHSERVER',
+    403: 'ERR_NOSUCHCHANNEL',
+    404: 'ERR_CANNOTSENDTOCHAN',
+    405: 'ERR_TOOMANYCHANNELS',
+    406: 'ERR_WASNOSUCHNICK',
+    407: 'ERR_TOOMANYTARGETS',
+    408: 'ERR_NOCOLORSONCHAN',
+    409: 'ERR_NOORIGIN',
+    411: 'ERR_NORECIPIENT',
+    412: 'ERR_NOTEXTTOSEND',
+    413: 'ERR_NOTOPLEVEL',
+    414: 'ERR_WILDTOPLEVEL',
+    415: 'ERR_BADMASK',
+    416: 'ERR_QUERYTOOLONG',
+    419: 'ERR_LENGTHTRUNCATED',
+    421: 'ERR_UNKNOWNCOMMAND',
+    422: 'ERR_NOMOTD',
+    423: 'ERR_NOADMININFO',
+    424: 'ERR_FILEERROR',
+    425: 'ERR_NOOPERMOTD',
+    429: 'ERR_TOOMANYAWAY',
+    430: 'ERR_EVENTNICKCHANGE',
+    431: 'ERR_NONICKNAMEGIVEN',
+    432: 'ERR_ERRONEUSNICKNAME',
+    433: 'ERR_NICKNAMEINUSE',
+    434: 'ERR_NORULES',
+    435: 'ERR_BANONCHAN',
+    436: 'ERR_NICKCOLLISION',
+    437: 'ERR_BANNICKCHANGE',
+    438: 'ERR_DEAD',
+    439: 'ERR_TARGETTOOFAST',
+    440: 'ERR_SERVICESDOWN',
+    441: 'ERR_USERNOTINCHANNEL',
+    442: 'ERR_NOTONCHANNEL',
+    443: 'ERR_USERONCHANNEL',
+    444: 'ERR_NOLOGIN',
+    445: 'ERR_SUMMONDISABLED',
+    446: 'ERR_USERSDISABLED',
+    447: 'ERR_NONICKCHANGE',
+    449: 'ERR_NOTIMPLEMENTED',
+    451: 'ERR_NOTREGISTERED',
+    452: 'ERR_IDCOLLISION',
+    453: 'ERR_NICKLOST',
+    455: 'ERR_HOSTILENAME',
+    456: 'ERR_ACCEPTFULL',
+    457: 'ERR_ACCEPTEXIST',
+    458: 'ERR_ACCEPTNOT',
+    459: 'ERR_NOHIDING',
+    460: 'ERR_NOTFORHALFOPS',
+    461: 'ERR_NEEDMOREPARAMS',
+    462: 'ERR_ALREADYREGISTERED',
+    463: 'ERR_NOPERMFORHOST',
+    464: 'ERR_PASSWDMISMATCH',
+    465: 'ERR_YOUREBANNEDCREEP',
+    466: 'ERR_YOUWILLBEBANNED',
+    467: 'ERR_KEYSET',
+    468: 'ERR_ONLYSERVERSCANCHANGE',
+    469: 'ERR_LINKSET',
+    470: 'ERR_KICKEDFROMCHAN',
+    471: 'ERR_CHANNELISFULL',
+    472: 'ERR_UNKNOWNMODE',
+    473: 'ERR_INVITEONLYCHAN',
+    474: 'ERR_BANNEDFROMCHAN',
+    475: 'ERR_BADCHANNELKEY',
+    476: 'ERR_BADCHANMASK',
+    477: 'ERR_NEEDREGGEDNICK',
+    478: 'ERR_BANLISTFULL',
+    479: 'ERR_LINKFAIL',
+    480: 'ERR_CANNOTKNOCK',
+    481: 'ERR_NOPRIVILEGES',
+    482: 'ERR_CHANOPRIVSNEEDED',
+    483: 'ERR_CANTKILLSERVER',
+    484: 'ERR_ATTACKDENY',
+    485: 'ERR_ISREALSERVICE',
+    486: 'ERR_ACCOUNTONLY',
+    487: 'ERR_MSGSERVICES',
+    488: 'ERR_TSLESSCHAN',
+    489: 'ERR_SECUREONLYCHAN',
+    491: 'ERR_NOOPERHOST',
+    492: 'ERR_NOSERVICEHOST',
+    493: 'ERR_NOFEATURE',
+    494: 'ERR_BADFEATURE',
+    495: 'ERR_BADLOGTYPE',
+    496: 'ERR_BADLOGSYS',
+    497: 'ERR_BADLOGVALUE',
+    498: 'ERR_ISOPERLCHAN',
+    499: 'ERR_CHANOWNPRIVNEEDED',
+    501: 'ERR_UMODEUNKNOWNFLAG',
+    502: 'ERR_USERSDONTMATCH',
+    503: 'ERR_VWORLDWARN',
+    504: 'ERR_USERNOTONSERV',
+    511: 'ERR_SILELISTFULL',
+    512: 'ERR_TOOMANYWATCH',
+    513: 'ERR_BADPING',
+    514: 'ERR_TOOMANYDCC',
+    515: 'ERR_BADEXPIRE',
+    516: 'ERR_DONTCHEAT',
+    517: 'ERR_DISABLED',
+    518: 'ERR_LONGMASK',
+    519: 'ERR_TOOMANYUSERS',
+    520: 'ERR_WHOTRUNC',
+    521: 'ERR_LISTSYNTAX',
+    522: 'ERR_WHOSYNTAX',
+    523: 'ERR_WHOLIMEXCEED',
+    524: 'ERR_OPERSPVERIFY',
+    525: 'ERR_REMOTEPFX',
+    526: 'ERR_PFXUNROUTABLE',
+    550: 'ERR_BADHOSTMASK',
+    551: 'ERR_HOSTUNAVAIL',
+    552: 'ERR_USINGSLINE',
+    553: 'ERR_STATSSLINE',
+    600: 'RPL_LOGON',
+    601: 'RPL_LOGOFF',
+    602: 'RPL_WATCHOFF',
+    603: 'RPL_WATCHSTAT',
+    604: 'RPL_NOWON',
+    605: 'RPL_NOWOFF',
+    606: 'RPL_WATCHLIST',
+    607: 'RPL_ENDOFWATCHLIST',
+    608: 'RPL_WATCHCLEAR',
+    610: 'RPL_ISOPER',
+    611: 'RPL_ISLOCOP',
+    612: 'RPL_ISNOTOPER',
+    613: 'RPL_ENDOFISOPER',
+    615: 'RPL_WHOISMODES',
+    616: 'RPL_WHOISHOST',
+    617: 'RPL_WHOISBOT',
+    618: 'RPL_DCCLIST',
+    619: 'RPL_WHOWASHOST',
+    620: 'RPL_RULESSTART',
+    621: 'RPL_RULES',
+    622: 'RPL_ENDOFRULES',
+    623: 'RPL_MAPMORE',
+    624: 'RPL_OMOTDSTART',
+    625: 'RPL_OMOTD',
+    626: 'RPL_ENDOFO',
+    630: 'RPL_SETTINGS',
+    631: 'RPL_ENDOFSETTINGS',
+    640: 'RPL_DUMPING',
+    641: 'RPL_DUMPRPL',
+    642: 'RPL_EODUMP',
+    660: 'RPL_TRACEROUTE_HOP',
+    661: 'RPL_TRACEROUTE_START',
+    662: 'RPL_MODECHANGEWARN',
+    663: 'RPL_CHANREDIR',
+    664: 'RPL_SERVMODEIS',
+    665: 'RPL_OTHERUMODEIS',
+    666: 'RPL_ENDOF_GENERIC',
+    670: 'RPL_WHOWASDETAILS',
+    671: 'RPL_WHOISSECURE',
+    672: 'RPL_UNKNOWNMODES',
+    673: 'RPL_CANNOTSETMODES',
+    678: 'RPL_LUSERSTAFF',
+    679: 'RPL_TIMEONSERVERIS',
+    682: 'RPL_NETWORKS',
+    687: 'RPL_YOURLANGUAGEIS',
+    688: 'RPL_LANGUAGE',
+    689: 'RPL_WHOISSTAFF',
+    690: 'RPL_WHOISLANGUAGE',
+    702: 'RPL_MODLIST',
+    703: 'RPL_ENDOFMODLIST',
+    704: 'RPL_HELPSTART',
+    705: 'RPL_HELPTXT',
+    706: 'RPL_ENDOFHELP',
+    708: 'RPL_ETRACEFULL',
+    709: 'RPL_ETRACE',
+    710: 'RPL_KNOCK',
+    711: 'RPL_KNOCKDLVR',
+    712: 'ERR_TOOMANYKNOCK',
+    713: 'ERR_CHANOPEN',
+    714: 'ERR_KNOCKONCHAN',
+    715: 'ERR_KNOCKDISABLED',
+    716: 'RPL_TARGUMODEG',
+    717: 'RPL_TARGNOTIFY',
+    718: 'RPL_UMODEGMSG',
+    720: 'RPL_OMOTDSTART',
+    721: 'RPL_OMOTD',
+    722: 'RPL_ENDOFOMOTD',
+    723: 'ERR_NOPRIVS',
+    724: 'RPL_TESTMARK',
+    725: 'RPL_TESTLINE',
+    726: 'RPL_NOTESTLINE',
+    771: 'RPL_XINFO',
+    773: 'RPL_XINFOSTART',
+    774: 'RPL_XINFOEND',
+    972: 'ERR_CANNOTDOCOMMAND',
+    973: 'ERR_CANNOTCHANGEUMODE',
+    974: 'ERR_CANNOTCHANGECHANMODE',
+    975: 'ERR_CANNOTCHANGESERVERMODE',
+    976: 'ERR_CANNOTSENDTONICK',
+    977: 'ERR_UNKNOWNSERVERMODE',
+    979: 'ERR_SERVERMODELOCK',
+    980: 'ERR_BADCHARENCODING',
+    981: 'ERR_TOOMANYLANGUAGES',
+    982: 'ERR_NOLANGUAGE',
+    983: 'ERR_TEXTTOOSHORT',
+    999: 'ERR_NUMERIC_ERR'
+}
+name_to_numeric: {
+    'RPL_SERVMODEIS': 664,
+    'ERR_NORECIPIENT': 411,
+    'RPL_STATSYLINE': 218,
+    'RPL_OMOTDSTART': 720,
+    'ERR_NOTEXTTOSEND': 412,
+    'RPL_CHANINFO_KICK': 295,
+    'RPL_LUSERCLIENT': 251,
+    'RPL_REHASHING': 382,
+    'RPL_CLOSING': 362,
+    'RPL_CHANINFO_BANNED': 291,
+    'RPL_WHOISHOST': 616,
+    'RPL_DUMPING': 640,
+    'RPL_TIME': 391,
+    'ERR_WHOLIMEXCEED': 523,
+    'RPL_CHANINFO_HANDLE': 285,
+    'RPL_NOTIFY': 273,
+    'RPL_LOGON': 600,
+    'RPL_BADCHANPASS': 339,
+    'RPL_WHOISVIRT': 320,
+    'ERR_WILDTOPLEVEL': 414,
+    'RPL_CREATED': 3,
+    'RPL_SETTINGS': 630,
+    'RPL_START_NETSTAT': 267,
+    'RPL_ENDOFWHO': 315,
+    'RPL_STATSDLINE': 275,
+    'ERR_BANLISTFULL': 478,
+    'RPL_CHANINFO_OPERS': 290,
+    'ERR_INVALIDUSERNAME': 468,
+    'RPL_ETRACE': 709,
+    'ERR_BADCHANNELKEY': 475,
+    'ERR_NOOPERHOST': 491,
+    'ERR_SERVICESDOWN': 440,
+    'ERR_SERVICECONFUSED': 435,
+    'RPL_ENDNOTIFY': 274,
+    'RPL_RULESSTART': 620,
+    'RPL_STATSILINE': 215,
+    'RPL_NAMREPLY': 353,
+    'RPL_GLIST': 280,
+    'RPL_LINKS': 364,
+    'RPL_CHANINFO_INVITES': 294,
+    'ERR_NOORIGIN': 409,
+    'RPL_WHOISSVCMSG': 310,
+    'RPL_LISTSYNTAX': 334,
+    'ERR_STATSSLINE': 553,
+    'RPL_STATMEM': 10,
+    'ERR_CHANOPEN': 713,
+    'RPL_NICKTRACE': 309,
+    'RPL_ENDOFUSERS': 394,
+    'ERR_KNOCKONCHAN': 714,
+    'RPL_SUSERHOST': 307,
+    'RPL_LUSERCHANNELS': 254,
+    'RPL_ENDOFNAMES': 366,
+    'ERR_BADCHANNAME': 479,
+    'ERR_NOOPERMOTD': 425,
+    'RPL_TOPICWHOTIME': 333,
+    'RPL_QLIST': 386,
+    'RPL_LISTEND': 323,
+    'RPL_UNAWAY': 305,
+    'RPL_USERHOST': 302,
+    'ERR_CANNOTDOCOMMAND': 972,
+    'ERR_WASNOSUCHNICK': 406,
+    'RPL_NETSTAT': 268,
+    'RPL_SAVENICK': 43,
+    'RPL_INVITED': 345,
+    'ERR_NUMERIC_ERR': 999,
+    'RPL_HELPSTART': 704,
+    'ERR_NICKCOLLISION': 436,
+    'RPL_COMMANDSYNTAX': 334,
+    'RPL_WHOISCHANOP': 316,
+    'ERR_BADLOGTYPE': 495,
+    'RPL_UMODEIS': 221,
+    'RPL_WHOWASDETAILS': 670,
+    'RPL_WHOSPCRPL': 354,
+    'RPL_DATASTR': 290,
+    'RPL_WHOISADMIN': 308,
+    'ERR_UNKNOWNCOMMAND': 421,
+    'ERR_INVITEONLYCHAN': 473,
+    'ERR_NOPRIVS': 723,
+    'RPL_ENDOFINFO': 374,
+    'RPL_UNIQOPIS': 325,
+    'ERR_BADCHARENCODING': 980,
+    'RPL_ENDOFISOPER': 613,
+    'ERR_DISABLED': 517,
+    'RPL_WHOISSERVER': 312,
+    'ERR_NOMOTD': 422,
+    'RPL_STATSBLINE': 247,
+    'ERR_NOADMININFO': 423,
+    'RPL_ISNOTOPER': 612,
+    'RPL_WHOWASUSER': 314,
+    'ERR_RESTRICTED': 484,
+    'RPL_NOWAWAY': 306,
+    'RPL_JUPELIST': 282,
+    'RPL_HELPTXT': 705,
+    'RPL_STATSDEBUG': 249,
+    'ERR_PFXUNROUTABLE': 526,
+    'RPL_ENDOFWHOIS': 318,
+    'ERR_ACCOUNTONLY': 486,
+    'RPL_TRACECLASS': 209,
+    'ERR_NOTIMPLEMENTED': 449,
+    'RPL_STATSPLINE': 220,
+    'ERR_DESYNC': 484,
+    'RPL_BANEXPIRED': 378,
+    'ERR_IDCOLLISION': 452,
+    'RPL_CHKHEAD': 286,
+    'RPL_TRACENEWTYPE': 208,
+    'RPL_VCHANHELP': 278,
+    'ERR_NOSUCHSERVICE': 408,
+    'ERR_PASSWDMISMATCH': 464,
+    'RPL_WHOISSPECIAL': 320,
+    'RPL_INVITING': 341,
+    'RPL_HELPIGN': 295,
+    'ERR_NOTONCHANNEL': 442,
+    'RPL_YOURHELPER': 380,
+    'RPL_TRACEROUTE_HOP': 660,
+    'RPL_HOSTHIDDEN': 396,
+    'RPL_OTHERUMODEIS': 665,
+    'RPL_ISON': 303,
+    'RPL_ENDOFLINKS': 365,
+    'RPL_WHOISACTUALLY': 338,
+    'RPL_WHOISSERVICE': 310,
+    'ERR_ALREADYREGISTERED': 462,
+    'ERR_BANNEDFROMCHAN': 474,
+    'RPL_STATMEMTOT': 9,
+    'RPL_TIMEONSERVERIS': 679,
+    'RPL_ENDOFMODLIST': 703,
+    'ERR_HOSTILENAME': 455,
+    'RPL_CHANPASSOK': 338,
+    'RPL_SERVICE': 233,
+    'RPL_ENDOFQLIST': 387,
+    'ERR_REMOTEPFX': 525,
+    'ERR_USERSDISABLED': 446,
+    'RPL_STATSCLINE': 213,
+    'ERR_ISCHANSERVICE': 484,
+    'ERR_NOTREGISTERED': 451,
+    'RPL_OMOTD': 721,
+    'RPL_ADMINEMAIL': 259,
+    'RPL_BANLIST': 367,
+    'RPL_TRACERECONNECT': 210,
+    'RPL_TRACELINK': 200,
+    'RPL_LANGUAGE': 688,
+    'ERR_BADLOGVALUE': 497,
+    'ERR_NOFEATURE': 493,
+    'RPL_STATSCOMMANDS': 212,
+    'RPL_NONE': 300,
+    'RPL_MOTDSTART': 375,
+    'RPL_ENDOFIRCOPS': 387,
+    'ERR_LINKFAIL': 479,
+    'RPL_TRACECONNECTING': 201,
+    'RPL_YOURLANGUAGEIS': 687,
+    'RPL_CHANUSER': 287,
+    'RPL_BOUNCE': 10,
+    'RPL_SERVLIST': 234,
+    'RPL_CHANINFO_VOICES': 288,
+    'RPL_HELPHLP': 293,
+    'ERR_CANTKICKADMIN': 485,
+    'RPL_LUSERME': 255,
+    'RPL_WHOISLANGUAGE': 690,
+    'ERR_UMODEUNKNOWNFLAG': 501,
+    'RPL_CHANINFO_AWAY': 289,
+    'RPL_MYPORTIS': 384,
+    'RPL_STATSCOUNT': 226,
+    'RPL_WHOISACCOUNT': 330,
+    'RPL_LISTUSAGE': 334,
+    'RPL_ENDOFGLIST': 281,
+    'ERR_VWORLDWARN': 503,
+    'RPL_TOPIC': 332,
+    'RPL_FEATURE': 284,
+    'RPL_DCCINFO': 620,
+    'RPL_XINFO': 771,
+    'RPL_KILLDONE': 361,
+    'RPL_CHPASSUNKNOWN': 327,
+    'RPL_WHOISCHANNELS': 319,
+    'ERR_BADMASK': 415,
+    'RPL_DUMPRPL': 641,
+    'ERR_NEEDREGGEDNICK': 477,
+    'RPL_MODLIST': 702,
+    'ERR_CHANTOORECENT': 487,
+    'ERR_ACCEPTFULL': 456,
+    'RPL_ENDOFWHOWAS': 369,
+    'RPL_STATSTLINE': 246,
+    'RPL_WHOISOPERATOR': 313,
+    'ERR_NICKTOOFAST': 438,
+    'ERR_ISREALSERVICE': 485,
+    'ERR_BADCHANMASK': 476,
+    'ERR_SECUREONLYCHAN': 489,
+    'ERR_CANNOTSENDTOCHAN': 404,
+    'RPL_WATCHSTAT': 603,
+    'RPL_SILELIST': 271,
+    'RPL_TRYAGAIN': 263,
+    'RPL_ENDOF_GENERIC': 666,
+    'RPL_ADMINLOC2': 258,
+    'RPL_STATSVLINE': 240,
+    'ERR_ACCEPTEXIST': 457,
+    'RPL_MAPEND': 359,
+    'RPL_ENDOFOMOTD': 722,
+    'RPL_RULES': 621,
+    'RPL_WHOISSECURE': 671,
+    'ERR_WHOSYNTAX': 522,
+    'RPL_ISUPPORT': 5,
+    'RPL_UNKNOWNMODES': 672,
+    'ERR_NONICKNAMEGIVEN': 431,
+    'RPL_ENDOFBANLIST': 368,
+    'RPL_ATTEMPTINGJUNC': 50,
+    'RPL_WHOIS_HIDDEN': 320,
+    'RPL_LIST': 322,
+    'ERR_MSGSERVICES': 487,
+    'RPL_HELPTLR': 292,
+    'RPL_TRACESERVER': 206,
+    'ERR_SERVERMODELOCK': 979,
+    'RPL_MOTD': 378,
+    'RPL_STATSULINE': 249,
+    'ERR_LENGTHTRUNCATED': 419,
+    'RPL_ENDOFINVITELIST': 347,
+    'RPL_ENDOFJUPELIST': 283,
+    'RPL_ETRACEFULL': 708,
+    'RPL_NOTOPIC': 331,
+    'RPL_USERS': 393,
+    'ERR_SUMMONDISABLED': 445,
+    'ERR_QUERYTOOLONG': 416,
+    'ERR_LINKCHANNEL': 470,
+    'RPL_VCHANEXIST': 276,
+    'ERR_MASKTOOWIDE': 520,
+    'ERR_EVENTNICKCHANGE': 430,
+    'ERR_NOINVITE': 518,
+    'ERR_KILLDENY': 485,
+    'RPL_NOUSERS': 395,
+    'RPL_ENDOFO': 626,
+    'RPL_STATSENGINE': 237,
+    'RPL_WHOWAS_TIME': 330,
+    'ERR_BADEXPIRE': 515,
+    'RPL_GLOBALUSERS': 266,
+    'RPL_ADMINLOC1': 257,
+    'ERR_DONTCHEAT': 516,
+    'RPL_PATCHHEAD': 288,
+    'ERR_CHANOPRIVSNEEDED': 482,
+    'RPL_ENDOFSETTINGS': 631,
+    'RPL_STATSIAUTH': 239,
+    'ERR_TOOMANYAWAY': 429,
+    'RPL_USERSSTART': 392,
+    'RPL_TRACEROUTE_START': 661,
+    'RPL_END_CHANINFO': 299,
+    'RPL_MYINFO': 4,
+    'ERR_TOOMANYUSERS': 519,
+    'ERR_NOTOPLEVEL': 413,
+    'RPL_ENDOFDCCLIST': 619,
+    'RPL_WHOISIDLE': 317,
+    'RPL_WHOISSADMIN': 309,
+    'ERR_NOSUCHSERVER': 402,
+    'RPL_VERSION': 351,
+    'RPL_ENDOFWATCHLIST': 607,
+    'ERR_INVALID_ERROR': 514,
+    'RPL_ENDOFALIST': 389,
+    'RPL_ISOPER': 610,
+    'RPL_MODECHANGEWARN': 662,
+    'ERR_BADLOGSYS': 496,
+    'RPL_CHANINFO_USERS': 286,
+    'RPL_CLOSEEND': 363,
+    'RPL_TRACELOG': 261,
+    'RPL_KICKEXPIRED': 377,
+    'ERR_ACCEPTNOT': 458,
+    'RPL_WHOISHELPER': 309,
+    'RPL_WELCOME': 1,
+    'RPL_CHANINFO_BANS': 292,
+    'ERR_BADPING': 513,
+    'ERR_CHANOWNPRIVNEEDED': 499,
+    'ERR_YOUWILLBEBANNED': 466,
+    'ERR_BADFEATURE': 494,
+    'ERR_OPERONLY': 520,
+    'RPL_CHANREDIR': 663,
+    'RPL_WHOISREGNICK': 307,
+    'RPL_SUMMONING': 342,
+    'ERR_NOPERMFORHOST': 463,
+    'RPL_WHOWASHOST': 619,
+    'RPL_INFOSTART': 373,
+    'RPL_ENDOFSILELIST': 272,
+    'RPL_STATSGLINE': 247,
+    'ERR_ATTACKDENY': 484,
+    'RPL_ENDOFMOTD': 376,
+    'RPL_WHOISMODES': 615,
+    'ERR_NOSUCHCHANNEL': 403,
+    'ERR_NOCOLORSONCHAN': 408,
+    'RPL_LUSEROP': 252,
+    'RPL_NOWON': 604,
+    'RPL_NAMREPLY_': 355,
+    'RPL_USERIP': 340,
+    'RPL_EXCEPTLIST': 348,
+    'ERR_BANONCHAN': 435,
+    'RPL_NOTOPERANYMORE': 385,
+    'RPL_LISTSTART': 321,
+    'RPL_STATSOLINE': 243,
+    'ERR_NOULINE': 480,
+    'ERR_NEEDMOREPARAMS': 461,
+    'RPL_SERVICEINFO': 231,
+    'RPL_ENDOFHELP': 706,
+    'RPL_UMODEGMSG': 718,
+    'RPL_XINFOSTART': 773,
+    'RPL_NETWORKS': 682,
+    'RPL_STATSPING': 246,
+    'RPL_EODUMP': 642,
+    'RPL_WHOREPLY': 352,
+    'RPL_NOTESTLINE': 726,
+    'RPL_STATSDELTA': 274,
+    'ERR_NONONREG': 486,
+    'RPL_STATSUPTIME': 242,
+    'RPL_ALIST': 388,
+    'RPL_LUSERUNKNOWN': 253,
+    'RPL_HELPHDR': 290,
+    'RPL_WHOISHELPOP': 310,
+    'ERR_QUARANTINED': 524,
+    'RPL_LOGOFF': 601,
+    'ERR_NOPRIVILEGES': 481,
+    'RPL_ENDOFRULES': 622,
+    'ERR_FILEERROR': 424,
+    'ERR_DEAD': 438,
+    'RPL_CHANINFO_KICKS': 296,
+    'ERR_CANNOTCHANGECHANMODE': 974,
+    'RPL_CHANNELPASSIS': 325,
+    'ERR_TOOMANYMATCHES': 416,
+    'ERR_TOOMANYDCC': 514,
+    'RPL_STATSLLINE': 241,
+    'ERR_CANTKILLSERVER': 483,
+    'RPL_BANLINKED': 380,
+    'ERR_USERONCHANNEL': 443,
+    'RPL_STATSDEFINE': 248,
+    'RPL_TARGNOTIFY': 717,
+    'RPL_NOCHANPASS': 326,
+    'RPL_ENDOFSERVICES': 232,
+    'RPL_ENDOFCHECK': 291,
+    'ERR_TOOMANYTARGETS': 407,
+    'RPL_STATSKLINE': 216,
+    'RPL_STATSFLINE': 238,
+    'ERR_UNKNOWNSERVERMODE': 977,
+    'RPL_YOURCOOKIE': 14,
+    'RPL_HELPOP': 291,
+    'ERR_LINKSET': 469,
+    'RPL_TRACEUSER': 205,
+    'RPL_TRACEEND': 262,
+    'RPL_ENDOFSTATS': 219,
+    'RPL_PRIVS': 270,
+    'RPL_SQLINE_NICK': 222,
+    'ERR_KNOCKDISABLED': 715,
+    'ERR_CANNOTCHANGESERVERMODE': 975,
+    'RPL_WATCHOFF': 602,
+    'RPL_TRACEHANDSHAKE': 202,
+    'RPL_STATSQLINE': 228,
+    'ERR_BANNICKCHANGE': 437,
+    'RPL_WHOISBOT': 617,
+    'ERR_TOOMANYWATCH': 512,
+    'ERR_NOLOGIN': 444,
+    'ERR_TARGETTOOFAST': 439,
+    'RPL_ACCEPTLIST': 281,
+    'RPL_CHANNEL_URL': 328,
+    'RPL_XINFOEND': 774,
+    'RPL_LUSERSTAFF': 678,
+    'RPL_PATCHCON': 289,
+    'RPL_YOUREOPER': 381,
+    'RPL_GLIST_HASH': 285,
+    'RPL_TRACESERVICE': 207,
+    'RPL_KNOCKDLVR': 711,
+    'ERR_OPERSPVERIFY': 524,
+    'ERR_LISTSYNTAX': 521,
+    'ERR_NORULES': 434,
+    'ERR_NOLANGUAGE': 982,
+    'RPL_TEXT': 304,
+    'ERR_TOOMANYKNOCK': 712,
+    'ERR_SERVICENAMEINUSE': 434,
+    'ERR_WHOTRUNC': 520,
+    'ERR_UNAVAILRESOURCE': 437,
+    'ERR_ERRONEUSNICKNAME': 432,
+    'RPL_CHANINFO_CHOPS': 287,
+    'ERR_NOCHANMODES': 477,
+    'ERR_LONGMASK': 518,
+    'ERR_TOOMANYLANGUAGES': 981,
+    'RPL_CREATIONTIME': 329,
+    'RPL_STATSSLINE': 245,
+    'RPL_AWAY': 301,
+    'RPL_TRACEUNKNOWN': 203,
+    'ERR_CANNOTCHANGEUMODE': 973,
+    'ERR_VOICENEEDED': 489,
+    'RPL_WHOISUSER': 311,
+    'ERR_GHOSTEDCLIENT': 503,
+    'ERR_NICKNAMEINUSE': 433,
+    'ERR_USERNOTONSERV': 504,
+    'RPL_ATTEMPTINGREROUTE': 51,
+    'ERR_YOUREBANNEDCREEP': 465,
+    'RPL_STATSELINE': 225,
+    'ERR_NOSUCHNICK': 401,
+    'RPL_STATSVERBOSE': 236,
+    'RPL_WATCHLIST': 606,
+    'RPL_NEWHOSTIS': 285,
+    'RPL_HELPFWD': 294,
+    'ERR_USERSDONTMATCH': 502,
+    'RPL_MAP': 357,
+    'RPL_STATSHLINE': 244,
+    'RPL_MAPMORE': 623,
+    'RPL_CANNOTSETMODES': 673,
+    'RPL_WATCHCLEAR': 608,
+    'ERR_NOSERVICEHOST': 492,
+    'ERR_TSLESSCHAN': 488,
+    'ERR_UNKNOWNERROR': 400,
+    'ERR_TEXTTOOSHORT': 983,
+    'ERR_ONLYSERVERSCANCHANGE': 468,
+    'RPL_TRACEPING': 262,
+    'ERR_TOOMANYCHANNELS': 405,
+    'RPL_KNOCK': 710,
+    'RPL_YOURID': 42,
+    'RPL_CHANINFO_INVITE': 293,
+    'RPL_SNOMASK': 8,
+    'RPL_STATSCONN': 250,
+    'ERR_ADMONLY': 519,
+    'RPL_STATS': 210,
+    'RPL_YOURHOST': 2,
+    'RPL_END_NETSTAT': 269,
+    'ERR_ISOPERLCHAN': 498,
+    'RPL_WHOISSTAFF': 689,
+    'ERR_HOSTUNAVAIL': 551,
+    'RPL_DCCLIST': 618,
+    'RPL_NOTIFYACTION': 308,
+    'RPL_IRCOPS': 386,
+    'ERR_KICKEDFROMCHAN': 470,
+    'ERR_BADHOSTMASK': 550,
+    'ERR_NOTFORHALFOPS': 460,
+    'ERR_UNKNOWNMODE': 472,
+    'RPL_STATSNLINE': 226,
+    'RPL_ADMINME': 256,
+    'RPL_DCCSTATUS': 617,
+    'RPL_STATSXLINE': 247,
+    'RPL_STATSLINKINFO': 211,
+    'ERR_USINGSLINE': 552,
+    'RPL_SPAM': 377,
+    'RPL_TESTLINE': 725,
+    'ERR_KEYSET': 467,
+    'RPL_INVITELIST': 346,
+    'RPL_CHANNELMODEIS': 324,
+    'RPL_KICKLINKED': 379,
+    'RPL_YOURESERVICE': 383,
+    'RPL_STATSZLINE': 225,
+    'RPL_ISLOCOP': 611,
+    'ERR_NICKLOST': 453,
+    'ERR_NOHIDING': 459,
+    'RPL_ENDOFACCEPT': 282,
+    'RPL_NOWOFF': 605,
+    'RPL_SERVLISTEND': 235,
+    'RPL_ENDOFEXCEPTLIST': 349,
+    'ERR_CANNOTSENDTONICK': 976,
+    'RPL_INFO': 371,
+    'RPL_TARGUMODEG': 716,
+    'ERR_CANNOTKNOCK': 480,
+    'RPL_LOCALUSERS': 265,
+    'RPL_TESTMARK': 724,
+    'ERR_NONICKCHANGE': 447,
+    'ERR_UNIQOPRIVSNEEDED': 485,
+    'ERR_SILELISTFULL': 511,
+    'RPL_TRACEOPERATOR': 204,
+    'ERR_CHANNELISFULL': 471,
+    'RPL_VCHANLIST': 277,
+    'ERR_HTMDISABLED': 486,
+    'ERR_USERNOTINCHANNEL': 441
+}

+ 48 - 0
network/IRC/messages.py

@@ -0,0 +1,48 @@
+
+# https://tools.ietf.org/html/rfc2812#section-2.3.1
+p_chanstring= r"[^\x00\x07\r\n ,:]"
+p_channelid = "[a-z0-9]{5}"
+p_user      = r"[^\x00\r\n @]+"
+p_key       = r"[\z01-05\x07-08\x0C\x0E-F1\x21-7F]{1,23}"
+p_letter    = "[a-zA-Z0-9]"
+p_digit     = "[0-9]"
+p_hexdigit  = "("+p_digit+")|[A-F]"
+p_special   = r"[\x5B-60\x7B-7D]"
+p_nospcrlfcl= r"[^\x00\x007\r\n :]"
+p_wildone   = r"\x3F"
+p_wildmany  = r"\x2A"
+p_nowild    = r"[\x01-29\x2B-3E\x40-FF]"
+p_noesc     = r"[\x01-5B\5D-FF]"
+
+
+p_mask      = "(("+p_nowild+")|("+p_noesc+p_wildone+")|("+p_noesc+p_wildmany+"))*"
+p_targetmask= "\$|#"+p_mask
+p_channel   = "(#|+|(!"+p_channelid+")|&)+("+p_chanstring+")(:"+p_chanstring+")?"
+p_nickname  = "("+p_letter+")|("+p_special+")(("+p_letter+")|("+p_digit+")|("+p_special+")){8}"
+
+p_shortname = "("+p_letter+")|("+p_digit+")(("+p_letter+")|("+p_digit+")|(-)*(("+p_letter+")|("+p_digit+"))*"
+p_hostname  = "("+p_shortname+")(."+p_shortname+")" # dot
+p_servername= p_hostname
+p_ip4addr   = "("+p_digit+"){1,3}."+"("+p_digit+"){1,3}."+"("+p_digit+"){1,3}."+"("+p_digit+"){1,3}"
+p_ip6addr   = "(("+p_hexdigit+")+(:("+p_hexdigit+")+){7})"\
+            + "|(0:0:0:0:0:(0|(FFFF):"+p_ip4addr+")"
+p_hostaddr  = "("+p_ip4addr+")|("+p_ip6addr+")"
+p_host      = "("+p_hostname+")|("+p_hostaddr+")"
+
+p_middle    = p_nospcrlfcl+"(:|("+p_nospcrlfcl+"))*"
+p_trailing  = "(:| |("+p_nospcrlfcl+"))*"
+p_params    = "(( "+p_middle+"){0,13}( :"+p_trailing+")?)|(( "+p_middle+"){14}( :?"+p_trailing+")?)"
+p_command   = "(\w+)|(\d{3})"
+p_prefix    = "("+p_servername+")|("+p_nickname+"((!"+p_user+")?@"+p_host+")?"
+p_message   = "(:"+p_prefix+" )?"+p_command+"("+p_params+")?"
+
+p_target    = "("+p_nickname+")|("+p_servername+")" #p_server
+p_msgto     = "("+p_channel+")"\
+            +"|("+p_user+"(%"+p_host+")@"+p_servername+")"\
+            +"|("+p_user+"%"+p_host+")"\
+            +"|("+p_targetmask+")"\
+            +"|("+p_nickname+")"\
+            +"|("+p_nickname+"!"+p_user+"@"+p_host+")"
+p_msgtarget = p_msgto+"(,"+p_msgto+")*"
+
+

+ 565 - 0
network/__init__.py

@@ -0,0 +1,565 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of DigiLib.
+#
+#    DigiLib is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    DigiLib is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with DigiLib.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import logging
+import logging.handlers
+import os
+import queue
+import select
+import socket
+import sys
+import threading
+import time
+import traceback
+import trio
+
+lclient = logging.getLogger(__name__+".client")
+lserver = logging.getLogger(__name__+".server")
+lchat = logging.getLogger(__name__+".chat")
+
+class ConnHandlerBase(object):
+    def __init__(self, socket, addr, server):
+        self.status = "init"
+        super(ConnHandlerBase, self).__init__()
+        self.socket = socket
+        self.addr = addr
+        self.server = server
+        self.block_size = 1024
+        self.welcome_client()
+        self.status="connected"
+    def welcome_client(self):
+        pass
+    def handle(self, data_decoded):
+        return
+    def recv(self):
+        try:
+            data_received = self.socket.recv(self.block_size)
+            data_decoded = data_received.decode("utf-8")
+            if data_decoded:
+                lchat.info(data_decoded.strip())
+                self.handle(data_decoded)
+                return True
+            else:
+                lserver.debug("connection corrupted")
+                return False
+        except Exception as e:
+            lserver.error(e, exc_info=True)
+        return False
+    def send(self, msg):
+        msg_encoded = bytes(msg, "utf-8")
+        lchat.info("Server:"+msg)
+        try:
+            self.socket.send(msg_encoded)
+            return True
+        except Exception as e:
+            lserver.error(e, exc_info=True)
+            return False
+    def close(self):
+        self.status = "closed"
+        try:
+            self.socket.shutdown(0)
+        except:
+            lserver.error("error during socket shutdown, ignoring")
+        try:
+            self.socket.close()
+        except:
+            lserver.error("error closing socket, maybe already closed")
+
+
+class ConnHandlerEcho(ConnHandlerBase):
+    def __init__(self, socket, addr, server):
+        self.status = "init"
+        self.super_class = super(ConnHandlerEcho, self)
+        self.super_class.__init__(socket, addr, server)
+        self.server = server
+    def welcome_client(self):
+        self.send("welcome to the client")
+    def handle(self, data_decoded):
+        lchat.info("Client:"+data_decoded)
+        for h in list(set(self.server.connection_handler)-{self}):
+            h.send(data_decoded)
+
+class Server(object):
+    """docstring for SocketHandler"""
+    def __init__(self,
+            host,
+            port=None,
+            af_family="AF_INET",
+            max_allowed_clients=5,
+            handler=None,
+            handler_kwargs={},
+            ):
+        super(Server, self).__init__()
+        self.exit_event =  False
+        self.host=host
+        self.port=port
+        self.af_family = af_family
+        self.handler_kwargs = handler_kwargs
+        self.handler = handler
+        self.max_allowed_clients = max_allowed_clients
+        self.socket = self.make_socket()
+        self.connection_handler = []
+        self.conn_to_addr = {}
+        self.addr_to_conn = {}
+        self.conn_to_handler ={}
+        self.handler_to_conn = {}
+        self.read_sockets_expected = [self.socket]
+        self.write_sockets_expected = []
+        self.exc_sockets_expected = []
+    def make_socket(self):
+        lserver.debug("making a {} socket".format(self.af_family))
+        if self.af_family == "AF_INET":
+            return socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+        elif self.af_family == "AF_UNIX":
+            return socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
+        else:
+            raise ValueError(
+                "AF_FAMILY '{}' not supported!".format(
+                    self.af_family
+                )
+            )
+    def make_handler(self, conn, addr):
+        return self.handler(conn, addr, self, **self.handler_kwargs)
+    def register_conn(self, conn, addr):
+        lserver.info(
+            "New Connection, addr: '{}', socket: '{}'".format(addr,conn)
+        )
+        self.read_sockets_expected.append(conn)
+        if addr:
+            self.conn_to_addr[conn] = addr
+            self.addr_to_conn[addr] = conn
+    def unregister_conn(self, conn):
+        self.read_sockets_expected.remove(conn)
+        addr = self.conn_to_addr.get(conn, False)
+        if addr:
+            del self.addr_to_conn[addr]
+            del self.conn_to_addr[conn]
+    def register_handler(self, handler, conn):
+        self.connection_handler.append(handler)
+        self.conn_to_handler[conn] = handler
+        self.handler_to_conn[handler] = conn
+    def unregister_handler(self, handler, conn):
+        self.connection_handler.remove(handler)
+        del self.conn_to_handler[conn]
+        del self.handler_to_conn[handler]
+    def setup(self):
+        lserver.info("setting up socket")
+        if self.af_family == "AF_INET":
+            self.socket.bind((self.host, self.port))
+        elif self.af_family == "AF_UNIX":
+            if os.path.exists(self.host):
+                lserver.debug("file already exists")
+                lserver.debug("attempting to remove it")
+                os.remove(self.host)
+            self.socket.bind(self.host)
+        self.socket.listen(self.max_allowed_clients)
+        # self.socket.settimeout(1)
+    def run(self):
+        self.setup()
+        lserver.debug("entering main loop")
+        while ( not self.exit_event ):
+            # lserver.debug(self.read_sockets_expected)
+            # lserver.debug(self.write_sockets_expected)
+            # lserver.debug(self.exc_sockets_expected)
+            read_sockets_confirmed, \
+            write_sockets_confirmed, \
+            exc_sockets_confirmed \
+                = select.select(self.read_sockets_expected,
+                            self.write_sockets_expected,
+                            self.exc_sockets_expected,
+                )
+            for s in read_sockets_confirmed:
+                socket_handler = self.conn_to_handler.get(s, None)
+                if ( s == self.socket ):
+                    lserver.debug("handling new client")
+                    conn, addr = self.socket.accept()
+                    handler = self.make_handler(conn, addr)
+                    self.register_conn(conn, addr)
+                    self.register_handler(handler, conn)
+                elif ( socket_handler
+                        and (socket_handler in self.connection_handler) ):
+                    lserver.debug("handling client connection")
+                    try:
+                        if not socket_handler.recv():
+                            lserver.info("connection is broken, closing socket and removing it")
+                            self.unregister_handler(socket_handler, s)
+                            self.unregister_conn(conn)
+                            socket_handler.close()
+                    except Exception as e:
+                        lserver.error(e, exc_info=True)
+                else:
+                    lserver.debug("else!")
+                    lserver.debug(socket_handler)
+                    time.sleep(1)
+        def cleanup(self):
+            pass
+
+class Client(threading.Thread):
+    """docstring for Client"""
+    is_connecting = False
+    is_connected = False
+    status = "uninitialized"
+    def __init__(self,
+                host,
+                port=None,
+                af_family="AF_INET",
+                handle_data_func=None,
+                error_handler=None,
+                block_size=1024,
+                ):
+        self.super_class = super(Client, self)
+        self.super_class.__init__()
+        self.name = "Client"
+        self.exit_event = False
+        self.host = host
+        self.port = port
+        self.af_family = af_family
+        self.block_size = block_size
+        self.handle_data_func = handle_data_func
+        self.is_connected = False
+        self.error_handler = error_handler
+        # self.socket = self.make_socket()
+        self.socket = None
+        self.status = "disconnected"
+    def connect(self):
+        self.status = "connecting"
+        self.socket = self.make_socket()
+        lclient.info(
+            "connecting to socket '{}' of type {}".format(
+                self.host,
+                self.af_family
+            )
+        )
+        try:
+            if self.af_family == "AF_INET":
+                self.socket.connect((self.host, self.port))
+            elif self.af_family == "AF_UNIX":
+                if os.path.exists(self.host):
+                    self.socket.connect(self.host)
+                else:
+                    lclient.warn("File not found. Aborting.")
+                    return
+            self.is_connected = True
+            self.status = "connected"
+            lclient.info("connected")
+            return True
+        except Exception as e:
+            lclient.debug(e, exc_info=True)
+            if type(e) is ConnectionRefusedError:
+                lclient.info("failed to connect to socket '{}'".format(self.host))
+                self.disconnect()
+                return False
+    def disconnect(self):
+        lclient.info("disconnecting from socket '{}'".format(self.host))
+        self.is_connected = False
+        self.status = "disconnected"
+        if self.socket:
+            try:
+                self.socket.shutdown(socket.SHUT_RDWR)
+            except Exception as e:
+                lclient.error(e)
+            try:
+                self.socket.close()
+            except Exception as e:
+                lclient.error("error occured while closing the socket, " +
+                    "maybe it is already closed",exc_info=e)
+        del self.socket
+        self.socket = None
+    def handle_data(self, data_received):
+        data_decoded = data_received.decode("utf-8")
+        lchat.info("Server: "+data_decoded)
+        if self.handle_data_func:
+            try:
+                self.handle_data_func(data_decoded)
+            except Exception as e:
+                lclient.error(e, exc_info=True)
+    def is_running(self):
+        return (self in threading.enumerate())
+    def make_socket(self):
+        lclient.info("creating a {} socket".format(self.af_family))
+        if self.af_family == "AF_INET":
+            s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+        elif self.af_family == "AF_UNIX":
+            s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
+        else:
+            raise ValueError(
+                "AF_FAMILY '{}' not supported!".format(
+                    self.af_family
+                )
+            )
+        return s
+    def main_loop(self):
+        lclient.debug("starting main loop")
+        while ( not self.exit_event ):
+            if not self.status in ["connected"]:
+                time.sleep(0.1)
+                continue
+            # print(0)
+            read_confirmed, write_confirmed, exc_confirmed \
+            = select.select(
+                [self.socket],
+                [],
+                [self.socket],
+                1
+            )
+            if self.socket in exc_confirmed:
+                self.is_connected = False
+                lclient.warning("socket is expected to corrupt, exiting")
+                self.disconnect()
+               # self.stop()
+                break
+            elif self.socket in read_confirmed:
+                try:
+                    data_received = self.read_from_socket()
+                    if data_received == b'':
+                        lclient.info("connection is broken, closing socket exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        self.handle_data(data_received)
+                except Exception as e:
+                    lclient.error(e, exc_info=True)
+                    if type(e) is OSError:
+                        self.is_connected = False
+                        lclient.warn("connection broken, exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        raise
+            else:
+                time.sleep(0.1)
+    def read_from_socket(self):
+        data_received = self.socket.recv(self.block_size)
+        return data_received
+    def run(self):
+        # self.connect()
+        if self.error_handler:
+            self.error_handler(self.main_loop)
+        else:
+            self.main_loop()
+    def send(self, msg):
+        msg = msg.rstrip()
+        msg_encoded = bytes(msg+"\r\n", "utf-8")
+        try:
+            lchat.info("Client: "+msg)
+            self.socket.send(msg_encoded)
+        except Exception as e:
+            self.is_connected = False
+            lclient.error(e, exc_info=True)
+            self.status = "shutdown"
+    def setup(self):
+        pass
+    def stop(self,reason=None):
+        self.disconnect()
+        self.exit_event = True
+        if reason:
+            print(reason)
+
+
+
+class AsyncClient(object):
+    """docstring for Client"""
+    is_connecting = False
+    is_connected = False
+    status = "uninitialized"
+    def __init__(self,
+                host,
+                port=None,
+                af_family="AF_INET",
+                handle_data_func=None,
+                error_handler=None,
+                block_size=1024,
+                ):
+        self.super_class = super(AsyncClient, self)
+        self.super_class.__init__()
+        self.name = "Client"
+        self.exit_event = False
+        self.host = host
+        self.port = port
+        self.af_family = af_family
+        self.block_size = block_size
+        self.handle_data_func = handle_data_func
+        self.is_connected = False
+        self.error_handler = error_handler
+        self.socket = None
+        self.status = "disconnected"
+    def connect(self):
+        self.status = "connecting"
+        self.socket = self.make_socket()
+        lclient.info("connecting to socket '{}' of type {}".format(
+                self.host,self.af_family))
+        try:
+            if self.af_family == "AF_INET":
+                self.socket.connect((self.host, self.port))
+            elif self.af_family == "AF_UNIX":
+                self.socket.connect(self.host)
+                # if os.path.exists(self.host):
+                #     pass
+                # else:
+                #     lclient.warn("File not found. Aborting.")
+                #     return
+            self.is_connected = True
+            self.status = "connected"
+            lclient.info("connected")
+            return True
+        except Exception as e:
+            lclient.debug(e, exc_info=True)
+            if type(e) is ConnectionRefusedError:
+                lclient.info("failed to connect to socket '{}'".format(self.host))
+                self.disconnect()
+                return False
+    def disconnect(self):
+        lclient.info("disconnecting from socket '{}'".format(self.host))
+        self.is_connected = False
+        self.status = "disconnected"
+        if self.socket:
+            try:
+                self.socket.shutdown(socket.SHUT_RDWR)
+            except Exception as e:
+                lclient.error(e)
+            try:
+                self.socket.close()
+            except Exception as e:
+                lclient.error("error occured while closing the socket, " +
+                    "maybe it is already closed",exc_info=e)
+        del self.socket
+        self.socket = None
+    def handle_data(self, data_received):
+        data_decoded = data_received.decode("utf-8")
+        lchat.info("Server: "+data_decoded)
+        if self.handle_data_func:
+            try:
+                self.handle_data_func(data_decoded)
+            except Exception as e:
+                lclient.error(e, exc_info=True)
+    def is_running(self):
+        return (self in threading.enumerate())
+    def make_socket(self):
+        lclient.info("creating a {} socket".format(self.af_family))
+        if self.af_family == "AF_INET":
+            s = trio.socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+        elif self.af_family == "AF_UNIX":
+            s = trio.socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
+        else:
+            raise ValueError(
+                "AF_FAMILY '{}' not supported!".format(
+                    self.af_family
+                )
+            )
+        return s
+    def main_loop(self):
+        lclient.debug("starting main loop")
+        while ( not self.exit_event ):
+            if not self.status in ["connected"]:
+                time.sleep(0.1)
+                continue
+            # print(0)
+            read_confirmed, write_confirmed, exc_confirmed \
+            = select.select(
+                [self.socket],
+                [],
+                [self.socket],
+                1
+            )
+            if self.socket in exc_confirmed:
+                self.is_connected = False
+                lclient.warning("socket is expected to corrupt, exiting")
+                self.disconnect()
+               # self.stop()
+                break
+            elif self.socket in read_confirmed:
+                try:
+                    data_received = self.read_from_socket()
+                    if data_received == b'':
+                        lclient.info("connection is broken, closing socket exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        self.handle_data(data_received)
+                except Exception as e:
+                    lclient.error(e, exc_info=True)
+                    if type(e) is OSError:
+                        self.is_connected = False
+                        lclient.warn("connection broken, exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        raise
+            else:
+                time.sleep(0.1)
+    def read_from_socket(self):
+        data_received = self.socket.recv(self.block_size)
+        return data_received
+    def run(self,connect=False):
+        if connect:
+            self.connect()
+        if self.error_handler:
+            self.error_handler(self.main_loop)
+        else:
+            self.main_loop()
+    def send(self, msg):
+        msg = msg.rstrip()
+        msg_encoded = bytes(msg+"\r\n", "utf-8")
+        try:
+            lchat.info("Client: "+msg)
+            self.socket.send(msg_encoded)
+        except Exception as e:
+            self.is_connected = False
+            lclient.error(e, exc_info=True)
+            self.status = "shutdown"
+    def setup(self):
+        pass
+    def stop(self,reason=None):
+        self.disconnect()
+        self.exit_event = True
+        if reason:
+            print(reason)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#

+ 567 - 0
network/__init__.py.old

@@ -0,0 +1,567 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of MyLibNetwork.
+#
+#    MyLibNetwork is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    MyLibNetwork is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with MyLibNetwork.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import logging
+import logging.handlers
+import os
+import queue
+import select
+import socket
+import sys
+import threading
+import time
+import traceback
+import trio
+
+lclient = logging.getLogger(__name__+".client")
+lserver = logging.getLogger(__name__+".server")
+lchat = logging.getLogger(__name__+".chat")
+
+class ConnHandlerBase(object):
+    def __init__(self, socket, addr, server):
+        self.status = "init"
+        super(ConnHandlerBase, self).__init__()
+        self.socket = socket
+        self.addr = addr
+        self.server = server
+        self.block_size = 1024
+        self.welcome_client()
+        self.status="connected"
+    def welcome_client(self):
+        pass
+    def handle(self, data_decoded):
+        return
+    def recv(self):
+        try:
+            data_received = self.socket.recv(self.block_size)
+            data_decoded = data_received.decode("utf-8")
+            if data_decoded:
+                lchat.info(data_decoded.strip())
+                self.handle(data_decoded)
+                return True
+            else:
+                lserver.debug("connection corrupted")
+                return False
+        except Exception as e:
+            lserver.error(e, exc_info=True)
+        return False
+    def send(self, msg):
+        msg_encoded = bytes(msg, "utf-8")
+        lchat.info("Server:"+msg)
+        try:
+            self.socket.send(msg_encoded)
+            return True
+        except Exception as e:
+            lserver.error(e, exc_info=True)
+            return False
+    def close(self):
+        self.status = "closed"
+        try:
+            self.socket.shutdown(0)
+        except:
+            lserver.error("error during socket shutdown, ignoring")
+        try:
+            self.socket.close()
+        except:
+            lserver.error("error closing socket, maybe already closed")
+
+
+class ConnHandlerEcho(ConnHandlerBase):
+    def __init__(self, socket, addr, server):
+        self.status = "init"
+        self.super_class = super(ConnHandlerEcho, self)
+        self.super_class.__init__(socket, addr, server)
+        self.server = server
+    def welcome_client(self):
+        self.send("welcome to the client")
+    def handle(self, data_decoded):
+        lchat.info("Client:"+data_decoded)
+        for h in list(set(self.server.connection_handler)-{self}):
+            h.send(data_decoded)
+
+class Server(object):
+    """docstring for SocketHandler"""
+    def __init__(self,
+            host,
+            port=None,
+            af_family="AF_INET",
+            max_allowed_clients=5,
+            handler=None,
+            handler_kwargs={},
+            ):
+        super(Server, self).__init__()
+        self.exit_event =  False
+        self.host=host
+        self.port=port
+        self.af_family = af_family
+        self.handler_kwargs = handler_kwargs
+        self.handler = handler
+        self.max_allowed_clients = max_allowed_clients
+        self.socket = self.make_socket()
+        self.connection_handler = []
+        self.conn_to_addr = {}
+        self.addr_to_conn = {}
+        self.conn_to_handler ={}
+        self.handler_to_conn = {}
+        self.read_sockets_expected = [self.socket]
+        self.write_sockets_expected = []
+        self.exc_sockets_expected = []
+    def make_socket(self):
+        lserver.debug("making a {} socket".format(self.af_family))
+        if self.af_family == "AF_INET":
+            return socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+        elif self.af_family == "AF_UNIX":
+            return socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
+        else:
+            raise ValueError(
+                "AF_FAMILY '{}' not supported!".format(
+                    self.af_family
+                )
+            )
+    def make_handler(self, conn, addr):
+        return self.handler(conn, addr, self, **self.handler_kwargs)
+    def register_conn(self, conn, addr):
+        lserver.info(
+            "New Connection, addr: '{}', socket: '{}'".format(addr,conn)
+        )
+        self.read_sockets_expected.append(conn)
+        if addr:
+            self.conn_to_addr[conn] = addr
+            self.addr_to_conn[addr] = conn
+    def unregister_conn(self, conn):
+        self.read_sockets_expected.remove(conn)
+        addr = self.conn_to_addr.get(conn, False)
+        if addr:
+            del self.addr_to_conn[addr]
+            del self.conn_to_addr[conn]
+    def register_handler(self, handler, conn):
+        self.connection_handler.append(handler)
+        self.conn_to_handler[conn] = handler
+        self.handler_to_conn[handler] = conn
+    def unregister_handler(self, handler, conn):
+        self.connection_handler.remove(handler)
+        del self.conn_to_handler[conn]
+        del self.handler_to_conn[handler]
+    def setup(self):
+        lserver.info("setting up socket")
+        if self.af_family == "AF_INET":
+            self.socket.bind((self.host, self.port))
+        elif self.af_family == "AF_UNIX":
+            if os.path.exists(self.host):
+                lserver.debug("file already exists")
+                lserver.debug("attempting to remove it")
+                os.remove(self.host)
+            self.socket.bind(self.host)
+        self.socket.listen(self.max_allowed_clients)
+        # self.socket.settimeout(1)
+    def run(self):
+        self.setup()
+        lserver.debug("entering main loop")
+        while ( not self.exit_event ):
+            # lserver.debug(self.read_sockets_expected)
+            # lserver.debug(self.write_sockets_expected)
+            # lserver.debug(self.exc_sockets_expected)
+            read_sockets_confirmed, \
+            write_sockets_confirmed, \
+            exc_sockets_confirmed \
+                = select.select(self.read_sockets_expected,
+                            self.write_sockets_expected,
+                            self.exc_sockets_expected,
+                )
+            lserver.debug("bleh")
+            for s in read_sockets_confirmed:
+                socket_handler = self.conn_to_handler.get(s, None)
+                if ( s == self.socket ):
+                    lserver.debug("handling new client")
+                    conn, addr = self.socket.accept()
+                    handler = self.make_handler(conn, addr)
+                    self.register_conn(conn, addr)
+                    self.register_handler(handler, conn)
+                elif ( socket_handler
+                        and (socket_handler in self.connection_handler) ):
+                    lserver.debug("handling client connection")
+                    try:
+                        if not socket_handler.recv():
+                            lserver.info("connection is broken, closing socket and removing it")
+                            self.unregister_handler(socket_handler, s)
+                            self.unregister_conn(conn)
+                            socket_handler.close()
+                    except Exception as e:
+                        lserver.error(e, exc_info=True)
+                else:
+                    lserver.debug("else!")
+                    lserver.debug(socket_handler)
+                    time.sleep(1)
+        def cleanup(self):
+            pass
+
+class Client(threading.Thread):
+    """docstring for Client"""
+    is_connecting = False
+    is_connected = False
+    status = "uninitialized"
+    def __init__(self,
+                host,
+                port=None,
+                af_family="AF_INET",
+                handle_data_func=None,
+                error_handler=None,
+                block_size=1024,
+                ):
+        self.super_class = super(Client, self)
+        self.super_class.__init__()
+        self.name = "Client"
+        self.exit_event = False
+        self.host = host
+        self.port = port
+        self.af_family = af_family
+        self.block_size = block_size
+        self.handle_data_func = handle_data_func
+        self.is_connected = False
+        self.error_handler = error_handler
+        # self.socket = self.make_socket()
+        self.socket = None
+        self.status = "disconnected"
+    def connect(self):
+        self.status = "connecting"
+        self.socket = self.make_socket()
+        lclient.info(
+            "connecting to socket '{}' of type {}".format(
+                self.host,
+                self.af_family
+            )
+        )
+        try:
+            if self.af_family == "AF_INET":
+                self.socket.connect((self.host, self.port))
+            elif self.af_family == "AF_UNIX":
+                if os.path.exists(self.host):
+                    self.socket.connect(self.host)
+                else:
+                    lclient.warn("File not found. Aborting.")
+                    return
+            self.is_connected = True
+            self.status = "connected"
+            lclient.info("connected")
+            return True
+        except Exception as e:
+            lclient.debug(e, exc_info=True)
+            if type(e) is ConnectionRefusedError:
+                lclient.info("failed to connect to socket '{}'".format(self.host))
+                self.disconnect()
+                return False
+    def disconnect(self):
+        lclient.info("disconnecting from socket '{}'".format(self.host))
+        self.is_connected = False
+        self.status = "disconnected"
+        if self.socket:
+            try:
+                self.socket.shutdown(socket.SHUT_RDWR)
+            except Exception as e:
+                lclient.error(e)
+            try:
+                self.socket.close()
+            except Exception as e:
+                lclient.error("error occured while closing the socket, " +
+                    "maybe it is already closed",exc_info=e)
+        del self.socket
+        self.socket = None
+    def handle_data(self, data_received):
+        data_decoded = data_received.decode("utf-8")
+        lchat.info("Server: "+data_decoded)
+        if self.handle_data_func:
+            try:
+                self.handle_data_func(data_decoded)
+            except Exception as e:
+                lclient.error(e, exc_info=True)
+    def is_running(self):
+        return (self in threading.enumerate())
+    def make_socket(self):
+        lclient.info("creating a {} socket".format(self.af_family))
+        if self.af_family == "AF_INET":
+            s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+        elif self.af_family == "AF_UNIX":
+            s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
+        else:
+            raise ValueError(
+                "AF_FAMILY '{}' not supported!".format(
+                    self.af_family
+                )
+            )
+        return s
+    def main_loop(self):
+        lclient.debug("starting main loop")
+        while ( not self.exit_event ):
+            if not self.status in ["connected"]:
+                time.sleep(0.1)
+                continue
+            # print(0)
+            read_confirmed, write_confirmed, exc_confirmed \
+            = select.select(
+                [self.socket],
+                [],
+                [self.socket],
+                1
+            )
+            if self.socket in exc_confirmed:
+                self.is_connected = False
+                lclient.warning("socket is expected to corrupt, exiting")
+                self.disconnect()
+               # self.stop()
+                break
+            elif self.socket in read_confirmed:
+                try:
+                    data_received = self.read_from_socket()
+                    if data_received == b'':
+                        lclient.info("connection is broken, closing socket exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        self.handle_data(data_received)
+                except Exception as e:
+                    lclient.error(e, exc_info=True)
+                    if type(e) is OSError:
+                        self.is_connected = False
+                        lclient.warn("connection broken, exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        raise
+            else:
+                time.sleep(0.1)
+    def read_from_socket(self):
+        data_received = self.socket.recv(self.block_size)
+        return data_received
+    def run(self):
+        # self.connect()
+        if self.error_handler:
+            self.error_handler(self.main_loop)
+        else:
+            self.main_loop()
+    def send(self, msg):
+        msg = msg.rstrip()
+        msg_encoded = bytes(msg+"\r\n", "utf-8")
+        try:
+            lchat.info("Client: "+msg)
+            self.socket.send(msg_encoded)
+        except Exception as e:
+            self.is_connected = False
+            lclient.error(e, exc_info=True)
+            self.status = "shutdown"
+    def setup(self):
+        pass
+    def stop(self,reason=None):
+        self.disconnect()
+        self.exit_event = True
+        if reason:
+            print(reason)
+
+class Client(threading.Thread):
+    """docstring for Client"""
+    is_connecting = False
+    is_connected = False
+    status = "uninitialized"
+    def __init__(self,
+                host,
+                port=None,
+                af_family="AF_INET",
+                handle_data_func=None,
+                error_handler=None,
+                block_size=1024,
+                ):
+        self.super_class = super(Client, self)
+        self.super_class.__init__()
+        self.name = "Client"
+        self.exit_event = False
+        self.host = host
+        self.port = port
+        self.af_family = af_family
+        self.block_size = block_size
+        self.handle_data_func = handle_data_func
+        self.is_connected = False
+        self.error_handler = error_handler
+        # self.socket = self.make_socket()
+        self.socket = None
+        self.status = "disconnected"
+    def connect(self):
+        self.status = "connecting"
+        self.socket = self.make_socket()
+        lclient.info(
+            "connecting to socket '{}' of type {}".format(
+                self.host,
+                self.af_family
+            )
+        )
+        try:
+            if self.af_family == "AF_INET":
+                self.socket.connect((self.host, self.port))
+            elif self.af_family == "AF_UNIX":
+                if os.path.exists(self.host):
+                    self.socket.connect(self.host)
+                else:
+                    lclient.warn("File not found. Aborting.")
+                    return
+            self.is_connected = True
+            self.status = "connected"
+            lclient.info("connected")
+            return True
+        except Exception as e:
+            lclient.debug(e, exc_info=True)
+            if type(e) is ConnectionRefusedError:
+                lclient.info("failed to connect to socket '{}'".format(self.host))
+                self.disconnect()
+                return False
+    def disconnect(self):
+        lclient.info("disconnecting from socket '{}'".format(self.host))
+        self.is_connected = False
+        self.status = "disconnected"
+        if self.socket:
+            try:
+                self.socket.shutdown(socket.SHUT_RDWR)
+            except Exception as e:
+                lclient.error(e)
+            try:
+                self.socket.close()
+            except Exception as e:
+                lclient.error("error occured while closing the socket, " +
+                    "maybe it is already closed",exc_info=e)
+        del self.socket
+        self.socket = None
+    def handle_data(self, data_received):
+        data_decoded = data_received.decode("utf-8")
+        lchat.info("Server: "+data_decoded)
+        if self.handle_data_func:
+            try:
+                self.handle_data_func(data_decoded)
+            except Exception as e:
+                lclient.error(e, exc_info=True)
+    def is_running(self):
+        return (self in threading.enumerate())
+    def make_socket(self):
+        lclient.info("creating a {} socket".format(self.af_family))
+        if self.af_family == "AF_INET":
+            s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+        elif self.af_family == "AF_UNIX":
+            s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
+        else:
+            raise ValueError(
+                "AF_FAMILY '{}' not supported!".format(
+                    self.af_family
+                )
+            )
+        return s
+    def main_loop(self):
+        lclient.debug("starting main loop")
+        while ( not self.exit_event ):
+            if not self.status in ["connected"]:
+                time.sleep(0.1)
+                continue
+            # print(0)
+            read_confirmed, write_confirmed, exc_confirmed \
+            = select.select(
+                [self.socket],
+                [],
+                [self.socket],
+                1
+            )
+            if self.socket in exc_confirmed:
+                self.is_connected = False
+                lclient.warning("socket is expected to corrupt, exiting")
+                self.disconnect()
+               # self.stop()
+                break
+            elif self.socket in read_confirmed:
+                try:
+                    data_received = self.read_from_socket()
+                    if data_received == b'':
+                        lclient.info("connection is broken, closing socket exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        self.handle_data(data_received)
+                except Exception as e:
+                    lclient.error(e, exc_info=True)
+                    if type(e) is OSError:
+                        self.is_connected = False
+                        lclient.warn("connection broken, exiting")
+                        self.disconnect()
+                        # self.stop()
+                        # break
+                    else:
+                        raise
+            else:
+                time.sleep(0.1)
+    def read_from_socket(self):
+        data_received = self.socket.recv(self.block_size)
+        return data_received
+    def run(self):
+        # self.connect()
+        if self.error_handler:
+            self.error_handler(self.main_loop)
+        else:
+            self.main_loop()
+    def send(self, msg):
+        msg = msg.rstrip()
+        msg_encoded = bytes(msg+"\r\n", "utf-8")
+        try:
+            lchat.info("Client: "+msg)
+            self.socket.send(msg_encoded)
+        except Exception as e:
+            self.is_connected = False
+            lclient.error(e, exc_info=True)
+            self.status = "shutdown"
+    def setup(self):
+        pass
+    def stop(self,reason=None):
+        self.disconnect()
+        self.exit_event = True
+        if reason:
+            print(reason)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#

+ 104 - 0
network/client.py

@@ -0,0 +1,104 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of Project BeeWatch.
+#
+#    Project BeeWatch is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    Project BeeWatch is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with Project BeeWatch.  If not, see <http://www.gnu.org/licenses/>.
+#
+## UNITS
+# speed: meter/second
+# temperature: kelvin
+# rotation: degree/second
+
+import argparse
+import logging
+import logging.config
+import logging.handlers
+import sys
+import threading
+import traceback
+import time
+import yaml
+import mylibnetwork
+
+lroot = logging.getLogger("root")
+
+def user_input():
+    while True:
+        data = input("type something:")
+        client.send(data)
+
+def configure_logging():
+    with open("../../bee_logging.yaml") as f:
+        data = f.read()
+    config = yaml.safe_load(data)
+    logging.config.dictConfig(config)
+
+parser = argparse.ArgumentParser(description="BeeWatch Server",)
+parser.add_argument("host",type=str,
+    help="hostname/ip address/filename (only if --use-file was specified)")
+parser.add_argument("port",type=int,default=80540,nargs="?",
+    help="""the port on wich the connection will open up. ignored if --use-file
+         is given""")
+parser.add_argument("-d","--debug",default=False,action="store_true",
+    help="enables logging")
+parser.add_argument("-a","--async_client",default=False,action="store_true",
+    help="Use an asynchronous client instead of a threaded one")
+group_af_family = parser.add_mutually_exclusive_group()
+group_af_family.add_argument(
+"-u","--af-unix",
+    dest="af_family",
+    action="store_const",
+    const="AF_UNIX",
+    help="""use an AF_UNIX socket (a file). can't be used in combination with
+        --af-inet."""
+)
+group_af_family.add_argument(
+    "-i","--af_inet",
+    default="AF_INET",
+    dest="af_family",
+    action="store_const",
+    const="AF_INET",
+    help="""use an AF_INET socket (hostname/ip address + port). can't be used
+        with --af-unix"""
+)
+args = parser.parse_args()
+if args.debug:
+    configure_logging()
+if args.async_client:
+    client_class = mylibnetwork.AsyncClient
+else:
+    client_class = mylibnetwork.Client
+client = client_class(
+    host=args.host,
+    port=args.port,
+    af_family=args.af_family,
+)
+input_thread = threading.Thread(target=user_input,daemon=True)
+try:
+    input_thread.start()
+    if args.async_client:
+        client.run(connect=True)
+    else:
+        client.start()
+        client.connect()
+    while True:
+        client.join(1)
+        if not client.is_running():
+            break
+except KeyboardInterrupt:
+    print("\r    ", end="\r")
+    lroot.warn("KeyboardInterrupt, aborting!")
+finally:
+    client.stop()

+ 113 - 0
network/logs/chat.log

@@ -0,0 +1,113 @@
+2017-03-17 15:37:01 (__init__.py:308) mylibnetwork.client DEBUG - starting main loop
+2017-03-17 15:37:01 (__init__.py:295) mylibnetwork.client INFO - creating a AF_UNIX socket
+2017-03-17 15:37:01 (__init__.py:246) mylibnetwork.client INFO - connecting to socket 'socket' of type AF_UNIX
+2017-03-17 15:37:01 (__init__.py:260) mylibnetwork.client INFO - connected
+2017-03-17 15:37:01 (__init__.py:65) mylibnetwork.chat INFO - Server:welcome to the client
+2017-03-17 15:37:01 (__init__.py:286) mylibnetwork.chat INFO - Server: welcome to the client
+2017-03-17 15:37:03 (__init__.py:362) mylibnetwork.chat INFO - Client: adf
+2017-03-17 15:37:03 (__init__.py:54) mylibnetwork.chat INFO - adf
+2017-03-17 15:37:03 (__init__.py:93) mylibnetwork.chat INFO - Client:adf
+
+2017-03-17 15:37:04 (__init__.py:362) mylibnetwork.chat INFO - Client: asdf
+2017-03-17 15:37:04 (__init__.py:54) mylibnetwork.chat INFO - asdf
+2017-03-17 15:37:04 (__init__.py:93) mylibnetwork.chat INFO - Client:asdf
+
+2017-03-17 15:37:04 (__init__.py:362) mylibnetwork.chat INFO - Client: asdf
+2017-03-17 15:37:04 (__init__.py:54) mylibnetwork.chat INFO - asdf
+2017-03-17 15:37:04 (__init__.py:93) mylibnetwork.chat INFO - Client:asdf
+
+2017-03-17 15:37:04 (__init__.py:362) mylibnetwork.chat INFO - Client: asdf
+2017-03-17 15:37:04 (__init__.py:54) mylibnetwork.chat INFO - asdf
+2017-03-17 15:37:04 (__init__.py:93) mylibnetwork.chat INFO - Client:asdf
+
+2017-03-17 15:37:05 (__init__.py:269) mylibnetwork.client INFO - disconnecting from socket 'socket'
+2017-03-17 15:50:03 (__init__.py:431) mylibnetwork.client INFO - disconnecting from socket 'socket'
+2017-03-17 15:52:45 (__init__.py:309) mylibnetwork.client DEBUG - starting main loop
+2017-03-17 15:52:45 (__init__.py:296) mylibnetwork.client INFO - creating a AF_UNIX socket
+2017-03-17 15:52:45 (__init__.py:247) mylibnetwork.client INFO - connecting to socket 'socket' of type AF_UNIX
+2017-03-17 15:52:45 (__init__.py:264) mylibnetwork.client DEBUG - [Errno 111] Connection refused
+Traceback (most recent call last):
+  File "/home/digital/.local/lib/python3.5/site-packages/mylibnetwork/__init__.py", line 255, in connect
+    self.socket.connect(self.host)
+ConnectionRefusedError: [Errno 111] Connection refused
+2017-03-17 15:52:45 (__init__.py:266) mylibnetwork.client INFO - failed to connect to socket 'socket'
+2017-03-17 15:52:45 (__init__.py:270) mylibnetwork.client INFO - disconnecting from socket 'socket'
+2017-03-17 15:52:57 (__init__.py:363) mylibnetwork.chat INFO - Client: 
+2017-03-17 15:52:57 (__init__.py:367) mylibnetwork.client ERROR - 'NoneType' object has no attribute 'send'
+Traceback (most recent call last):
+  File "/home/digital/.local/lib/python3.5/site-packages/mylibnetwork/__init__.py", line 364, in send
+    self.socket.send(msg_encoded)
+AttributeError: 'NoneType' object has no attribute 'send'
+2017-03-17 15:52:59 (__init__.py:363) mylibnetwork.chat INFO - Client: adf
+2017-03-17 15:52:59 (__init__.py:367) mylibnetwork.client ERROR - 'NoneType' object has no attribute 'send'
+Traceback (most recent call last):
+  File "/home/digital/.local/lib/python3.5/site-packages/mylibnetwork/__init__.py", line 364, in send
+    self.socket.send(msg_encoded)
+AttributeError: 'NoneType' object has no attribute 'send'
+2017-03-17 15:53:01 (__init__.py:270) mylibnetwork.client INFO - disconnecting from socket 'socket'
+2017-03-17 15:53:43 (__init__.py:309) mylibnetwork.client DEBUG - starting main loop
+2017-03-17 15:53:43 (__init__.py:296) mylibnetwork.client INFO - creating a AF_UNIX socket
+2017-03-17 15:53:43 (__init__.py:247) mylibnetwork.client INFO - connecting to socket 'socket' of type AF_UNIX
+2017-03-17 15:53:43 (__init__.py:261) mylibnetwork.client INFO - connected
+2017-03-17 15:53:43 (__init__.py:66) mylibnetwork.chat INFO - Server:welcome to the client
+2017-03-17 15:53:43 (__init__.py:287) mylibnetwork.chat INFO - Server: welcome to the client
+2017-03-17 15:53:44 (__init__.py:363) mylibnetwork.chat INFO - Client: adf
+2017-03-17 15:53:44 (__init__.py:55) mylibnetwork.chat INFO - adf
+2017-03-17 15:53:44 (__init__.py:94) mylibnetwork.chat INFO - Client:adf
+
+2017-03-17 15:53:46 (__init__.py:363) mylibnetwork.chat INFO - Client: sadf
+2017-03-17 15:53:46 (__init__.py:55) mylibnetwork.chat INFO - sadf
+2017-03-17 15:53:46 (__init__.py:94) mylibnetwork.chat INFO - Client:sadf
+
+2017-03-17 15:53:47 (__init__.py:363) mylibnetwork.chat INFO - Client: asd
+2017-03-17 15:53:47 (__init__.py:55) mylibnetwork.chat INFO - asd
+2017-03-17 15:53:47 (__init__.py:94) mylibnetwork.chat INFO - Client:asd
+
+2017-03-17 15:53:47 (__init__.py:363) mylibnetwork.chat INFO - Client: fasd
+2017-03-17 15:53:47 (__init__.py:55) mylibnetwork.chat INFO - fasd
+2017-03-17 15:53:47 (__init__.py:94) mylibnetwork.chat INFO - Client:fasd
+
+2017-03-17 15:53:47 (__init__.py:363) mylibnetwork.chat INFO - Client: fasd
+2017-03-17 15:53:47 (__init__.py:55) mylibnetwork.chat INFO - fasd
+2017-03-17 15:53:47 (__init__.py:94) mylibnetwork.chat INFO - Client:fasd
+
+2017-03-17 15:53:48 (__init__.py:363) mylibnetwork.chat INFO - Client: fasdf
+2017-03-17 15:53:48 (__init__.py:55) mylibnetwork.chat INFO - fasdf
+2017-03-17 15:53:48 (__init__.py:94) mylibnetwork.chat INFO - Client:fasdf
+
+2017-03-17 15:53:48 (__init__.py:270) mylibnetwork.client INFO - disconnecting from socket 'socket'
+2017-03-17 15:54:22 (__init__.py:309) mylibnetwork.client DEBUG - starting main loop
+2017-03-17 15:54:22 (__init__.py:296) mylibnetwork.client INFO - creating a AF_UNIX socket
+2017-03-17 15:54:22 (__init__.py:247) mylibnetwork.client INFO - connecting to socket 'socket' of type AF_UNIX
+2017-03-17 15:54:22 (__init__.py:261) mylibnetwork.client INFO - connected
+2017-03-17 15:54:22 (__init__.py:66) mylibnetwork.chat INFO - Server:welcome to the client
+2017-03-17 15:54:22 (__init__.py:287) mylibnetwork.chat INFO - Server: welcome to the client
+2017-03-17 15:54:24 (__init__.py:363) mylibnetwork.chat INFO - Client: asdf
+2017-03-17 15:54:24 (__init__.py:55) mylibnetwork.chat INFO - asdf
+2017-03-17 15:54:24 (__init__.py:94) mylibnetwork.chat INFO - Client:asdf
+
+2017-03-17 15:54:26 (__init__.py:363) mylibnetwork.chat INFO - Client: adf
+2017-03-17 15:54:26 (__init__.py:55) mylibnetwork.chat INFO - adf
+2017-03-17 15:54:26 (__init__.py:94) mylibnetwork.chat INFO - Client:adf
+
+2017-03-17 15:54:26 (__init__.py:363) mylibnetwork.chat INFO - Client: asdf
+2017-03-17 15:54:26 (__init__.py:55) mylibnetwork.chat INFO - asdf
+2017-03-17 15:54:26 (__init__.py:94) mylibnetwork.chat INFO - Client:asdf
+
+2017-03-17 15:54:27 (__init__.py:363) mylibnetwork.chat INFO - Client: asdf
+2017-03-17 15:54:27 (__init__.py:55) mylibnetwork.chat INFO - asdf
+2017-03-17 15:54:27 (__init__.py:94) mylibnetwork.chat INFO - Client:asdf
+
+2017-03-17 15:54:27 (__init__.py:270) mylibnetwork.client INFO - disconnecting from socket 'socket'
+2017-03-17 16:01:47 (__init__.py:456) mylibnetwork.client INFO - creating a AF_UNIX socket
+2017-03-17 16:01:47 (__init__.py:408) mylibnetwork.client INFO - connecting to socket 'socket' of type AF_UNIX
+2017-03-17 16:01:47 (__init__.py:421) mylibnetwork.client INFO - connected
+2017-03-17 16:01:47 (__init__.py:469) mylibnetwork.client DEBUG - starting main loop
+2017-03-17 16:01:47 (__init__.py:499) mylibnetwork.client ERROR - 'coroutine' object has no attribute 'decode'
+Traceback (most recent call last):
+  File "/home/digital/.local/lib/python3.5/site-packages/mylibnetwork/__init__.py", line 497, in main_loop
+    self.handle_data(data_received)
+  File "/home/digital/.local/lib/python3.5/site-packages/mylibnetwork/__init__.py", line 446, in handle_data
+    data_decoded = data_received.decode("utf-8")
+AttributeError: 'coroutine' object has no attribute 'decode'
+2017-03-17 16:01:47 (__init__.py:430) mylibnetwork.client INFO - disconnecting from socket 'socket'

+ 0 - 0
network/logs/client.log


+ 59 - 0
network/logs/network.log

@@ -0,0 +1,59 @@
+2017-03-17 15:36:26 (__init__.py:125) mylibnetwork.server DEBUG - making a AF_INET socket
+2017-03-17 15:36:26 (__init__.py:161) mylibnetwork.server INFO - setting up socket
+2017-03-17 15:36:31 (__init__.py:125) mylibnetwork.server DEBUG - making a AF_UNIX socket
+2017-03-17 15:36:31 (__init__.py:161) mylibnetwork.server INFO - setting up socket
+2017-03-17 15:36:31 (__init__.py:174) mylibnetwork.server DEBUG - entering main loop
+2017-03-17 15:37:01 (__init__.py:186) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:37:01 (__init__.py:190) mylibnetwork.server DEBUG - handling new client
+2017-03-17 15:37:01 (__init__.py:140) mylibnetwork.server INFO - New Connection, addr: 'b''', socket: '<socket.socket fd=7, family=AddressFamily.AF_UNIX, type=SocketKind.SOCK_STREAM, proto=0, laddr=socket>'
+2017-03-17 15:37:03 (__init__.py:186) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:37:03 (__init__.py:197) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:37:04 (__init__.py:186) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:37:04 (__init__.py:197) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:37:04 (__init__.py:186) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:37:04 (__init__.py:197) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:37:04 (__init__.py:186) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:37:04 (__init__.py:197) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:37:05 (__init__.py:186) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:37:05 (__init__.py:197) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:37:05 (__init__.py:58) mylibnetwork.server DEBUG - connection corrupted
+2017-03-17 15:37:05 (__init__.py:200) mylibnetwork.server INFO - connection is broken, closing socket and removing it
+2017-03-17 15:52:55 (__init__.py:126) mylibnetwork.server DEBUG - making a AF_UNIX socket
+2017-03-17 15:52:55 (__init__.py:162) mylibnetwork.server INFO - setting up socket
+2017-03-17 15:52:55 (__init__.py:167) mylibnetwork.server DEBUG - file already exists
+2017-03-17 15:52:55 (__init__.py:168) mylibnetwork.server DEBUG - attempting to remove it
+2017-03-17 15:52:55 (__init__.py:175) mylibnetwork.server DEBUG - entering main loop
+2017-03-17 15:53:43 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:43 (__init__.py:191) mylibnetwork.server DEBUG - handling new client
+2017-03-17 15:53:43 (__init__.py:141) mylibnetwork.server INFO - New Connection, addr: 'b''', socket: '<socket.socket fd=7, family=AddressFamily.AF_UNIX, type=SocketKind.SOCK_STREAM, proto=0, laddr=socket>'
+2017-03-17 15:53:44 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:44 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:53:46 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:46 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:53:47 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:47 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:53:47 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:47 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:53:47 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:47 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:53:48 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:48 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:53:48 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:53:48 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:53:48 (__init__.py:59) mylibnetwork.server DEBUG - connection corrupted
+2017-03-17 15:53:48 (__init__.py:201) mylibnetwork.server INFO - connection is broken, closing socket and removing it
+2017-03-17 15:54:22 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:54:22 (__init__.py:191) mylibnetwork.server DEBUG - handling new client
+2017-03-17 15:54:22 (__init__.py:141) mylibnetwork.server INFO - New Connection, addr: 'b''', socket: '<socket.socket fd=7, family=AddressFamily.AF_UNIX, type=SocketKind.SOCK_STREAM, proto=0, laddr=socket>'
+2017-03-17 15:54:24 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:54:24 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:54:26 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:54:26 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:54:26 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:54:26 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:54:27 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:54:27 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:54:27 (__init__.py:187) mylibnetwork.server DEBUG - bleh
+2017-03-17 15:54:27 (__init__.py:198) mylibnetwork.server DEBUG - handling client connection
+2017-03-17 15:54:27 (__init__.py:59) mylibnetwork.server DEBUG - connection corrupted
+2017-03-17 15:54:27 (__init__.py:201) mylibnetwork.server INFO - connection is broken, closing socket and removing it

+ 97 - 0
network/server.py

@@ -0,0 +1,97 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of Project BeeWatch.
+#
+#    Project BeeWatch is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    Project BeeWatch is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with Project BeeWatch.  If not, see <http://www.gnu.org/licenses/>.
+#
+## UNITS
+# speed: meter/second
+# temperature: kelvin
+# rotation: degree/second
+
+import argparse
+import logging
+import logging.config
+import logging.handlers
+import sys
+import traceback
+import time
+import yaml
+import mylibnetwork
+lroot = logging.getLogger("root")
+parser = argparse.ArgumentParser(
+    description="BeeWatch Server",
+)
+parser.add_argument(
+    "host",
+    type=str,
+    help="hostname/ip address/filename (only if --use-file was specified)"
+)
+parser.add_argument(
+    "port",
+    type=int,
+    default=80540,
+    nargs="?",
+    help="the port on wich the connection will open up. ignored if --use-file \
+        is given"
+)
+parser.add_argument(
+    "-d","--debug",
+    default=False,
+    action="store_true",
+    help="enables logging"
+)
+group_af_family = parser.add_mutually_exclusive_group()
+group_af_family.add_argument(
+    "-u","--af-unix",
+    dest="af_family",
+    # default=False,
+    action="store_const",
+    const="AF_UNIX",
+    help="""use an AF_UNIX socket (a file). can't be used in combination with
+        --af-inet."""
+    # " will be used \
+    #     instead of an AF_INET socket (hostname/ip address + port)."
+)
+group_af_family.add_argument(
+    "-i","--af_inet",
+    default="AF_INET",
+    dest="af_family",
+    action="store_const",
+    const="AF_INET",
+    help="""use an AF_INET socket (hostname/ip address + port). can't be used
+        with --af-unix"""
+        # (hostname/ip address + port) will be used instead of an AF_INET \
+        # socket (a file)."
+)
+args = parser.parse_args()
+print(args)
+if args.debug:
+    with open("../../bee_logging.yaml") as f:
+        data = f.read()
+    config = yaml.safe_load(data)
+    logging.config.dictConfig(config)
+server = mylibnetwork.Server(
+    host=args.host,
+    port=args.port,
+    af_family=args.af_family,
+    handler=mylibnetwork.ConnHandlerEcho
+)
+try:
+    server.run()
+except KeyboardInterrupt:
+    lroot.warn("KeyboardInterrupt, aborting!")
+finally:
+    pass

+ 172 - 0
pin/__init__.py

@@ -0,0 +1,172 @@
+#! /usr/bin/python3
+#
+## LICENSE
+#    This file is part of DigiLib.
+#
+#    DigiLib is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    DigiLib is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with DigiLib.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import digilib.network
+
+log = logging.getLogger(__name__+"")
+lpin = logging.getLogger(__name__+".pin")
+
+client = digilib.network.Client("perry",5069,af_family="AF_INET")
+client.daemon = True
+client.start()
+client.connect()
+
+#
+# class PinBase(object):
+#     """docstring for PinBase."""
+#     # lockdir = "/var/lock/pins/"
+#     lockdir = "lock/pins/"
+#     pin_number = None
+#     lockfile = lockdir + str(pin_number)
+#     value_high = True
+#     value_low = False
+#     value = value_low
+#     def __init__(self,pin_number):
+#         super(PinBase,self).__init__()
+#         self.pin_number = pin_number
+#         self.lockfile = self.lockdir + str(self.pin_number)
+
+class PinBase(object):
+    """PinBase is the base class for all classes representing a gpio pin"""
+    pin_number = None
+    value = None
+    def __init__(self,pin_number):
+        super(PinBase,self).__init__()
+        self.pin_number = pin_number
+        self.value = self.value_low
+    def output(self,value):
+        lpin.info(
+            "pin {} set to {}".format(
+                str(self.pin_number),
+                str(value)
+            )
+        )
+        self.value = value
+    def read(self):
+        lpin.debug(
+            "pin {} has value {}".format(
+                str(self.pin_number),
+                str(self.value)
+            )
+        )
+        return self.value
+
+class RemotePin(PinBase):
+    """RemotePin represents a remote pin"""
+    def output(self,value):
+        lpin.info(
+            "pin {} set to {}".format(
+                str(self.pin_number),
+                str(value)
+            )
+        )
+        self.value = value
+        client.send("set {} {}".format(
+                str(self.pin_number),
+                str(value)
+            )
+        )
+    def read(self):
+        lpin.debug(
+            "pin {} has value {}".format(
+                str(self.pin_number),
+                str(self.value)
+            )
+        )
+        return self.value
+
+class DigitalPin(RemotePin):
+    value_high = True
+    value_low = False
+    def __init__(self,pin_number):
+        super(DigitalPin,self).__init__(pin_number)
+
+class AnalogPin(PinBase):
+    value_high = 1
+    value_low = 0
+    def __init__(self,pin_number):
+        super(AnalogPin,self).__init__()
+
+class PinControllerBase(object):
+    """docstring for PinControllerBase.
+    PinControllerBase is the base class for all classes controlling a physical
+    device connected to multiple pins
+    """
+    pins = []
+    def __init__(self):
+        super(PinControllerBase, self).__init__()
+
+class PCEngine(PinControllerBase):
+    """Test Class"""
+    max_speed=1
+    speed = 0
+    turn_on_speed = 1
+    is_on = False
+    def __init__(self,pin_on_off,pin_analog):
+        super(PCEngine, self).__init__()
+        self.pin_on_off = self.make_pin(pin_on_off)
+        self.pin_analog = self.make_pin(pin_analog)
+        self.pins.append(self.pin_on_off)
+        self.pins.append(self.pin_analog)
+    def make_pin(self,*args):
+        return DigitalPin(*args)
+    def set_speed(self,speed):
+        self.pin_analog.output(speed)
+        self.speed = speed
+    def turn_on(self,speed=turn_on_speed):
+        if self.is_on:
+            return
+        self.set_speed(speed)
+        self.pin_on_off.output(1)
+        self.is_on = True
+    def turn_off(self,speed=turn_on_speed):
+        if not self.is_on:
+            return
+        self.set_speed(speed)
+        self.pin_on_off.output(1)
+        self.is_on = True
+
+
+class PinAPIBase(object):
+    """docstring for PinAPI.
+    PinAPIBase is the base class for all classes providing an api to multiple
+    PinController.
+    """
+    def __init__(self):
+        super(PinAPIBase, self).__init__()
+
+
+
+
+if __name__ == "__main__":
+    pass
+
+
+
+
+
+
+
+
+
+
+
+
+
+#