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]
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]