diff --git a/deferred.py b/deferred.py new file mode 100644 index 0000000..0bbc4e2 --- /dev/null +++ b/deferred.py @@ -0,0 +1,97 @@ +class Deferred(object): + """ + Represents a delayed computation. This is a more elegant way to deal with + callbacks. + + A Deferred object can be thought of as a computation whose value is yet to + be determined. We can manipulate the Deferred as if it where a regular + value by using the then method. Computations dependent on the Deferred will + only proceed when the run method is called. + + Attributes of a Deferred can be accessed directly as methods. + + Example: + image = Deferred() + getImageWithCallback(image.run) + image.then(displayFunc) + + colors = Deferred() + colors.append('blue') + colors.then(print) + colors.run(['red', 'green']) #=> ['red', 'green', 'blue'] + """ + + def __init__(self): + self.subscribers = [] + self.computed = False + self.args = None + self.kwargs = None + + def run(self, *args, **kwargs): + """ + Give a value to the deferred. Calling this method more than once will + result in a DeferredHasValue exception to be raised. + """ + if self.computed: + raise DeferredHasValue("Deferred object already has a value.") + else: + self.args = args + self.kwargs = kwargs + for func, deferred in self.subscribers: + deferred.run(func(*args, **kwargs)) + self.computed = True + + def then(self, func): + """ + Apply func to Deferred value. Returns a Deferred whose value will be + the result of applying func. + """ + result = Deferred() + if self.computed: + result.run(func(*self.args, **self.kwargs)) + else: + self.subscribers.append((func, result)) + return result + + def arg(self, n): + """ + Returns the nth positional argument of a deferred as a deferred + + Args: + n - the index of the positional argument + """ + def helper(*args, **kwargs): + return args[n] + return self.then(helper) + + def __getattr__(self, method_name): + return getattr(Then(self), method_name) + + +class Then(object): + """ + Allows you to call methods on a Deferred. + + Example: + colors = Deferred() + Then(colors).append('blue') + colors.run(['red', 'green']) + colors.then(print) #=> ['red', 'green', 'blue'] + """ + def __init__(self, deferred): + self.deferred = deferred + + def __getattr__(self, name): + def tryCall(obj, *args, **kwargs): + if callable(obj): + return obj(*args, **kwargs) + else: + return obj + def helper(*args, **kwargs): + func = (lambda x: tryCall(getattr(x, name), *args, **kwargs)) + return self.deferred.then(func) + return helper + +class DeferredHasValue(Exception): + def __init__(self, string): + super(DeferredHasValue, self).__init__(string) diff --git a/session.py b/session.py index 7e7f8b0..b21af20 100644 --- a/session.py +++ b/session.py @@ -39,7 +39,9 @@ from buddy import BuddyList from threading import Timer from group import Group from bot import Bot +import deferred from yowsupwrapper import YowsupApp +from functools import partial class MsgIDs: @@ -723,23 +725,34 @@ class Session(YowsupApp): self.buddies.remove(buddy) def requestVCard(self, buddy, ID=None): - def onSuccess(response, request): - self.logger.debug('Sending VCard (%s) with image id %s', - ID, response.pictureId) - image_hash = utils.sha1hash(response.pictureData) - self.logger.debug('Image hash is %s', image_hash) - if ID != None: - self.backend.handleVCard(self.user, ID, buddy, "", "", response.pictureData) - if not (buddy == self.user or buddy == self.user.split('@')[0]): - obuddy = self.buddies[buddy] - self.updateBuddy(buddy, obuddy.nick, obuddy.groups, image_hash) - if buddy == self.user or buddy == self.user.split('@')[0]: - newbuddy = self.legacyName - else: - newbuddy = buddy - self.logger.debug('Requesting profile picture of %s', newbuddy) - self.requestProfilePicture(newbuddy, onSuccess = onSuccess) + buddy = self.legacyName + + # Get profile picture + self.logger.debug('Requesting profile picture of %s', buddy) + response = deferred.Deferred() + self.requestProfilePicture(buddy, onSuccess = response.run) + response = response.arg(0) + + # Send VCard + if ID != None: + response.pictureId().then(partial( + self.logger.debug, 'Sending VCard (%s) with image id %s', ID + )) + pictureData = response.pictureData() + response.pictureData().then(partial( + self.backend.handleVCard, self.user, ID, buddy, "", "" + )) + + # Send image hash + if not buddy == self.legacyName: + obuddy = self.buddies[buddy] + image_hash = pictureData.then(utils.sha1hash) + image_hash.then(partial(self.logger.debug, 'Image hash is %s')) + image_hash.then(partial( + self.updateBuddy, buddy, obuddy.nick, obuddy.groups + )) + def onDlsuccess(self, path): self.logger.info("Success: Image downloaded to %s", path)