mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-04-20 19:38:39 +00:00
See #765, #761 Collections were relying on keeping in-memory list of all the objects for any kind of operation which doesn't scale well the number of objects in the database. With this rewrite, objects are loaded only on demand which might be pessimization in some edge cases but should improve performance and memory footprint signifcantly.
261 lines
6.0 KiB
Go
261 lines
6.0 KiB
Go
package deb
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
|
|
"github.com/aptly-dev/aptly/database"
|
|
"github.com/pborman/uuid"
|
|
"github.com/ugorji/go/codec"
|
|
)
|
|
|
|
// LocalRepo is a collection of packages created locally
|
|
type LocalRepo struct {
|
|
// Permanent internal ID
|
|
UUID string `json:"-"`
|
|
// User-assigned name
|
|
Name string
|
|
// Comment
|
|
Comment string
|
|
// DefaultDistribution
|
|
DefaultDistribution string `codec:",omitempty"`
|
|
// DefaultComponent
|
|
DefaultComponent string `codec:",omitempty"`
|
|
// Uploaders configuration
|
|
Uploaders *Uploaders `code:",omitempty" json:"-"`
|
|
// "Snapshot" of current list of packages
|
|
packageRefs *PackageRefList
|
|
}
|
|
|
|
// NewLocalRepo creates new instance of Debian local repository
|
|
func NewLocalRepo(name string, comment string) *LocalRepo {
|
|
return &LocalRepo{
|
|
UUID: uuid.New(),
|
|
Name: name,
|
|
Comment: comment,
|
|
}
|
|
}
|
|
|
|
// String interface
|
|
func (repo *LocalRepo) String() string {
|
|
if repo.Comment != "" {
|
|
return fmt.Sprintf("[%s]: %s", repo.Name, repo.Comment)
|
|
}
|
|
return fmt.Sprintf("[%s]", repo.Name)
|
|
}
|
|
|
|
// NumPackages return number of packages in local repo
|
|
func (repo *LocalRepo) NumPackages() int {
|
|
if repo.packageRefs == nil {
|
|
return 0
|
|
}
|
|
return repo.packageRefs.Len()
|
|
}
|
|
|
|
// RefList returns package list for repo
|
|
func (repo *LocalRepo) RefList() *PackageRefList {
|
|
return repo.packageRefs
|
|
}
|
|
|
|
// UpdateRefList changes package list for local repo
|
|
func (repo *LocalRepo) UpdateRefList(reflist *PackageRefList) {
|
|
repo.packageRefs = reflist
|
|
}
|
|
|
|
// Encode does msgpack encoding of LocalRepo
|
|
func (repo *LocalRepo) Encode() []byte {
|
|
var buf bytes.Buffer
|
|
|
|
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
|
encoder.Encode(repo)
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// Decode decodes msgpack representation into LocalRepo
|
|
func (repo *LocalRepo) Decode(input []byte) error {
|
|
decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
|
|
return decoder.Decode(repo)
|
|
}
|
|
|
|
// Key is a unique id in DB
|
|
func (repo *LocalRepo) Key() []byte {
|
|
return []byte("L" + repo.UUID)
|
|
}
|
|
|
|
// RefKey is a unique id for package reference list
|
|
func (repo *LocalRepo) RefKey() []byte {
|
|
return []byte("E" + repo.UUID)
|
|
}
|
|
|
|
// LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
|
|
type LocalRepoCollection struct {
|
|
*sync.RWMutex
|
|
db database.Storage
|
|
cache map[string]*LocalRepo
|
|
}
|
|
|
|
// NewLocalRepoCollection loads LocalRepos from DB and makes up collection
|
|
func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection {
|
|
return &LocalRepoCollection{
|
|
RWMutex: &sync.RWMutex{},
|
|
db: db,
|
|
cache: make(map[string]*LocalRepo),
|
|
}
|
|
}
|
|
|
|
func (collection *LocalRepoCollection) search(filter func(*LocalRepo) bool, unique bool) []*LocalRepo {
|
|
result := []*LocalRepo(nil)
|
|
for _, r := range collection.cache {
|
|
if filter(r) {
|
|
result = append(result, 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
|
|
func (collection *LocalRepoCollection) Add(repo *LocalRepo) error {
|
|
_, err := collection.ByName(repo.Name)
|
|
|
|
if err == nil {
|
|
return fmt.Errorf("local repo with name %s already exists", repo.Name)
|
|
}
|
|
|
|
err = collection.Update(repo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
collection.cache[repo.UUID] = repo
|
|
return nil
|
|
}
|
|
|
|
// Update stores updated information about repo in DB
|
|
func (collection *LocalRepoCollection) Update(repo *LocalRepo) error {
|
|
err := collection.db.Put(repo.Key(), repo.Encode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if repo.packageRefs != nil {
|
|
err = collection.db.Put(repo.RefKey(), repo.packageRefs.Encode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LoadComplete loads additional information for local repo
|
|
func (collection *LocalRepoCollection) LoadComplete(repo *LocalRepo) error {
|
|
encoded, err := collection.db.Get(repo.RefKey())
|
|
if err == database.ErrNotFound {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repo.packageRefs = &PackageRefList{}
|
|
return repo.packageRefs.Decode(encoded)
|
|
}
|
|
|
|
// ByName looks up repository by name
|
|
func (collection *LocalRepoCollection) ByName(name string) (*LocalRepo, error) {
|
|
result := collection.search(func(r *LocalRepo) bool { return r.Name == name }, true)
|
|
if len(result) == 0 {
|
|
return nil, fmt.Errorf("local repo with name %s not found", name)
|
|
}
|
|
|
|
return result[0], nil
|
|
}
|
|
|
|
// ByUUID looks up repository by uuid
|
|
func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
|
|
if r, ok := collection.cache[uuid]; ok {
|
|
return r, nil
|
|
}
|
|
|
|
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
|
|
func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) error {
|
|
return collection.db.ProcessByPrefix([]byte("L"), func(key, blob []byte) error {
|
|
r := &LocalRepo{}
|
|
if err := r.Decode(blob); err != nil {
|
|
log.Printf("Error decoding repo: %s\n", err)
|
|
return nil
|
|
}
|
|
|
|
return handler(r)
|
|
})
|
|
}
|
|
|
|
// Len returns number of remote repos
|
|
func (collection *LocalRepoCollection) Len() int {
|
|
return len(collection.db.KeysByPrefix([]byte("L")))
|
|
}
|
|
|
|
// Drop removes remote repo from collection
|
|
func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
|
|
if _, err := collection.db.Get(repo.Key()); err == database.ErrNotFound {
|
|
panic("local repo not found!")
|
|
}
|
|
|
|
delete(collection.cache, repo.UUID)
|
|
|
|
err := collection.db.Delete(repo.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return collection.db.Delete(repo.RefKey())
|
|
}
|