1 package battlemap 2 3 import ( 4 "compress/gzip" 5 "encoding/json" 6 "fmt" 7 "io" 8 "path/filepath" 9 "strconv" 10 "sync" 11 "time" 12 13 "vimagination.zapto.org/byteio" 14 "vimagination.zapto.org/keystore" 15 "vimagination.zapto.org/memio" 16 ) 17 18 type musicTrack struct { 19 ID uint64 `json:"id"` 20 Volume uint8 `json:"volume"` 21 Repeat int32 `json:"repeat"` 22 } 23 24 type musicPack struct { 25 Name string `json:"name"` 26 Tracks []musicTrack `json:"tracks"` 27 Volume uint8 `json:"volume"` 28 PlayTime uint64 `json:"playTime"` 29 } 30 31 func (m *musicPack) ReadFrom(r io.Reader) (int64, error) { 32 g, err := gzip.NewReader(r) 33 if err != nil { 34 return 0, err 35 } 36 br := byteio.StickyLittleEndianReader{Reader: g} 37 m.Name = br.ReadString32() 38 m.Tracks = make([]musicTrack, br.ReadUint64()) 39 for n := range m.Tracks { 40 m.Tracks[n] = musicTrack{ 41 ID: br.ReadUint64(), 42 Volume: br.ReadUint8(), 43 Repeat: br.ReadInt32(), 44 } 45 } 46 m.Volume = br.ReadUint8() 47 m.PlayTime = br.ReadUint64() 48 return br.Count, br.Err 49 } 50 51 func (m *musicPack) WriteTo(w io.Writer) (int64, error) { 52 g, err := gzip.NewWriterLevel(w, gzip.BestCompression) 53 if err != nil { 54 return 0, err 55 } 56 bw := byteio.StickyLittleEndianWriter{Writer: g} 57 bw.WriteString32(m.Name) 58 bw.WriteUint64(uint64(len(m.Tracks))) 59 for _, t := range m.Tracks { 60 bw.WriteUint64(t.ID) 61 bw.WriteUint8(t.Volume) 62 bw.WriteInt32(t.Repeat) 63 } 64 bw.WriteUint8(m.Volume) 65 bw.WriteUint64(m.PlayTime) 66 g.Close() 67 return bw.Count, bw.Err 68 } 69 70 type musicPacksDir struct { 71 *Battlemap 72 fileStore *keystore.FileStore 73 74 mu sync.RWMutex 75 packs map[uint64]*musicPack 76 names map[string]struct{} 77 lastID uint64 78 } 79 80 func (m *musicPacksDir) Init(b *Battlemap, links links) error { 81 m.Battlemap = b 82 var location keystore.String 83 err := b.config.Get("MusicPacksDir", &location) 84 if err != nil { 85 return fmt.Errorf("error retrieving music packs location: %w", err) 86 } 87 mp := filepath.Join(b.config.BaseDir, string(location)) 88 m.fileStore, err = keystore.NewFileStore(mp, mp, keystore.NoMangle) 89 if err != nil { 90 return fmt.Errorf("error creating music packs keystore: %w", err) 91 } 92 m.packs = make(map[uint64]*musicPack) 93 m.names = make(map[string]struct{}) 94 for _, idStr := range m.fileStore.Keys() { 95 id, err := strconv.ParseUint(idStr, 10, 64) 96 if err != nil { 97 m.lastID++ 98 id = m.lastID 99 } 100 pack := new(musicPack) 101 if err := m.fileStore.Get(idStr, pack); err != nil { 102 return fmt.Errorf("error reading music pack %d: %w", id, err) 103 } 104 m.packs[id] = pack 105 for _, track := range pack.Tracks { 106 links.audio.setLink(track.ID) 107 } 108 if id > m.lastID { 109 m.lastID = id 110 } 111 on := pack.Name 112 if on == "" { 113 continue 114 } 115 pack.Name = uniqueName(pack.Name, func(name string) bool { 116 _, ok := m.names[name] 117 return !ok 118 }) 119 if pack.Name != on { 120 if err := m.fileStore.Set(idStr, pack); err != nil { 121 return fmt.Errorf("error correcting pack name %d: %w", id, err) 122 } 123 } 124 m.names[pack.Name] = struct{}{} 125 links.music.setLink(id) 126 } 127 return nil 128 } 129 130 func (m *musicPacksDir) cleanup(l linkManager) { 131 for id := range m.packs { 132 if _, ok := l[id]; !ok { 133 m.fileStore.Remove(strconv.FormatUint(id, 10)) 134 delete(m.packs, id) 135 } 136 } 137 } 138 139 func (m *musicPacksDir) getPack(id uint64, fn func(*musicPack) bool) error { 140 var err error 141 m.mu.Lock() 142 if p, ok := m.packs[id]; ok { 143 if fn(p) { 144 err = m.fileStore.Set(strconv.FormatUint(id, 10), p) 145 } 146 } else { 147 err = ErrUnknownMusicPack 148 } 149 m.mu.Unlock() 150 return err 151 } 152 153 func (m *musicPacksDir) getTrack(id uint64, track uint, fn func(*musicPack, *musicTrack) bool) error { 154 var errr error 155 if err := m.getPack(id, func(mp *musicPack) bool { 156 if track >= uint(len(mp.Tracks)) { 157 errr = ErrUnknownMusicTrack 158 return false 159 } 160 return fn(mp, &mp.Tracks[track]) 161 }); err != nil { 162 return err 163 } 164 return errr 165 } 166 167 func (m *musicPacksDir) RPCData(cd ConnData, method string, data json.RawMessage) (interface{}, error) { 168 cd.CurrentMap = 0 169 switch method { 170 case "list": 171 buf := memio.Buffer("[") 172 first := true 173 j := json.NewEncoder(&buf) 174 m.mu.RLock() 175 for id, p := range m.packs { 176 if p.Name == "" { 177 continue 178 } 179 if first { 180 first = false 181 } else { 182 buf = append(buf, ',') 183 } 184 j.Encode(p) 185 buf = append(strconv.AppendUint(append(buf[:len(buf)-2], ",\"id\":"...), id, 10), '}') 186 } 187 m.mu.RUnlock() 188 buf = append(buf, ']') 189 return json.RawMessage(buf), nil 190 case "new": 191 var name string 192 if err := json.Unmarshal(data, &name); err != nil { 193 return nil, err 194 } 195 m.mu.Lock() 196 oName := name 197 name = uniqueName(oName, func(name string) bool { 198 _, ok := m.names[name] 199 return !ok 200 }) 201 p := &musicPack{ 202 Name: name, 203 Tracks: make([]musicTrack, 0), 204 } 205 p.Volume = 255 206 m.lastID++ 207 m.packs[m.lastID] = p 208 err := m.fileStore.Set(strconv.FormatUint(m.lastID, 10), p) 209 m.mu.Unlock() 210 if err != nil { 211 return nil, err 212 } 213 data = append(appendString(append(strconv.AppendUint(append(data[:0], "{\"id\":"...), m.lastID, 10), ",\"name\":"...), name), '}') 214 m.socket.broadcastMapChange(cd, broadcastMusicPackAdd, data, userAny) 215 return data, nil 216 case "rename": 217 var names struct { 218 ID uint64 `json:"id"` 219 Name string `json:"name"` 220 } 221 if err := json.Unmarshal(data, &names); err != nil { 222 return nil, err 223 } 224 m.mu.Lock() 225 mp, ok := m.packs[names.ID] 226 if !ok { 227 m.mu.Unlock() 228 return nil, ErrUnknownMusicPack 229 } 230 delete(m.names, mp.Name) 231 mp.Name = uniqueName(names.Name, func(name string) bool { 232 _, ok := m.names[name] 233 return !ok 234 }) 235 if mp.Name != names.Name { 236 data = json.RawMessage(append(appendString(append(strconv.AppendUint(append(data[:0], "{\"id\":"...), names.ID, 10), ",\"name\":"...), names.Name), '}')) 237 } 238 m.names[mp.Name] = struct{}{} 239 m.mu.Unlock() 240 m.socket.broadcastMapChange(cd, broadcastMusicPackRename, data, userAdmin) 241 return mp.Name, nil 242 case "remove": 243 var id uint64 244 if err := json.Unmarshal(data, &id); err != nil { 245 return nil, err 246 } 247 m.mu.Lock() 248 mp, ok := m.packs[id] 249 if !ok { 250 m.mu.Unlock() 251 return nil, ErrUnknownMusicPack 252 } 253 mp.Name = "" 254 m.fileStore.Set(strconv.FormatUint(id, 10), mp) 255 delete(m.packs, id) 256 delete(m.names, mp.Name) 257 m.mu.Unlock() 258 m.socket.broadcastMapChange(cd, broadcastMusicPackRemove, data, userAny) 259 return nil, nil 260 case "copy": 261 var names struct { 262 ID uint64 `json:"id"` 263 Name string `json:"name"` 264 } 265 if err := json.Unmarshal(data, &names); err != nil { 266 return nil, err 267 } 268 m.mu.Lock() 269 mp, ok := m.packs[names.ID] 270 if !ok { 271 m.mu.Unlock() 272 return nil, ErrUnknownMusicPack 273 } 274 nName := names.Name 275 names.Name = uniqueName(nName, func(name string) bool { 276 _, ok := m.names[name] 277 return !ok 278 }) 279 np := &musicPack{ 280 Name: names.Name, 281 Tracks: make([]musicTrack, len(mp.Tracks)), 282 Volume: mp.Volume, 283 PlayTime: 0, 284 } 285 copy(np.Tracks, mp.Tracks) 286 m.lastID++ 287 id := m.lastID 288 m.packs[id] = np 289 m.fileStore.Set(strconv.FormatUint(id, 10), np) 290 m.mu.Unlock() 291 data = appendString(append(strconv.AppendUint(append(data[:0], "{\"id\":"...), id, 10), ",\"name\":"...), names.Name) 292 pos := len(data) 293 data = append(strconv.AppendUint(append(data, ",\"newID\":"...), id, 10), '}') 294 m.socket.broadcastMapChange(cd, broadcastMusicPackCopy, data, userAny) 295 data = append(data[:pos], '}') 296 return data, nil 297 case "setVolume": 298 var packData struct { 299 ID uint64 `json:"id"` 300 Volume uint8 `json:"volume"` 301 } 302 if err := json.Unmarshal(data, &packData); err != nil { 303 return nil, err 304 } 305 if err := m.getPack(packData.ID, func(mp *musicPack) bool { 306 if mp.Volume == packData.Volume { 307 return false 308 } 309 mp.Volume = packData.Volume 310 return true 311 }); err != nil { 312 return nil, err 313 } 314 m.socket.broadcastMapChange(cd, broadcastMusicPackVolume, data, userAny) 315 return nil, nil 316 case "playPack": 317 var packData struct { 318 ID uint64 `json:"id"` 319 PlayTime uint64 `jons:"playTime"` 320 } 321 if err := json.Unmarshal(data, &packData); err != nil { 322 return nil, err 323 } 324 if packData.PlayTime == 0 { 325 packData.PlayTime = uint64(time.Now().Unix()) 326 data = json.RawMessage(append(strconv.AppendUint(append(strconv.AppendUint(append(data[:0], "{\"id\":"...), packData.ID, 10), ",\"playTime\":"...), packData.PlayTime, 10), '}')) 327 } 328 if err := m.getPack(packData.ID, func(mp *musicPack) bool { 329 if mp.PlayTime == packData.PlayTime { 330 return false 331 } 332 mp.PlayTime = packData.PlayTime 333 return true 334 }); err != nil { 335 return nil, err 336 } 337 m.socket.broadcastMapChange(cd, broadcastMusicPackPlay, data, userAny) 338 return packData.PlayTime, nil 339 case "stopPack": 340 var packData uint64 341 if err := json.Unmarshal(data, &packData); err != nil { 342 return nil, err 343 } 344 if err := m.getPack(packData, func(mp *musicPack) bool { 345 if mp.PlayTime == 0 { 346 return false 347 } 348 mp.PlayTime = 0 349 return true 350 }); err != nil { 351 return nil, err 352 } 353 m.socket.broadcastMapChange(cd, broadcastMusicPackStop, data, userAny) 354 return nil, nil 355 case "stopAllPacks": 356 m.mu.Lock() 357 for k, p := range m.packs { 358 if p.PlayTime == 0 { 359 continue 360 } 361 p.PlayTime = 0 362 m.fileStore.Set(strconv.FormatUint(k, 10), p) 363 } 364 m.mu.Unlock() 365 m.socket.broadcastMapChange(cd, broadcastMusicPackStopAll, data, userAny) 366 return nil, nil 367 case "addTracks": 368 var trackData struct { 369 ID uint64 `json:"id"` 370 Tracks []uint64 `json:"tracks"` 371 } 372 if err := json.Unmarshal(data, &trackData); err != nil { 373 return nil, err 374 } 375 if err := m.getPack(trackData.ID, func(mp *musicPack) bool { 376 for _, t := range trackData.Tracks { 377 mp.Tracks = append(mp.Tracks, musicTrack{ID: t, Volume: 255}) 378 } 379 return true 380 }); err != nil { 381 return nil, err 382 } 383 m.socket.broadcastMapChange(cd, broadcastMusicPackTrackAdd, data, userAny) 384 return nil, nil 385 case "removeTrack": 386 var trackData struct { 387 ID uint64 `json:"id"` 388 Track uint `json:"track"` 389 } 390 if err := json.Unmarshal(data, &trackData); err != nil { 391 return nil, err 392 } 393 if err := m.getTrack(trackData.ID, trackData.Track, func(mp *musicPack, mt *musicTrack) bool { 394 mp.Tracks = append(mp.Tracks[:trackData.Track], mp.Tracks[trackData.Track+1:]...) 395 return true 396 }); err != nil { 397 return nil, err 398 } 399 m.socket.broadcastMapChange(cd, broadcastMusicPackTrackRemove, data, userAny) 400 return nil, nil 401 case "setTrackVolume": 402 var trackData struct { 403 ID uint64 `json:"id"` 404 Track uint `json:"track"` 405 Volume uint8 `json:"volume"` 406 } 407 if err := json.Unmarshal(data, &trackData); err != nil { 408 return nil, err 409 } 410 if err := m.getTrack(trackData.ID, trackData.Track, func(mp *musicPack, mt *musicTrack) bool { 411 if mt.Volume == trackData.Volume { 412 return false 413 } 414 mt.Volume = trackData.Volume 415 return true 416 }); err != nil { 417 return nil, err 418 } 419 m.socket.broadcastMapChange(cd, broadcastMusicPackTrackVolume, data, userAny) 420 return nil, nil 421 case "setTrackRepeat": 422 var trackData struct { 423 ID uint64 `json:"id"` 424 Track uint `json:"track"` 425 Repeat int32 `json:"repeat"` 426 } 427 if err := json.Unmarshal(data, &trackData); err != nil { 428 return nil, err 429 } 430 if err := m.getTrack(trackData.ID, trackData.Track, func(mp *musicPack, mt *musicTrack) bool { 431 if mt.Repeat == trackData.Repeat { 432 return false 433 } 434 mt.Repeat = trackData.Repeat 435 return true 436 }); err != nil { 437 return nil, err 438 } 439 m.socket.broadcastMapChange(cd, broadcastMusicPackTrackRepeat, data, userAny) 440 return nil, nil 441 } 442 return nil, ErrUnknownMethod 443 } 444