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