publish.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # -*- test-case-name: twisted.spread.test.test_pb -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Persistently cached objects for PB.
  6. Maintainer: Glyph Lefkowitz
  7. Future Plans: None known.
  8. """
  9. from __future__ import absolute_import, division
  10. import time
  11. from twisted.internet import defer
  12. from twisted.spread import banana, jelly, flavors
  13. class Publishable(flavors.Cacheable):
  14. """An object whose cached state persists across sessions.
  15. """
  16. def __init__(self, publishedID):
  17. self.republish()
  18. self.publishedID = publishedID
  19. def republish(self):
  20. """Set the timestamp to current and (TODO) update all observers.
  21. """
  22. self.timestamp = time.time()
  23. def view_getStateToPublish(self, perspective):
  24. '(internal)'
  25. return self.getStateToPublishFor(perspective)
  26. def getStateToPublishFor(self, perspective):
  27. """Implement me to special-case your state for a perspective.
  28. """
  29. return self.getStateToPublish()
  30. def getStateToPublish(self):
  31. """Implement me to return state to copy as part of the publish phase.
  32. """
  33. raise NotImplementedError("%s.getStateToPublishFor" % self.__class__)
  34. def getStateToCacheAndObserveFor(self, perspective, observer):
  35. """Get all necessary metadata to keep a clientside cache.
  36. """
  37. if perspective:
  38. pname = perspective.perspectiveName
  39. sname = perspective.getService().serviceName
  40. else:
  41. pname = "None"
  42. sname = "None"
  43. return {"remote": flavors.ViewPoint(perspective, self),
  44. "publishedID": self.publishedID,
  45. "perspective": pname,
  46. "service": sname,
  47. "timestamp": self.timestamp}
  48. class RemotePublished(flavors.RemoteCache):
  49. """The local representation of remote Publishable object.
  50. """
  51. isActivated = 0
  52. _wasCleanWhenLoaded = 0
  53. def getFileName(self, ext='pub'):
  54. return ("%s-%s-%s.%s" %
  55. (self.service, self.perspective, str(self.publishedID), ext))
  56. def setCopyableState(self, state):
  57. self.__dict__.update(state)
  58. self._activationListeners = []
  59. try:
  60. with open(self.getFileName(), "rb") as dataFile:
  61. data = dataFile.read()
  62. except IOError:
  63. recent = 0
  64. else:
  65. newself = jelly.unjelly(banana.decode(data))
  66. recent = (newself.timestamp == self.timestamp)
  67. if recent:
  68. self._cbGotUpdate(newself.__dict__)
  69. self._wasCleanWhenLoaded = 1
  70. else:
  71. self.remote.callRemote('getStateToPublish').addCallbacks(self._cbGotUpdate)
  72. def __getstate__(self):
  73. other = self.__dict__.copy()
  74. # Remove PB-specific attributes
  75. del other['broker']
  76. del other['remote']
  77. del other['luid']
  78. # remove my own runtime-tracking stuff
  79. del other['_activationListeners']
  80. del other['isActivated']
  81. return other
  82. def _cbGotUpdate(self, newState):
  83. self.__dict__.update(newState)
  84. self.isActivated = 1
  85. # send out notifications
  86. for listener in self._activationListeners:
  87. listener(self)
  88. self._activationListeners = []
  89. self.activated()
  90. with open(self.getFileName(), "wb") as dataFile:
  91. dataFile.write(banana.encode(jelly.jelly(self)))
  92. def activated(self):
  93. """Implement this method if you want to be notified when your
  94. publishable subclass is activated.
  95. """
  96. def callWhenActivated(self, callback):
  97. """Externally register for notification when this publishable has received all relevant data.
  98. """
  99. if self.isActivated:
  100. callback(self)
  101. else:
  102. self._activationListeners.append(callback)
  103. def whenReady(d):
  104. """
  105. Wrap a deferred returned from a pb method in another deferred that
  106. expects a RemotePublished as a result. This will allow you to wait until
  107. the result is really available.
  108. Idiomatic usage would look like::
  109. publish.whenReady(serverObject.getMeAPublishable()).addCallback(lookAtThePublishable)
  110. """
  111. d2 = defer.Deferred()
  112. d.addCallbacks(_pubReady, d2.errback,
  113. callbackArgs=(d2,))
  114. return d2
  115. def _pubReady(result, d2):
  116. '(internal)'
  117. result.callWhenActivated(d2.callback)