From bf3f32c0d3f04db30435c5391332fbd66f6a13f4 Mon Sep 17 00:00:00 2001 From: Philip Trauner Date: Thu, 23 Feb 2017 12:01:04 +0100 Subject: [PATCH 1/3] Deprecated, was not used anywhere --- Server/Broadcast.py | 47 --------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 Server/Broadcast.py diff --git a/Server/Broadcast.py b/Server/Broadcast.py deleted file mode 100644 index fb7d180..0000000 --- a/Server/Broadcast.py +++ /dev/null @@ -1,47 +0,0 @@ -class Broadcast: - class ChannelError(IndexError): - def __init__(self, channel): - super(Broadcast.ChannelError, self).__init__("channel '%s' does not exist" % channel) - - def __init__(self): - self.channels = {} - - def broadcast(self, data, route, channel, exclude=[]): - if channel in self.channels: - for handler in self.channels[channel]: - if not handler in exclude: - handler.send(data, route) - else: - raise Broadcast.ChannelError(channel) - - def remove(self, handler, channel): - if channel in self.channels: - if handler in self.channels[channel]: - del self.channels[channel][self.channels[channel].index(handler)] - else: - raise Broadcast.ChannelError(channel) - - def add(self, handler, channel): - if channel in self.channels: - if not handler in self.channels[channel]: - self.channels[channel].append(handler) - else: - raise Broadcast.ChannelError(channel) - - def add_channel(self, channel): - self.channels[channel] = [] - - def remove_channel(self, channel): - if channel in self.channels: - del self.channels[channel] - else: - raise Broadcast.ChannelError(channel) - - def __repr__(self): - out = "Channels:\n" - for channel in self.channels: - out += "%s: %d socks\n" % (channel, len(self.channels[channel])) - return out.rstrip("\n") - - def __str__(self): - return self.__repr__() \ No newline at end of file From 5dfeee59777e92ed0a13f4917f6f8c44210ed64a Mon Sep 17 00:00:00 2001 From: Philip Trauner Date: Thu, 23 Feb 2017 12:01:43 +0100 Subject: [PATCH 2/3] Replaced Config with newer version --- Shared/Config.py | 118 ----------------- Shared/Meh.py | 328 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+), 118 deletions(-) delete mode 100644 Shared/Config.py create mode 100644 Shared/Meh.py diff --git a/Shared/Config.py b/Shared/Config.py deleted file mode 100644 index 26a509d..0000000 --- a/Shared/Config.py +++ /dev/null @@ -1,118 +0,0 @@ -from imp import load_source -from os.path import isfile -from marshal import dumps, loads -from types import FunctionType - - -class OptionDuplicateError(IndexError): - def __init__(self, name): - super(IndexError, self).__init__("'%s' already exists" % name) - - -class OptionNotFoundError(IndexError): - def __init__(self, name): - super(IndexError, self).__init__("'%s' does not exist" % name) - - -class NameMustBeStringError(Exception): - def __init__(self): - super(Exception, self).__init__("option names have to be strings") - - -def make_value(value): - if type(value) is str: - value = '"%s"' % value - elif type(value) in (list, tuple, dict): - value = str(value) - elif type(value) is FunctionType: - value = dumps(value.__code__) - return value - - -class Option: - def __init__(self, name, default_value, validator=None, comment=""): - if not type(name) is str: - raise NameMustBeStringError() - self.name = name - self.default_value = default_value - self.validator = validator - self.comment = comment - - -class Config: - def __init__(self, options=[], validation_failed=None, override_on_error=False): - if type(options) in (list, tuple): - for option in options: - if not type(option) is Option: - raise TypeError("all options must be of type Option") - else: - raise TypeError("options must be a list or tuple containing options of type Option") - self.options = options - self.validation_failed = validation_failed - self.override_on_error = override_on_error - - - def read_from_file(self, file): - if isfile(file): - config = load_source("config", file) - error = False - for option in self.options: - # Make sure all options are avaliable - if option.name not in dir(config): - setattr(config, option.name, option.default_value) - error = True - else: - # Make sure all validators pass - if option.validator != None: - value = getattr(config, option.name) - if not option.validator(value): - setattr(config, option.name, option.default_value) - if self.validation_failed != None: - self.validation_failed(option.name, value) - error = True - if self.override_on_error: - if error: - self.write_to_file(file) - return config - else: - raise FileNotFoundError() - - - def add(self, new_option): - if type(new_option) is Option: - for option in self.options: - if new_option.name == option.name: - raise OptionDuplicateError(option.name) - self.options.append(new_option) - else: - raise TypeError("invalid type supplied") - - - def remove(self, option): - if option in self.options: - del self.options[self.options.index(option)] - else: - raise OptionNotFoundError(option.name) - - - def write_to_file(self, file): - open(file, "w").write(self.get()) - - - def get(self): - contains_function = False - out = "" - for option in self.options: - value = make_value(option.default_value) - if type(option.default_value) is FunctionType: - if not contains_function: - out = "from marshal import loads; from types import FunctionType\n\n" + out - contains_function = True - value = 'FunctionType(loads(%s), globals(), "%s")' % (value, option.name) - out += "%s = %s%s\n" % (option.name, value, - (" # %s" % option.comment) if option.comment else "") - return out - - - def __repr__(self): - return self.get() \ No newline at end of file diff --git a/Shared/Meh.py b/Shared/Meh.py new file mode 100644 index 0000000..e704158 --- /dev/null +++ b/Shared/Meh.py @@ -0,0 +1,328 @@ +from imp import load_source +from os.path import isfile +from sys import version + +class OptionDuplicateError(IndexError): + def __init__(self, name): + super(IndexError, self).__init__("'%s' already exists" % name) + + +class OptionNotFoundError(IndexError): + def __init__(self, name): + super(IndexError, self).__init__("'%s' does not exist" % name) + + +class NameMustBeStringError(Exception): + def __init__(self): + super(Exception, self).__init__("option names have to be strings") + + +class ValidationError(Exception): + def __init__(self, option): + super(Exception, self).__init__("invalid value for option '%s'" % option) + + +class UnsupportedTypeError(TypeError): + def __init__(self): + super(TypeError, self).__init__("only list, tuple, dict, bytes, " + "str, float, complex, int and bool are supported (same " + "thing applies to list, dict and tuple contents)") + + +class ExceptionInConfigError(Exception): + """ + Raised if an exception occurs while importing a config file. + + IN: error (hint: error that occured on import) + """ + def __init__(self, error): + self.error = error + super(Exception, self).__init__("error occured during config import (%s)" % + error.__class__.__name__) + +def validate_value(value): + type_value = type(value) + if type_value in (list, tuple): + for element in value: + if not validate_value(element): + return False + elif type_value is dict: + return validate_value(tuple(value.keys())) and validate_value(tuple(value.values())) + elif type_value in (bytes, str, float, complex, int, bool): + return True + else: + return False + return True + +def make_value(value): + if validate_value(value): + if type(value) is str: + value = '"%s"' % value + elif type(value) in (list, tuple, dict): + value = str(value) + return value + else: + raise UnsupportedTypeError() + + +class _EditableConfig: + """ + Automatically created proxy class. + + HINTS: + _values: All options with their respective values + _options: All Option instances that were originally added to + the Config instance + _file: Path to the config file + _validation_failed: Optional function that's called on a validation error + _debug: Debug mode on/off (obviously) + """ + def __init__(self, values, options, file, validation_failed=None, debug=False): + self._values = values + self._options = options + self._file = file + self._validation_failed = validation_failed + self._debug = debug + + + def __getattr__(self, name): + if name in self._values: + return self._values[name] + else: + raise AttributeError("config no attribute '%s'" % name) + + + def __setattr__(self, name, value): + if name == "_values" or name not in self._values: + self.__dict__[name] = value + else: + dump_required = False + for option in self._options: + if option.name == name: + if validate_value(value): + if option.validator != None: + if option.validator(value): + self._values[name] = value + dump_required = True + else: + if self._validation_failed != None: + self._validation_failed(option.name, value) + else: + raise ValidationError(option.name) + else: + self._values[name] = value + dump_required = True + else: + raise UnsupportedTypeError() + if dump_required: + if self._debug: + print("Rewriting config because the value of '%s' changed." % name) + open(self._file, "w").write(self._dumps()) + + + def _dumps(self): + out = "" + for option in self._options: + value = make_value(self._values[option.name]) + out += "%s = %s%s\n" % (option.name, value, + (" # %s" % option.comment) if option.comment else "") + return out.rstrip("\n") + + + def __repr__(self): + return self._dumps() + + +class Option: + + def __init__(self, name, default_value, validator=None, comment=""): + if not type(name) is str: + raise NameMustBeStringError() + if name.startswith("__"): + raise InvalidOptionName() + self._name = name + self.default_value = default_value + self.validator = validator + self.comment = comment + + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + if name.startswith("__"): + raise InvalidOptionName() + self._name = value + + + def __eq__(self, other): + if other.__class__ == Option: + return self.__dict__ == other.__dict__ + + + def __repr__(self): + return "%s = %s" % (self.name, str(self.default_value)) + + +class Config: + """ + The central element of Meh (TM). + IN: + options=[] (type: list, hint: a list of options) + validation_failed=None (type: function, hint: function accepting two + parameters that's called when a validation fails) + + Example usage: + from Meh import Config, Option + config = Config() + config.add(Option("number", 42, validator=lambda x: type(x) is int)) + + CONFIG_PATH = "awesome_config.cfg" + try: + config = config.load(CONFIG_PATH) + except IOError: + config.dump(CONFIG_PATH) + config = config.load(CONFIG_PATH) + + print(config.number) + """ + def __init__(self, options=[], validation_failed=None, debug=False): + if type(options) in (list, tuple): + for option in options: + if not option.__class__ == Option: + raise TypeError("all options must be of type Option") + else: + raise TypeError("options must be a list or tuple containing options of type Option") + self.options = options + self.validation_failed = validation_failed + self.debug = debug + self._iterator_index = 0 + + + def __iter__(self): + return self + + + def __next__(self): + if self._iterator_index < len(self.options): + self._iterator_index += 1 + return self.options[self._iterator_index - 1] + self._iterator_index = 0 + raise StopIteration + + + def load(self, file): + """ + Returns the actual read- and editable config + IN: file (type: str, hint: should be a valid path) + """ + if isfile(file): + try: + config = load_source("config", file) + except Exception as e: + raise ExceptionInConfigError(e) + option_missing = False + values = {} + for option in self.options: + # Make sure all options are avaliable (validators aren't run in this case + # because there are no values defined) + if option.name not in dir(config): + values[option.name] = option.default_value + option_missing = True + else: + # Retrieve the option value + value = getattr(config, option.name) + # Make sure validator passes + if option.validator != None: + # If validation doesn't pass + if not option.validator(value): + # Resort to default value + values[option.name] = option.default_value + if self.validation_failed != None: + self.validation_failed(option.name, value) + else: + raise ValidationError(option.name) + option_missing = True + # If validation passes + else: + values[option.name] = value + else: + values[option.name] = value + if option_missing: + self.dump(file) + return _EditableConfig(values, self.options, file, + validation_failed=self.validation_failed, debug=self.debug) + else: + error = "'%s' not found" % file + raise FileNotFoundError(error) if version.startswith("3") else IOError(error) + + + def __add__(self, other): + try: + self.add(other) + except TypeError: + return NotImplemented + return self + + + def __sub__(self, other): + try: + self.remove(other) + except TypeError: + return NotImplemented + return self + + + def add(self, option): + """ + Adds an option to a Config instance + IN: option (type: Option) + """ + if option.__class__ == Option: + for _option in self.options: + if option.name == _option.name: + raise OptionDuplicateError(_option.name) + self.options.append(option) + else: + raise TypeError("invalid type supplied") + + + def remove(self, option): + """ + Removes an option from a Config instance + IN: option (type: Option) + """ + if option.__class__ == Option: + if option in self.options: + del self.options[self.options.index(option)] + else: + raise OptionNotFoundError(option.name) + else: + raise TypeError("invalid type supplied") + + + def dump(self, file): + """ + Writes output of dumps() to the path provided + IN: file (type: str, hint: should be a valid path) + """ + open(file, "w").write(self.dumps()) + + + def dumps(self): + """ + Returns contents of config file as string + OUT: out (type: str, hint: config content) + """ + out = "" + for option in self.options: + value = make_value(option.default_value) + out += "%s = %s%s\n" % (option.name, value, + (" # %s" % option.comment) if option.comment else "") + return out.rstrip("\n") + + + def __repr__(self): + return self.dumps() \ No newline at end of file From 2d58f095bdc2f5e33ba17424e2befd94d5913a47 Mon Sep 17 00:00:00 2001 From: Philip Trauner Date: Thu, 23 Feb 2017 12:04:05 +0100 Subject: [PATCH 3/3] Create fl0w user by default --- Wallaby/Wallaby.py | 69 +++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/Wallaby/Wallaby.py b/Wallaby/Wallaby.py index 24b5e99..0b5e85a 100644 --- a/Wallaby/Wallaby.py +++ b/Wallaby/Wallaby.py @@ -1,6 +1,6 @@ from Highway import Route, Pipe, Client +from Meh import Config, Option, ExceptionInConfigError import Logging -import Config import Utils import socket @@ -10,29 +10,28 @@ import sys import subprocess from random import randint from _thread import start_new_thread +from ctypes import cdll import threading +import json CHANNEL = 2 IS_WALLABY = Utils.is_wallaby() -PATH = "/home/root/Documents/KISS/bin/" if IS_WALLABY else (sys.argv[1] if len(sys.argv) > 1 else None) - -PATH = os.path.abspath(PATH) - -if PATH[-1] != "/": - PATH = PATH + "/" +USERS_LOCATION = "/home/root/Documents/KISS/users.json" LIB_WALLABY = "/usr/lib/libwallaby.so" -WALLABY_PROGRAMS = "/root/Documents/KISS/bin/" -if not PATH: - Logging.error("No path specified. (Necessary on simulated Wallaby controllers.)") - exit(1) if not IS_WALLABY: Logging.warning("Binaries that were created for Wallaby Controllers will not run on a simulated Wallaby.") +def get_users(): + if IS_WALLABY: + return list(json.loads(open(USERS_LOCATION, "r").read()).keys()) + return ["Default User"] + + class SensorReadout: ANALOG = 1 DIGITAL = 2 @@ -40,7 +39,7 @@ class SensorReadout: MODES = tuple(NAMED_MODES.keys()) - def __init__(self, handler, poll_rate=0.5): + def __init__(self, handler, poll_rate=0.2): self.poll_rate = poll_rate self.handler = handler self.peer_lock = threading.Lock() @@ -159,6 +158,7 @@ class Identify(Pipe): Logging.success("I was identified!") + class ListPrograms(Pipe): def run(self, data, peer, handler): programs = [] @@ -178,7 +178,7 @@ class StopPrograms(Pipe): def run(self, data, peer, handler): if handler.debug: Logging.info("Stopping all botball programs.") - if subprocess.call(["killall", "botball_user_program"], + if subprocess.call(["killall", "botball_user_program"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT): handler.pipe(self.__class__.NO_PROGRAMS_RUNNING, "stop_programs", peer) @@ -196,7 +196,7 @@ class RunProgram(Pipe): data = data + "/" path = "%s%s/botball_user_program" % (PATH, data) if os.path.isfile(path): - program = subprocess.Popen(self.command + [path], + program = subprocess.Popen(self.command + [path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) start_new_thread(self.stream_stdout, (program, peer, handler)) else: @@ -313,21 +313,40 @@ class Handler(Client): Logging.info("Unsubscribing '%s' from all sensor updates." % peer) self.routes["sensor"].sensor_readout.unsubscribe_all(peer) +if IS_WALLABY: + if not "fl0w" in get_users(): + json.loads(open(USERS_LOCATION, "r").read())["fl0w"] = {"mode" : "Advanced"} + try: + os.mkdir(FL0W_USER_PATH) + except FileExistsError: + pass + PATH = FL0W_USER_PATH + "bin/" +else: + if len(sys.argv) == 2: + if os.path.exists(sys.argv[1]): + PATH = os.path.abspath(sys.argv[1]) + else: + Logging.error("Location has to be provided in dev-env.") + exit(1) + +if PATH[-1] != "/": + PATH = PATH + "/" CONFIG_PATH = "wallaby.cfg" -config = Config.Config() -config.add(Config.Option("server_address", "ws://127.0.0.1:3077")) -config.add(Config.Option("debug", False, validator=lambda x: True if True or False else False)) -config.add(Config.Option("output_unbuffer", "stdbuf")) -config.add(Config.Option("identify_sound", "Wallaby/identify.wav", - validator=lambda x: os.path.isfile(x))) +config = Config() +config.add(Option("server_address", "ws://127.0.0.1:3077")) +config.add(Option("debug", True, validator=lambda x: True if True or False else False)) +config.add(Option("output_unbuffer", "stdbuf")) +config.add(Option("identify_sound", "Wallaby/identify.wav", + validator=lambda sound: os.path.isfile(sound))) + try: - config = config.read_from_file(CONFIG_PATH) -except FileNotFoundError: - config.write_to_file(CONFIG_PATH) - config = config.read_from_file(CONFIG_PATH) + config = config.load(CONFIG_PATH) +except (IOError, ExceptionInConfigError): + config.dump(CONFIG_PATH) + config = config.load(CONFIG_PATH) try: @@ -335,7 +354,7 @@ try: # setup has to be called before the connection is established ws.setup({"subscribe" : Subscribe(), "hostname" : Hostname(), "processes" : Processes(), "sensor" : Sensor(), - "identify" : Identify(), "list_programs" : ListPrograms(), + "identify" : Identify(), "list_programs" : ListPrograms(), "whoami" : WhoAmI(), "run_program" : RunProgram(config.output_unbuffer), "stop_programs" : StopPrograms(), "shutdown" : Shutdown(), "reboot" : Reboot()},