lipre

Stream text files for live (coding) representations
Log | Files | Refs

lipre.go (4128B)


      1 package main
      2 
      3 import (
      4 	"fmt"
      5 	"io/ioutil"
      6 	"log"
      7 	"net/http"
      8 	"strconv"
      9 	"sync"
     10 
     11 	"github.com/gorilla/mux"
     12 	"github.com/gorilla/websocket"
     13 )
     14 
     15 // Not used - either read password from config / flag or define the valid rooms in a file on server
     16 const temporaryCorrectRoomCode = "tester"
     17 
     18 type File struct {
     19 	Name     string `json:"name"`
     20 	Contents string `json:"contents"`
     21 }
     22 
     23 type Room struct {
     24 	mu        sync.Mutex
     25 	code      string
     26 	presenter *websocket.Conn
     27 	viewers   []*websocket.Conn
     28 	// Store files so that they can be sent to new viewers upon connection
     29 	files map[string]File
     30 	// Number of minutes for which the room should continue to be open after the presenter disconnects
     31 	linger int
     32 }
     33 
     34 var rooms = make(map[string]*Room)
     35 
     36 // Thread safe Room functions.
     37 // The rooms map should only be written to from these
     38 
     39 func (room *Room) open() {
     40 	room.mu.Lock()
     41 	defer room.mu.Unlock()
     42 	existingRoom := rooms[room.code]
     43 	if existingRoom != nil {
     44 		existingRoom.close()
     45 	}
     46 	rooms[room.code] = room
     47 	room.presenter.SetCloseHandler(func(code int, text string) error {
     48 		room.close()
     49 		return nil
     50 	})
     51 	go room.listen()
     52 }
     53 
     54 func (room *Room) close() {
     55 	room.mu.Lock()
     56 	defer room.mu.Unlock()
     57 	// The actual room close code is handled in the presenter connection close handler
     58 	fmt.Printf("Closing room '%v'\n", room.code)
     59 	for _, viewer := range room.viewers {
     60 		if viewer != nil {
     61 			viewer.Close()
     62 		}
     63 	}
     64 	room.presenter.Close()
     65 	delete(rooms, room.code)
     66 }
     67 
     68 func (room *Room) addViewer(viewerConn *websocket.Conn) {
     69 	room.mu.Lock()
     70 	room.viewers = append(room.viewers, viewerConn)
     71 	room.mu.Unlock()
     72 	// And write all existing files
     73 	for _, filedata := range room.files {
     74 		viewerConn.WriteJSON(&filedata)
     75 	}
     76 }
     77 
     78 func (room *Room) listen() {
     79 	for {
     80 		var file File
     81 		err := room.presenter.ReadJSON(&file)
     82 		if err != nil {
     83 			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseAbnormalClosure) {
     84 				log.Printf("Unexpected close error: %v", err)
     85 			}
     86 			// TODO: Handle JSON errors (send info to presenter)
     87 			return
     88 		}
     89 		room.files[file.Name] = file
     90 		for _, viewerConn := range room.viewers {
     91 			if viewerConn == nil {
     92 				break
     93 			}
     94 			viewerConn.WriteJSON(&file)
     95 		}
     96 	}
     97 }
     98 
     99 // HTTP
    100 var wsUpgrader = websocket.Upgrader{
    101 	ReadBufferSize:  1024,
    102 	WriteBufferSize: 1024,
    103 }
    104 
    105 // fileHandler looks in ui/dist directory for static files matching the path
    106 // Writes a 404 message if not found
    107 func fileHandler(w http.ResponseWriter, r *http.Request) {
    108 	filePath := r.URL.Path
    109 	if filePath == "/" {
    110 		filePath = "/index.html"
    111 	}
    112 	// Check if the file exists among the static assets
    113 	// At time of writing, this is only true for index.html and lipre.js,
    114 	// but code splitting may be introduced and change that
    115 	htmlData, err := ioutil.ReadFile(fmt.Sprintf("ui/dist%v", filePath))
    116 	if err != nil {
    117 		w.WriteHeader(http.StatusNotFound)
    118 		w.Write([]byte("404 :("))
    119 		return
    120 	}
    121 	w.Write(htmlData)
    122 	return
    123 }
    124 
    125 func presentHandler(w http.ResponseWriter, r *http.Request) {
    126 	roomCode := mux.Vars(r)["roomCode"]
    127 	qparams := r.URL.Query()
    128 	pLinger := qparams["linger"]
    129 	var iLinger int
    130 	if len(pLinger) == 1 {
    131 		iLinger, _ = strconv.Atoi(pLinger[0])
    132 	}
    133 	/*if roomCode != temporaryCorrectRoomCode {
    134 		w.WriteHeader(http.StatusBadRequest)
    135 		return
    136 	}*/
    137 	fmt.Println("Upgrading connection")
    138 	conn, err := wsUpgrader.Upgrade(w, r, nil)
    139 	if err != nil {
    140 		log.Println(err)
    141 		return
    142 	}
    143 	room := &Room{code: roomCode, presenter: conn, linger: iLinger, files: make(map[string]File)}
    144 	room.open()
    145 }
    146 
    147 func viewHandler(w http.ResponseWriter, r *http.Request) {
    148 	roomCode := mux.Vars(r)["roomCode"]
    149 	room := rooms[roomCode]
    150 	if room == nil {
    151 		return
    152 	}
    153 	conn, err := wsUpgrader.Upgrade(w, r, nil)
    154 	if err != nil {
    155 		log.Println(err)
    156 		return
    157 	}
    158 	room.addViewer(conn)
    159 }
    160 
    161 func main() {
    162 	fmt.Println("Server starting")
    163 	router := mux.NewRouter()
    164 	router.HandleFunc("/ws/pres/{roomCode}", presentHandler)
    165 	router.HandleFunc("/ws/view/{roomCode}", viewHandler)
    166 	router.PathPrefix("/").HandlerFunc(fileHandler)
    167 	http.Handle("/", router)
    168 	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", 8080), nil))
    169 }