github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/data/queue/queue.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  // Queue is a standard FIFO (first in, first out) queue.
    14  type Queue struct {
    15  	sync.RWMutex
    16  	DataDir string
    17  	db      *leveldb.DB
    18  	head    uint64
    19  	tail    uint64
    20  	isOpen  bool
    21  }
    22  
    23  // OpenQueue opens a queue if one exists at the given directory. If one
    24  // does not already exist, a new queue is created.
    25  func OpenQueue(dataDir string) (*Queue, error) {
    26  	var err error
    27  
    28  	// Create a new Queue.
    29  	q := &Queue{
    30  		DataDir: dataDir,
    31  		db:      &leveldb.DB{},
    32  		head:    0,
    33  		tail:    0,
    34  		isOpen:  false,
    35  	}
    36  
    37  	// Open database for the queue.
    38  	q.db, err = leveldb.OpenFile(dataDir, nil)
    39  	if err != nil {
    40  		return q, err
    41  	}
    42  
    43  	// Check if this queue type can open the requested data directory.
    44  	ok, err := checkQueueType(dataDir, queueQueue)
    45  	if err != nil {
    46  		return q, err
    47  	}
    48  	if !ok {
    49  		return q, ErrIncompatibleType
    50  	}
    51  
    52  	// SetHeader isOpen and return.
    53  	q.isOpen = true
    54  	return q, q.init()
    55  }
    56  
    57  // Enqueue adds an item to the queue.
    58  func (q *Queue) Enqueue(value []byte) (*Item, error) {
    59  	q.Lock()
    60  	defer q.Unlock()
    61  
    62  	// Check if queue is closed.
    63  	if !q.isOpen {
    64  		return nil, ErrDBClosed
    65  	}
    66  
    67  	// Create new Item.
    68  	item := &Item{
    69  		ID:    q.tail + 1,
    70  		Key:   idToKey(q.tail + 1),
    71  		Value: value,
    72  	}
    73  
    74  	// Add it to the queue.
    75  	if err := q.db.Put(item.Key, item.Value, nil); err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	// Increment tail position.
    80  	q.tail++
    81  
    82  	return item, nil
    83  }
    84  
    85  // EnqueueString is a helper function for Enqueue that accepts a
    86  // value as a string rather than a byte slice.
    87  func (q *Queue) EnqueueString(value string) (*Item, error) {
    88  	return q.Enqueue([]byte(value))
    89  }
    90  
    91  // EnqueueObject is a helper function for Enqueue 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 (q *Queue) EnqueueObject(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 q.Enqueue(buffer.Bytes())
   107  }
   108  
   109  // EnqueueObjectAsJSON is a helper function for Enqueue that accepts
   110  // any 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 (q *Queue) EnqueueObjectAsJSON(value interface{}) (*Item, error) {
   115  	jsonBytes, err := f.EncodeJson(value)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	return q.Enqueue(jsonBytes)
   121  }
   122  
   123  // Dequeue removes the next item in the queue and returns it.
   124  func (q *Queue) Dequeue() (*Item, error) {
   125  	q.Lock()
   126  	defer q.Unlock()
   127  
   128  	// Check if queue is closed.
   129  	if !q.isOpen {
   130  		return nil, ErrDBClosed
   131  	}
   132  
   133  	// Try to get the next item in the queue.
   134  	item, err := q.getItemByID(q.head + 1)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	// Remove this item from the queue.
   140  	if err := q.db.Delete(item.Key, nil); err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	// Increment head position.
   145  	q.head++
   146  
   147  	return item, nil
   148  }
   149  
   150  // Peek returns the next item in the queue without removing it.
   151  func (q *Queue) Peek() (*Item, error) {
   152  	q.RLock()
   153  	defer q.RUnlock()
   154  
   155  	// Check if queue is closed.
   156  	if !q.isOpen {
   157  		return nil, ErrDBClosed
   158  	}
   159  
   160  	return q.getItemByID(q.head + 1)
   161  }
   162  
   163  // PeekByOffset returns the item located at the given offset,
   164  // starting from the head of the queue, without removing it.
   165  func (q *Queue) PeekByOffset(offset uint64) (*Item, error) {
   166  	q.RLock()
   167  	defer q.RUnlock()
   168  
   169  	// Check if queue is closed.
   170  	if !q.isOpen {
   171  		return nil, ErrDBClosed
   172  	}
   173  
   174  	return q.getItemByID(q.head + offset + 1)
   175  }
   176  
   177  // PeekByID returns the item with the given ID without removing it.
   178  func (q *Queue) PeekByID(id uint64) (*Item, error) {
   179  	q.RLock()
   180  	defer q.RUnlock()
   181  
   182  	// Check if queue is closed.
   183  	if !q.isOpen {
   184  		return nil, ErrDBClosed
   185  	}
   186  
   187  	return q.getItemByID(id)
   188  }
   189  
   190  // Update updates an item in the queue without changing its position.
   191  func (q *Queue) Update(id uint64, newValue []byte) (*Item, error) {
   192  	q.Lock()
   193  	defer q.Unlock()
   194  
   195  	// Check if queue is closed.
   196  	if !q.isOpen {
   197  		return nil, ErrDBClosed
   198  	}
   199  
   200  	// Check if item exists in queue.
   201  	if id <= q.head || id > q.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 queue.
   213  	if err := q.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 (q *Queue) UpdateString(id uint64, newValue string) (*Item, error) {
   223  	return q.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 (q *Queue) 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 q.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 (q *Queue) 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 q.Update(id, jsonBytes)
   255  }
   256  
   257  // Length returns the total number of items in the queue.
   258  func (q *Queue) Length() uint64 {
   259  	return q.tail - q.head
   260  }
   261  
   262  // Close closes the LevelDB database of the queue.
   263  func (q *Queue) Close() error {
   264  	q.Lock()
   265  	defer q.Unlock()
   266  
   267  	// Check if queue is already closed.
   268  	if !q.isOpen {
   269  		return nil
   270  	}
   271  
   272  	// Close the LevelDB database.
   273  	if err := q.db.Close(); err != nil {
   274  		return err
   275  	}
   276  
   277  	// Reset queue head and tail and set
   278  	// isOpen to false.
   279  	q.head = 0
   280  	q.tail = 0
   281  	q.isOpen = false
   282  
   283  	return nil
   284  }
   285  
   286  // Drop closes and deletes the LevelDB database of the queue.
   287  func (q *Queue) Drop() error {
   288  	if err := q.Close(); err != nil {
   289  		return err
   290  	}
   291  
   292  	return os.RemoveAll(q.DataDir)
   293  }
   294  
   295  // getItemByID returns an item, if found, for the given ID.
   296  func (q *Queue) getItemByID(id uint64) (*Item, error) {
   297  	// Check if empty or out of bounds.
   298  	if q.Length() == 0 {
   299  		return nil, ErrEmpty
   300  	} else if id <= q.head || id > q.tail {
   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 = q.db.Get(item.Key, nil); err != nil {
   308  		return nil, err
   309  	}
   310  
   311  	return item, nil
   312  }
   313  
   314  // init initializes the queue data.
   315  func (q *Queue) init() error {
   316  	// Create a new LevelDB Iterator.
   317  	iter := q.db.NewIterator(nil, nil)
   318  	defer iter.Release()
   319  
   320  	// SetHeader queue head to the first item.
   321  	if iter.First() {
   322  		q.head = keyToID(iter.Key()) - 1
   323  	}
   324  
   325  	// SetHeader queue tail to the last item.
   326  	if iter.Last() {
   327  		q.tail = keyToID(iter.Key())
   328  	}
   329  
   330  	return iter.Error()
   331  }