1package deb
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"log"
8	"sort"
9	"strings"
10	"sync"
11	"time"
12
13	"github.com/aptly-dev/aptly/database"
14	"github.com/aptly-dev/aptly/utils"
15	"github.com/pborman/uuid"
16	"github.com/ugorji/go/codec"
17)
18
19// Snapshot is immutable state of repository: list of packages
20type Snapshot struct {
21	// Persisten internal ID
22	UUID string `json:"-"`
23	// Human-readable name
24	Name string
25	// Date of creation
26	CreatedAt time.Time
27
28	// Source: kind + ID
29	SourceKind string   `json:"-"`
30	SourceIDs  []string `json:"-"`
31	// Description of how snapshot was created
32	Description string
33
34	Origin               string
35	NotAutomatic         string
36	ButAutomaticUpgrades string
37
38	packageRefs *PackageRefList
39}
40
41// NewSnapshotFromRepository creates snapshot from current state of repository
42func NewSnapshotFromRepository(name string, repo *RemoteRepo) (*Snapshot, error) {
43	if repo.packageRefs == nil {
44		return nil, errors.New("mirror not updated")
45	}
46
47	return &Snapshot{
48		UUID:                 uuid.New(),
49		Name:                 name,
50		CreatedAt:            time.Now(),
51		SourceKind:           SourceRemoteRepo,
52		SourceIDs:            []string{repo.UUID},
53		Description:          fmt.Sprintf("Snapshot from mirror %s", repo),
54		Origin:               repo.Meta["Origin"],
55		NotAutomatic:         repo.Meta["NotAutomatic"],
56		ButAutomaticUpgrades: repo.Meta["ButAutomaticUpgrades"],
57		packageRefs:          repo.packageRefs,
58	}, nil
59}
60
61// NewSnapshotFromLocalRepo creates snapshot from current state of local repository
62func NewSnapshotFromLocalRepo(name string, repo *LocalRepo) (*Snapshot, error) {
63	snap := &Snapshot{
64		UUID:        uuid.New(),
65		Name:        name,
66		CreatedAt:   time.Now(),
67		SourceKind:  SourceLocalRepo,
68		SourceIDs:   []string{repo.UUID},
69		Description: fmt.Sprintf("Snapshot from local repo %s", repo),
70		packageRefs: repo.packageRefs,
71	}
72
73	if snap.packageRefs == nil {
74		snap.packageRefs = NewPackageRefList()
75	}
76
77	return snap, nil
78}
79
80// NewSnapshotFromPackageList creates snapshot from PackageList
81func NewSnapshotFromPackageList(name string, sources []*Snapshot, list *PackageList, description string) *Snapshot {
82	return NewSnapshotFromRefList(name, sources, NewPackageRefListFromPackageList(list), description)
83}
84
85// NewSnapshotFromRefList creates snapshot from PackageRefList
86func NewSnapshotFromRefList(name string, sources []*Snapshot, list *PackageRefList, description string) *Snapshot {
87	sourceUUIDs := make([]string, len(sources))
88	for i := range sources {
89		sourceUUIDs[i] = sources[i].UUID
90	}
91
92	return &Snapshot{
93		UUID:        uuid.New(),
94		Name:        name,
95		CreatedAt:   time.Now(),
96		SourceKind:  "snapshot",
97		SourceIDs:   sourceUUIDs,
98		Description: description,
99		packageRefs: list,
100	}
101}
102
103// String returns string representation of snapshot
104func (s *Snapshot) String() string {
105	return fmt.Sprintf("[%s]: %s", s.Name, s.Description)
106}
107
108// NumPackages returns number of packages in snapshot
109func (s *Snapshot) NumPackages() int {
110	return s.packageRefs.Len()
111}
112
113// RefList returns list of package refs in snapshot
114func (s *Snapshot) RefList() *PackageRefList {
115	return s.packageRefs
116}
117
118// Key is a unique id in DB
119func (s *Snapshot) Key() []byte {
120	return []byte("S" + s.UUID)
121}
122
123// RefKey is a unique id for package reference list
124func (s *Snapshot) RefKey() []byte {
125	return []byte("E" + s.UUID)
126}
127
128// Encode does msgpack encoding of Snapshot
129func (s *Snapshot) Encode() []byte {
130	var buf bytes.Buffer
131
132	encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
133	encoder.Encode(s)
134
135	return buf.Bytes()
136}
137
138// Decode decodes msgpack representation into Snapshot
139func (s *Snapshot) Decode(input []byte) error {
140	decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
141	err := decoder.Decode(s)
142	if err != nil {
143		if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") {
144			// probably it is broken DB from go < 1.2, try decoding w/o time.Time
145			var snapshot11 struct {
146				UUID      string
147				Name      string
148				CreatedAt []byte
149
150				SourceKind  string
151				SourceIDs   []string
152				Description string
153			}
154
155			decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
156			err2 := decoder.Decode(&snapshot11)
157			if err2 != nil {
158				return err
159			}
160
161			s.UUID = snapshot11.UUID
162			s.Name = snapshot11.Name
163			s.SourceKind = snapshot11.SourceKind
164			s.SourceIDs = snapshot11.SourceIDs
165			s.Description = snapshot11.Description
166		} else {
167			return err
168		}
169	}
170	return nil
171}
172
173// SnapshotCollection does listing, updating/adding/deleting of Snapshots
174type SnapshotCollection struct {
175	*sync.RWMutex
176	db    database.Storage
177	cache map[string]*Snapshot
178}
179
180// NewSnapshotCollection loads Snapshots from DB and makes up collection
181func NewSnapshotCollection(db database.Storage) *SnapshotCollection {
182	return &SnapshotCollection{
183		RWMutex: &sync.RWMutex{},
184		db:      db,
185		cache:   map[string]*Snapshot{},
186	}
187}
188
189// Add appends new repo to collection and saves it
190func (collection *SnapshotCollection) Add(snapshot *Snapshot) error {
191	_, err := collection.ByName(snapshot.Name)
192	if err == nil {
193		return fmt.Errorf("snapshot with name %s already exists", snapshot.Name)
194	}
195
196	err = collection.Update(snapshot)
197	if err != nil {
198		return err
199	}
200
201	collection.cache[snapshot.UUID] = snapshot
202	return nil
203}
204
205// Update stores updated information about snapshot in DB
206func (collection *SnapshotCollection) Update(snapshot *Snapshot) error {
207	err := collection.db.Put(snapshot.Key(), snapshot.Encode())
208	if err != nil {
209		return err
210	}
211	if snapshot.packageRefs != nil {
212		return collection.db.Put(snapshot.RefKey(), snapshot.packageRefs.Encode())
213	}
214	return nil
215}
216
217// LoadComplete loads additional information about snapshot
218func (collection *SnapshotCollection) LoadComplete(snapshot *Snapshot) error {
219	encoded, err := collection.db.Get(snapshot.RefKey())
220	if err != nil {
221		return err
222	}
223
224	snapshot.packageRefs = &PackageRefList{}
225	return snapshot.packageRefs.Decode(encoded)
226}
227
228func (collection *SnapshotCollection) search(filter func(*Snapshot) bool, unique bool) []*Snapshot {
229	result := []*Snapshot(nil)
230	for _, s := range collection.cache {
231		if filter(s) {
232			result = append(result, s)
233		}
234	}
235
236	if unique && len(result) > 0 {
237		return result
238	}
239
240	collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error {
241		s := &Snapshot{}
242		if err := s.Decode(blob); err != nil {
243			log.Printf("Error decoding snapshot: %s\n", err)
244			return nil
245		}
246
247		if filter(s) {
248			if _, exists := collection.cache[s.UUID]; !exists {
249				collection.cache[s.UUID] = s
250				result = append(result, s)
251				if unique {
252					return errors.New("abort")
253				}
254			}
255		}
256
257		return nil
258	})
259
260	return result
261}
262
263// ByName looks up snapshot by name
264func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) {
265	result := collection.search(func(s *Snapshot) bool { return s.Name == name }, true)
266	if len(result) > 0 {
267		return result[0], nil
268	}
269
270	return nil, fmt.Errorf("snapshot with name %s not found", name)
271}
272
273// ByUUID looks up snapshot by UUID
274func (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) {
275	if s, ok := collection.cache[uuid]; ok {
276		return s, nil
277	}
278
279	key := (&Snapshot{UUID: uuid}).Key()
280
281	value, err := collection.db.Get(key)
282	if err == database.ErrNotFound {
283		return nil, fmt.Errorf("snapshot with uuid %s not found", uuid)
284	}
285	if err != nil {
286		return nil, err
287	}
288
289	s := &Snapshot{}
290	err = s.Decode(value)
291
292	if err == nil {
293		collection.cache[s.UUID] = s
294	}
295
296	return s, err
297}
298
299// ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source
300func (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot {
301	return collection.search(func(s *Snapshot) bool {
302		return s.SourceKind == SourceRemoteRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
303	}, false)
304}
305
306// ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source
307func (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot {
308	return collection.search(func(s *Snapshot) bool {
309		return s.SourceKind == SourceLocalRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID)
310	}, false)
311}
312
313// BySnapshotSource looks up snapshots that have specified snapshot as a source
314func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot {
315	return collection.search(func(s *Snapshot) bool {
316		return s.SourceKind == "snapshot" && utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID)
317	}, false)
318}
319
320// ForEach runs method for each snapshot
321func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) error {
322	return collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error {
323		s := &Snapshot{}
324		if err := s.Decode(blob); err != nil {
325			log.Printf("Error decoding snapshot: %s\n", err)
326			return nil
327		}
328
329		return handler(s)
330	})
331}
332
333// ForEachSorted runs method for each snapshot following some sort order
334func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error {
335	blobs := collection.db.FetchByPrefix([]byte("S"))
336	list := make([]*Snapshot, 0, len(blobs))
337
338	for _, blob := range blobs {
339		s := &Snapshot{}
340		if err := s.Decode(blob); err != nil {
341			log.Printf("Error decoding snapshot: %s\n", err)
342		} else {
343			list = append(list, s)
344		}
345	}
346
347	sorter, err := newSnapshotSorter(sortMethod, list)
348	if err != nil {
349		return err
350	}
351
352	for _, s := range sorter.list {
353		err = handler(s)
354		if err != nil {
355			return err
356		}
357	}
358
359	return nil
360}
361
362// Len returns number of snapshots in collection
363// ForEach runs method for each snapshot
364func (collection *SnapshotCollection) Len() int {
365	return len(collection.db.KeysByPrefix([]byte("S")))
366}
367
368// Drop removes snapshot from collection
369func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error {
370	if _, err := collection.db.Get(snapshot.Key()); err == database.ErrNotFound {
371		panic("snapshot not found!")
372	}
373
374	delete(collection.cache, snapshot.UUID)
375
376	err := collection.db.Delete(snapshot.Key())
377	if err != nil {
378		return err
379	}
380
381	return collection.db.Delete(snapshot.RefKey())
382}
383
384// Snapshot sorting methods
385const (
386	SortName = iota
387	SortTime
388)
389
390type snapshotSorter struct {
391	list       []*Snapshot
392	sortMethod int
393}
394
395func newSnapshotSorter(sortMethod string, list []*Snapshot) (*snapshotSorter, error) {
396	s := &snapshotSorter{list: list}
397
398	switch sortMethod {
399	case "time", "Time":
400		s.sortMethod = SortTime
401	case "name", "Name":
402		s.sortMethod = SortName
403	default:
404		return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod)
405	}
406
407	sort.Sort(s)
408
409	return s, nil
410}
411
412func (s *snapshotSorter) Swap(i, j int) {
413	s.list[i], s.list[j] = s.list[j], s.list[i]
414}
415
416func (s *snapshotSorter) Less(i, j int) bool {
417	switch s.sortMethod {
418	case SortName:
419		return s.list[i].Name < s.list[j].Name
420	case SortTime:
421		return s.list[i].CreatedAt.Before(s.list[j].CreatedAt)
422	}
423	panic("unknown sort method")
424}
425
426func (s *snapshotSorter) Len() int {
427	return len(s.list)
428}
429