mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-06 05:30:57 +00:00
Merge pull request #766 from aptly-dev/761-more-lazy
Reimplement DB collections for mirrors, repos and snapshots
This commit is contained in:
@@ -13,7 +13,7 @@ RUN_LONG_TESTS?=yes
|
|||||||
|
|
||||||
GO_1_10_AND_HIGHER=$(shell (printf '%s\n' go1.10 $(GOVERSION) | sort -cV >/dev/null 2>&1) && echo "yes")
|
GO_1_10_AND_HIGHER=$(shell (printf '%s\n' go1.10 $(GOVERSION) | sort -cV >/dev/null 2>&1) && echo "yes")
|
||||||
|
|
||||||
all: test check system-test
|
all: test bench check system-test
|
||||||
|
|
||||||
prepare:
|
prepare:
|
||||||
go get -u github.com/alecthomas/gometalinter
|
go get -u github.com/alecthomas/gometalinter
|
||||||
@@ -57,6 +57,9 @@ else
|
|||||||
go test -v `go list ./... | grep -v vendor/` -gocheck.v=true
|
go test -v `go list ./... | grep -v vendor/` -gocheck.v=true
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
bench:
|
||||||
|
go test -v ./deb -run=nothing -bench=. -benchmem
|
||||||
|
|
||||||
mem.png: mem.dat mem.gp
|
mem.png: mem.dat mem.gp
|
||||||
gnuplot mem.gp
|
gnuplot mem.gp
|
||||||
open mem.png
|
open mem.png
|
||||||
|
|||||||
+70
-56
@@ -2,6 +2,7 @@ package deb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -93,8 +94,8 @@ func (repo *LocalRepo) RefKey() []byte {
|
|||||||
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
|
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
|
||||||
type LocalRepoCollection struct {
|
type LocalRepoCollection struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
db database.Storage
|
db database.Storage
|
||||||
list []*LocalRepo
|
cache map[string]*LocalRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection
|
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection
|
||||||
@@ -102,43 +103,59 @@ func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
|
|||||||
return &LocalRepoCollection{
|
return &LocalRepoCollection{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
db: db,
|
db: db,
|
||||||
|
cache: make(map[string]*LocalRepo),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (collection *LocalRepoCollection) loadList() {
|
func (collection *LocalRepoCollection) search(filter func(*LocalRepo) bool, unique bool) []*LocalRepo {
|
||||||
if collection.list != nil {
|
result := []*LocalRepo(nil)
|
||||||
return
|
for _, r := range collection.cache {
|
||||||
}
|
if filter(r) {
|
||||||
|
result = append(result, r)
|
||||||
blobs := collection.db.FetchByPrefix([]byte("L"))
|
|
||||||
collection.list = make([]*LocalRepo, 0, len(blobs))
|
|
||||||
|
|
||||||
for _, blob := range blobs {
|
|
||||||
r := &LocalRepo{}
|
|
||||||
if err := r.Decode(blob); err != nil {
|
|
||||||
log.Printf("Error decoding repo: %s\n", err)
|
|
||||||
} else {
|
|
||||||
collection.list = append(collection.list, r)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unique && len(result) > 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.db.ProcessByPrefix([]byte("L"), func(key, blob []byte) error {
|
||||||
|
r := &LocalRepo{}
|
||||||
|
if err := r.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding local repo: %s\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter(r) {
|
||||||
|
if _, exists := collection.cache[r.UUID]; !exists {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
result = append(result, r)
|
||||||
|
if unique {
|
||||||
|
return errors.New("abort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appends new repo to collection and saves it
|
// Add appends new repo to collection and saves it
|
||||||
func (collection *LocalRepoCollection) Add(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) Add(repo *LocalRepo) error {
|
||||||
collection.loadList()
|
_, err := collection.ByName(repo.Name)
|
||||||
|
|
||||||
for _, r := range collection.list {
|
if err == nil {
|
||||||
if r.Name == repo.Name {
|
return fmt.Errorf("local repo with name %s already exists", repo.Name)
|
||||||
return fmt.Errorf("local repo with name %s already exists", repo.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := collection.Update(repo)
|
err = collection.Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list = append(collection.list, repo)
|
collection.cache[repo.UUID] = repo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +176,6 @@ func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
|
|||||||
|
|
||||||
// LoadComplete loads additional information for local repo
|
// LoadComplete loads additional information for local repo
|
||||||
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
||||||
collection.loadList()
|
|
||||||
|
|
||||||
encoded, err := collection.db.Get(repo.RefKey())
|
encoded, err := collection.db.Get(repo.RefKey())
|
||||||
if err == database.ErrNotFound {
|
if err == database.ErrNotFound {
|
||||||
return nil
|
return nil
|
||||||
@@ -175,26 +190,39 @@ func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
|||||||
|
|
||||||
// ByName looks up repository by name
|
// ByName looks up repository by name
|
||||||
func (collection *LocalRepoCollection) ByName(name string) (*LocalRepo, error) {
|
func (collection *LocalRepoCollection) ByName(name string) (*LocalRepo, error) {
|
||||||
collection.loadList()
|
result := collection.search(func(r *LocalRepo) bool { return r.Name == name }, true)
|
||||||
|
if len(result) == 0 {
|
||||||
for _, r := range collection.list {
|
return nil, fmt.Errorf("local repo with name %s not found", name)
|
||||||
if r.Name == name {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("local repo with name %s not found", name)
|
|
||||||
|
return result[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByUUID looks up repository by uuid
|
// ByUUID looks up repository by uuid
|
||||||
func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
|
func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
|
||||||
collection.loadList()
|
if r, ok := collection.cache[uuid]; ok {
|
||||||
|
return r, nil
|
||||||
for _, r := range collection.list {
|
|
||||||
if r.UUID == uuid {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("local repo with uuid %s not found", uuid)
|
|
||||||
|
key := (&LocalRepo{UUID: uuid}).Key()
|
||||||
|
|
||||||
|
value, err := collection.db.Get(key)
|
||||||
|
if err == database.ErrNotFound {
|
||||||
|
return nil, fmt.Errorf("local repo with uuid %s not found", uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &LocalRepo{}
|
||||||
|
err = r.Decode(value)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach runs method for each repository
|
// ForEach runs method for each repository
|
||||||
@@ -212,30 +240,16 @@ func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) e
|
|||||||
|
|
||||||
// Len returns number of remote repos
|
// Len returns number of remote repos
|
||||||
func (collection *LocalRepoCollection) Len() int {
|
func (collection *LocalRepoCollection) Len() int {
|
||||||
collection.loadList()
|
return len(collection.db.KeysByPrefix([]byte("L")))
|
||||||
|
|
||||||
return len(collection.list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop removes remote repo from collection
|
// Drop removes remote repo from collection
|
||||||
func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
|
func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
|
||||||
collection.loadList()
|
if _, err := collection.db.Get(repo.Key()); err == database.ErrNotFound {
|
||||||
|
|
||||||
repoPosition := -1
|
|
||||||
|
|
||||||
for i, r := range collection.list {
|
|
||||||
if r == repo {
|
|
||||||
repoPosition = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repoPosition == -1 {
|
|
||||||
panic("local repo not found!")
|
panic("local repo not found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list[len(collection.list)-1], collection.list[repoPosition], collection.list =
|
delete(collection.cache, repo.UUID)
|
||||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
|
||||||
|
|
||||||
err := collection.db.Delete(repo.Key())
|
err := collection.db.Delete(repo.Key())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ func (s *LocalRepoCollectionSuite) TestByUUID(c *C) {
|
|||||||
|
|
||||||
r, err := s.collection.ByUUID(repo.UUID)
|
r, err := s.collection.ByUUID(repo.UUID)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(r, Equals, repo)
|
||||||
|
|
||||||
|
collection := NewLocalRepoCollection(s.db)
|
||||||
|
r, err = collection.ByUUID(repo.UUID)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
c.Assert(r.String(), Equals, repo.String())
|
c.Assert(r.String(), Equals, repo.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+69
-54
@@ -3,6 +3,7 @@ package deb
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
gocontext "context"
|
gocontext "context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -654,8 +655,8 @@ func (repo *RemoteRepo) RefKey() []byte {
|
|||||||
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
|
// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos
|
||||||
type RemoteRepoCollection struct {
|
type RemoteRepoCollection struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
db database.Storage
|
db database.Storage
|
||||||
list []*RemoteRepo
|
cache map[string]*RemoteRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
|
// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection
|
||||||
@@ -663,43 +664,59 @@ func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection {
|
|||||||
return &RemoteRepoCollection{
|
return &RemoteRepoCollection{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
db: db,
|
db: db,
|
||||||
|
cache: make(map[string]*RemoteRepo),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (collection *RemoteRepoCollection) loadList() {
|
func (collection *RemoteRepoCollection) search(filter func(*RemoteRepo) bool, unique bool) []*RemoteRepo {
|
||||||
if collection.list != nil {
|
result := []*RemoteRepo(nil)
|
||||||
return
|
for _, r := range collection.cache {
|
||||||
}
|
if filter(r) {
|
||||||
|
result = append(result, r)
|
||||||
blobs := collection.db.FetchByPrefix([]byte("R"))
|
|
||||||
collection.list = make([]*RemoteRepo, 0, len(blobs))
|
|
||||||
|
|
||||||
for _, blob := range blobs {
|
|
||||||
r := &RemoteRepo{}
|
|
||||||
if err := r.Decode(blob); err != nil {
|
|
||||||
log.Printf("Error decoding mirror: %s\n", err)
|
|
||||||
} else {
|
|
||||||
collection.list = append(collection.list, r)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unique && len(result) > 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error {
|
||||||
|
r := &RemoteRepo{}
|
||||||
|
if err := r.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding remote repo: %s\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter(r) {
|
||||||
|
if _, exists := collection.cache[r.UUID]; !exists {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
result = append(result, r)
|
||||||
|
if unique {
|
||||||
|
return errors.New("abort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appends new repo to collection and saves it
|
// Add appends new repo to collection and saves it
|
||||||
func (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error {
|
func (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error {
|
||||||
collection.loadList()
|
_, err := collection.ByName(repo.Name)
|
||||||
|
|
||||||
for _, r := range collection.list {
|
if err == nil {
|
||||||
if r.Name == repo.Name {
|
return fmt.Errorf("mirror with name %s already exists", repo.Name)
|
||||||
return fmt.Errorf("mirror with name %s already exists", repo.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := collection.Update(repo)
|
err = collection.Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list = append(collection.list, repo)
|
collection.cache[repo.UUID] = repo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,26 +751,38 @@ func (collection *RemoteRepoCollection) LoadComplete(repo *RemoteRepo) error {
|
|||||||
|
|
||||||
// ByName looks up repository by name
|
// ByName looks up repository by name
|
||||||
func (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) {
|
func (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) {
|
||||||
collection.loadList()
|
result := collection.search(func(r *RemoteRepo) bool { return r.Name == name }, true)
|
||||||
|
if len(result) == 0 {
|
||||||
for _, r := range collection.list {
|
return nil, fmt.Errorf("mirror with name %s not found", name)
|
||||||
if r.Name == name {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("mirror with name %s not found", name)
|
|
||||||
|
return result[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByUUID looks up repository by uuid
|
// ByUUID looks up repository by uuid
|
||||||
func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) {
|
func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) {
|
||||||
collection.loadList()
|
if r, ok := collection.cache[uuid]; ok {
|
||||||
|
return r, nil
|
||||||
for _, r := range collection.list {
|
|
||||||
if r.UUID == uuid {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("mirror with uuid %s not found", uuid)
|
|
||||||
|
key := (&RemoteRepo{UUID: uuid}).Key()
|
||||||
|
|
||||||
|
value, err := collection.db.Get(key)
|
||||||
|
if err == database.ErrNotFound {
|
||||||
|
return nil, fmt.Errorf("mirror with uuid %s not found", uuid)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &RemoteRepo{}
|
||||||
|
err = r.Decode(value)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
collection.cache[r.UUID] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach runs method for each repository
|
// ForEach runs method for each repository
|
||||||
@@ -771,30 +800,16 @@ func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error)
|
|||||||
|
|
||||||
// Len returns number of remote repos
|
// Len returns number of remote repos
|
||||||
func (collection *RemoteRepoCollection) Len() int {
|
func (collection *RemoteRepoCollection) Len() int {
|
||||||
collection.loadList()
|
return len(collection.db.KeysByPrefix([]byte("R")))
|
||||||
|
|
||||||
return len(collection.list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop removes remote repo from collection
|
// Drop removes remote repo from collection
|
||||||
func (collection *RemoteRepoCollection) Drop(repo *RemoteRepo) error {
|
func (collection *RemoteRepoCollection) Drop(repo *RemoteRepo) error {
|
||||||
collection.loadList()
|
if _, err := collection.db.Get(repo.Key()); err == database.ErrNotFound {
|
||||||
|
|
||||||
repoPosition := -1
|
|
||||||
|
|
||||||
for i, r := range collection.list {
|
|
||||||
if r == repo {
|
|
||||||
repoPosition = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repoPosition == -1 {
|
|
||||||
panic("repo not found!")
|
panic("repo not found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list[len(collection.list)-1], collection.list[repoPosition], collection.list =
|
delete(collection.cache, repo.UUID)
|
||||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
|
||||||
|
|
||||||
err := collection.db.Delete(repo.Key())
|
err := collection.db.Delete(repo.Key())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -651,6 +651,11 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
|||||||
|
|
||||||
r, err := s.collection.ByUUID(repo.UUID)
|
r, err := s.collection.ByUUID(repo.UUID)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(r, Equals, repo)
|
||||||
|
|
||||||
|
collection := NewRemoteRepoCollection(s.db)
|
||||||
|
r, err = collection.ByUUID(repo.UUID)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
c.Assert(r.String(), Equals, repo.String())
|
c.Assert(r.String(), Equals, repo.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+100
-104
@@ -173,8 +173,8 @@ func (s *Snapshot) Decode(input []byte) error {
|
|||||||
// SnapshotCollection does listing, updating/adding/deleting of Snapshots
|
// SnapshotCollection does listing, updating/adding/deleting of Snapshots
|
||||||
type SnapshotCollection struct {
|
type SnapshotCollection struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
db database.Storage
|
db database.Storage
|
||||||
list []*Snapshot
|
cache map[string]*Snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSnapshotCollection loads Snapshots from DB and makes up collection
|
// NewSnapshotCollection loads Snapshots from DB and makes up collection
|
||||||
@@ -182,43 +182,23 @@ func NewSnapshotCollection(db database.Storage) *SnapshotCollection {
|
|||||||
return &SnapshotCollection{
|
return &SnapshotCollection{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
db: db,
|
db: db,
|
||||||
}
|
cache: map[string]*Snapshot{},
|
||||||
}
|
|
||||||
|
|
||||||
func (collection *SnapshotCollection) loadList() {
|
|
||||||
if collection.list != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
blobs := collection.db.FetchByPrefix([]byte("S"))
|
|
||||||
collection.list = make([]*Snapshot, 0, len(blobs))
|
|
||||||
|
|
||||||
for _, blob := range blobs {
|
|
||||||
s := &Snapshot{}
|
|
||||||
if err := s.Decode(blob); err != nil {
|
|
||||||
log.Printf("Error decoding snapshot: %s\n", err)
|
|
||||||
} else {
|
|
||||||
collection.list = append(collection.list, s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appends new repo to collection and saves it
|
// Add appends new repo to collection and saves it
|
||||||
func (collection *SnapshotCollection) Add(snapshot *Snapshot) error {
|
func (collection *SnapshotCollection) Add(snapshot *Snapshot) error {
|
||||||
collection.loadList()
|
_, err := collection.ByName(snapshot.Name)
|
||||||
|
if err == nil {
|
||||||
for _, s := range collection.list {
|
return fmt.Errorf("snapshot with name %s already exists", snapshot.Name)
|
||||||
if s.Name == snapshot.Name {
|
|
||||||
return fmt.Errorf("snapshot with name %s already exists", snapshot.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := collection.Update(snapshot)
|
err = collection.Update(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list = append(collection.list, snapshot)
|
collection.cache[snapshot.UUID] = snapshot
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,70 +225,96 @@ func (collection *SnapshotCollection) LoadComplete(snapshot *Snapshot) error {
|
|||||||
return snapshot.packageRefs.Decode(encoded)
|
return snapshot.packageRefs.Decode(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByName looks up snapshot by name
|
func (collection *SnapshotCollection) search(filter func(*Snapshot) bool, unique bool) []*Snapshot {
|
||||||
func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) {
|
result := []*Snapshot(nil)
|
||||||
collection.loadList()
|
for _, s := range collection.cache {
|
||||||
|
if filter(s) {
|
||||||
for _, s := range collection.list {
|
result = append(result, s)
|
||||||
if s.Name == name {
|
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unique && len(result) > 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error {
|
||||||
|
s := &Snapshot{}
|
||||||
|
if err := s.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding snapshot: %s\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter(s) {
|
||||||
|
if _, exists := collection.cache[s.UUID]; !exists {
|
||||||
|
collection.cache[s.UUID] = s
|
||||||
|
result = append(result, s)
|
||||||
|
if unique {
|
||||||
|
return errors.New("abort")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByName looks up snapshot by name
|
||||||
|
func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) {
|
||||||
|
result := collection.search(func(s *Snapshot) bool { return s.Name == name }, true)
|
||||||
|
if len(result) > 0 {
|
||||||
|
return result[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("snapshot with name %s not found", name)
|
return nil, fmt.Errorf("snapshot with name %s not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByUUID looks up snapshot by UUID
|
// ByUUID looks up snapshot by UUID
|
||||||
func (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) {
|
func (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) {
|
||||||
collection.loadList()
|
if s, ok := collection.cache[uuid]; ok {
|
||||||
|
return s, nil
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.UUID == uuid {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("snapshot with uuid %s not found", uuid)
|
|
||||||
|
key := (&Snapshot{UUID: uuid}).Key()
|
||||||
|
|
||||||
|
value, err := collection.db.Get(key)
|
||||||
|
if err == database.ErrNotFound {
|
||||||
|
return nil, fmt.Errorf("snapshot with uuid %s not found", uuid)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Snapshot{}
|
||||||
|
err = s.Decode(value)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
collection.cache[s.UUID] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source
|
// ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source
|
||||||
func (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot {
|
func (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot {
|
||||||
collection.loadList()
|
return collection.search(func(s *Snapshot) bool {
|
||||||
|
return s.SourceKind == SourceRemoteRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
|
||||||
var result []*Snapshot
|
}, false)
|
||||||
|
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.SourceKind == SourceRemoteRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source
|
// ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source
|
||||||
func (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot {
|
func (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot {
|
||||||
collection.loadList()
|
return collection.search(func(s *Snapshot) bool {
|
||||||
|
return s.SourceKind == SourceLocalRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
|
||||||
var result []*Snapshot
|
}, false)
|
||||||
|
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.SourceKind == SourceLocalRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BySnapshotSource looks up snapshots that have specified snapshot as a source
|
// BySnapshotSource looks up snapshots that have specified snapshot as a source
|
||||||
func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot {
|
func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot {
|
||||||
collection.loadList()
|
return collection.search(func(s *Snapshot) bool {
|
||||||
|
return s.SourceKind == "snapshot" && utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID)
|
||||||
var result []*Snapshot
|
}, false)
|
||||||
|
|
||||||
for _, s := range collection.list {
|
|
||||||
if s.SourceKind == "snapshot" && utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID) {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach runs method for each snapshot
|
// ForEach runs method for each snapshot
|
||||||
@@ -326,15 +332,25 @@ func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) err
|
|||||||
|
|
||||||
// ForEachSorted runs method for each snapshot following some sort order
|
// ForEachSorted runs method for each snapshot following some sort order
|
||||||
func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error {
|
func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error {
|
||||||
collection.loadList()
|
blobs := collection.db.FetchByPrefix([]byte("S"))
|
||||||
|
list := make([]*Snapshot, 0, len(blobs))
|
||||||
|
|
||||||
sorter, err := newSnapshotSorter(sortMethod, collection)
|
for _, blob := range blobs {
|
||||||
|
s := &Snapshot{}
|
||||||
|
if err := s.Decode(blob); err != nil {
|
||||||
|
log.Printf("Error decoding snapshot: %s\n", err)
|
||||||
|
} else {
|
||||||
|
list = append(list, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sorter, err := newSnapshotSorter(sortMethod, list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range sorter.list {
|
for _, s := range sorter.list {
|
||||||
err = handler(collection.list[i])
|
err = handler(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -346,30 +362,16 @@ func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler f
|
|||||||
// Len returns number of snapshots in collection
|
// Len returns number of snapshots in collection
|
||||||
// ForEach runs method for each snapshot
|
// ForEach runs method for each snapshot
|
||||||
func (collection *SnapshotCollection) Len() int {
|
func (collection *SnapshotCollection) Len() int {
|
||||||
collection.loadList()
|
return len(collection.db.KeysByPrefix([]byte("S")))
|
||||||
|
|
||||||
return len(collection.list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop removes snapshot from collection
|
// Drop removes snapshot from collection
|
||||||
func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
|
func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
|
||||||
collection.loadList()
|
if _, err := collection.db.Get(snapshot.Key()); err == database.ErrNotFound {
|
||||||
|
|
||||||
snapshotPosition := -1
|
|
||||||
|
|
||||||
for i, s := range collection.list {
|
|
||||||
if s == snapshot {
|
|
||||||
snapshotPosition = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if snapshotPosition == -1 {
|
|
||||||
panic("snapshot not found!")
|
panic("snapshot not found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.list[len(collection.list)-1], collection.list[snapshotPosition], collection.list =
|
delete(collection.cache, snapshot.UUID)
|
||||||
nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1]
|
|
||||||
|
|
||||||
err := collection.db.Delete(snapshot.Key())
|
err := collection.db.Delete(snapshot.Key())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -386,13 +388,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type snapshotSorter struct {
|
type snapshotSorter struct {
|
||||||
list []int
|
list []*Snapshot
|
||||||
collection *SnapshotCollection
|
|
||||||
sortMethod int
|
sortMethod int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSnapshotSorter(sortMethod string, collection *SnapshotCollection) (*snapshotSorter, error) {
|
func newSnapshotSorter(sortMethod string, list []*Snapshot) (*snapshotSorter, error) {
|
||||||
s := &snapshotSorter{collection: collection}
|
s := &snapshotSorter{list: list}
|
||||||
|
|
||||||
switch sortMethod {
|
switch sortMethod {
|
||||||
case "time", "Time":
|
case "time", "Time":
|
||||||
@@ -403,11 +404,6 @@ func newSnapshotSorter(sortMethod string, collection *SnapshotCollection) (*snap
|
|||||||
return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
|
return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.list = make([]int, len(collection.list))
|
|
||||||
for i := range s.list {
|
|
||||||
s.list[i] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(s)
|
sort.Sort(s)
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -420,9 +416,9 @@ func (s *snapshotSorter) Swap(i, j int) {
|
|||||||
func (s *snapshotSorter) Less(i, j int) bool {
|
func (s *snapshotSorter) Less(i, j int) bool {
|
||||||
switch s.sortMethod {
|
switch s.sortMethod {
|
||||||
case SortName:
|
case SortName:
|
||||||
return s.collection.list[s.list[i]].Name < s.collection.list[s.list[j]].Name
|
return s.list[i].Name < s.list[j].Name
|
||||||
case SortTime:
|
case SortTime:
|
||||||
return s.collection.list[s.list[i]].CreatedAt.Before(s.collection.list[s.list[j]].CreatedAt)
|
return s.list[i].CreatedAt.Before(s.list[j].CreatedAt)
|
||||||
}
|
}
|
||||||
panic("unknown sort method")
|
panic("unknown sort method")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,3 +36,63 @@ func BenchmarkSnapshotCollectionForEach(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkSnapshotCollectionByUUID(b *testing.B) {
|
||||||
|
const count = 1024
|
||||||
|
|
||||||
|
tmpDir := os.TempDir()
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
db, _ := database.NewOpenDB(tmpDir)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
collection := NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
uuids := []string{}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
snapshot := NewSnapshotFromRefList(fmt.Sprintf("snapshot%d", i), nil, NewPackageRefList(), fmt.Sprintf("Snapshot number %d", i))
|
||||||
|
if collection.Add(snapshot) != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
uuids = append(uuids, snapshot.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
collection = NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
if _, err := collection.ByUUID(uuids[i%len(uuids)]); err != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSnapshotCollectionByName(b *testing.B) {
|
||||||
|
const count = 1024
|
||||||
|
|
||||||
|
tmpDir := os.TempDir()
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
db, _ := database.NewOpenDB(tmpDir)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
collection := NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
snapshot := NewSnapshotFromRefList(fmt.Sprintf("snapshot%d", i), nil, NewPackageRefList(), fmt.Sprintf("Snapshot number %d", i))
|
||||||
|
if collection.Add(snapshot) != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
collection = NewSnapshotCollection(db)
|
||||||
|
|
||||||
|
if _, err := collection.ByName(fmt.Sprintf("snapshot%d", i%count)); err != nil {
|
||||||
|
b.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+27
-1
@@ -2,6 +2,7 @@ package deb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
|
|
||||||
@@ -158,6 +159,10 @@ func (s *SnapshotCollectionSuite) TestAddByNameByUUID(c *C) {
|
|||||||
snapshot, err = collection.ByUUID(s.snapshot1.UUID)
|
snapshot, err = collection.ByUUID(s.snapshot1.UUID)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(snapshot.String(), Equals, s.snapshot1.String())
|
c.Assert(snapshot.String(), Equals, s.snapshot1.String())
|
||||||
|
|
||||||
|
snapshot, err = collection.ByUUID(s.snapshot2.UUID)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(snapshot.String(), Equals, s.snapshot2.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SnapshotCollectionSuite) TestUpdateLoadComplete(c *C) {
|
func (s *SnapshotCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||||
@@ -193,6 +198,23 @@ func (s *SnapshotCollectionSuite) TestForEachAndLen(c *C) {
|
|||||||
c.Assert(err, Equals, e)
|
c.Assert(err, Equals, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SnapshotCollectionSuite) TestForEachSorted(c *C) {
|
||||||
|
s.collection.Add(s.snapshot2)
|
||||||
|
s.collection.Add(s.snapshot1)
|
||||||
|
s.collection.Add(s.snapshot4)
|
||||||
|
s.collection.Add(s.snapshot3)
|
||||||
|
|
||||||
|
names := []string{}
|
||||||
|
|
||||||
|
err := s.collection.ForEachSorted("name", func(snapshot *Snapshot) error {
|
||||||
|
names = append(names, snapshot.Name)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Check(sort.StringsAreSorted(names), Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) {
|
func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) {
|
||||||
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
c.Assert(s.collection.Add(s.snapshot1), IsNil)
|
||||||
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
c.Assert(s.collection.Add(s.snapshot2), IsNil)
|
||||||
@@ -230,7 +252,11 @@ func (s *SnapshotCollectionSuite) TestFindSnapshotSource(c *C) {
|
|||||||
c.Assert(s.collection.Add(snapshot4), IsNil)
|
c.Assert(s.collection.Add(snapshot4), IsNil)
|
||||||
c.Assert(s.collection.Add(snapshot5), IsNil)
|
c.Assert(s.collection.Add(snapshot5), IsNil)
|
||||||
|
|
||||||
c.Check(s.collection.BySnapshotSource(s.snapshot1), DeepEquals, []*Snapshot{snapshot3, snapshot4})
|
list := s.collection.BySnapshotSource(s.snapshot1)
|
||||||
|
sorter, _ := newSnapshotSorter("name", list)
|
||||||
|
sort.Sort(sorter)
|
||||||
|
|
||||||
|
c.Check(sorter.list, DeepEquals, []*Snapshot{snapshot3, snapshot4})
|
||||||
c.Check(s.collection.BySnapshotSource(s.snapshot2), DeepEquals, []*Snapshot{snapshot3})
|
c.Check(s.collection.BySnapshotSource(s.snapshot2), DeepEquals, []*Snapshot{snapshot3})
|
||||||
c.Check(s.collection.BySnapshotSource(snapshot5), DeepEquals, []*Snapshot(nil))
|
c.Check(s.collection.BySnapshotSource(snapshot5), DeepEquals, []*Snapshot(nil))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user