Пример #1
0
class pypilotClient(object):
    def __init__(self, host=False):
        self.values = ClientValues(self)
        self.watches = {}
        self.wwatches = {}
        self.received = []
        self.last_values_list = False

        if False:
            self.server = host
            host = '127.0.0.1'

        if host and type(host) != type(''):
            # host is the server object
            self.server = host
            self.connection = host.pipe()
            self.poller = select.poll()
            fd = self.connection.fileno()
            if fd:
                self.poller.register(fd, select.POLLIN)
                self.values.onconnected()
            return

        config = {}
        try:
            configfilepath = os.getenv('HOME') + '/.pypilot/'
            if not os.path.exists(configfilepath):
                os.makedirs(configfilepath)
            if not os.path.isdir(configfilepath):
                raise Exception(configfilepath + 'should be a directory')
        except Exception as e:
            print('os not supported')
            configfilepath = '/.pypilot/'
        self.configfilename = configfilepath + 'pypilot_client.conf'

        try:
            file = open(self.configfilename)
            config = pyjson.loads(file.readline())
            file.close()

        except Exception as e:
            print('failed to read config file:', self.configfilename, e)
            config = {}

        if host:
            if ':' in host:
                i = host.index(':')
                config['host'] = host[:i]
                config['port'] = host[i + 1:]
            else:
                config['host'] = host
        if not 'host' in config:
            config['host'] = '127.0.0.1'

        if not 'port' in config:
            config['port'] = DEFAULT_PORT
        self.config = config

        self.connection = False  # connect later
        self.connection_in_progress = False

    def onconnected(self):
        #print('connected to pypilot server', time.time())
        self.last_values_list = False

        # write config if connection succeeds
        try:
            file = open(self.configfilename, 'w')
            file.write(pyjson.dumps(self.config) + '\n')
            file.close()
            self.write_config = False
        except IOError:
            print('failed to write config file:', self.configfilename)
        except Exception as e:
            print('Exception writing config file:', self.configfilename, e)

        self.connection = LineBufferedNonBlockingSocket(
            self.connection_in_progress, self.config['host'])
        self.connection_in_progress = False
        self.poller = select.poll()
        self.poller.register(self.connection.socket, select.POLLIN)
        self.wwatches = {}
        for name, value in self.watches.items():
            self.wwatches[name] = value  # resend watches

        self.values.onconnected()

    def poll(self, timeout=0):
        if not self.connection:
            if self.connection_in_progress:
                events = self.poller_in_progress.poll(0)
                if events:
                    fd, flag = events.pop()
                    if not (flag & select.POLLOUT):
                        # hung hup
                        self.connection_in_progress.close()
                        self.connection_in_progress = False
                        return

                    self.onconnected()
                return
            else:
                if not self.connect(False):
                    time.sleep(timeout)
                return

        # inform server of any watches we have changed
        if self.wwatches:
            self.connection.write('watch=' + pyjson.dumps(self.wwatches) +
                                  '\n')
            #print('watch', watches, self.wwatches, self.watches)
            self.wwatches = {}

        # send any delayed watched values
        self.values.send_watches()

        if self.connection.fileno():
            # flush output
            self.connection.flush()
            try:
                events = self.poller.poll(int(1000 * timeout))
            except Exception as e:
                print('exception polling', e, os.getpid())
                self.disconnect()
                return

            if not events:
                return  # no data ready

            fd, flag = events.pop()
            if not (flag & select.POLLIN) or (self.connection and
                                              not self.connection.recvdata()):
                # other flags indicate disconnect
                self.disconnect()  # recv returns 0 means connection closed
                return

        # read incoming data line by line
        while True:
            t0 = time.monotonic()
            line = self.connection.readline()
            if not line:
                return
            try:
                name, data = line.rstrip().split('=', 1)
                if name == 'error':
                    print('server error:', data)
                    continue
                value = pyjson.loads(data)
            except ValueError as e:
                print('client value error:', line, e)
                #raise Exception
                continue

            except Exception as e:
                print('invalid message from server:', line, e)
                raise Exception()

            if name in self.values.values:  # did this client register this value
                self.values.values[name].set(value)
            else:
                self.received.append((name, value))  # remote value

    # polls at least as long as timeout
    def disconnect(self):
        if self.connection:
            self.connection.close()
        self.connection = False

    def connect(self, verbose=True):
        if self.connection:
            print('warning, client aleady has connection')

        try:
            host_port = self.config['host'], self.config['port']
            self.connection_in_progress = False
            self.connection_in_progress = socket.socket(
                socket.AF_INET, socket.SOCK_STREAM)

            self.connection_in_progress.settimeout(1)  # set to 0 ?
            self.connection_in_progress.connect(host_port)
        except OSError as e:
            import errno
            if e.args[0] is errno.EINPROGRESS:
                self.poller_in_progress = select.poll()
                self.poller_in_progress.register(
                    self.connection_in_progress.fileno(), select.POLLOUT)
                return True

            self.connection_in_progress = False
            if e.args[0] == 111:  # refused
                pass
            else:
                print('connect failed to %s:%d' % host_port, e)
            #time.sleep(.25)

            return False

        #except Exception as e:
        #    if verbose:
        #        print('connect failed to %s:%d' % host_port, e)

        self.onconnected()
        return True

    def receive_single(self):
        if self.received:
            ret = self.received[0]
            self.received = self.received[1:]
            return ret
        return False

    def receive(self, timeout=0):
        self.poll(timeout)
        ret = {}
        for msg in self.received:
            name, value = msg
            ret[name] = value
        self.received = []
        return ret

    def send(self, msg):
        if self.connection:
            self.connection.write(msg)

    def set(self, name, value):
        # quote strings
        if type(value) == type('') or type(value) == type(u''):
            value = '"' + value + '"'
        elif type(value) == type(True):
            value = 'true' if value else 'false'
        self.send(name + '=' + str(value) + '\n')

    def watch(self, name, value=True):
        if name in self.watches:  # already watching
            if value is False:
                del self.watches[name]
                self.wwatches[name] = value
                return
            elif self.watches[name] is value:
                return  # same watch ignore
        elif value is False:
            return  # already not watching

        self.watches[name] = value
        self.wwatches[name] = value

    def clear_watches(self):
        for name in self.watches:
            self.wwatches[name] = False
        self.watches = {}

    def register(self, value):
        self.values.register(value)
        value.client = self
        return value

    def get_values(self):
        if self.values.value:
            return self.values.value
        return {}

    def list_values(self, timeout=0):
        self.watch('values')
        t0, dt, ret = time.monotonic(), timeout, self.values.value
        while not ret and dt >= 0:
            self.poll(dt)
            ret = self.values.value
            dt = timeout - (time.monotonic() - t0)
        if self.last_values_list == ret:
            return False
        self.last_values_list = ret
        return ret

    def info(self, name):
        return self.values.value[name]
Пример #2
0
class pypilotClient(object):
    def __init__(self, host=False):
        if sys.version_info[0] < 3:
            import failedimports

        self.values = ClientValues(self)
        self.watches = {}
        self.wwatches = {}
        self.received = []
        self.last_values_list = False

        if False:
            self.server = host
            host='127.0.0.1'

        if host and type(host) != type(''):
            # host is the server object for direct pipe connection
            self.server = host
            self.connection = host.pipe()
            self.poller = select.poll()
            fd = self.connection.fileno()
            if fd:
                self.poller.register(fd, select.POLLIN)
                self.values.onconnected()
            self.timeout_time = False # no timeout for pipe connection
            return

        self.timeout_time = time.monotonic()

        config = {}
        try:
            configfilepath = os.getenv('HOME') + '/.pypilot/'
            if not os.path.exists(configfilepath):
                os.makedirs(configfilepath)
            if not os.path.isdir(configfilepath):
                raise Exception(configfilepath + 'should be a directory')
        except Exception as e:
            print('os not supported')
            configfilepath = '/.pypilot/'
        self.configfilename = configfilepath + 'pypilot_client.conf'

        try:
            file = open(self.configfilename)
            config = pyjson.loads(file.readline())
            file.close()
                
        except Exception as e:
            print(_('failed to read config file:'), self.configfilename, e)
            config = {}

        if host:
            if ':' in host:
                i = host.index(':')
                config['host'] = host[:i]
                config['port'] = host[i+1:]
            else:
                config['host'] = host
        
        if not 'host' in config:
            config['host'] = '127.0.0.1'

        if not 'port' in config:
            config['port'] = DEFAULT_PORT
        self.config = config
            
        self.connection = False # connect later
        self.connection_in_progress = False
        self.can_probe = True
        self.probed = False

    def onconnected(self):
        #print('connected to pypilot server', time.time())
        self.last_values_list = False

        # write config if connection succeeds
        try:
            file = open(self.configfilename, 'w')
            file.write(pyjson.dumps(self.config) + '\n')
            file.close()
            self.write_config = False
        except IOError:
            print(_('failed to write config file:'), self.configfilename)
        except Exception as e:
            print(_('Exception writing config file:'), self.configfilename, e)

        self.connection = LineBufferedNonBlockingSocket(self.connection_in_progress, self.config['host'])
        self.connection_in_progress = False
        self.poller = select.poll()
        self.poller.register(self.connection.socket, select.POLLIN)
        self.wwatches = {}
        for name, value in self.watches.items():
            self.wwatches[name] = value # resend watches

        self.values.onconnected()

    def probe(self):
        if not self.can_probe:
            return # do not search if host is specified by commandline, or again

        try:
            from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
        except Exception as e:
            print(_('failed to') + ' import zeroconf, ' + _('autodetecting pypilot server not possible'))
            print(_('try') + ' pip3 install zeroconf' + _('or') + ' apt install python3-zeroconf')

        class Listener:
            def __init__(self, client):
                self.client = client

            def remove_service(self, zeroconf, type, name):
                pass

            def add_service(self, zeroconf, type, name):
                #print('service', name)
                self.name_type = name, type
                info = zeroconf.get_service_info(type, name)
                #print('info', info, info.parsed_addresses()[0])
                if not info:
                    return
                try:
                    #for name, value in info.properties.items():
                    config = self.client.config
                    #print('info', info.addresses)
                    config['host'] = socket.inet_ntoa(info.addresses[0])
                    config['port'] = info.port
                    print('found pypilot', config['host'], config['port'])
                    self.client.probed = True
                    zeroconf.close()
                except Exception as e:
                    print('zeroconf service exception', e)
                    

        self.can_probe = False
        zeroconf = Zeroconf()
        listener = Listener(self)
        browser = ServiceBrowser(zeroconf, "_pypilot._tcp.local.", listener)

    def poll(self, timeout=0):
        if not self.connection:
            if self.connection_in_progress:
                events = self.poller_in_progress.poll(0)                
                if events:
                    fd, flag = events.pop()
                    if not (flag & select.POLLOUT):
                        # hung hup
                        self.connection_in_progress.close()
                        self.connection_in_progress = False
                        self.probe()
                        return

                    self.onconnected()
                return
            else:
                if not self.connect(False):
                    time.sleep(timeout)
                return
            
        # inform server of any watches we have changed
        if self.wwatches:
            self.connection.write('watch=' + pyjson.dumps(self.wwatches) + '\n')
            #print('watch', watches, self.wwatches, self.watches)
            self.wwatches = {}

        # send any delayed watched values
        self.values.send_watches()

        if self.connection.fileno():
            # flush output
            self.connection.flush()
            try:
                events = self.poller.poll(int(1000 * timeout))
            except Exception as e:
                print('exception polling', e, os.getpid())
                self.disconnect()
                return

            if not events:
                # 3 seconds without data in either direction, send linefeed
                # if the connection is lost, sending some data is needed
                # useful to cause the connection to reset
                if self.timeout_time and time.monotonic() - self.timeout_time > 3:
                    self.update_timeout()
                    self.send('\n')
                return # no data ready

            self.update_timeout()
            
            fd, flag = events.pop()
            if not (flag & select.POLLIN) or (self.connection and not self.connection.recvdata()):
                # other flags indicate disconnect
                self.disconnect() # recv returns 0 means connection closed
                return

        # read incoming data line by line
        while True:
            line = self.connection.readline()
            if not line:
                return
            #print('line', line, time.monotonic())
            try:
                name, data = line.rstrip().split('=', 1)
                if name == 'error':
                    print('server error:', data)
                    continue
                value = pyjson.loads(data)
            except ValueError as e:
                print('client value error:', line, e)
                #raise Exception
                continue

            except Exception as e:
                print(_('invalid message from server:'), line, e)
                raise Exception()

            if name in self.values.values: # did this client register this value
                self.values.values[name].set(value)
            else:
                self.received.append((name, value)) # remote value

    # polls at least as long as timeout
    def disconnect(self):
        if self.connection:
            self.connection.close()
        self.connection = False

    def probewait(self, timeout):
        t0 = time.monotonic()
        while time.monotonic() - t0 < timeout:
            if self.probed:
                return True
            time.sleep(.1)
        return False

    def connect(self, verbose=True):
        if self.connection:
            print(_('warning, pypilot client aleady has connection'))

        try:
            host_port = self.config['host'], self.config['port']
            self.connection_in_progress = False
            self.poller_in_progress = select.poll()
            self.connection_in_progress = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            
            self.connection_in_progress.settimeout(1) # set to 0 ?
            self.connection_in_progress.connect(host_port)
        except OSError as e:
            import errno
            if e.args[0] is errno.EINPROGRESS:
                self.poller_in_progress.register(self.connection_in_progress.fileno(), select.POLLOUT)
                return True

            self.connection_in_progress = False
            if e.args[0] == 111: # refused
                pass
            else:
                print(_('connect failed to') + (' %s:%d' % host_port), e)

            self.probe()
            #time.sleep(.25)
                
            return False
                
        #except Exception as e:
        #    if verbose:
        #        print('connect failed to %s:%d' % host_port, e)

        self.onconnected()
        return True
    
    def receive_single(self):
        if self.received:
            ret = self.received[0]
            self.received = self.received[1:]
            return ret
        return False

    def receive(self, timeout=0):
        self.poll(timeout)
        ret = {}
        for msg in self.received:
            name, value = msg
            ret[name] = value
        self.received = []
        return ret

    def update_timeout(self):
        if self.timeout_time:
            self.timeout_time = time.monotonic()            

    def send(self, msg):
        if self.connection:
            self.update_timeout()
            self.connection.write(msg)
    
    def set(self, name, value):
        # quote strings
        if type(value) == type('') or type(value) == type(u''):
            value = '"' + value + '"'
        elif type(value) == type(True):
            value = 'true' if value else 'false'
        self.send(name + '=' + str(value) + '\n')

    def watch(self, name, value=True):
        if name in self.watches: # already watching
            if value is False:
                del self.watches[name]
                self.wwatches[name] = value
                return
            elif self.watches[name] is value:
                return # same watch ignore
        elif value is False:
            return # already not watching

        self.watches[name] = value
        self.wwatches[name] = value

    def clear_watches(self):
        for name in self.watches:
            self.wwatches[name] = False
        self.watches = {}

    def register(self, value):
        self.values.register(value)
        value.client = self
        return value

    def get_values(self):
        if self.values.value:
            return self.values.value
        return {}

    def list_values(self, timeout=0):
        self.watch('values')
        t0, dt, ret = time.monotonic(), timeout, self.values.value
        while not ret and dt >= 0:
            self.poll(dt)
            ret = self.values.value
            dt = timeout - (time.monotonic()-t0)
        if self.last_values_list == ret:
            return False
        self.last_values_list = ret
        return ret

    def info(self, name):
        return self.values.value[name]