From 7da203e8d23f5c59ecae8bf96356fe15ee45cb8b Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 19 Feb 2014 23:29:52 +0400 Subject: [PATCH] Local repo: model + collection. --- debian/local.go | 214 +++++++++++++++++++++++++++++++++++++++++++ debian/local_test.go | 190 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 404 insertions(+) create mode 100644 debian/local.go create mode 100644 debian/local_test.go diff --git a/debian/local.go b/debian/local.go new file mode 100644 index 00000000..a63adb0c --- /dev/null +++ b/debian/local.go @@ -0,0 +1,214 @@ +package debian + +import ( + "bytes" + "code.google.com/p/go-uuid/uuid" + "fmt" + "github.com/smira/aptly/database" + "github.com/ugorji/go/codec" + "log" +) + +// LocalRepo is a collection of packages created locally +type LocalRepo struct { + // Permanent internal ID + UUID string + // User-assigned name + Name string + // Comment + Comment string + // "Snapshot" of current list of packages + packageRefs *PackageRefList +} + +// NewLocalRepo creates new instance of Debian local repository +func NewLocalRepo(name string, comment string) *LocalRepo { + if comment == "" { + comment = "local repo" + } + return &LocalRepo{ + UUID: uuid.New(), + Name: name, + Comment: comment, + } +} + +// String interface +func (repo *LocalRepo) String() string { + return fmt.Sprintf("[%s]: %s", repo.Name, repo.Comment) +} + +// 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 +} + +// 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 { + db database.Storage + list []*LocalRepo +} + +// NewLocalRepoCollection loads LocalRepos from DB and makes up collection +func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection { + result := &LocalRepoCollection{ + db: db, + } + + blobs := db.FetchByPrefix([]byte("L")) + result.list = make([]*LocalRepo, 0, len(blobs)) + + for _, blob := range blobs { + r := &LocalRepo{} + if err := r.Decode(blob); err != nil { + log.Printf("Error decoding mirror: %s\n", err) + } else { + result.list = append(result.list, r) + } + } + + return result +} + +// Add appends new repo to collection and saves it +func (collection *LocalRepoCollection) Add(repo *LocalRepo) error { + for _, r := range collection.list { + if r.Name == repo.Name { + return fmt.Errorf("local repo with name %s already exists", repo.Name) + } + } + + err := collection.Update(repo) + if err != nil { + return err + } + + collection.list = append(collection.list, 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) { + for _, r := range collection.list { + if r.Name == name { + return r, nil + } + } + return nil, fmt.Errorf("local repo with name %s not found", name) +} + +// ByUUID looks up repository by uuid +func (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) { + for _, r := range collection.list { + if r.UUID == uuid { + return r, nil + } + } + return nil, fmt.Errorf("local repo with uuid %s not found", uuid) +} + +// ForEach runs method for each repository +func (collection *LocalRepoCollection) ForEach(handler func(*LocalRepo) error) error { + var err error + for _, r := range collection.list { + err = handler(r) + if err != nil { + return err + } + } + return err +} + +// Len returns number of remote repos +func (collection *LocalRepoCollection) Len() int { + return len(collection.list) +} + +// Drop removes remote repo from collection +func (collection *LocalRepoCollection) Drop(repo *LocalRepo) error { + repoPosition := -1 + + for i, r := range collection.list { + if r == repo { + repoPosition = i + break + } + } + + if repoPosition == -1 { + panic("local repo not found!") + } + + collection.list[len(collection.list)-1], collection.list[repoPosition], collection.list = + nil, collection.list[len(collection.list)-1], collection.list[:len(collection.list)-1] + + err := collection.db.Delete(repo.Key()) + if err != nil { + return err + } + + return collection.db.Delete(repo.RefKey()) +} diff --git a/debian/local_test.go b/debian/local_test.go new file mode 100644 index 00000000..93fd15f6 --- /dev/null +++ b/debian/local_test.go @@ -0,0 +1,190 @@ +package debian + +import ( + "errors" + "github.com/smira/aptly/database" + . "launchpad.net/gocheck" +) + +type LocalRepoSuite struct { + db database.Storage + list *PackageList + reflist *PackageRefList + repo *LocalRepo +} + +var _ = Suite(&LocalRepoSuite{}) + +func (s *LocalRepoSuite) SetUpTest(c *C) { + s.db, _ = database.OpenDB(c.MkDir()) + s.list = NewPackageList() + s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"}) + s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"}) + + s.reflist = NewPackageRefListFromPackageList(s.list) + + s.repo = NewLocalRepo("lrepo", "Super repo") + s.repo.packageRefs = s.reflist +} + +func (s *LocalRepoSuite) TearDownTest(c *C) { + s.db.Close() +} + +func (s *LocalRepoSuite) TestString(c *C) { + c.Check(NewLocalRepo("lrepo", "My first repo").String(), Equals, "[lrepo]: My first repo") + c.Check(NewLocalRepo("lrepo2", "").String(), Equals, "[lrepo2]: local repo") +} + +func (s *LocalRepoSuite) TestNumPackages(c *C) { + c.Check(NewLocalRepo("lrepo", "My first repo").NumPackages(), Equals, 0) + c.Check(s.repo.NumPackages(), Equals, 2) +} + +func (s *LocalRepoSuite) TestRefList(c *C) { + c.Check(NewLocalRepo("lrepo", "My first repo").RefList(), IsNil) + c.Check(s.repo.RefList(), Equals, s.reflist) +} + +func (s *LocalRepoSuite) TestEncodeDecode(c *C) { + repo := &LocalRepo{} + err := repo.Decode(s.repo.Encode()) + c.Assert(err, IsNil) + + c.Check(repo.Name, Equals, s.repo.Name) + c.Check(repo.Comment, Equals, s.repo.Comment) +} + +func (s *LocalRepoSuite) TestKey(c *C) { + c.Assert(len(s.repo.Key()), Equals, 37) + c.Assert(s.repo.Key()[0], Equals, byte('L')) +} + +func (s *LocalRepoSuite) TestRefKey(c *C) { + c.Assert(len(s.repo.RefKey()), Equals, 37) + c.Assert(s.repo.RefKey()[0], Equals, byte('E')) + c.Assert(s.repo.RefKey()[1:], DeepEquals, s.repo.Key()[1:]) +} + +type LocalRepoCollectionSuite struct { + db database.Storage + collection *LocalRepoCollection + list *PackageList + reflist *PackageRefList +} + +var _ = Suite(&LocalRepoCollectionSuite{}) + +func (s *LocalRepoCollectionSuite) SetUpTest(c *C) { + s.db, _ = database.OpenDB(c.MkDir()) + s.collection = NewLocalRepoCollection(s.db) + + s.list = NewPackageList() + s.list.Add(&Package{Name: "lib", Version: "1.7", Architecture: "i386"}) + s.list.Add(&Package{Name: "app", Version: "1.9", Architecture: "amd64"}) + + s.reflist = NewPackageRefListFromPackageList(s.list) +} + +func (s *LocalRepoCollectionSuite) TearDownTest(c *C) { + s.db.Close() +} + +func (s *LocalRepoCollectionSuite) TestAddByName(c *C) { + r, err := s.collection.ByName("local1") + c.Assert(err, ErrorMatches, "*.not found") + + repo := NewLocalRepo("local1", "Comment 1") + c.Assert(s.collection.Add(repo), IsNil) + c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists") + + r, err = s.collection.ByName("local1") + c.Assert(err, IsNil) + c.Assert(r.String(), Equals, repo.String()) + + collection := NewLocalRepoCollection(s.db) + r, err = collection.ByName("local1") + c.Assert(err, IsNil) + c.Assert(r.String(), Equals, repo.String()) +} + +func (s *LocalRepoCollectionSuite) TestByUUID(c *C) { + r, err := s.collection.ByUUID("some-uuid") + c.Assert(err, ErrorMatches, "*.not found") + + repo := NewLocalRepo("local1", "Comment 1") + c.Assert(s.collection.Add(repo), IsNil) + + r, err = s.collection.ByUUID(repo.UUID) + c.Assert(err, IsNil) + c.Assert(r.String(), Equals, repo.String()) +} + +func (s *LocalRepoCollectionSuite) TestUpdateLoadComplete(c *C) { + repo := NewLocalRepo("local1", "Comment 1") + c.Assert(s.collection.Update(repo), IsNil) + + collection := NewLocalRepoCollection(s.db) + r, err := collection.ByName("local1") + c.Assert(err, IsNil) + c.Assert(r.packageRefs, IsNil) + + repo.packageRefs = s.reflist + c.Assert(s.collection.Update(repo), IsNil) + + collection = NewLocalRepoCollection(s.db) + r, err = collection.ByName("local1") + c.Assert(err, IsNil) + c.Assert(r.packageRefs, IsNil) + c.Assert(r.NumPackages(), Equals, 0) + c.Assert(s.collection.LoadComplete(r), IsNil) + c.Assert(r.NumPackages(), Equals, 2) +} + +func (s *LocalRepoCollectionSuite) TestForEachAndLen(c *C) { + repo := NewLocalRepo("local1", "Comment 1") + s.collection.Add(repo) + + count := 0 + err := s.collection.ForEach(func(*LocalRepo) error { + count++ + return nil + }) + c.Assert(count, Equals, 1) + c.Assert(err, IsNil) + + c.Check(s.collection.Len(), Equals, 1) + + e := errors.New("c") + + err = s.collection.ForEach(func(*LocalRepo) error { + return e + }) + c.Assert(err, Equals, e) +} + +func (s *LocalRepoCollectionSuite) TestDrop(c *C) { + repo1 := NewLocalRepo("local1", "Comment 1") + s.collection.Add(repo1) + + repo2 := NewLocalRepo("local2", "Comment 2") + s.collection.Add(repo2) + + r1, _ := s.collection.ByUUID(repo1.UUID) + c.Check(r1, Equals, repo1) + + err := s.collection.Drop(repo1) + c.Check(err, IsNil) + + _, err = s.collection.ByUUID(repo1.UUID) + c.Check(err, ErrorMatches, "local repo .* not found") + + collection := NewLocalRepoCollection(s.db) + _, err = collection.ByName("local1") + c.Check(err, ErrorMatches, "local repo .* not found") + + r2, _ := collection.ByName("local2") + c.Check(r2.String(), Equals, repo2.String()) + + c.Check(func() { s.collection.Drop(repo1) }, Panics, "local repo not found!") +}