github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/pkg/truncindex/truncindex.go (about) 1 // Package truncindex provides a general 'index tree', used by Docker 2 // in order to be able to reference containers by only a few unambiguous 3 // characters of their id. 4 package truncindex 5 6 import ( 7 "errors" 8 "fmt" 9 "strings" 10 "sync" 11 12 "github.com/tchap/go-patricia/patricia" 13 ) 14 15 var ( 16 // ErrEmptyPrefix is an error returned if the prefix was empty. 17 ErrEmptyPrefix = errors.New("Prefix can't be empty") 18 19 // ErrAmbiguousPrefix is returned if the prefix was ambiguous 20 // (multiple ids for the prefix). 21 ErrAmbiguousPrefix = errors.New("Multiple IDs found with provided prefix") 22 23 // ErrIllegalChar is returned when a space is in the ID 24 ErrIllegalChar = errors.New("illegal character: ' '") 25 26 // ErrNotExist is returned when ID or its prefix not found in index. 27 ErrNotExist = errors.New("ID does not exist") 28 ) 29 30 // TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. 31 // This is used to retrieve image and container IDs by more convenient shorthand prefixes. 32 type TruncIndex struct { 33 sync.RWMutex 34 trie *patricia.Trie 35 ids map[string]struct{} 36 } 37 38 // NewTruncIndex creates a new TruncIndex and initializes with a list of IDs. 39 func NewTruncIndex(ids []string) (idx *TruncIndex) { 40 idx = &TruncIndex{ 41 ids: make(map[string]struct{}), 42 43 // Change patricia max prefix per node length, 44 // because our len(ID) always 64 45 trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)), 46 } 47 for _, id := range ids { 48 idx.addID(id) 49 } 50 return 51 } 52 53 func (idx *TruncIndex) addID(id string) error { 54 if strings.Contains(id, " ") { 55 return ErrIllegalChar 56 } 57 if id == "" { 58 return ErrEmptyPrefix 59 } 60 if _, exists := idx.ids[id]; exists { 61 return fmt.Errorf("id already exists: '%s'", id) 62 } 63 idx.ids[id] = struct{}{} 64 if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted { 65 return fmt.Errorf("failed to insert id: %s", id) 66 } 67 return nil 68 } 69 70 // Add adds a new ID to the TruncIndex. 71 func (idx *TruncIndex) Add(id string) error { 72 idx.Lock() 73 defer idx.Unlock() 74 if err := idx.addID(id); err != nil { 75 return err 76 } 77 return nil 78 } 79 80 // Delete removes an ID from the TruncIndex. If there are multiple IDs 81 // with the given prefix, an error is thrown. 82 func (idx *TruncIndex) Delete(id string) error { 83 idx.Lock() 84 defer idx.Unlock() 85 if _, exists := idx.ids[id]; !exists || id == "" { 86 return fmt.Errorf("no such id: '%s'", id) 87 } 88 delete(idx.ids, id) 89 if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted { 90 return fmt.Errorf("no such id: '%s'", id) 91 } 92 return nil 93 } 94 95 // Get retrieves an ID from the TruncIndex. If there are multiple IDs 96 // with the given prefix, an error is thrown. 97 func (idx *TruncIndex) Get(s string) (string, error) { 98 if s == "" { 99 return "", ErrEmptyPrefix 100 } 101 var ( 102 id string 103 ) 104 subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error { 105 if id != "" { 106 // we haven't found the ID if there are two or more IDs 107 id = "" 108 return ErrAmbiguousPrefix 109 } 110 id = string(prefix) 111 return nil 112 } 113 114 idx.RLock() 115 defer idx.RUnlock() 116 if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil { 117 return "", err 118 } 119 if id != "" { 120 return id, nil 121 } 122 return "", ErrNotExist 123 } 124 125 // Iterate iterates over all stored IDs, and passes each of them to the given handler. 126 func (idx *TruncIndex) Iterate(handler func(id string)) { 127 idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error { 128 handler(string(prefix)) 129 return nil 130 }) 131 }