package uiserver import ( "encoding/json" "fmt" "net" "sync" ) // Worker handles a single connection, providing updates to the clients type Worker struct { Update chan interface{} // this is called whenever a worker should update Exit chan interface{} // this is called to force the worker to Close Conn net.Conn } // NewWorker creates a new Worker from an ongoing connection func NewWorker(conn net.Conn) Worker { w := Worker{Conn: conn} w.Update = make(chan interface{}, 10) w.Exit = make(chan interface{}) return w } func (w *Worker) send(obj interface{}) error { marshalled, err := json.Marshal(obj) if err != nil { return err } marshalled = append(marshalled, byte('\n')) _, err = w.Conn.Write(marshalled) return err } // Work loops on Worker channel to do what is needed func (w *Worker) Work() { for { select { case <-w.Exit: w.Conn.Close() return case obj := <-w.Update: err := w.send(obj) if err != nil { w.Conn.Close() return } } } } // NetUI handles a server for the protocol documented in this package // the API is designed so to make it possible to attach it to multiple // listeners, despite this is NOT possible at the moment type NetUI struct { Object interface{} workers []Worker workersMutex sync.Mutex sock *net.Listener sockMu sync.Mutex exit chan interface{} } // protocolHello is the hello message that the server will give to clients // a server doesn't expect any introduction from clients // the hello message is useful to clients to recognize protocol version cleanly // the clients are guaranteed that the hello message will always be present in // future versions of the protocol, always terminated by a newline and of // exactly 15bytes var protocolHello = "DIRETTOFORO V1\n" // NewNetUI creates a NetUI object. Don't create it otherwise! // the parameter "obj" must be a pointer; if it's not a pointer, no error will // be raised but bad things could happen later func NewNetUI(obj interface{}) NetUI { exitchan := make(chan interface{}) return NetUI{exit: exitchan, Object: obj} } // Update sends the new state to every connected client func (n *NetUI) Update() { todelete := make([]Worker, 0) n.workersMutex.Lock() for _, w := range n.workers { select { case w.Update <- n.Object: default: todelete = append(todelete, w) } } n.workersMutex.Unlock() for _, w := range todelete { n.removeWorker(w) } } // Close shuts the TCPserver down synchronously func (n *NetUI) Close() { n.sockMu.Lock() defer n.sockMu.Unlock() if n.sock != nil { (*n.sock).Close() } // for _, w := range n.workers { // close(w.Exit) // close(w.Update) // } } func (n *NetUI) removeWorker(worker Worker) error { n.workersMutex.Lock() err := n.removeWorkerNoLock(worker) n.workersMutex.Unlock() return err } func (n *NetUI) removeWorkerNoLock(worker Worker) error { fmt.Println("avevo una lista lunga", len(n.workers)) i := -1 for pos, w := range n.workers { if worker == w { i = pos break } } if i == -1 { return fmt.Errorf("No such worker") } n.workers[i] = n.workers[0] n.workers = n.workers[1:] fmt.Println("ora ho una lista lunga", len(n.workers)) return nil } func (n *NetUI) addWorker(connworker Worker) { n.workersMutex.Lock() n.workers = append(n.workers, connworker) n.workersMutex.Unlock() } // Run does the heavy work, and is blocking. It expects a Listener (typically // got with net.Listener) // It will exit when NetUI.Close() is called or when an error on the socket // happens func (n *NetUI) Run(sock net.Listener) error { if n.sock != nil { return fmt.Errorf("More than one Run for a single NetUI is currently unsupported") } n.sockMu.Lock() n.sock = &sock n.sockMu.Unlock() defer sock.Close() for { conn, err := sock.Accept() if err != nil { return nil } // FIXME: performance problem here: we're waiting each client to be // validated before passing to sth else; we'd better run this in a // goroutine, but then the access to n.workers would be concurrent, // which is not allowed err = validateRequest(conn) if err != nil { continue } connworker := NewWorker(conn) connworker.Update <- n.Object n.addWorker(connworker) go connworker.Work() } } func validateRequest(conn net.Conn) error { _, err := conn.Write([]byte(protocolHello)) if err != nil { conn.Close() return err } return nil }