#!/usr/bin/env python import sys import socket import select import time import threading tmout = 3.0 class Empty: def __init__(self, dic={}): self.to_attr(dic) def to_attr(self, dic): for (k, v) in dic.items(): setattr(self, k, v) return self def str_div(s, delim): i = s.index(delim) if delim in s else len(s) return (s[:i], s[i+len(delim):]) readable = lambda fd: select.select([fd], [], [], tmout)[0] == [fd] def rest_new(): e = Empty() e.s = '' def clear(): e.s = '' def get(): (r, e.s) = str_div(e.s, '\n') return r def add_get(s): e.s = e.s + s return get() return e.to_attr( locals() ) def recv_base(fd, rest, dis_conn_func, conn_func=None): s = rest.get() if s: return s if conn_func: fd = conn_func() if not fd: return False # no connection if not readable(fd): return None # timeout try: bufmax = 4096 s = fd.recv(bufmax).decode('utf-8') except: return dis_conn_func(False) if not s: return dis_conn_func(False) return rest.add_get(s) def send_base(s, fd, dis_conn_func, conn_func=None): if conn_func: fd = conn_func() if not fd: return False # no connection try: fd.sendall( (s+'\n').encode('utf-8') ) except: return dis_conn_func(False) return True def conn_lst_new(): # for srv_accept_conn_new lst = [] def add(conn): if conn not in lst: lst.append(conn) def remove(conn): if conn in lst: lst.remove(conn) def get_by_name(name): for conn in lst: if conn.name == name: return conn return None others = lambda conn: list( filter( lambda o: o.name != conn.name, lst ) ) names = lambda : list( map( lambda conn: conn.name, lst ) ) other_names = lambda conn: list( map( lambda o: o.name, others(conn) ) ) return Empty( locals() ) def srv_accept_conn_new(fd, conn_lst): e = Empty() e.name = None cond = threading.Condition() e.lock_name = None rest = rest_new() def dis_conn(v=None): if e.name: send_others('srv', 'logout ' + e.name) e.name = None rest.clear() if fd: fd.close() conn_lst.remove(e) return v recv = lambda : recv_base(fd, rest, dis_conn) send = lambda s: send_base(s, fd, dis_conn) def name_modify(org): if ' ' in org: org = org.replace(' ', '_') id = 2 name = org while conn_lst.get_by_name(name): name = '{}_{}'.format(org, id) id += 1 return name def login(): if not send('who?'): return dis_conn(False) name = None lmt = time.time() + 30 while time.time() < lmt: name = recv() if name != None: break if not name: return dis_conn(False) e.name = name_modify(name) if not send(e.name): return dis_conn(False) send_others('srv', 'login ' + e.name) return True def lock_name_set(name): cond.acquire() if e.lock_name != name: if name != None: while e.lock_name != None: cond.wait() e.lock_name = name else: e.lock_name = None cond.notify() cond.release() def send_from(from_name, s): s = '{}> {}'.format(from_name, s) f = False cond.acquire() if e.lock_name == from_name: r = send(s) else: while e.lock_name != None: cond.wait() e.lock_name = from_name r = send(s) e.lock_name = None cond.notify() cond.release() return r def send_others(from_name, s): if not from_name: from_name = 'srv' for to in conn_lst.others(e): to.send_from(from_name, s) def recv_to_names(): s = recv() if not s: return s to_names = [] while s.startswith('@'): (name, s) = str_div(s[1:], ' ') to_names.append(name) if not to_names: to_names = conn_lst.other_names(e) return (s, to_names) conn_lst.add(e) return e.to_attr( locals() ) def srv_th_func(conn, th_quit, srv_quit): if not conn.login(): return conn_lst = conn.conn_lst def is_lock_unlock(s): r = False for k in ('lock', 'unlock'): if not s.startswith(k+' '): continue name = s[len(k)+1:] o = conn_lst.get_by_name(name) if not o: break conn.lock_name_set(o.name if k == 'lock' else None) o.lock_name_set(conn.name if k == 'lock' else None) r = True return r def srv_proc(s): r = "? '{}'".format(s) # err if s == 'kill': srv_quit.set() return elif s in ('exit', 'quit', 'bye'): th_quit.set() return elif s == 'names': r = ' '.join( conn_lst.names() ) elif s == 'other_names': r = ' '.join( other_names(conn) ) elif is_lock_unlock(s): return conn.send_from('srv', r) while not th_quit.wait(0): r = conn.recv_to_names() if r == False: break if not r: # None or '' continue (s, to_names) = r for to_name in to_names: if to_name == 'srv': srv_proc(s) continue to = conn_lst.get_by_name(to_name) if to: to.send_from(conn.name, s) conn.dis_conn() def srv(port): srv_quit = threading.Event() ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ss.bind(('', port)) ss.listen(5) ths = [] conn_lst = conn_lst_new() while not srv_quit.wait(0): if not readable(ss): continue (fd, adr) = ss.accept() conn = srv_accept_conn_new(fd, conn_lst) th_quit = threading.Event() th = threading.Thread( target=srv_th_func, args=(conn, th_quit, srv_quit) ) ths.append( (th, th_quit) ) th.daemon = True th.start() for to in conn_lst.lst: to.send_from('srv', 'shutdown') ss.close() for (th, th_quit) in ths: th_quit.set() th.join() def cli_new(host, port, name0, watch_func=None): e = Empty() e.fd = None e.name = None e.watch_func = watch_func # func(name, 'login' or 'logout') others = [] rest = rest_new() def dis_conn(v=None): if e.name: e.name = None rest.clear() if e.fd: e.fd.close() e.fd = None return v def conn(): if e.fd == None: e.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: e.fd.connect((host, port)) except: time.sleep(tmout) return dis_conn(False) return e.fd # for recv_base, send_base recv = lambda : recv_base(e.fd, rest, dis_conn, conn) send = lambda s: send_base(s, e.fd, dis_conn, conn) def login(): if not e.name: if recv() != 'who?': return dis_conn(False) if not send(name0): return False s = recv() if not s: return dis_conn(False) e.name = s if e.watch_func: others.extend( others_get() ) for o in others: e.watch_func(o, 'login') return e.name def send_to(name, s): if not login(): return False if name: s = '@{} {}'.format(name, s) return send(s) def split_from(s): k = '> ' return str_div(s, k) if k in s else ('', s) def recv_split(): if not login(): return False s = recv() if s in (False, None): return s (f, s) = split_from(s) if e.watch_func and f == 'srv': for k in ('login', 'logout'): if s.startswith(k+' '): name = s[len(k)+1:] e.watch_func(name, k) return (f, s) def recv_from(name): r = recv_split() if not r: return r (f, s) = r return s if f == name else False # not dis_conn lock = lambda name: send_to('srv', 'lock ' + name) unlock = lambda name: send_to('srv', 'unlock ' + name) def send_recv_name(name, s): send_to(name, s) return recv_from(name) def send_recv_lst_name_lock(name, lst): if not lst: return [] lock(name) rvs = list( map( lambda s: send_recv_name(name, s), lst ) ) unlock(name) return rvs def names_get(): send_to('srv', 'names') s = recv_from('srv') return s.split(' ') if s else [] others_get = lambda : list( filter( lambda name: name != e.name, names_get() ) ) return e.to_attr( locals() ) if __name__ == "__main__": if sys.argv[1:]: srv( int(sys.argv[1]) ) else: print('Usage: {} '.format(sys.argv[0])) # EOF