multiserver/media.go

442 lines
8.3 KiB
Go

package main
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"io"
"log"
"net"
"os"
"strings"
"github.com/anon55555/mt/rudp"
)
const BytesPerBunch = 5000
var media map[string]*mediaFile
var nodedefs map[string][]byte
var itemdefs map[string][]byte
var detachedinvs map[string][][]byte
type mediaFile struct {
digest []byte
data []byte
noCache bool
}
func (c *Conn) fetchMedia() {
if !c.IsSrv() {
return
}
for {
pkt, err := c.Recv()
if err != nil {
if errors.Is(err, net.ErrClosed) {
return
}
go func() {
<-LogReady()
log.Print(err)
}()
continue
}
r := ByteReader(pkt)
switch cmd := ReadUint16(r); cmd {
case ToClientNodeDef:
servers := ConfKey("servers").(map[interface{}]interface{})
var srvname string
for server := range servers {
if ConfKey("servers:"+server.(string)+":address") == c.Addr().String() {
srvname = server.(string)
break
}
}
r.Seek(6, io.SeekStart)
nodedefs[srvname] = make([]byte, r.Len())
r.Read(nodedefs[srvname])
case ToClientItemDef:
servers := ConfKey("servers").(map[interface{}]interface{})
var srvname string
for server := range servers {
if ConfKey("servers:"+server.(string)+":address") == c.Addr().String() {
srvname = server.(string)
break
}
}
r.Seek(6, io.SeekStart)
itemdefs[srvname] = make([]byte, r.Len())
r.Read(itemdefs[srvname])
case ToClientDetachedInventory:
servers := ConfKey("servers").(map[interface{}]interface{})
var srvname string
for server := range servers {
if ConfKey("servers:"+server.(string)+":address") == c.Addr().String() {
srvname = server.(string)
break
}
}
inv := make([]byte, r.Len())
r.Read(inv)
detachedinvs[srvname] = append(detachedinvs[srvname], inv)
case ToClientAnnounceMedia:
var rq []string
count := ReadUint16(r)
for i := uint16(0); i < count; i++ {
name := string(ReadBytes16(r))
digest := ReadBytes16(r)
if media[name] == nil && !isCached(name, digest) {
rq = append(rq, name)
media[name] = &mediaFile{digest: digest}
}
}
// Request the media
pktlen := 0
for f := range rq {
pktlen += 2 + len(rq[f])
}
w := bytes.NewBuffer([]byte{0x00, ToServerRequestMedia})
WriteUint16(w, uint16(len(rq)))
for f := range rq {
WriteBytes16(w, []byte(rq[f]))
}
_, err := c.Send(rudp.Pkt{
Reader: w,
PktInfo: rudp.PktInfo{
Channel: 1,
},
})
if err != nil {
go func() {
<-LogReady()
log.Print(err)
}()
continue
}
case ToClientMedia:
bunchCount := ReadUint16(r)
bunchID := ReadUint16(r)
fileCount := ReadUint32(r)
for i := uint32(0); i < fileCount; i++ {
name := string(ReadBytes16(r))
data := ReadBytes32(r)
if media[name] != nil && len(media[name].data) == 0 {
media[name].data = data
}
}
if bunchID >= bunchCount-1 {
c.Close()
return
}
}
}
}
func (c *Conn) updateDetachedInvs(srvname string) {
for i := range detachedinvs[srvname] {
w := bytes.NewBuffer([]byte{0x00, ToClientDetachedInventory})
w.Write(detachedinvs[srvname][i])
ack, err := c.Send(rudp.Pkt{Reader: w})
if err != nil {
log.Print(err)
continue
}
<-ack
}
}
func (c *Conn) announceMedia() {
srvname, ok := ConfKey("default_server").(string)
if !ok {
log.Print("Default server name not set or not a string")
return
}
data := make([]byte, 6+len(nodedef))
data[0] = uint8(0x00)
data[1] = uint8(ToClientNodeDef)
binary.BigEndian.PutUint32(data[2:6], uint32(len(nodedef)))
copy(data[6:], nodedef)
ack, err := c.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
if err != nil {
log.Print(err)
}
<-ack
data = make([]byte, 6+len(itemdef))
data[0] = uint8(0x00)
data[1] = uint8(ToClientItemDef)
binary.BigEndian.PutUint32(data[2:6], uint32(len(itemdef)))
copy(data[6:], itemdef)
ack, err = c.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
if err != nil {
log.Print(err)
}
<-ack
c.updateDetachedInvs(srvname)
csmrf, ok := ConfKey("csm_restriction_flags").(int)
if !ok {
csmrf = 0
}
csmnr, ok := ConfKey("csm_restriction_noderange").(int)
if !ok {
csmnr = 8
}
data = make([]byte, 14)
data[0] = uint8(0x00)
data[1] = uint8(ToClientCSMRestrictionFlags)
binary.BigEndian.PutUint32(data[2:6], uint32(0))
binary.BigEndian.PutUint32(data[6:10], uint32(csmrf))
binary.BigEndian.PutUint32(data[10:], uint32(csmnr))
ack, err = c.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
if err != nil {
log.Print(err)
}
<-ack
w := bytes.NewBuffer([]byte{0x00, ToClientAnnounceMedia})
WriteUint16(w, uint16(len(media)))
for f := range media {
WriteBytes16(w, []byte(f))
WriteBytes16(w, media[f].digest)
}
remote, ok := ConfKey("remote_media_server").(string)
if !ok {
remote = ""
}
WriteBytes16(w, []byte(remote))
ack, err = c.Send(rudp.Pkt{Reader: w})
if err != nil {
log.Print(err)
return
}
<-ack
}
func (c *Conn) sendMedia(r *bytes.Reader) {
count := ReadUint16(r)
var rq []string
for i := uint16(0); i < count; i++ {
name := string(ReadBytes16(r))
rq = append(rq, name)
}
bunches := []map[string]*mediaFile{make(map[string]*mediaFile)}
var bunchlen int
for _, f := range rq {
bunches[len(bunches)-1][f] = media[f]
bunchlen += len(media[f].data)
if bunchlen >= BytesPerBunch {
bunches = append(bunches, make(map[string]*mediaFile))
bunchlen = 0
}
}
for i, bunch := range bunches {
w := bytes.NewBuffer([]byte{0x00, ToClientMedia})
WriteUint16(w, uint16(len(bunches)))
WriteUint16(w, uint16(i))
WriteUint32(w, uint32(len(bunch)))
for f, m := range bunch {
WriteBytes16(w, []byte(f))
WriteBytes32(w, m.data)
}
ack, err := c.Send(rudp.Pkt{
Reader: w,
PktInfo: rudp.PktInfo{
Channel: 2,
},
})
if err != nil {
log.Print(err)
return
}
<-ack
}
}
func loadMediaCache() error {
os.Mkdir("cache", 0777)
files, err := os.ReadDir("cache")
if err != nil {
return err
}
for _, file := range files {
if !file.IsDir() {
meta := strings.Split(file.Name(), "#")
if len(meta) != 2 {
os.Remove("cache/" + file.Name())
continue
}
data, err := os.ReadFile("cache/" + file.Name())
if err != nil {
continue
}
media[meta[0]] = &mediaFile{digest: stringToDigest(meta[1]), data: data}
}
}
return nil
}
func isCached(name string, digest []byte) bool {
os.Mkdir("cache", 0777)
_, err := os.Stat("cache/" + name + "#" + digestToString(digest))
if os.IsNotExist(err) {
return false
}
return true
}
func updateMediaCache() {
os.Mkdir("cache", 0777)
for mfname, mfile := range media {
if mfile.noCache {
continue
}
cfname := "cache/" + mfname + "#" + digestToString(mfile.digest)
_, err := os.Stat(cfname)
if os.IsNotExist(err) {
os.WriteFile(cfname, mfile.data, 0666)
}
}
}
func digestToString(d []byte) string {
return hex.EncodeToString(d)
}
func stringToDigest(s string) []byte {
r, err := hex.DecodeString(s)
if err != nil {
return []byte{}
}
return r
}
func loadMedia(servers map[string]struct{}) {
log.Print("Fetching media")
media = make(map[string]*mediaFile)
detachedinvs = make(map[string][][]byte)
loadMediaCache()
for server := range servers {
straddr := ConfKey("servers:" + server + ":address")
srvaddr, err := net.ResolveUDPAddr("udp", straddr.(string))
if err != nil {
go func() {
<-LogReady()
log.Fatal(err)
}()
}
conn, err := net.DialUDP("udp", nil, srvaddr)
if err != nil {
go func() {
<-LogReady()
log.Fatal(err)
}()
}
srv, err := Connect(conn)
if err != nil {
go func() {
<-LogReady()
log.Print(err)
}()
continue
}
clt := &Conn{username: "media"}
fin := make(chan *Conn) // close-only
go Init(clt, srv, false, true, fin)
<-fin
srv.fetchMedia()
}
if err := mergeNodedefs(nodedefs); err != nil {
go func() {
<-LogReady()
log.Fatal(err)
}()
}
if err := mergeItemdefs(itemdefs); err != nil {
go func() {
<-LogReady()
log.Fatal(err)
}()
}
updateMediaCache()
}
func init() {
nodedefs = make(map[string][]byte)
itemdefs = make(map[string][]byte)
servers, ok := ConfKey("servers").(map[interface{}]interface{})
if !ok {
go func() {
<-LogReady()
log.Fatal("Server list inexistent or not a dictionary")
}()
}
srvs := make(map[string]struct{})
for server := range servers {
srvs[server.(string)] = struct{}{}
}
loadMedia(srvs)
}