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  }