1 package battlemap 2 3 import ( 4 "compress/gzip" 5 "encoding/json" 6 "fmt" 7 "io" 8 "path/filepath" 9 "strconv" 10 11 "vimagination.zapto.org/byteio" 12 "vimagination.zapto.org/keystore" 13 ) 14 15 type keystoreData struct { 16 User bool `json:"user"` 17 Data json.RawMessage `json:"data"` 18 } 19 20 type characterData map[string]keystoreData 21 22 func (c characterData) ReadFrom(r io.Reader) (int64, error) { 23 g, err := gzip.NewReader(r) 24 if err != nil { 25 return 0, err 26 } 27 br := byteio.StickyLittleEndianReader{Reader: g} 28 l := br.ReadUint64() 29 for i := uint64(0); i < l; i++ { 30 key := br.ReadString64() 31 user := br.ReadBool() 32 data := br.ReadBytes64() 33 c[key] = keystoreData{ 34 User: user, 35 Data: data, 36 } 37 } 38 return br.Count, br.Err 39 } 40 41 func (c characterData) WriteTo(w io.Writer) (int64, error) { 42 g, err := gzip.NewWriterLevel(w, gzip.BestCompression) 43 if err != nil { 44 return 0, err 45 } 46 bw := byteio.StickyLittleEndianWriter{Writer: g} 47 bw.WriteUint64(uint64(len(c))) 48 for key, data := range c { 49 bw.WriteString64(key) 50 bw.WriteBool(data.User) 51 bw.WriteBytes64(data.Data) 52 } 53 g.Close() 54 return bw.Count, bw.Err 55 } 56 57 type charactersDir struct { 58 folders 59 Name string 60 61 fileStore *keystore.FileStore 62 63 data map[string]characterData 64 } 65 66 func (c *charactersDir) Init(b *Battlemap, links links) error { 67 var location keystore.String 68 err := b.config.Get("CharsDir", &location) 69 if err != nil { 70 return fmt.Errorf("error retrieving characters location: %w", err) 71 } 72 sp := filepath.Join(b.config.BaseDir, string(location)) 73 c.fileStore, err = keystore.NewFileStore(sp, sp, keystore.NoMangle) 74 if err != nil { 75 return fmt.Errorf("error creating characters keystore: %w", err) 76 } 77 c.fileType = fileTypeCharacter 78 if err := c.folders.Init(b, c.fileStore, links.chars); err != nil { 79 return fmt.Errorf("error parsing characters keystore folders: %w", err) 80 } 81 c.data = make(map[string]characterData) 82 for id := range links.chars { 83 idStr := strconv.FormatUint(id, 10) 84 km := make(characterData) 85 if err := c.fileStore.Get(idStr, km); err != nil { 86 return err 87 } 88 for key, val := range km { 89 if f := links.getLinkKey(key); f != nil { 90 f.setJSONLinks(val.Data) 91 } 92 } 93 c.data[idStr] = km 94 } 95 return nil 96 } 97 98 func (c *charactersDir) RPCData(cd ConnData, method string, data json.RawMessage) (interface{}, error) { 99 switch method { 100 case "create": 101 return c.create(cd, data) 102 case "modify": 103 return nil, c.modify(cd, data) 104 case "get": 105 return c.get(cd, data) 106 case "copy": 107 return c.copy(cd, data) 108 default: 109 return c.folders.RPCData(cd, method, data) 110 } 111 } 112 113 func (c *charactersDir) create(cd ConnData, data json.RawMessage) (json.RawMessage, error) { 114 var nameData struct { 115 Path string `json:"path"` 116 Data characterData `json:"data"` 117 } 118 if err := json.Unmarshal(data, &nameData); err != nil { 119 return nil, err 120 } 121 if nameData.Data == nil { 122 nameData.Data = make(characterData) 123 } 124 c.mu.Lock() 125 c.lastID++ 126 kid := c.lastID 127 nameData.Path = addItemTo(c.root.Items, nameData.Path, kid) 128 c.saveFolders() 129 strID := strconv.FormatUint(kid, 10) 130 c.data[strID] = nameData.Data 131 c.mu.Unlock() 132 c.fileStore.Set(strID, nameData.Data) 133 buf := append(appendString(append(append(append(json.RawMessage{}, "[{\"id\":"...), strID...), ",\"path\":"...), nameData.Path), '}', ']') 134 c.socket.broadcastAdminChange(broadcastCharacterItemAdd, buf, cd.ID) 135 return buf[1 : len(buf)-1], nil 136 } 137 138 func (c *charactersDir) modify(cd ConnData, data json.RawMessage) error { 139 var m struct { 140 ID json.RawMessage `json:"id"` 141 Setting map[string]keystoreData `json:"setting"` 142 Removing []string `json:"removing"` 143 } 144 if err := json.Unmarshal(data, &m); err != nil { 145 return err 146 } 147 if len(m.Setting) == 0 && len(m.Removing) == 0 { 148 return nil 149 } 150 c.mu.Lock() 151 defer c.mu.Unlock() 152 ms, ok := c.data[string(m.ID)] 153 if !ok { 154 return keystore.ErrUnknownKey 155 } 156 c.socket.broadcastAdminChange(broadcastCharacterDataChange, data, cd.ID) 157 buf := append(append(data[:0], "{\"id\":"...), m.ID...) 158 buf = append(buf, ",\"setting\":{"...) 159 var userRemoves []string 160 first := true 161 for key, val := range m.Setting { 162 if val.User { 163 if first { 164 first = false 165 } else { 166 buf = append(buf, ',') 167 } 168 buf = append(append(append(appendString(buf, key), ":{\"user\":true,\"data\":"...), val.Data...), '}') 169 } else if mv, ok := ms[key]; ok && mv.User { 170 userRemoves = append(userRemoves, key) 171 } 172 ms[key] = val 173 } 174 buf = append(buf, "},\"removing\":["...) 175 first = true 176 for _, key := range m.Removing { 177 val, ok := ms[key] 178 if !ok { 179 continue 180 } 181 if val.User { 182 if !first { 183 buf = append(buf, ',') 184 } else { 185 first = false 186 } 187 buf = appendString(buf, key) 188 } 189 delete(ms, key) 190 } 191 for _, key := range userRemoves { 192 if !first { 193 buf = append(buf, ',') 194 } else { 195 first = false 196 } 197 buf = appendString(buf, key) 198 } 199 buf = append(buf, ']', '}') 200 cd.CurrentMap = 0 201 c.socket.broadcastMapChange(cd, broadcastCharacterDataChange, buf, userNotAdmin) 202 return c.fileStore.Set(string(m.ID), ms) 203 } 204 205 func (c *charactersDir) get(cd ConnData, id json.RawMessage) (json.RawMessage, error) { 206 c.mu.RLock() 207 ms, ok := c.data[string(id)] 208 if !ok { 209 c.mu.RUnlock() 210 return nil, keystore.ErrUnknownKey 211 } 212 var buf json.RawMessage 213 for key, val := range ms { 214 if cd.IsAdmin() || val.User { 215 buf = append(append(append(strconv.AppendBool(append(appendString(append(buf, ','), key), ":{\"user\":"...), val.User), ",\"data\":"...), val.Data...), '}') 216 } 217 } 218 c.mu.RUnlock() 219 if len(buf) == 0 { 220 buf = json.RawMessage{'{', '}'} 221 } else { 222 buf[0] = '{' 223 buf = append(buf, '}') 224 } 225 return buf, nil 226 } 227 228 func (c *charactersDir) copy(cd ConnData, data json.RawMessage) (json.RawMessage, error) { 229 var ip struct { 230 ID json.RawMessage `json:"id"` 231 Path string `json:"path"` 232 } 233 if err := json.Unmarshal(data, &ip); err != nil { 234 return nil, err 235 } 236 c.mu.Lock() 237 p, name, _ := c.getFolderItem(ip.Path) 238 if p == nil { 239 c.mu.Unlock() 240 return nil, ErrFolderNotFound 241 } 242 ms, ok := c.data[string(ip.ID)] 243 if !ok { 244 c.mu.Unlock() 245 return nil, keystore.ErrUnknownKey 246 } 247 d := make(characterData) 248 for key, val := range ms { 249 d[key] = val 250 } 251 c.lastID++ 252 kid := c.lastID 253 strID := strconv.FormatUint(kid, 10) 254 c.fileStore.Set(strID, d) 255 newName := addItemTo(p.Items, name, kid) 256 c.saveFolders() 257 c.data[strID] = d 258 c.mu.Unlock() 259 ip.Path = ip.Path[:len(ip.Path)-len(name)] + newName 260 data = append(appendString(append(strconv.AppendUint(append(append(append(data[:0], "{\"oldID\":"...), ip.ID...), ",\"newID\":"...), kid, 10), ",\"path\":"...), ip.Path), '}') 261 c.socket.broadcastAdminChange(broadcastCharacterItemCopy, data, cd.ID) 262 data = append(appendString(append(strconv.AppendUint(append(data[:0], "{\"id\":"...), kid, 10), ",\"path\":"...), ip.Path), '}') 263 return data, nil 264 } 265