180 lines
4.3 KiB
Go
180 lines
4.3 KiB
Go
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
|
|
|
|
}
|