blob: eec24906f3e041b08508db9032e2e8f2bffbc5e4 [file] [log] [blame]
package digeststore
import (
"encoding/json"
"fmt"
"os"
"path"
"github.com/boltdb/bolt"
)
const (
SUB_DIR_NAME = "digeststore"
DIGEST_DB_NAME = "digest_store.boltdb"
)
// DigestInfo aggregates all information we have about an individual digest.
type DigestInfo struct {
// TestName for this digest.
TestName string
// Digest that uniquely identifies the digest within this test.
Digest string
// First containes the timestamp of the first occurance of this digest.
First int64
// Last contains the timestamp of the last time we have seen this digest.
Last int64
// Exception stores a string representing the exception that was encountered
// retrieving this digest. If Exception is "" then there was no problem.
Exception string
// IssueIDs is a list of issue ids that are associated with this digest.
IssueIDs []int
}
// UpdateTimestamps updates the time stamps of a DigestInfo based on the
// arguments. It returns true if the digest info was modified.
func (d *DigestInfo) UpdateTimestamps(first int64, last int64) bool {
changed := false
if first < d.First {
d.First = first
changed = true
}
if last > d.Last {
d.Last = last
changed = true
}
return changed
}
type DigestStore interface {
// Get returns the information about the given testName/digest pair.
Get(testName, digest string) (*DigestInfo, bool, error)
// Update updates the stored information about the testname/digest
// pairs identified in the list of DigestInfos.
Update(digetInfos []*DigestInfo) error
}
type BoltDigestStore struct {
digestDB *bolt.DB
}
func New(storageDir string) (DigestStore, error) {
dbDir := path.Join(storageDir, SUB_DIR_NAME)
if err := os.MkdirAll(dbDir, 0755); err != nil {
return nil, err
}
db, err := bolt.Open(path.Join(dbDir, DIGEST_DB_NAME), 0666, nil)
if err != nil {
return nil, err
}
return &BoltDigestStore{digestDB: db}, nil
}
func (b BoltDigestStore) Get(testName, digest string) (*DigestInfo, bool, error) {
if testName == "" {
return nil, false, fmt.Errorf("No testname provided for digest '%s'", digest)
}
var ret *DigestInfo = nil
err := b.digestDB.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(testName))
if bucket == nil {
return nil
}
if retBytes := bucket.Get([]byte(digest)); retBytes != nil {
if err := json.Unmarshal(retBytes, &ret); err != nil {
return err
}
}
return nil
})
return ret, ret != nil, err
}
func (b BoltDigestStore) Update(digestInfos []*DigestInfo) error {
return b.digestDB.Update(func(tx *bolt.Tx) error {
// Wrap everything into a single transaction. This avoids a write lock
// by using the lock of the transaction.
writeDigestInfos := make([]*DigestInfo, 0, len(digestInfos))
for _, digestInfo := range digestInfos {
di, found, err := b.Get(digestInfo.TestName, digestInfo.Digest)
if err != nil {
return err
}
// If the testname/digest was not found or needs to be updated we
// record it.
if !found {
writeDigestInfos = append(writeDigestInfos, digestInfo)
} else if di.UpdateTimestamps(digestInfo.First, digestInfo.Last) {
writeDigestInfos = append(writeDigestInfos, di)
}
}
// If no digest needs updating we are done.
if len(writeDigestInfos) == 0 {
return nil
}
for _, digestInfo := range writeDigestInfos {
bucket, err := tx.CreateBucketIfNotExists([]byte(digestInfo.TestName))
if err != nil {
return err
}
jsonBytes, err := json.Marshal(digestInfo)
if err != nil {
return err
}
if err = bucket.Put([]byte(digestInfo.Digest), jsonBytes); err != nil {
return err
}
}
return nil
})
}