Server2.go (6366B)
1 package main 2 3 import ( 4 "bytes" 5 "image" 6 "image/color" 7 "image/gif" 8 "os" 9 10 "io/ioutil" 11 "log" 12 "net/http" 13 "path" 14 15 "github.com/gorilla/websocket" 16 17 "github.com/MFAshby/unicornpaint/unicorn" 18 ) 19 20 var ( 21 un unicorn.Unicorn2 22 23 upgrader = websocket.Upgrader{ 24 ReadBufferSize: 1024, 25 WriteBufferSize: 1024, 26 CheckOrigin: func(*http.Request) bool { return true }, 27 } 28 delCh = make(chan *websocket.Conn) 29 addCh = make(chan *websocket.Conn) 30 broadcastCh = make(chan interface{}) 31 clients = map[*websocket.Conn]bool{} 32 ) 33 34 // Types of commands 35 type commandType string 36 37 const ( 38 noop commandType = "NO_OP" 39 setPixel commandType = "SET_PIXEL" 40 clear commandType = "CLEAR" 41 save commandType = "SAVE" 42 load commandType = "LOAD" 43 addFrame commandType = "ADD_FRAME" 44 removeFrame commandType = "REMOVE_FRAME" 45 ) 46 47 type Command struct { 48 Type commandType `json:"type"` 49 X uint8 `json:"x"` 50 Y uint8 `json:"y"` 51 R uint8 `json:"r"` 52 G uint8 `json:"g"` 53 B uint8 `json:"b"` 54 Frame int `json:"frame"` 55 Delay int `json:"delay"` 56 SaveName string `json:"saveName"` 57 } 58 59 const ( 60 savesDir = "saves" 61 ) 62 63 type State struct { 64 Saves []string `json:"saves"` 65 ImageData []byte `json:"imageData"` 66 } 67 68 func getGifBytes() ([]byte, error) { 69 g := un.GetGif() 70 buf := &bytes.Buffer{} 71 err := gif.EncodeAll(buf, g) 72 return buf.Bytes(), err 73 } 74 75 func getState() *State { 76 infos, err := ioutil.ReadDir(savesDir) 77 if err != nil { 78 log.Printf("Error reading saves dir %v", err) 79 } 80 saveFileNames := make([]string, len(infos)) 81 for ix, info := range infos { 82 saveFileNames[ix] = info.Name() 83 } 84 85 // Write out as GIF file 86 gifBytes, err := getGifBytes() 87 if err != nil { 88 log.Printf("Error getting GIF bytes %v", err) 89 } 90 91 return &State{ 92 ImageData: gifBytes, 93 Saves: saveFileNames, 94 } 95 } 96 97 func savePic(saveFileName string) { 98 f, err := os.Create(path.Join(savesDir, saveFileName)) 99 if err != nil { 100 log.Printf("Failed to open file for saving %v", err) 101 return 102 } 103 defer f.Close() 104 105 g := un.GetGif() 106 err = gif.EncodeAll(f, g) 107 } 108 109 func loadPic(saveFileName string) { 110 f, err := os.Open(path.Join(savesDir, saveFileName)) 111 if err != nil { 112 log.Printf("Failed to read file %v", err) 113 return 114 } 115 defer f.Close() 116 117 g, err := gif.DecodeAll(f) 118 if err != nil { 119 log.Printf("Failed to decode GIF file %v", err) 120 return 121 } 122 123 un.SetGif(g) 124 } 125 126 func doSetPixel(x, y, r, g, b uint8, frame int) { 127 gf := un.GetGif() 128 if int(frame) >= len(gf.Image) { 129 log.Printf("Tried to set pixel in frame out of range %v", frame) 130 return 131 } 132 im := gf.Image[frame] 133 im.Set(int(x), int(y), color.RGBA{ 134 R: uint8(r), 135 G: uint8(g), 136 B: uint8(b), 137 A: uint8(255), 138 }) 139 } 140 141 func doAddFrame(frame int, delay int) { 142 gf := un.GetGif() 143 if frame < 1 || frame > len(gf.Image) { 144 log.Printf("Trying to add frame in invalid position %v", frame) 145 return 146 } 147 148 // Copy the image before it 149 sourceImage := gf.Image[frame-1] 150 151 buf := &bytes.Buffer{} 152 if err := gif.Encode(buf, sourceImage, nil); err != nil { 153 log.Printf("Failed to encode image %v", err) 154 return 155 } 156 im, err := gif.Decode(buf) 157 if err != nil { 158 log.Printf("Failed to decode image %v", err) 159 return 160 } 161 162 newImage, ok := im.(*image.Paletted) 163 if !ok { 164 log.Printf("Wrong image type %v", err) 165 } 166 167 gf.Image = append(gf.Image[:frame], append([]*image.Paletted{newImage}, gf.Image[frame:]...)...) 168 gf.Delay = append(gf.Delay[:frame], append([]int{delay}, gf.Delay[frame:]...)...) 169 gf.Disposal = append(gf.Disposal[:frame], append([]byte{gif.DisposalBackground}, gf.Disposal[frame:]...)...) 170 } 171 172 func doRemoveFrame(frame int) { 173 // Can't remove the first frame 174 gf := un.GetGif() 175 frameCount := len(gf.Image) 176 if frame < 0 || frame >= frameCount { 177 log.Printf("Trying to remove frame %v which is invalid", frame) 178 return 179 } 180 181 if frameCount == 1 { 182 log.Printf("Trying to remove the last frame, ignoring") 183 return 184 } 185 186 gf.Image = append(gf.Image[:frame], gf.Image[frame+1:]...) 187 gf.Delay = append(gf.Delay[:frame], gf.Delay[frame+1:]...) 188 gf.Disposal = append(gf.Disposal[:frame], gf.Disposal[frame+1:]...) 189 } 190 191 func doClear(frame int) { 192 gf := un.GetGif() 193 if frame >= len(gf.Image) { 194 log.Printf("Trying to clear invalid frame %v", frame) 195 return 196 } 197 198 im := gf.Image[frame] 199 b := im.Bounds() 200 width := b.Dx() 201 height := b.Dy() 202 for x := 0; x < width; x++ { 203 for y := 0; y < height; y++ { 204 im.SetColorIndex(x, y, 0) // 0 in WebSafe colors is black 205 } 206 } 207 } 208 209 func upgradeHandler(w http.ResponseWriter, r *http.Request) { 210 conn, err := upgrader.Upgrade(w, r, nil) 211 if err != nil { 212 log.Printf("Failed to upgrade someone %v", err) 213 return 214 } 215 216 // Add you to my list, remember to remove later 217 addCh <- conn 218 defer func() { delCh <- conn }() 219 220 // Get you up to speed 221 err = conn.WriteJSON(getState()) 222 if err != nil { 223 log.Printf("Failed to send initial state %v", err) 224 return 225 } 226 227 // Read & execute commands in a loop until error 228 for { 229 cmd := Command{} 230 err = conn.ReadJSON(&cmd) 231 if err != nil { 232 log.Printf("Error reading from client %v", err) 233 break 234 } 235 236 switch cmd.Type { 237 case noop: 238 // Don't do anything 239 case setPixel: 240 doSetPixel(cmd.X, cmd.Y, cmd.R, cmd.G, cmd.B, cmd.Frame) 241 case clear: 242 doClear(cmd.Frame) 243 case save: 244 savePic(cmd.SaveName) 245 case load: 246 loadPic(cmd.SaveName) 247 case addFrame: 248 doAddFrame(cmd.Frame, cmd.Delay) 249 case removeFrame: 250 doRemoveFrame(cmd.Frame) 251 } 252 // Pretty much all commands demand a change of state, 253 // do a broadcast each time 254 broadcastCh <- getState() 255 } 256 } 257 258 func handleClients() { 259 for { 260 select { 261 case c := <-addCh: 262 clients[c] = true 263 case c := <-delCh: 264 delete(clients, c) 265 case msg := <-broadcastCh: 266 doBroadcast(msg) 267 } 268 } 269 } 270 271 func main() { 272 uni, err := unicorn.NewUnicorn2() 273 if err != nil { 274 log.Fatalf("Couldn't get a unicorn :( %v", err) 275 } 276 un = uni 277 un.StartRender() 278 279 log.Println("Starting server on port 3001") 280 http.Handle("/", http.FileServer(http.Dir("build"))) 281 http.HandleFunc("/ws", upgradeHandler) 282 go http.ListenAndServe(":3001", nil) 283 go handleClients() 284 un.MainLoop() 285 } 286 287 func doBroadcast(obj interface{}) { 288 for client := range clients { 289 err := client.WriteJSON(obj) 290 291 if err != nil { 292 log.Printf("Error writing to client, closing %v", err) 293 client.Close() 294 delete(clients, client) 295 } 296 } 297 }