github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/data/queue/stack.go (about) 1 package queue 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "github.com/angenalZZZ/gofunc/f" 7 "os" 8 "sync" 9 10 "github.com/syndtr/goleveldb/leveldb" 11 ) 12 13 // Stack is a standard LIFO (last in, first out) stack. 14 type Stack struct { 15 sync.RWMutex 16 DataDir string 17 db *leveldb.DB 18 head uint64 19 tail uint64 20 isOpen bool 21 } 22 23 // OpenStack opens a stack if one exists at the given directory. If one 24 // does not already exist, a new stack is created. 25 func OpenStack(dataDir string) (*Stack, error) { 26 var err error 27 28 // Create a new Stack. 29 s := &Stack{ 30 DataDir: dataDir, 31 db: &leveldb.DB{}, 32 head: 0, 33 tail: 0, 34 isOpen: false, 35 } 36 37 // Open database for the stack. 38 s.db, err = leveldb.OpenFile(dataDir, nil) 39 if err != nil { 40 return s, err 41 } 42 43 // Check if this queue type can open the requested data directory. 44 ok, err := checkQueueType(dataDir, queueStack) 45 if err != nil { 46 return s, err 47 } 48 if !ok { 49 return s, ErrIncompatibleType 50 } 51 52 // SetHeader isOpen and return. 53 s.isOpen = true 54 return s, s.init() 55 } 56 57 // Push adds an item to the stack. 58 func (s *Stack) Push(value []byte) (*Item, error) { 59 s.Lock() 60 defer s.Unlock() 61 62 // Check if stack is closed. 63 if !s.isOpen { 64 return nil, ErrDBClosed 65 } 66 67 // Create new Item. 68 item := &Item{ 69 ID: s.head + 1, 70 Key: idToKey(s.head + 1), 71 Value: value, 72 } 73 74 // Add it to the stack. 75 if err := s.db.Put(item.Key, item.Value, nil); err != nil { 76 return nil, err 77 } 78 79 // Increment head position. 80 s.head++ 81 82 return item, nil 83 } 84 85 // PushString is a helper function for Push that accepts a 86 // value as a string rather than a byte slice. 87 func (s *Stack) PushString(value string) (*Item, error) { 88 return s.Push([]byte(value)) 89 } 90 91 // PushObject is a helper function for Push that accepts any 92 // value type, which is then encoded into a byte slice using 93 // encoding/gob. 94 // 95 // Objects containing pointers with zero values will decode to nil 96 // when using this function. This is due to how the encoding/gob 97 // package works. Because of this, you should only use this function 98 // to encode simple types. 99 func (s *Stack) PushObject(value interface{}) (*Item, error) { 100 var buffer bytes.Buffer 101 enc := gob.NewEncoder(&buffer) 102 if err := enc.Encode(value); err != nil { 103 return nil, err 104 } 105 106 return s.Push(buffer.Bytes()) 107 } 108 109 // PushObjectAsJSON is a helper function for Push that accepts any 110 // value type, which is then encoded into a JSON byte slice using 111 // encoding/json. 112 // 113 // Use this function to handle encoding of complex types. 114 func (s *Stack) PushObjectAsJSON(value interface{}) (*Item, error) { 115 jsonBytes, err := f.EncodeJson(value) 116 if err != nil { 117 return nil, err 118 } 119 120 return s.Push(jsonBytes) 121 } 122 123 // Pop removes the next item in the stack and returns it. 124 func (s *Stack) Pop() (*Item, error) { 125 s.Lock() 126 defer s.Unlock() 127 128 // Check if stack is closed. 129 if !s.isOpen { 130 return nil, ErrDBClosed 131 } 132 133 // Try to get the next item in the stack. 134 item, err := s.getItemByID(s.head) 135 if err != nil { 136 return nil, err 137 } 138 139 // Remove this item from the stack. 140 if err := s.db.Delete(item.Key, nil); err != nil { 141 return nil, err 142 } 143 144 // Decrement head position. 145 s.head-- 146 147 return item, nil 148 } 149 150 // Peek returns the next item in the stack without removing it. 151 func (s *Stack) Peek() (*Item, error) { 152 s.RLock() 153 defer s.RUnlock() 154 155 // Check if stack is closed. 156 if !s.isOpen { 157 return nil, ErrDBClosed 158 } 159 160 return s.getItemByID(s.head) 161 } 162 163 // PeekByOffset returns the item located at the given offset, 164 // starting from the head of the stack, without removing it. 165 func (s *Stack) PeekByOffset(offset uint64) (*Item, error) { 166 s.RLock() 167 defer s.RUnlock() 168 169 // Check if stack is closed. 170 if !s.isOpen { 171 return nil, ErrDBClosed 172 } 173 174 return s.getItemByID(s.head - offset) 175 } 176 177 // PeekByID returns the item with the given ID without removing it. 178 func (s *Stack) PeekByID(id uint64) (*Item, error) { 179 s.RLock() 180 defer s.RUnlock() 181 182 // Check if stack is closed. 183 if !s.isOpen { 184 return nil, ErrDBClosed 185 } 186 187 return s.getItemByID(id) 188 } 189 190 // Update updates an item in the stack without changing its position. 191 func (s *Stack) Update(id uint64, newValue []byte) (*Item, error) { 192 s.Lock() 193 defer s.Unlock() 194 195 // Check if stack is closed. 196 if !s.isOpen { 197 return nil, ErrDBClosed 198 } 199 200 // Check if item exists in stack. 201 if id > s.head || id <= s.tail { 202 return nil, ErrOutOfBounds 203 } 204 205 // Create new Item. 206 item := &Item{ 207 ID: id, 208 Key: idToKey(id), 209 Value: newValue, 210 } 211 212 // Update this item in the stack. 213 if err := s.db.Put(item.Key, item.Value, nil); err != nil { 214 return nil, err 215 } 216 217 return item, nil 218 } 219 220 // UpdateString is a helper function for Update that accepts a value 221 // as a string rather than a byte slice. 222 func (s *Stack) UpdateString(id uint64, newValue string) (*Item, error) { 223 return s.Update(id, []byte(newValue)) 224 } 225 226 // UpdateObject is a helper function for Update that accepts any 227 // value type, which is then encoded into a byte slice using 228 // encoding/gob. 229 // 230 // Objects containing pointers with zero values will decode to nil 231 // when using this function. This is due to how the encoding/gob 232 // package works. Because of this, you should only use this function 233 // to encode simple types. 234 func (s *Stack) UpdateObject(id uint64, newValue interface{}) (*Item, error) { 235 var buffer bytes.Buffer 236 enc := gob.NewEncoder(&buffer) 237 if err := enc.Encode(newValue); err != nil { 238 return nil, err 239 } 240 return s.Update(id, buffer.Bytes()) 241 } 242 243 // UpdateObjectAsJSON is a helper function for Update that accepts 244 // any value type, which is then encoded into a JSON byte slice using 245 // encoding/json. 246 // 247 // Use this function to handle encoding of complex types. 248 func (s *Stack) UpdateObjectAsJSON(id uint64, newValue interface{}) (*Item, error) { 249 jsonBytes, err := f.EncodeJson(newValue) 250 if err != nil { 251 return nil, err 252 } 253 254 return s.Update(id, jsonBytes) 255 } 256 257 // Length returns the total number of items in the stack. 258 func (s *Stack) Length() uint64 { 259 return s.head - s.tail 260 } 261 262 // Close closes the LevelDB database of the stack. 263 func (s *Stack) Close() error { 264 s.Lock() 265 defer s.Unlock() 266 267 // Check if stack is already closed. 268 if !s.isOpen { 269 return nil 270 } 271 272 // Close the LevelDB database. 273 if err := s.db.Close(); err != nil { 274 return err 275 } 276 277 // Reset stack head and tail and set 278 // isOpen to false. 279 s.head = 0 280 s.tail = 0 281 s.isOpen = false 282 283 return nil 284 } 285 286 // Drop closes and deletes the LevelDB database of the stack. 287 func (s *Stack) Drop() error { 288 if err := s.Close(); err != nil { 289 return err 290 } 291 292 return os.RemoveAll(s.DataDir) 293 } 294 295 // getItemByID returns an item, if found, for the given ID. 296 func (s *Stack) getItemByID(id uint64) (*Item, error) { 297 // Check if empty or out of bounds. 298 if s.Length() == 0 { 299 return nil, ErrEmpty 300 } else if id <= s.tail || id > s.head { 301 return nil, ErrOutOfBounds 302 } 303 304 // GetHeader item from database. 305 var err error 306 item := &Item{ID: id, Key: idToKey(id)} 307 if item.Value, err = s.db.Get(item.Key, nil); err != nil { 308 return nil, err 309 } 310 311 return item, nil 312 } 313 314 // init initializes the stack data. 315 func (s *Stack) init() error { 316 // Create a new LevelDB Iterator. 317 iter := s.db.NewIterator(nil, nil) 318 defer iter.Release() 319 320 // SetHeader stack head to the last item. 321 if iter.Last() { 322 s.head = keyToID(iter.Key()) 323 } 324 325 // SetHeader stack tail to the first item. 326 if iter.First() { 327 s.tail = keyToID(iter.Key()) - 1 328 } 329 330 return iter.Error() 331 }