You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
6.5 KiB

4 years ago
  1. import os
  2. import glob
  3. import json
  4. import struct
  5. import sqlite3
  6. from collections import deque
  7. class FifoMemoryQueue(object):
  8. """In-memory FIFO queue, API compliant with FifoDiskQueue."""
  9. def __init__(self):
  10. self.q = deque()
  11. self.push = self.q.append
  12. def pop(self):
  13. q = self.q
  14. return q.popleft() if q else None
  15. def close(self):
  16. pass
  17. def __len__(self):
  18. return len(self.q)
  19. class LifoMemoryQueue(FifoMemoryQueue):
  20. """In-memory LIFO queue, API compliant with LifoDiskQueue."""
  21. def pop(self):
  22. q = self.q
  23. return q.pop() if q else None
  24. class FifoDiskQueue(object):
  25. """Persistent FIFO queue."""
  26. szhdr_format = ">L"
  27. szhdr_size = struct.calcsize(szhdr_format)
  28. def __init__(self, path, chunksize=100000):
  29. self.path = path
  30. if not os.path.exists(path):
  31. os.makedirs(path)
  32. self.info = self._loadinfo(chunksize)
  33. self.chunksize = self.info['chunksize']
  34. self.headf = self._openchunk(self.info['head'][0], 'ab+')
  35. self.tailf = self._openchunk(self.info['tail'][0])
  36. os.lseek(self.tailf.fileno(), self.info['tail'][2], os.SEEK_SET)
  37. def push(self, string):
  38. if not isinstance(string, bytes):
  39. raise TypeError('Unsupported type: {}'.format(type(string).__name__))
  40. hnum, hpos = self.info['head']
  41. hpos += 1
  42. szhdr = struct.pack(self.szhdr_format, len(string))
  43. os.write(self.headf.fileno(), szhdr + string)
  44. if hpos == self.chunksize:
  45. hpos = 0
  46. hnum += 1
  47. self.headf.close()
  48. self.headf = self._openchunk(hnum, 'ab+')
  49. self.info['size'] += 1
  50. self.info['head'] = [hnum, hpos]
  51. def _openchunk(self, number, mode='rb'):
  52. return open(os.path.join(self.path, 'q%05d' % number), mode)
  53. def pop(self):
  54. tnum, tcnt, toffset = self.info['tail']
  55. if [tnum, tcnt] >= self.info['head']:
  56. return
  57. tfd = self.tailf.fileno()
  58. szhdr = os.read(tfd, self.szhdr_size)
  59. if not szhdr:
  60. return
  61. size, = struct.unpack(self.szhdr_format, szhdr)
  62. data = os.read(tfd, size)
  63. tcnt += 1
  64. toffset += self.szhdr_size + size
  65. if tcnt == self.chunksize and tnum <= self.info['head'][0]:
  66. tcnt = toffset = 0
  67. tnum += 1
  68. self.tailf.close()
  69. os.remove(self.tailf.name)
  70. self.tailf = self._openchunk(tnum)
  71. self.info['size'] -= 1
  72. self.info['tail'] = [tnum, tcnt, toffset]
  73. return data
  74. def close(self):
  75. self.headf.close()
  76. self.tailf.close()
  77. self._saveinfo(self.info)
  78. if len(self) == 0:
  79. self._cleanup()
  80. def __len__(self):
  81. return self.info['size']
  82. def _loadinfo(self, chunksize):
  83. infopath = self._infopath()
  84. if os.path.exists(infopath):
  85. with open(infopath) as f:
  86. info = json.load(f)
  87. else:
  88. info = {
  89. 'chunksize': chunksize,
  90. 'size': 0,
  91. 'tail': [0, 0, 0],
  92. 'head': [0, 0],
  93. }
  94. return info
  95. def _saveinfo(self, info):
  96. with open(self._infopath(), 'w') as f:
  97. json.dump(info, f)
  98. def _infopath(self):
  99. return os.path.join(self.path, 'info.json')
  100. def _cleanup(self):
  101. for x in glob.glob(os.path.join(self.path, 'q*')):
  102. os.remove(x)
  103. os.remove(os.path.join(self.path, 'info.json'))
  104. if not os.listdir(self.path):
  105. os.rmdir(self.path)
  106. class LifoDiskQueue(object):
  107. """Persistent LIFO queue."""
  108. SIZE_FORMAT = ">L"
  109. SIZE_SIZE = struct.calcsize(SIZE_FORMAT)
  110. def __init__(self, path):
  111. self.path = path
  112. if os.path.exists(path):
  113. self.f = open(path, 'rb+')
  114. qsize = self.f.read(self.SIZE_SIZE)
  115. self.size, = struct.unpack(self.SIZE_FORMAT, qsize)
  116. self.f.seek(0, os.SEEK_END)
  117. else:
  118. self.f = open(path, 'wb+')
  119. self.f.write(struct.pack(self.SIZE_FORMAT, 0))
  120. self.size = 0
  121. def push(self, string):
  122. if not isinstance(string, bytes):
  123. raise TypeError('Unsupported type: {}'.format(type(string).__name__))
  124. self.f.write(string)
  125. ssize = struct.pack(self.SIZE_FORMAT, len(string))
  126. self.f.write(ssize)
  127. self.size += 1
  128. def pop(self):
  129. if not self.size:
  130. return
  131. self.f.seek(-self.SIZE_SIZE, os.SEEK_END)
  132. size, = struct.unpack(self.SIZE_FORMAT, self.f.read())
  133. self.f.seek(-size-self.SIZE_SIZE, os.SEEK_END)
  134. data = self.f.read(size)
  135. self.f.seek(-size, os.SEEK_CUR)
  136. self.f.truncate()
  137. self.size -= 1
  138. return data
  139. def close(self):
  140. if self.size:
  141. self.f.seek(0)
  142. self.f.write(struct.pack(self.SIZE_FORMAT, self.size))
  143. self.f.close()
  144. if not self.size:
  145. os.remove(self.path)
  146. def __len__(self):
  147. return self.size
  148. class FifoSQLiteQueue(object):
  149. _sql_create = (
  150. 'CREATE TABLE IF NOT EXISTS queue '
  151. '(id INTEGER PRIMARY KEY AUTOINCREMENT, item BLOB)'
  152. )
  153. _sql_size = 'SELECT COUNT(*) FROM queue'
  154. _sql_push = 'INSERT INTO queue (item) VALUES (?)'
  155. _sql_pop = 'SELECT id, item FROM queue ORDER BY id LIMIT 1'
  156. _sql_del = 'DELETE FROM queue WHERE id = ?'
  157. def __init__(self, path):
  158. self._path = os.path.abspath(path)
  159. self._db = sqlite3.Connection(self._path, timeout=60)
  160. self._db.text_factory = bytes
  161. with self._db as conn:
  162. conn.execute(self._sql_create)
  163. def push(self, item):
  164. if not isinstance(item, bytes):
  165. raise TypeError('Unsupported type: {}'.format(type(item).__name__))
  166. with self._db as conn:
  167. conn.execute(self._sql_push, (item,))
  168. def pop(self):
  169. with self._db as conn:
  170. for id_, item in conn.execute(self._sql_pop):
  171. conn.execute(self._sql_del, (id_,))
  172. return item
  173. def close(self):
  174. size = len(self)
  175. self._db.close()
  176. if not size:
  177. os.remove(self._path)
  178. def __len__(self):
  179. with self._db as conn:
  180. return next(conn.execute(self._sql_size))[0]
  181. class LifoSQLiteQueue(FifoSQLiteQueue):
  182. _sql_pop = 'SELECT id, item FROM queue ORDER BY id DESC LIMIT 1'
  183. #FifoDiskQueue = FifoSQLiteQueue # noqa
  184. #LifoDiskQueue = LifoSQLiteQueue # noqa