import os
|
|
import glob
|
|
import json
|
|
import struct
|
|
import sqlite3
|
|
from collections import deque
|
|
|
|
|
|
class FifoMemoryQueue(object):
|
|
"""In-memory FIFO queue, API compliant with FifoDiskQueue."""
|
|
|
|
def __init__(self):
|
|
self.q = deque()
|
|
self.push = self.q.append
|
|
|
|
def pop(self):
|
|
q = self.q
|
|
return q.popleft() if q else None
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
def __len__(self):
|
|
return len(self.q)
|
|
|
|
|
|
class LifoMemoryQueue(FifoMemoryQueue):
|
|
"""In-memory LIFO queue, API compliant with LifoDiskQueue."""
|
|
|
|
def pop(self):
|
|
q = self.q
|
|
return q.pop() if q else None
|
|
|
|
|
|
class FifoDiskQueue(object):
|
|
"""Persistent FIFO queue."""
|
|
|
|
szhdr_format = ">L"
|
|
szhdr_size = struct.calcsize(szhdr_format)
|
|
|
|
def __init__(self, path, chunksize=100000):
|
|
self.path = path
|
|
if not os.path.exists(path):
|
|
os.makedirs(path)
|
|
self.info = self._loadinfo(chunksize)
|
|
self.chunksize = self.info['chunksize']
|
|
self.headf = self._openchunk(self.info['head'][0], 'ab+')
|
|
self.tailf = self._openchunk(self.info['tail'][0])
|
|
os.lseek(self.tailf.fileno(), self.info['tail'][2], os.SEEK_SET)
|
|
|
|
def push(self, string):
|
|
if not isinstance(string, bytes):
|
|
raise TypeError('Unsupported type: {}'.format(type(string).__name__))
|
|
hnum, hpos = self.info['head']
|
|
hpos += 1
|
|
szhdr = struct.pack(self.szhdr_format, len(string))
|
|
os.write(self.headf.fileno(), szhdr + string)
|
|
if hpos == self.chunksize:
|
|
hpos = 0
|
|
hnum += 1
|
|
self.headf.close()
|
|
self.headf = self._openchunk(hnum, 'ab+')
|
|
self.info['size'] += 1
|
|
self.info['head'] = [hnum, hpos]
|
|
|
|
def _openchunk(self, number, mode='rb'):
|
|
return open(os.path.join(self.path, 'q%05d' % number), mode)
|
|
|
|
def pop(self):
|
|
tnum, tcnt, toffset = self.info['tail']
|
|
if [tnum, tcnt] >= self.info['head']:
|
|
return
|
|
tfd = self.tailf.fileno()
|
|
szhdr = os.read(tfd, self.szhdr_size)
|
|
if not szhdr:
|
|
return
|
|
size, = struct.unpack(self.szhdr_format, szhdr)
|
|
data = os.read(tfd, size)
|
|
tcnt += 1
|
|
toffset += self.szhdr_size + size
|
|
if tcnt == self.chunksize and tnum <= self.info['head'][0]:
|
|
tcnt = toffset = 0
|
|
tnum += 1
|
|
self.tailf.close()
|
|
os.remove(self.tailf.name)
|
|
self.tailf = self._openchunk(tnum)
|
|
self.info['size'] -= 1
|
|
self.info['tail'] = [tnum, tcnt, toffset]
|
|
return data
|
|
|
|
def close(self):
|
|
self.headf.close()
|
|
self.tailf.close()
|
|
self._saveinfo(self.info)
|
|
if len(self) == 0:
|
|
self._cleanup()
|
|
|
|
def __len__(self):
|
|
return self.info['size']
|
|
|
|
def _loadinfo(self, chunksize):
|
|
infopath = self._infopath()
|
|
if os.path.exists(infopath):
|
|
with open(infopath) as f:
|
|
info = json.load(f)
|
|
else:
|
|
info = {
|
|
'chunksize': chunksize,
|
|
'size': 0,
|
|
'tail': [0, 0, 0],
|
|
'head': [0, 0],
|
|
}
|
|
return info
|
|
|
|
def _saveinfo(self, info):
|
|
with open(self._infopath(), 'w') as f:
|
|
json.dump(info, f)
|
|
|
|
def _infopath(self):
|
|
return os.path.join(self.path, 'info.json')
|
|
|
|
def _cleanup(self):
|
|
for x in glob.glob(os.path.join(self.path, 'q*')):
|
|
os.remove(x)
|
|
os.remove(os.path.join(self.path, 'info.json'))
|
|
if not os.listdir(self.path):
|
|
os.rmdir(self.path)
|
|
|
|
|
|
|
|
class LifoDiskQueue(object):
|
|
"""Persistent LIFO queue."""
|
|
|
|
SIZE_FORMAT = ">L"
|
|
SIZE_SIZE = struct.calcsize(SIZE_FORMAT)
|
|
|
|
def __init__(self, path):
|
|
self.path = path
|
|
if os.path.exists(path):
|
|
self.f = open(path, 'rb+')
|
|
qsize = self.f.read(self.SIZE_SIZE)
|
|
self.size, = struct.unpack(self.SIZE_FORMAT, qsize)
|
|
self.f.seek(0, os.SEEK_END)
|
|
else:
|
|
self.f = open(path, 'wb+')
|
|
self.f.write(struct.pack(self.SIZE_FORMAT, 0))
|
|
self.size = 0
|
|
|
|
def push(self, string):
|
|
if not isinstance(string, bytes):
|
|
raise TypeError('Unsupported type: {}'.format(type(string).__name__))
|
|
self.f.write(string)
|
|
ssize = struct.pack(self.SIZE_FORMAT, len(string))
|
|
self.f.write(ssize)
|
|
self.size += 1
|
|
|
|
def pop(self):
|
|
if not self.size:
|
|
return
|
|
self.f.seek(-self.SIZE_SIZE, os.SEEK_END)
|
|
size, = struct.unpack(self.SIZE_FORMAT, self.f.read())
|
|
self.f.seek(-size-self.SIZE_SIZE, os.SEEK_END)
|
|
data = self.f.read(size)
|
|
self.f.seek(-size, os.SEEK_CUR)
|
|
self.f.truncate()
|
|
self.size -= 1
|
|
return data
|
|
|
|
def close(self):
|
|
if self.size:
|
|
self.f.seek(0)
|
|
self.f.write(struct.pack(self.SIZE_FORMAT, self.size))
|
|
self.f.close()
|
|
if not self.size:
|
|
os.remove(self.path)
|
|
|
|
def __len__(self):
|
|
return self.size
|
|
|
|
|
|
class FifoSQLiteQueue(object):
|
|
|
|
_sql_create = (
|
|
'CREATE TABLE IF NOT EXISTS queue '
|
|
'(id INTEGER PRIMARY KEY AUTOINCREMENT, item BLOB)'
|
|
)
|
|
_sql_size = 'SELECT COUNT(*) FROM queue'
|
|
_sql_push = 'INSERT INTO queue (item) VALUES (?)'
|
|
_sql_pop = 'SELECT id, item FROM queue ORDER BY id LIMIT 1'
|
|
_sql_del = 'DELETE FROM queue WHERE id = ?'
|
|
|
|
def __init__(self, path):
|
|
self._path = os.path.abspath(path)
|
|
self._db = sqlite3.Connection(self._path, timeout=60)
|
|
self._db.text_factory = bytes
|
|
with self._db as conn:
|
|
conn.execute(self._sql_create)
|
|
|
|
def push(self, item):
|
|
if not isinstance(item, bytes):
|
|
raise TypeError('Unsupported type: {}'.format(type(item).__name__))
|
|
|
|
with self._db as conn:
|
|
conn.execute(self._sql_push, (item,))
|
|
|
|
def pop(self):
|
|
with self._db as conn:
|
|
for id_, item in conn.execute(self._sql_pop):
|
|
conn.execute(self._sql_del, (id_,))
|
|
return item
|
|
|
|
def close(self):
|
|
size = len(self)
|
|
self._db.close()
|
|
if not size:
|
|
os.remove(self._path)
|
|
|
|
def __len__(self):
|
|
with self._db as conn:
|
|
return next(conn.execute(self._sql_size))[0]
|
|
|
|
|
|
class LifoSQLiteQueue(FifoSQLiteQueue):
|
|
|
|
_sql_pop = 'SELECT id, item FROM queue ORDER BY id DESC LIMIT 1'
|
|
|
|
|
|
#FifoDiskQueue = FifoSQLiteQueue # noqa
|
|
#LifoDiskQueue = LifoSQLiteQueue # noqa
|