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