diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bus.py | 169 | ||||
-rw-r--r-- | src/native_bus.pyx | 221 |
2 files changed, 390 insertions, 0 deletions
diff --git a/src/bus.py b/src/bus.py new file mode 100644 index 0000000..267a435 --- /dev/null +++ b/src/bus.py @@ -0,0 +1,169 @@ +# -*- python -*- +''' +MIT/X Consortium License + +Copyright © 2015 Mattias Andrée <maandree@member.fsf.org> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +''' + + +class Bus: + ''' + Message broadcasting interprocess communication + ''' + + + RDONLY = 1 + ''' + Open the bus for reading only + ''' + + WRONLY = 0 + ''' + Open the bus for writing only + ''' + + RDWR = 0 + ''' + Open the bus for both reading and writing only + ''' + + EXCL = 2 + ''' + Fail to create bus if its file already exists + ''' + + INTR = 4 + ''' + Fail if interrupted + ''' + + + def __init__(self, pathname : str = None): + ''' + Constructor + + @param pathname:str The pathname of the bus, `None` if `create` should select a random pathname + ''' + self.pathname = pathname + self.bus = None + + + def create(self, flags : int = 0) -> str: + ''' + Create the bus + + @param flags:or_flag `Bus.EXCL` (if the pathname is not `None`) to fail if the file + already exists, otherwise if the file exists, nothing will happen; + `Bus.INTR` to fail if interrupted + @return :str The pathname of the bus + ''' + from native_bus import bus_create_wrapped + self.pathname = bus_create_wrapped(self.pathname, flags) + if self.pathname is None: + raise self.__oserror() + return self.pathname + + + def unlink(self): + ''' + Remove the bus + ''' + from native_bus import bus_unlink_wrapped + if bus_unlink_wrapped(self.pathname) == -1: + raise self.__oserror() + + + def open(self, flags : int = 0): + ''' + Open an existing bus + + @param flags:int `Bus.RDONLY`, `Bus.WRONLY` or `Bus.RDWR`, the value must not be negative + ''' + from native_bus import bus_close_wrapped, bus_allocate, bus_open_wrapped + if self.bus is not None: + if bus_close_wrapped(self.bus) == -1: + raise self.__oserror() + else: + self.bus = bus_allocate() + if self.bus == 0: + raise self.__oserror() + if bus_open_wrapped(self.bus, self.pathname, flags) == -1: + raise self.__oserror() + + + def close(self): + ''' + Close the bus + ''' + from native_bus import bus_close_wrapped, bus_deallocate + if self.bus is not None: + if bus_close_wrapped(self.bus) == -1: + raise self.__oserror() + bus_deallocate(self.bus) + self.bus = None + + + def write(self, message : str): + ''' + Broadcast a message a bus + + @param message:str The message to write, may not be longer than 2047 bytes after UTF-8 encoding + ''' + from native_bus import bus_write + if bus_write(self.bus, message) == -1: + raise self.__oserror() + + + def read(self, callback : callable, user_data = None): + ''' + Listen (in a loop, forever) for new message on a bus + + @param bus Bus information + @param callback Function to call when a message is received, the + input parameters will be the read message and + `user_data` from the function's [Bus.read] parameter + with the same name. The message must have been parsed + or copied when `callback` returns as it may be over + overridden after that time. `callback` should + return either of the the values: + 0: stop listening + 1: continue listening + -1: an error has occurred + @param user_data See description of `callback` + ''' + from native_bus import bus_read + if bus_read(self.bus, callback, user_data) == -1: + raise self.__oserror() + + + def __oserror(self): + ''' + Create an OSError + + @return :OSError The OS error + ''' + import os, ctypes + err = ctypes.get_errno() + err = OSError(err, os.strerror(err)) + if err.errno == os.errno.ENOENT: + err.filename = self.pathname + return err + diff --git a/src/native_bus.pyx b/src/native_bus.pyx new file mode 100644 index 0000000..016fa3c --- /dev/null +++ b/src/native_bus.pyx @@ -0,0 +1,221 @@ +# -*- python -*- +''' +MIT/X Consortium License + +Copyright © 2015 Mattias Andrée <maandree@member.fsf.org> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +''' + +cimport cython + +from libc.stdlib cimport malloc, free + + +cdef extern int bus_create(const char *, int, char **) +''' +Create a new bus + +@param file The pathname of the bus, `NULL` to create a random one +@param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file + already exists, otherwise if the file exists, nothing + will happen; + `BUS_INTR` to fail if interrupted +@param out_file Output parameter for the pathname of the bus +@return 0 on success, -1 on error +''' + +cdef extern int bus_unlink(const char *) +''' +Remove a bus + +@param file The pathname of the bus +@return 0 on success, -1 on error +''' + +cdef extern int bus_open(long, const char *, int) +''' +Open an existing bus + +@param bus Bus information to fill +@param file The filename of the bus +@param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR`, + the value must not be negative +@return 0 on success, -1 on error +''' + +cdef extern int bus_close(long) +''' +Close a bus + +@param bus Bus information +@return 0 on success, -1 on error +''' + +cdef extern int bus_write(long, const char *) +''' +Broadcast a message a bus + +@param bus Bus information +@param message The message to write, may not be longer than + `BUS_MEMORY_SIZE` including the NUL-termination +@return 0 on success, -1 on error +''' + +cdef extern int bus_read(long, int (*)(const char *, void *), void *) +''' +Listen (in a loop, forever) for new message on a bus + +@param bus Bus information +@param callback Function to call when a message is received, the + input parameters will be the read message and + `user_data` from `bus_read`'s parameter with the + same name. The message must have been parsed or + copied when `callback` returns as it may be over + overridden after that time. `callback` should + return either of the the values: + 0: stop listening + 1: continue listening + -1: an error has occurred +@return 0 on success, -1 on error +''' + + +def bus_allocate() -> int: + ''' + Allocate memory for a bus + + @return The address of the allocated memory + ''' + n = 2 * sizeof(long long) + sizeof(int) + sizeof(char *) + return <long>malloc(n) + + +def bus_deallocate(address : int): + ''' + Deallocate memory for a bus + + @param address The address of the allocated memory + ''' + free(<void *><long>address) + + +def bus_create_wrapped(file : str, flags : int) -> str: + ''' + Create a new bus + + @param file The pathname of the bus, `None` to create a random one + @param flags `BUS_EXCL` (if `file` is not `None`) to fail if the file + already exists, otherwise if the file exists, nothing + will happen; + `BUS_INTR` to fail if interrupted + @return The pathname of the bus, `None` on error; + `file` is returned unless `file` is `None` + ''' + cdef const char* cfile + cdef char* ofile + cdef bytes bs + if file is not None: + bs = file.encode('utf-8') + bytes([0]) + cfile = bs + r = bus_create(cfile, flags, <char **>NULL) + return file if r == 0 else None + r = bus_create(<char *>NULL, flags, &ofile) + if r == 0: + bs = ofile + return bs.encode('utf-8', 'strict') + return None + + +def bus_unlink_wrapped(file : str) -> int: + ''' + Remove a bus + + @param file The pathname of the bus + @return 0 on success, -1 on error + ''' + cdef const char* cfile + cdef bytes bs + bs = file.encode('utf-8') + bytes([0]) + cfile = bs + return bus_unlink(cfile) + + +def bus_open_wrapped(bus : int, file : str, flags : int) -> int: + ''' + Open an existing bus + + @param bus Bus information to fill + @param file The filename of the bus + @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR`, + the value must not be negative + @return 0 on success, -1 on error + ''' + cdef const char* cfile + cdef bytes bs + bs = file.encode('utf-8') + bytes([0]) + cfile = bs + return bus_open(<long>bus, cfile, <int>flags) + + +def bus_close_wrapped(bus : int) -> int: + ''' + Close a bus + + @param bus Bus information + @return 0 on success, -1 on error + ''' + return bus_close(<long>bus) + + +def bus_write_wrapped(bus : int, message : str) -> int: + ''' + Broadcast a message a bus + + @param bus Bus information + @param message The message to write, may not be longer than + `BUS_MEMORY_SIZE` including the NUL-termination + @return 0 on success, -1 on error + ''' + cdef const char* cmessage + cdef bytes bs + bs = message.encode('utf-8') + bytes([0]) + cmessage = bs + return bus_write(<long>bus, cmessage) + + +def bus_read_wrapped(bus : int, callback : callable, user_data) -> int: + ''' + Listen (in a loop, forever) for new message on a bus + + @param bus Bus information + @param callback Function to call when a message is received, the + input parameters will be the read message and + `user_data` from `bus_read`'s parameter with the + same name. The message must have been parsed or + copied when `callback` returns as it may be over + overridden after that time. `callback` should + return either of the the values: + 0: stop listening + 1: continue listening + -1: an error has occurred + @return 0 on success, -1 on error + ''' + return bus_read(<long>bus, <int (*)(const char *, void *)><void *>callback, <void *>user_data) + |