/* NAME meta.go DESCRIPTION See Readme.md AUTHOR Saxon Nelson-Milton LICENSE meta.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. */ package meta import ( "encoding/binary" "errors" "strings" "sync" ) // This is the headsize of our metadata string, // which is encoded int the data body of a pmt descriptor. const headSize = 4 const ( majVer = 1 minVer = 0 ) // Indices of bytes for uint16 metadata length. const ( dataLenIdx = 2 ) var ( errKeyAbsent = errors.New("Key does not exist in map") errNoHeader = errors.New("Metadata string does not contain header") errInvalidHeader = errors.New("Metadata string does not contain valid header") ) // Metadata provides functionality for the storage and encoding of metadata // using a map. type Metadata struct { mu sync.RWMutex data map[string]string order []string enc []byte } // New returns a pointer to a new Metadata. func New() *Metadata { return &Metadata{ data: make(map[string]string), enc: []byte{ 0x00, // Reserved byte (majVer << 4) | minVer, // MS and LS versions 0x00, // Data len byte1 0x00, // Data len byte2 }, } } // Add adds metadata with key and val. func (m *Metadata) Add(key, val string) { m.mu.Lock() defer m.mu.Unlock() m.data[key] = val for _, k := range m.order { if k == key { return } } m.order = append(m.order, key) return } // All returns the a copy of the map containing the meta data. func (m *Metadata) All() map[string]string { m.mu.Lock() cpy := make(map[string]string) for k, v := range m.data { cpy[k] = v } m.mu.Unlock() return cpy } // Get returns the meta data for the passed key. func (m *Metadata) Get(key string) (string, error) { m.mu.Lock() val, ok := m.data[key] m.mu.Unlock() if !ok { return "", errKeyAbsent } return val, nil } // Delete deletes a meta entry in the map and returns error if it doesn’t exist. func (m *Metadata) Delete(key string) error { m.mu.Lock() defer m.mu.Unlock() if _, ok := m.data[key]; ok { delete(m.data, key) for i, k := range m.order { if k == key { copy(m.order[:i], m.order[i+1:]) m.order = m.order[:len(m.order)-1] break } } return nil } return errKeyAbsent } // Encode takes the meta data map and encodes into a byte slice with header // describing the version, length of data and data in TSV format. func (m *Metadata) Encode() []byte { m.enc = m.enc[:headSize] // Iterate over map and append entries, only adding tab if we're not on the // last entry. var entry string for i, k := range m.order { v := m.data[k] entry += k + "=" + v if i+1 < len(m.data) { entry += "\t" } } m.enc = append(m.enc, []byte(entry)...) // Calculate and set data length in encoded meta header. dataLen := len(m.enc[headSize:]) binary.BigEndian.PutUint16(m.enc[dataLenIdx:dataLenIdx+2], uint16(dataLen)) return m.enc } // ReadFrom extracts a value from a metadata string d, for the given key. If the // key is not present in the metadata string, an error is returned. If the // metadata header is not present in the string, an error is returned. func ReadFrom(d []byte, key string) (string, error) { if d[0] != 0 { return "", errNoHeader } else if d[0] == 0 && binary.BigEndian.Uint16(d[2:headSize]) != uint16(len(d[headSize:])) { return "", errInvalidHeader } d = d[headSize:] entries := strings.Split(string(d), "\t") for _, entry := range entries { kv := strings.Split(entry, "=") if kv[0] == key { return kv[1], nil } } return "", errKeyAbsent }