diff --git a/debian/snapshot.go b/debian/snapshot.go index f2ea02b6..a0a0d605 100644 --- a/debian/snapshot.go +++ b/debian/snapshot.go @@ -1,8 +1,12 @@ package debian import ( + "bytes" "code.google.com/p/go-uuid/uuid" "fmt" + "github.com/smira/aptly/database" + "github.com/ugorji/go/codec" + "log" "time" ) @@ -41,6 +45,11 @@ func NewSnapshotFromRepository(name string, repo *RemoteRepo) *Snapshot { } } +// String returns string representation of snapshot +func (s *Snapshot) String() string { + return fmt.Sprintf("[%s]: %s", s.Name, s.Description) +} + // NumPackages returns number of packages in snapshot func (s *Snapshot) NumPackages() int { return s.packageRefs.Len() @@ -50,3 +59,108 @@ func (s *Snapshot) NumPackages() int { func (s *Snapshot) Key() []byte { return []byte("S" + s.UUID) } + +// RefKey is a unique id for package reference list +func (s *Snapshot) RefKey() []byte { + return []byte("E" + s.UUID) +} + +// Encode does msgpack encoding of Snapshot +func (s *Snapshot) Encode() []byte { + var buf bytes.Buffer + + encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{}) + encoder.Encode(s) + + return buf.Bytes() +} + +// Decode decodes msgpack representation into Snapshot +func (s *Snapshot) Decode(input []byte) error { + decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{}) + return decoder.Decode(s) +} + +// SnapshotCollection does listing, updating/adding/deleting of Snapshots +type SnapshotCollection struct { + db database.Storage + list []*Snapshot +} + +// NewSnapshotCollection loads Snapshots from DB and makes up collection +func NewSnapshotCollection(db database.Storage) *SnapshotCollection { + result := &SnapshotCollection{ + db: db, + } + + blobs := db.FetchByPrefix([]byte("S")) + result.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 { + result.list = append(result.list, s) + } + } + + return result +} + +// Add appends new repo to collection and saves it +func (collection *SnapshotCollection) Add(snapshot *Snapshot) error { + for _, s := range collection.list { + if s.Name == snapshot.Name { + return fmt.Errorf("snapshot with name %s already exists", snapshot.Name) + } + } + + err := collection.Update(snapshot) + if err != nil { + return err + } + + collection.list = append(collection.list, snapshot) + return nil +} + +// Update stores updated information about repo in DB +func (collection *SnapshotCollection) Update(snapshot *Snapshot) error { + err := collection.db.Put(snapshot.Key(), snapshot.Encode()) + if err != nil { + return err + } + return collection.db.Put(snapshot.RefKey(), snapshot.packageRefs.Encode()) +} + +// LoadComplete loads additional information about snapshot +func (collection *SnapshotCollection) LoadComplete(snapshot *Snapshot) error { + encoded, err := collection.db.Get(snapshot.RefKey()) + if err == database.ErrNotFound { + return nil + } + if err != nil { + return err + } + + snapshot.packageRefs = &PackageRefList{} + return snapshot.packageRefs.Decode(encoded) +} + +// ByName looks up snapshot by name +func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) { + for _, s := range collection.list { + if s.Name == name { + return s, nil + } + } + return nil, fmt.Errorf("snapshot with name %s not found", name) +} + +// ForEach runs method for each snapshot +func (collection *SnapshotCollection) ForEach(handler func(*Snapshot)) { + for _, s := range collection.list { + handler(s) + } +} diff --git a/debian/snapshot_test.go b/debian/snapshot_test.go index 665f4947..c34487c1 100644 --- a/debian/snapshot_test.go +++ b/debian/snapshot_test.go @@ -1,6 +1,7 @@ package debian import ( + "github.com/smira/aptly/database" . "launchpad.net/gocheck" ) @@ -31,3 +32,88 @@ func (s *SnapshotSuite) TestKey(c *C) { c.Assert(len(snapshot.Key()), Equals, 37) c.Assert(snapshot.Key()[0], Equals, byte('S')) } + +func (s *SnapshotSuite) TestRefKey(c *C) { + snapshot := NewSnapshotFromRepository("snap1", s.repo) + c.Assert(len(snapshot.RefKey()), Equals, 37) + c.Assert(snapshot.RefKey()[0], Equals, byte('E')) + c.Assert(snapshot.RefKey()[1:], DeepEquals, snapshot.Key()[1:]) +} + +func (s *SnapshotSuite) TestEncodeDecode(c *C) { + snapshot := NewSnapshotFromRepository("snap1", s.repo) + s.repo.packageRefs = s.reflist + + snapshot2 := &Snapshot{} + c.Assert(snapshot2.Decode(snapshot.Encode()), IsNil) + c.Assert(snapshot2.Name, Equals, snapshot.Name) + c.Assert(snapshot2.packageRefs, IsNil) +} + +type SnapshotCollectionSuite struct { + PackageListMixinSuite + db database.Storage + repo1, repo2 *RemoteRepo + snapshot1, snapshot2 *Snapshot + collection *SnapshotCollection +} + +var _ = Suite(&SnapshotCollectionSuite{}) + +func (s *SnapshotCollectionSuite) SetUpTest(c *C) { + s.db, _ = database.OpenDB(c.MkDir()) + s.collection = NewSnapshotCollection(s.db) + s.SetUpPackages() + + s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}) + s.repo1.packageRefs = s.reflist + s.snapshot1 = NewSnapshotFromRepository("snap1", s.repo1) + + s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}) + s.repo2.packageRefs = s.reflist + s.snapshot2 = NewSnapshotFromRepository("snap2", s.repo2) +} + +func (s *SnapshotCollectionSuite) TearDownTest(c *C) { + s.db.Close() +} + +func (s *SnapshotCollectionSuite) TestAddByName(c *C) { + snapshot, err := s.collection.ByName("snap1") + c.Assert(err, ErrorMatches, "*.not found") + + c.Assert(s.collection.Add(s.snapshot1), IsNil) + c.Assert(s.collection.Add(s.snapshot1), ErrorMatches, ".*already exists") + + c.Assert(s.collection.Add(s.snapshot2), IsNil) + + snapshot, err = s.collection.ByName("snap1") + c.Assert(err, IsNil) + c.Assert(snapshot.String(), Equals, s.snapshot1.String()) + + collection := NewSnapshotCollection(s.db) + snapshot, err = collection.ByName("snap1") + c.Assert(err, IsNil) + c.Assert(snapshot.String(), Equals, s.snapshot1.String()) +} + +func (s *SnapshotCollectionSuite) TestUpdateLoadComplete(c *C) { + c.Assert(s.collection.Update(s.snapshot1), IsNil) + + collection := NewSnapshotCollection(s.db) + snapshot, err := collection.ByName("snap1") + c.Assert(err, IsNil) + c.Assert(snapshot.packageRefs, IsNil) + + c.Assert(s.collection.LoadComplete(snapshot), IsNil) + c.Assert(snapshot.NumPackages(), Equals, 3) +} + +func (s *SnapshotCollectionSuite) TestForEach(c *C) { + s.collection.Add(s.snapshot1) + s.collection.Add(s.snapshot2) + + count := 0 + s.collection.ForEach(func(*Snapshot) { count++ }) + c.Assert(count, Equals, 2) +}