You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
5.5 KiB
238 lines
5.5 KiB
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/chrislusf/weed-fs/go/util"
|
|
"github.com/tgulacsi/go-cdb"
|
|
)
|
|
|
|
// CDB-backed read-only needle map
|
|
type cdbMap struct {
|
|
c1, c2 *cdb.Cdb
|
|
fn1, fn2 string
|
|
mapMetric
|
|
}
|
|
|
|
const maxCdbRecCount = 100000000
|
|
|
|
var errReadOnly = errors.New("cannot modify a read-only map")
|
|
|
|
// opens the cdb file(s) (base.cdb OR base.1.cdb AND base.2.cdb)
|
|
// in case of two files, the metric (at key 'M') must be in base.2.cdb
|
|
func OpenCdbMap(fileName string) (m *cdbMap, err error) {
|
|
m = new(cdbMap)
|
|
if m.c1, err = cdb.Open(fileName); err == nil {
|
|
m.fn1 = fileName
|
|
err = getMetric(m.c1, &m.mapMetric)
|
|
return
|
|
}
|
|
if os.IsNotExist(err) {
|
|
bn, ext := baseFilename(fileName)
|
|
m.fn1 = bn + ".1" + ext
|
|
if m.c1, err = cdb.Open(m.fn1); err != nil {
|
|
return nil, err
|
|
}
|
|
m.fn2 = bn + ".2" + ext
|
|
if m.c2, err = cdb.Open(m.fn2); err != nil {
|
|
return nil, err
|
|
}
|
|
err = getMetric(m.c2, &m.mapMetric)
|
|
return
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
func (m *cdbMap) Put(key uint64, offset uint32, size uint32) (int, error) {
|
|
return -1, errReadOnly
|
|
}
|
|
func (m *cdbMap) Delete(key uint64) error {
|
|
return errReadOnly
|
|
}
|
|
|
|
func (m *cdbMap) Close() {
|
|
if m.c2 != nil {
|
|
m.c2.Close()
|
|
m.c2 = nil
|
|
}
|
|
if m.c1 != nil {
|
|
m.c1.Close()
|
|
m.c1 = nil
|
|
}
|
|
}
|
|
|
|
func (m *cdbMap) Destroy() error {
|
|
return errors.New("Can not delete readonly volumes")
|
|
}
|
|
|
|
func (m cdbMap) ContentSize() uint64 {
|
|
return m.FileByteCounter
|
|
}
|
|
func (m cdbMap) DeletedSize() uint64 {
|
|
return m.DeletionByteCounter
|
|
}
|
|
func (m cdbMap) FileCount() int {
|
|
return m.FileCounter
|
|
}
|
|
func (m *cdbMap) DeletedCount() int {
|
|
return m.DeletionCounter
|
|
}
|
|
func (m *cdbMap) MaxFileKey() uint64 {
|
|
return m.MaximumFileKey
|
|
}
|
|
|
|
func getMetric(c *cdb.Cdb, m *mapMetric) error {
|
|
data, err := c.Data([]byte{'M'})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, m)
|
|
}
|
|
|
|
func (m cdbMap) Get(key uint64) (element *NeedleValue, ok bool) {
|
|
var (
|
|
data []byte
|
|
k []byte = make([]byte, 8)
|
|
err error
|
|
)
|
|
util.Uint64toBytes(k, key)
|
|
if data, err = m.c1.Data(k); err != nil || data == nil {
|
|
if m.c2 == nil {
|
|
return nil, false
|
|
}
|
|
if data, err = m.c2.Data(k); err != nil || data == nil {
|
|
return nil, false
|
|
}
|
|
}
|
|
return &NeedleValue{Key: Key(key), Offset: util.BytesToUint32(data[:4]),
|
|
Size: util.BytesToUint32(data[4:])}, true
|
|
}
|
|
|
|
func (m cdbMap) Visit(visit func(NeedleValue) error) (err error) {
|
|
fh, err := os.Open(m.fn1)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot open %s: %s", m.fn1, err)
|
|
}
|
|
defer fh.Close()
|
|
walk := func(elt cdb.Element) error {
|
|
if len(elt.Key) != 8 {
|
|
return nil
|
|
}
|
|
return visit(NeedleValue{Key: Key(util.BytesToUint64(elt.Key)),
|
|
Offset: util.BytesToUint32(elt.Data[:4]),
|
|
Size: util.BytesToUint32(elt.Data[4:8])})
|
|
}
|
|
if err = cdb.DumpMap(fh, walk); err != nil {
|
|
return err
|
|
}
|
|
if m.c2 == nil {
|
|
return nil
|
|
}
|
|
fh.Close()
|
|
if fh, err = os.Open(m.fn2); err != nil {
|
|
return fmt.Errorf("cannot open %s: %s", m.fn2, err)
|
|
}
|
|
return cdb.DumpMap(fh, walk)
|
|
}
|
|
|
|
// converts an .idx index to a cdb
|
|
func ConvertIndexToCdb(cdbName string, index *os.File) error {
|
|
idx, err := LoadNeedleMap(index)
|
|
if err != nil {
|
|
return fmt.Errorf("error loading needle map %s: %s", index.Name(), err)
|
|
}
|
|
defer idx.Close()
|
|
return DumpNeedleMapToCdb(cdbName, idx)
|
|
}
|
|
|
|
// dumps a NeedleMap into a cdb
|
|
func DumpNeedleMapToCdb(cdbName string, nm *NeedleMap) error {
|
|
tempnam := cdbName + "t"
|
|
fnames := make([]string, 1, 2)
|
|
adder, closer, err := openTempCdb(tempnam)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating factory: %s", err)
|
|
}
|
|
fnames[0] = tempnam
|
|
|
|
elt := cdb.Element{Key: make([]byte, 8), Data: make([]byte, 8)}
|
|
|
|
fcount := uint64(0)
|
|
walk := func(key uint64, offset, size uint32) error {
|
|
if fcount >= maxCdbRecCount {
|
|
if err = closer(); err != nil {
|
|
return err
|
|
}
|
|
tempnam = cdbName + "t2"
|
|
if adder, closer, err = openTempCdb(tempnam); err != nil {
|
|
return fmt.Errorf("error creating second factory: %s", err)
|
|
}
|
|
fnames = append(fnames, tempnam)
|
|
fcount = 0
|
|
}
|
|
util.Uint64toBytes(elt.Key, key)
|
|
util.Uint32toBytes(elt.Data[:4], offset)
|
|
util.Uint32toBytes(elt.Data[4:], size)
|
|
fcount++
|
|
return adder(elt)
|
|
}
|
|
// and write out the cdb from there
|
|
err = nm.Visit(func(nv NeedleValue) error {
|
|
return walk(uint64(nv.Key), nv.Offset, nv.Size)
|
|
})
|
|
if err != nil {
|
|
closer()
|
|
return fmt.Errorf("error walking index %v: %s", nm, err)
|
|
}
|
|
// store fileBytes
|
|
data, e := json.Marshal(nm.mapMetric)
|
|
if e != nil {
|
|
return fmt.Errorf("error marshaling metric %v: %s", nm.mapMetric, e)
|
|
}
|
|
if err = adder(cdb.Element{Key: []byte{'M'}, Data: data}); err != nil {
|
|
return err
|
|
}
|
|
if err = closer(); err != nil {
|
|
return err
|
|
}
|
|
|
|
os.Remove(cdbName)
|
|
if len(fnames) == 1 {
|
|
return os.Rename(fnames[0], cdbName)
|
|
}
|
|
bn, ext := baseFilename(cdbName)
|
|
if err = os.Rename(fnames[0], bn+".1"+ext); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(fnames[1], bn+".2"+ext)
|
|
}
|
|
|
|
func openTempCdb(fileName string) (cdb.AdderFunc, cdb.CloserFunc, error) {
|
|
fh, err := os.Create(fileName)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("cannot create cdb file %s: %v", fileName, err)
|
|
}
|
|
adder, closer, err := cdb.MakeFactory(fh)
|
|
if err != nil {
|
|
fh.Close()
|
|
return nil, nil, fmt.Errorf("error creating factory: %v", err)
|
|
}
|
|
return adder, func() error {
|
|
if e := closer(); e != nil {
|
|
fh.Close()
|
|
return e
|
|
}
|
|
fh.Close()
|
|
return nil
|
|
}, nil
|
|
}
|
|
|
|
// returns filename without extension, and the extension
|
|
func baseFilename(fileName string) (string, string) {
|
|
ext := filepath.Ext(fileName)
|
|
return fileName[:len(fileName)-len(ext)], ext
|
|
}
|