github.com/stchris/docker@v1.4.2-0.20150106053530-1510a324dbd5/pkg/truncindex/truncindex.go (about)

     1  package truncindex
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/tchap/go-patricia/patricia"
    10  )
    11  
    12  var (
    13  	// ErrNoID is thrown when attempting to use empty prefixes
    14  	ErrNoID        = errors.New("prefix can't be empty")
    15  	errDuplicateID = errors.New("multiple IDs were found")
    16  )
    17  
    18  func init() {
    19  	// Change patricia max prefix per node length,
    20  	// because our len(ID) always 64
    21  	patricia.MaxPrefixPerNode = 64
    22  }
    23  
    24  // TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
    25  // This is used to retrieve image and container IDs by more convenient shorthand prefixes.
    26  type TruncIndex struct {
    27  	sync.RWMutex
    28  	trie *patricia.Trie
    29  	ids  map[string]struct{}
    30  }
    31  
    32  // NewTruncIndex creates a new TruncIndex and initializes with a list of IDs
    33  func NewTruncIndex(ids []string) (idx *TruncIndex) {
    34  	idx = &TruncIndex{
    35  		ids:  make(map[string]struct{}),
    36  		trie: patricia.NewTrie(),
    37  	}
    38  	for _, id := range ids {
    39  		idx.addID(id)
    40  	}
    41  	return
    42  }
    43  
    44  func (idx *TruncIndex) addID(id string) error {
    45  	if strings.Contains(id, " ") {
    46  		return fmt.Errorf("illegal character: ' '")
    47  	}
    48  	if id == "" {
    49  		return ErrNoID
    50  	}
    51  	if _, exists := idx.ids[id]; exists {
    52  		return fmt.Errorf("id already exists: '%s'", id)
    53  	}
    54  	idx.ids[id] = struct{}{}
    55  	if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted {
    56  		return fmt.Errorf("failed to insert id: %s", id)
    57  	}
    58  	return nil
    59  }
    60  
    61  // Add adds a new ID to the TruncIndex
    62  func (idx *TruncIndex) Add(id string) error {
    63  	idx.Lock()
    64  	defer idx.Unlock()
    65  	if err := idx.addID(id); err != nil {
    66  		return err
    67  	}
    68  	return nil
    69  }
    70  
    71  // Delete removes an ID from the TruncIndex. If there are multiple IDs
    72  // with the given prefix, an error is thrown.
    73  func (idx *TruncIndex) Delete(id string) error {
    74  	idx.Lock()
    75  	defer idx.Unlock()
    76  	if _, exists := idx.ids[id]; !exists || id == "" {
    77  		return fmt.Errorf("no such id: '%s'", id)
    78  	}
    79  	delete(idx.ids, id)
    80  	if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted {
    81  		return fmt.Errorf("no such id: '%s'", id)
    82  	}
    83  	return nil
    84  }
    85  
    86  // Get retrieves an ID from the TruncIndex. If there are multiple IDs
    87  // with the given prefix, an error is thrown.
    88  func (idx *TruncIndex) Get(s string) (string, error) {
    89  	idx.RLock()
    90  	defer idx.RUnlock()
    91  	var (
    92  		id string
    93  	)
    94  	if s == "" {
    95  		return "", ErrNoID
    96  	}
    97  	subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error {
    98  		if id != "" {
    99  			// we haven't found the ID if there are two or more IDs
   100  			id = ""
   101  			return errDuplicateID
   102  		}
   103  		id = string(prefix)
   104  		return nil
   105  	}
   106  
   107  	if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil {
   108  		return "", fmt.Errorf("no such id: %s", s)
   109  	}
   110  	if id != "" {
   111  		return id, nil
   112  	}
   113  	return "", fmt.Errorf("no such id: %s", s)
   114  }