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