Development of an internal social media platform with personalised dashboards for students
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.

MappingStorage.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. ##############################################################################
  2. #
  3. # Copyright (c) Zope Foundation and Contributors.
  4. # All Rights Reserved.
  5. #
  6. # This software is subject to the provisions of the Zope Public License,
  7. # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
  8. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
  9. # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  10. # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
  11. # FOR A PARTICULAR PURPOSE
  12. #
  13. ##############################################################################
  14. """A simple in-memory mapping-based ZODB storage
  15. This storage provides an example implementation of a fairly full
  16. storage without distracting storage details.
  17. """
  18. import BTrees
  19. import time
  20. import ZODB.BaseStorage
  21. import ZODB.interfaces
  22. import ZODB.POSException
  23. import ZODB.TimeStamp
  24. import ZODB.utils
  25. import zope.interface
  26. @zope.interface.implementer(
  27. ZODB.interfaces.IStorage,
  28. ZODB.interfaces.IStorageIteration,
  29. )
  30. class MappingStorage(object):
  31. """In-memory storage implementation
  32. Note that this implementation is somewhat naive and inefficient
  33. with regard to locking. Its implementation is primarily meant to
  34. be a simple illustration of storage implementation. It's also
  35. useful for testing and exploration where scalability and efficiency
  36. are unimportant.
  37. """
  38. def __init__(self, name='MappingStorage'):
  39. """Create a mapping storage
  40. The name parameter is used by the
  41. :meth:`~ZODB.interfaces.IStorage.getName` and
  42. :meth:`~ZODB.interfaces.IStorage.sortKey` methods.
  43. """
  44. self.__name__ = name
  45. self._data = {} # {oid->{tid->pickle}}
  46. self._transactions = BTrees.OOBTree.OOBTree() # {tid->TransactionRecord}
  47. self._ltid = ZODB.utils.z64
  48. self._last_pack = None
  49. self._lock = ZODB.utils.RLock()
  50. self._commit_lock = ZODB.utils.Lock()
  51. self._opened = True
  52. self._transaction = None
  53. self._oid = 0
  54. ######################################################################
  55. # Preconditions:
  56. def opened(self):
  57. """The storage is open
  58. """
  59. return self._opened
  60. def not_in_transaction(self):
  61. """The storage is not committing a transaction
  62. """
  63. return self._transaction is None
  64. #
  65. ######################################################################
  66. # testing framework (lame)
  67. def cleanup(self):
  68. pass
  69. # ZODB.interfaces.IStorage
  70. @ZODB.utils.locked
  71. def close(self):
  72. self._opened = False
  73. # ZODB.interfaces.IStorage
  74. def getName(self):
  75. return self.__name__
  76. # ZODB.interfaces.IStorage
  77. @ZODB.utils.locked(opened)
  78. def getSize(self):
  79. size = 0
  80. for oid, tid_data in self._data.items():
  81. size += 50
  82. for tid, pickle in tid_data.items():
  83. size += 100+len(pickle)
  84. return size
  85. # ZEO.interfaces.IServeable
  86. @ZODB.utils.locked(opened)
  87. def getTid(self, oid):
  88. tid_data = self._data.get(oid)
  89. if tid_data:
  90. return tid_data.maxKey()
  91. raise ZODB.POSException.POSKeyError(oid)
  92. # ZODB.interfaces.IStorage
  93. @ZODB.utils.locked(opened)
  94. def history(self, oid, size=1):
  95. tid_data = self._data.get(oid)
  96. if not tid_data:
  97. raise ZODB.POSException.POSKeyError(oid)
  98. tids = tid_data.keys()[-size:]
  99. tids.reverse()
  100. return [
  101. dict(
  102. time = ZODB.TimeStamp.TimeStamp(tid).timeTime(),
  103. tid = tid,
  104. serial = tid,
  105. user_name = self._transactions[tid].user,
  106. description = self._transactions[tid].description,
  107. extension = self._transactions[tid].extension,
  108. size = len(tid_data[tid])
  109. )
  110. for tid in tids]
  111. # ZODB.interfaces.IStorage
  112. def isReadOnly(self):
  113. return False
  114. # ZODB.interfaces.IStorageIteration
  115. def iterator(self, start=None, end=None):
  116. for transaction_record in self._transactions.values(start, end):
  117. yield transaction_record
  118. # ZODB.interfaces.IStorage
  119. @ZODB.utils.locked(opened)
  120. def lastTransaction(self):
  121. return self._ltid
  122. # ZODB.interfaces.IStorage
  123. @ZODB.utils.locked(opened)
  124. def __len__(self):
  125. return len(self._data)
  126. load = ZODB.utils.load_current
  127. # ZODB.interfaces.IStorage
  128. @ZODB.utils.locked(opened)
  129. def loadBefore(self, oid, tid):
  130. tid_data = self._data.get(oid)
  131. if tid_data:
  132. before = ZODB.utils.u64(tid)
  133. if not before:
  134. return None
  135. before = ZODB.utils.p64(before-1)
  136. tids_before = tid_data.keys(None, before)
  137. if tids_before:
  138. tids_after = tid_data.keys(tid, None)
  139. tid = tids_before[-1]
  140. return (tid_data[tid], tid,
  141. (tids_after and tids_after[0] or None)
  142. )
  143. else:
  144. raise ZODB.POSException.POSKeyError(oid)
  145. # ZODB.interfaces.IStorage
  146. @ZODB.utils.locked(opened)
  147. def loadSerial(self, oid, serial):
  148. tid_data = self._data.get(oid)
  149. if tid_data:
  150. try:
  151. return tid_data[serial]
  152. except KeyError:
  153. pass
  154. raise ZODB.POSException.POSKeyError(oid, serial)
  155. # ZODB.interfaces.IStorage
  156. @ZODB.utils.locked(opened)
  157. def new_oid(self):
  158. self._oid += 1
  159. return ZODB.utils.p64(self._oid)
  160. # ZODB.interfaces.IStorage
  161. @ZODB.utils.locked(opened)
  162. def pack(self, t, referencesf, gc=True):
  163. if not self._data:
  164. return
  165. stop = ZODB.TimeStamp.TimeStamp(*time.gmtime(t)[:5]+(t%60,)).raw()
  166. if self._last_pack is not None and self._last_pack >= stop:
  167. if self._last_pack == stop:
  168. return
  169. raise ValueError("Already packed to a later time")
  170. self._last_pack = stop
  171. transactions = self._transactions
  172. # Step 1, remove old non-current records
  173. for oid, tid_data in self._data.items():
  174. tids_to_remove = tid_data.keys(None, stop)
  175. if tids_to_remove:
  176. tids_to_remove.pop() # Keep the last, if any
  177. if tids_to_remove:
  178. for tid in tids_to_remove:
  179. del tid_data[tid]
  180. if transactions[tid].pack(oid):
  181. del transactions[tid]
  182. if gc:
  183. # Step 2, GC. A simple sweep+copy
  184. new_data = BTrees.OOBTree.OOBTree()
  185. to_copy = set([ZODB.utils.z64])
  186. while to_copy:
  187. oid = to_copy.pop()
  188. tid_data = self._data.pop(oid)
  189. new_data[oid] = tid_data
  190. for pickle in tid_data.values():
  191. for oid in referencesf(pickle):
  192. if oid in new_data:
  193. continue
  194. to_copy.add(oid)
  195. # Remove left over data from transactions
  196. for oid, tid_data in self._data.items():
  197. for tid in tid_data:
  198. if transactions[tid].pack(oid):
  199. del transactions[tid]
  200. self._data.clear()
  201. self._data.update(new_data)
  202. # ZODB.interfaces.IStorage
  203. def registerDB(self, db):
  204. pass
  205. # ZODB.interfaces.IStorage
  206. def sortKey(self):
  207. return self.__name__
  208. # ZODB.interfaces.IStorage
  209. @ZODB.utils.locked(opened)
  210. def store(self, oid, serial, data, version, transaction):
  211. assert not version, "Versions are not supported"
  212. if transaction is not self._transaction:
  213. raise ZODB.POSException.StorageTransactionError(self, transaction)
  214. old_tid = None
  215. tid_data = self._data.get(oid)
  216. if tid_data:
  217. old_tid = tid_data.maxKey()
  218. if serial != old_tid:
  219. raise ZODB.POSException.ConflictError(
  220. oid=oid, serials=(old_tid, serial), data=data)
  221. self._tdata[oid] = data
  222. checkCurrentSerialInTransaction = (
  223. ZODB.BaseStorage.checkCurrentSerialInTransaction)
  224. # ZODB.interfaces.IStorage
  225. @ZODB.utils.locked(opened)
  226. def tpc_abort(self, transaction):
  227. if transaction is not self._transaction:
  228. return
  229. self._transaction = None
  230. self._commit_lock.release()
  231. # ZODB.interfaces.IStorage
  232. def tpc_begin(self, transaction, tid=None):
  233. with self._lock:
  234. ZODB.utils.check_precondition(self.opened)
  235. # The tid argument exists to support testing.
  236. if transaction is self._transaction:
  237. raise ZODB.POSException.StorageTransactionError(
  238. "Duplicate tpc_begin calls for same transaction")
  239. self._commit_lock.acquire()
  240. with self._lock:
  241. self._transaction = transaction
  242. self._tdata = {}
  243. if tid is None:
  244. if self._transactions:
  245. old_tid = self._transactions.maxKey()
  246. else:
  247. old_tid = None
  248. tid = ZODB.utils.newTid(old_tid)
  249. self._tid = tid
  250. # ZODB.interfaces.IStorage
  251. @ZODB.utils.locked(opened)
  252. def tpc_finish(self, transaction, func = lambda tid: None):
  253. if (transaction is not self._transaction):
  254. raise ZODB.POSException.StorageTransactionError(
  255. "tpc_finish called with wrong transaction")
  256. tid = self._tid
  257. func(tid)
  258. tdata = self._tdata
  259. for oid in tdata:
  260. tid_data = self._data.get(oid)
  261. if tid_data is None:
  262. tid_data = BTrees.OOBTree.OOBucket()
  263. self._data[oid] = tid_data
  264. tid_data[tid] = tdata[oid]
  265. self._ltid = tid
  266. self._transactions[tid] = TransactionRecord(tid, transaction, tdata)
  267. self._transaction = None
  268. del self._tdata
  269. self._commit_lock.release()
  270. return tid
  271. # ZEO.interfaces.IServeable
  272. @ZODB.utils.locked(opened)
  273. def tpc_transaction(self):
  274. return self._transaction
  275. # ZODB.interfaces.IStorage
  276. def tpc_vote(self, transaction):
  277. if transaction is not self._transaction:
  278. raise ZODB.POSException.StorageTransactionError(
  279. "tpc_vote called with wrong transaction")
  280. class TransactionRecord(object):
  281. status = ' '
  282. def __init__(self, tid, transaction, data):
  283. self.tid = tid
  284. self.user = transaction.user
  285. self.description = transaction.description
  286. extension = transaction.extension
  287. self.extension = extension
  288. self.data = data
  289. _extension = property(lambda self: self.extension,
  290. lambda self, v: setattr(self, 'extension', v),
  291. )
  292. def __iter__(self):
  293. for oid, data in self.data.items():
  294. yield DataRecord(oid, self.tid, data)
  295. def pack(self, oid):
  296. self.status = 'p'
  297. del self.data[oid]
  298. return not self.data
  299. @zope.interface.implementer(ZODB.interfaces.IStorageRecordInformation)
  300. class DataRecord(object):
  301. """Abstract base class for iterator protocol"""
  302. version = ''
  303. data_txn = None
  304. def __init__(self, oid, tid, data):
  305. self.oid = oid
  306. self.tid = tid
  307. self.data = data
  308. def DB(*args, **kw):
  309. return ZODB.DB(MappingStorage(), *args, **kw)