1 # -*- test-case-name: twisted.words.test.test_service -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 A module that needs a better name.
8 Implements new cred things for words.
10 How does this thing work?
12 - Network connection on some port expecting to speak some protocol
14 - Protocol-specific authentication, resulting in some kind of credentials object
16 - twisted.cred.portal login using those credentials for the interface
17 IUser and with something implementing IChatClient as the mind
19 - successful login results in an IUser avatar the protocol can call
20 methods on, and state added to the realm such that the mind will have
21 methods called on it as is necessary
23 - protocol specific actions lead to calls onto the avatar; remote events
24 lead to calls onto the mind
26 - protocol specific hangup, realm is notified, user is removed from active
30 from time import time, ctime
32 from zope.interface import implements
34 from twisted.words import iwords, ewords
36 from twisted.python.components import registerAdapter
37 from twisted.cred import portal, credentials, error as ecred
38 from twisted.spread import pb
39 from twisted.words.protocols import irc
40 from twisted.internet import defer, protocol
41 from twisted.python import log, failure, reflect
42 from twisted import copyright
46 implements(iwords.IGroup)
48 def __init__(self, name):
57 def _ebUserCall(self, err, p):
58 return failure.Failure(Exception(p, err))
61 def _cbUserCall(self, results):
62 for (success, result) in results:
64 user, err = result.value # XXX
65 self.remove(user, err.getErrorMessage())
69 assert iwords.IChatClient.providedBy(user), "%r is not a chat client" % (user,)
70 if user.name not in self.users:
72 self.users[user.name] = user
73 for p in self.users.itervalues():
75 d = defer.maybeDeferred(p.userJoined, self, user)
76 d.addErrback(self._ebUserCall, p=p)
78 defer.DeferredList(additions).addCallback(self._cbUserCall)
79 return defer.succeed(None)
82 def remove(self, user, reason=None):
83 assert reason is None or isinstance(reason, unicode)
85 del self.users[user.name]
90 for p in self.users.itervalues():
92 d = defer.maybeDeferred(p.userLeft, self, user, reason)
93 d.addErrback(self._ebUserCall, p=p)
95 defer.DeferredList(removals).addCallback(self._cbUserCall)
96 return defer.succeed(None)
100 return defer.succeed(len(self.users))
103 def receive(self, sender, recipient, message):
104 assert recipient is self
106 for p in self.users.itervalues():
108 d = defer.maybeDeferred(p.receive, sender, self, message)
109 d.addErrback(self._ebUserCall, p=p)
111 defer.DeferredList(receives).addCallback(self._cbUserCall)
112 return defer.succeed(None)
115 def setMetadata(self, meta):
118 for p in self.users.itervalues():
119 d = defer.maybeDeferred(p.groupMetaUpdate, self, meta)
120 d.addErrback(self._ebUserCall, p=p)
122 defer.DeferredList(sets).addCallback(self._cbUserCall)
123 return defer.succeed(None)
128 return iter(self.users.values())
132 implements(iwords.IUser)
137 def __init__(self, name):
140 self.lastMessage = time()
143 def loggedIn(self, realm, mind):
149 def join(self, group):
151 self.groups.append(group)
153 return group.add(self.mind).addCallback(cbJoin)
156 def leave(self, group, reason=None):
158 self.groups.remove(group)
160 return group.remove(self.mind, reason).addCallback(cbLeave)
163 def send(self, recipient, message):
164 self.lastMessage = time()
165 return recipient.receive(self.mind, recipient, message)
168 def itergroups(self):
169 return iter(self.groups)
173 for g in self.groups[:]:
177 NICKSERV = 'NickServ!NickServ@services'
180 class IRCUser(irc.IRC):
182 Protocol instance representing an IRC user connected to the server.
184 implements(iwords.IChatClient)
186 # A list of IGroups in which I am participating
189 # A no-argument callable I should invoke when I go away
192 # An IUser we use to interact with the chat service
198 # How to handle unicode (TODO: Make this customizable on a per-user basis)
202 def connectionMade(self):
203 self.irc_PRIVMSG = self.irc_NICKSERV_PRIVMSG
204 self.realm = self.factory.realm
205 self.hostname = self.realm.name
208 def connectionLost(self, reason):
209 if self.logout is not None:
214 # Make sendMessage a bit more useful to us
215 def sendMessage(self, command, *parameter_list, **kw):
216 if not kw.has_key('prefix'):
217 kw['prefix'] = self.hostname
218 if not kw.has_key('to'):
219 kw['to'] = self.name.encode(self.encoding)
221 arglist = [self, command, kw['to']] + list(parameter_list)
222 irc.IRC.sendMessage(*arglist, **kw)
225 # IChatClient implementation
226 def userJoined(self, group, user):
228 "%s!%s@%s" % (user.name, user.name, self.hostname),
232 def userLeft(self, group, user, reason=None):
233 assert reason is None or isinstance(reason, unicode)
235 "%s!%s@%s" % (user.name, user.name, self.hostname),
237 (reason or u"leaving").encode(self.encoding, 'replace'))
240 def receive(self, sender, recipient, message):
241 #>> :glyph!glyph@adsl-64-123-27-108.dsl.austtx.swbell.net PRIVMSG glyph_ :hello
244 if iwords.IGroup.providedBy(recipient):
245 recipientName = '#' + recipient.name
247 recipientName = recipient.name
249 text = message.get('text', '<an unrepresentable message>')
250 for L in text.splitlines():
252 '%s!%s@%s' % (sender.name, sender.name, self.hostname),
257 def groupMetaUpdate(self, group, meta):
259 topic = meta['topic']
260 author = meta.get('topic_author', '')
265 '%s!%s@%s' % (author, author, self.hostname)
268 # irc.IRC callbacks - starting with login related stuff.
272 def irc_PASS(self, prefix, params):
273 """Password message -- Register a password.
275 Parameters: <password>
279 Note that IRC requires the client send this *before* NICK
282 self.password = params[-1]
285 def irc_NICK(self, prefix, params):
286 """Nick message -- Set your nickname.
288 Parameters: <nickname>
293 nickname = params[0].decode(self.encoding)
294 except UnicodeDecodeError:
298 'Your nickname is cannot be decoded. Please use ASCII or UTF-8.')
299 self.transport.loseConnection()
302 self.nickname = nickname
305 for code, text in self._motdMessages:
306 self.sendMessage(code, text % self.factory._serverInfo)
308 if self.password is None:
314 password = self.password
316 self.logInAs(nickname, password)
319 def irc_USER(self, prefix, params):
320 """User message -- Set your realname.
322 Parameters: <user> <mode> <unused> <realname>
324 # Note: who gives a crap about this? The IUser has the real
325 # information we care about. Save it anyway, I guess, just
327 self.realname = params[-1]
330 def irc_NICKSERV_PRIVMSG(self, prefix, params):
331 """Send a (private) message.
333 Parameters: <msgtarget> <text to be sent>
336 password = params[-1]
338 if self.nickname is None:
339 # XXX Send an error response here
340 self.transport.loseConnection()
341 elif target.lower() != "nickserv":
345 "Denied. Please send me (NickServ) your password.")
347 nickname = self.nickname
349 self.logInAs(nickname, password)
352 def logInAs(self, nickname, password):
353 d = self.factory.portal.login(
354 credentials.UsernamePassword(nickname, password),
357 d.addCallbacks(self._cbLogin, self._ebLogin, errbackArgs=(nickname,))
362 ":connected to Twisted IRC"),
364 ":Your host is %(serviceName)s, running version %(serviceVersion)s"),
366 ":This server was created on %(creationDate)s"),
368 # "Bummer. This server returned a worthless 004 numeric.
369 # I'll have to guess at all the values"
372 # w and n are the currently supported channel and user modes
373 # -- specify this better
374 "%(serviceName)s %(serviceVersion)s w n")
379 ":- %(serviceName)s Message of the Day - "),
381 ":End of /MOTD command.")
384 def _cbLogin(self, (iface, avatar, logout)):
385 assert iface is iwords.IUser, "Realm is buggy, got %r" % (iface,)
387 # Let them send messages to the world
392 for code, text in self._welcomeMessages:
393 self.sendMessage(code, text % self.factory._serverInfo)
396 def _ebLogin(self, err, nickname):
397 if err.check(ewords.AlreadyLoggedIn):
401 "Already logged in. No pod people allowed!")
402 elif err.check(ecred.UnauthorizedLogin):
406 "Login failed. Goodbye.")
408 log.msg("Unhandled error during login:")
413 "Server error during login. Sorry.")
414 self.transport.loseConnection()
417 # Great, now that's out of the way, here's some of the interesting
419 def irc_PING(self, prefix, params):
422 Parameters: <server1> [ <server2> ]
424 if self.realm is not None:
425 self.sendMessage('PONG', self.hostname)
428 def irc_QUIT(self, prefix, params):
431 Parameters: [ <Quit Message> ]
433 self.transport.loseConnection()
436 def _channelMode(self, group, modes=None, *args):
440 ":Unknown MODE flag.")
442 self.channelMode(self.name, '#' + group.name, '+')
445 def _userMode(self, user, modes=None):
449 ":Unknown MODE flag.")
450 elif user is self.avatar:
456 irc.ERR_USERSDONTMATCH,
457 ":You can't look at someone else's modes.")
460 def irc_MODE(self, prefix, params):
463 Parameters: <nickname>
464 *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
468 channelOrUser = params[0].decode(self.encoding)
469 except UnicodeDecodeError:
471 irc.ERR_NOSUCHNICK, params[0],
472 ":No such nickname (could not decode your unicode!)")
475 if channelOrUser.startswith('#'):
477 err.trap(ewords.NoSuchGroup)
479 irc.ERR_NOSUCHCHANNEL, params[0],
480 ":That channel doesn't exist.")
481 d = self.realm.lookupGroup(channelOrUser[1:])
485 callbackArgs=tuple(params[1:]))
490 ":No such nickname.")
492 d = self.realm.lookupUser(channelOrUser)
496 callbackArgs=tuple(params[1:]))
499 def irc_USERHOST(self, prefix, params):
502 Parameters: <nickname> *( SPACE <nickname> )
509 def irc_PRIVMSG(self, prefix, params):
510 """Send a (private) message.
512 Parameters: <msgtarget> <text to be sent>
515 targetName = params[0].decode(self.encoding)
516 except UnicodeDecodeError:
518 irc.ERR_NOSUCHNICK, params[0],
519 ":No such nick/channel (could not decode your unicode!)")
522 messageText = params[-1]
523 if targetName.startswith('#'):
524 target = self.realm.lookupGroup(targetName[1:])
526 target = self.realm.lookupUser(targetName).addCallback(lambda user: user.mind)
530 return self.avatar.send(targ, {"text": messageText})
534 irc.ERR_NOSUCHNICK, targetName,
535 ":No such nick/channel.")
537 target.addCallbacks(cbTarget, ebTarget)
540 def irc_JOIN(self, prefix, params):
543 Parameters: ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] )
546 groupName = params[0].decode(self.encoding)
547 except UnicodeDecodeError:
549 irc.ERR_NOSUCHCHANNEL, params[0],
550 ":No such channel (could not decode your unicode!)")
553 if groupName.startswith('#'):
554 groupName = groupName[1:]
558 self.userJoined(group, self)
562 [user.name for user in group.iterusers()])
563 self._sendTopic(group)
564 return self.avatar.join(group).addCallback(cbJoin)
568 irc.ERR_NOSUCHCHANNEL, '#' + groupName,
571 self.realm.getGroup(groupName).addCallbacks(cbGroup, ebGroup)
574 def irc_PART(self, prefix, params):
577 Parameters: <channel> *( "," <channel> ) [ <Part Message> ]
580 groupName = params[0].decode(self.encoding)
581 except UnicodeDecodeError:
583 irc.ERR_NOTONCHANNEL, params[0],
584 ":Could not decode your unicode!")
587 if groupName.startswith('#'):
588 groupName = groupName[1:]
591 reason = params[1].decode('utf-8')
597 self.userLeft(group, self, reason)
598 return self.avatar.leave(group, reason).addCallback(cbLeave)
601 err.trap(ewords.NoSuchGroup)
603 irc.ERR_NOTONCHANNEL,
605 ":" + err.getErrorMessage())
607 self.realm.lookupGroup(groupName).addCallbacks(cbGroup, ebGroup)
610 def irc_NAMES(self, prefix, params):
613 Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
616 #>> :benford.openprojects.net 353 glyph = #python :Orban ... @glyph ... Zymurgy skreech
617 #>> :benford.openprojects.net 366 glyph #python :End of /NAMES list.
619 channel = params[-1].decode(self.encoding)
620 except UnicodeDecodeError:
622 irc.ERR_NOSUCHCHANNEL, params[-1],
623 ":No such channel (could not decode your unicode!)")
626 if channel.startswith('#'):
627 channel = channel[1:]
633 [user.name for user in group.iterusers()])
636 err.trap(ewords.NoSuchGroup)
637 # No group? Fine, no names!
643 self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup)
646 def irc_TOPIC(self, prefix, params):
649 Parameters: <channel> [ <topic> ]
652 channel = params[0].decode(self.encoding)
653 except UnicodeDecodeError:
655 irc.ERR_NOSUCHCHANNEL,
656 ":That channel doesn't exist (could not decode your unicode!)")
659 if channel.startswith('#'):
660 channel = channel[1:]
663 self._setTopic(channel, params[1])
665 self._getTopic(channel)
668 def _sendTopic(self, group):
670 Send the topic of the given group to this user, if it has one.
672 topic = group.meta.get("topic")
674 author = group.meta.get("topic_author") or "<noone>"
675 date = group.meta.get("topic_date", 0)
676 self.topic(self.name, '#' + group.name, topic)
677 self.topicAuthor(self.name, '#' + group.name, author, date)
680 def _getTopic(self, channel):
682 #>> :benford.openprojects.net 332 glyph #python :<churchr> I really did. I sprained all my toes.
683 #>> :benford.openprojects.net 333 glyph #python itamar|nyc 994713482
685 err.trap(ewords.NoSuchGroup)
687 irc.ERR_NOSUCHCHANNEL, '=', channel,
688 ":That channel doesn't exist.")
690 self.realm.lookupGroup(channel).addCallbacks(self._sendTopic, ebGroup)
693 def _setTopic(self, channel, topic):
694 #<< TOPIC #divunal :foo
695 #>> :glyph!glyph@adsl-64-123-27-108.dsl.austtx.swbell.net TOPIC #divunal :foo
698 newMeta = group.meta.copy()
699 newMeta['topic'] = topic
700 newMeta['topic_author'] = self.name
701 newMeta['topic_date'] = int(time())
705 irc.ERR_CHANOPRIVSNEEDED,
707 ":You need to be a channel operator to do that.")
709 return group.setMetadata(newMeta).addErrback(ebSet)
712 err.trap(ewords.NoSuchGroup)
714 irc.ERR_NOSUCHCHANNEL, '=', channel,
715 ":That channel doesn't exist.")
717 self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup)
720 def list(self, channels):
721 """Send a group of LIST response lines
723 @type channel: C{list} of C{(str, int, str)}
724 @param channel: Information about the channels being sent:
725 their name, the number of participants, and their topic.
727 for (name, size, topic) in channels:
728 self.sendMessage(irc.RPL_LIST, name, str(size), ":" + topic)
729 self.sendMessage(irc.RPL_LISTEND, ":End of /LIST")
732 def irc_LIST(self, prefix, params):
735 Return information about the indicated channels, or about all
736 channels if none are specified.
738 Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
741 #>> :orwell.freenode.net 321 exarkun Channel :Users Name
742 #>> :orwell.freenode.net 322 exarkun #python 358 :The Python programming language
743 #>> :orwell.freenode.net 323 exarkun :End of /LIST
745 # Return information about indicated channels
747 channels = params[0].decode(self.encoding).split(',')
748 except UnicodeDecodeError:
750 irc.ERR_NOSUCHCHANNEL, params[0],
751 ":No such channel (could not decode your unicode!)")
756 if ch.startswith('#'):
758 groups.append(self.realm.lookupGroup(ch))
760 groups = defer.DeferredList(groups, consumeErrors=True)
761 groups.addCallback(lambda gs: [r for (s, r) in gs if s])
763 # Return information about all channels
764 groups = self.realm.itergroups()
766 def cbGroups(groups):
767 def gotSize(size, group):
768 return group.name, size, group.meta.get('topic')
769 d = defer.DeferredList([
770 group.size().addCallback(gotSize, group) for group in groups])
771 d.addCallback(lambda results: self.list([r for (s, r) in results if s]))
773 groups.addCallback(cbGroups)
776 def _channelWho(self, group):
777 self.who(self.name, '#' + group.name,
778 [(m.name, self.hostname, self.realm.name, m.name, "H", 0, m.name) for m in group.iterusers()])
781 def _userWho(self, user):
782 self.sendMessage(irc.RPL_ENDOFWHO,
783 ":User /WHO not implemented")
786 def irc_WHO(self, prefix, params):
789 Parameters: [ <mask> [ "o" ] ]
792 #>> :x.opn 352 glyph #python aquarius pc-62-31-193-114-du.blueyonder.co.uk y.opn Aquarius H :3 Aquarius
794 #>> :x.opn 352 glyph #python foobar europa.tranquility.net z.opn skreech H :0 skreech
795 #>> :x.opn 315 glyph #python :End of /WHO list.
798 #>> :x.opn 352 glyph #python glyph adsl-64-123-27-108.dsl.austtx.swbell.net x.opn glyph H :0 glyph
799 #>> :x.opn 315 glyph glyph :End of /WHO list.
801 self.sendMessage(irc.RPL_ENDOFWHO, ":/WHO not supported.")
805 channelOrUser = params[0].decode(self.encoding)
806 except UnicodeDecodeError:
808 irc.RPL_ENDOFWHO, params[0],
809 ":End of /WHO list (could not decode your unicode!)")
812 if channelOrUser.startswith('#'):
814 err.trap(ewords.NoSuchGroup)
816 irc.RPL_ENDOFWHO, channelOrUser,
817 ":End of /WHO list.")
818 d = self.realm.lookupGroup(channelOrUser[1:])
819 d.addCallbacks(self._channelWho, ebGroup)
822 err.trap(ewords.NoSuchUser)
824 irc.RPL_ENDOFWHO, channelOrUser,
825 ":End of /WHO list.")
826 d = self.realm.lookupUser(channelOrUser)
827 d.addCallbacks(self._userWho, ebUser)
831 def irc_WHOIS(self, prefix, params):
834 Parameters: [ <target> ] <mask> *( "," <mask> )
839 user.name, user.name, self.realm.name,
840 user.name, self.realm.name, 'Hi mom!', False,
841 int(time() - user.lastMessage), user.signOn,
842 ['#' + group.name for group in user.itergroups()])
845 err.trap(ewords.NoSuchUser)
849 ":No such nick/channel")
852 user = params[0].decode(self.encoding)
853 except UnicodeDecodeError:
857 ":No such nick/channel")
860 self.realm.lookupUser(user).addCallbacks(cbUser, ebUser)
863 # Unsupported commands, here for legacy compatibility
864 def irc_OPER(self, prefix, params):
867 Parameters: <name> <password>
869 self.sendMessage(irc.ERR_NOOPERHOST, ":O-lines not applicable")
872 class IRCFactory(protocol.ServerFactory):
874 IRC server that creates instances of the L{IRCUser} protocol.
876 @ivar _serverInfo: A dictionary mapping:
877 "serviceName" to the name of the server,
878 "serviceVersion" to the copyright version,
879 "creationDate" to the time that the server was started.
883 def __init__(self, realm, portal):
887 "serviceName": self.realm.name,
888 "serviceVersion": copyright.version,
889 "creationDate": ctime()
894 class PBMind(pb.Referenceable):
898 def jellyFor(self, jellier):
899 return reflect.qual(PBMind), jellier.invoker.registerReference(self)
901 def remote_userJoined(self, user, group):
904 def remote_userLeft(self, user, group, reason):
907 def remote_receive(self, sender, recipient, message):
910 def remote_groupMetaUpdate(self, group, meta):
914 class PBMindReference(pb.RemoteReference):
915 implements(iwords.IChatClient)
917 def receive(self, sender, recipient, message):
918 if iwords.IGroup.providedBy(recipient):
919 rec = PBGroup(self.realm, self.avatar, recipient)
921 rec = PBUser(self.realm, self.avatar, recipient)
922 return self.callRemote(
924 PBUser(self.realm, self.avatar, sender),
928 def groupMetaUpdate(self, group, meta):
929 return self.callRemote(
931 PBGroup(self.realm, self.avatar, group),
934 def userJoined(self, group, user):
935 return self.callRemote(
937 PBGroup(self.realm, self.avatar, group),
938 PBUser(self.realm, self.avatar, user))
940 def userLeft(self, group, user, reason=None):
941 assert reason is None or isinstance(reason, unicode)
942 return self.callRemote(
944 PBGroup(self.realm, self.avatar, group),
945 PBUser(self.realm, self.avatar, user),
947 pb.setUnjellyableForClass(PBMind, PBMindReference)
950 class PBGroup(pb.Referenceable):
951 def __init__(self, realm, avatar, group):
957 def processUniqueID(self):
958 return hash((self.realm.name, self.avatar.name, self.group.name))
961 def jellyFor(self, jellier):
962 return reflect.qual(self.__class__), self.group.name.encode('utf-8'), jellier.invoker.registerReference(self)
965 def remote_leave(self, reason=None):
966 return self.avatar.leave(self.group, reason)
969 def remote_send(self, message):
970 return self.avatar.send(self.group, message)
973 class PBGroupReference(pb.RemoteReference):
974 implements(iwords.IGroup)
976 def unjellyFor(self, unjellier, unjellyList):
977 clsName, name, ref = unjellyList
978 self.name = name.decode('utf-8')
979 return pb.RemoteReference.unjellyFor(self, unjellier, [clsName, ref])
981 def leave(self, reason=None):
982 return self.callRemote("leave", reason)
984 def send(self, message):
985 return self.callRemote("send", message)
986 pb.setUnjellyableForClass(PBGroup, PBGroupReference)
988 class PBUser(pb.Referenceable):
989 def __init__(self, realm, avatar, user):
994 def processUniqueID(self):
995 return hash((self.realm.name, self.avatar.name, self.user.name))
998 class ChatAvatar(pb.Referenceable):
999 implements(iwords.IChatClient)
1001 def __init__(self, avatar):
1002 self.avatar = avatar
1005 def jellyFor(self, jellier):
1006 return reflect.qual(self.__class__), jellier.invoker.registerReference(self)
1009 def remote_join(self, groupName):
1010 assert isinstance(groupName, unicode)
1012 def cbJoin(ignored):
1013 return PBGroup(self.avatar.realm, self.avatar, group)
1014 d = self.avatar.join(group)
1015 d.addCallback(cbJoin)
1017 d = self.avatar.realm.getGroup(groupName)
1018 d.addCallback(cbGroup)
1020 registerAdapter(ChatAvatar, iwords.IUser, pb.IPerspective)
1022 class AvatarReference(pb.RemoteReference):
1023 def join(self, groupName):
1024 return self.callRemote('join', groupName)
1027 d = defer.Deferred()
1028 self.broker.notifyOnDisconnect(lambda: d.callback(None))
1029 self.broker.transport.loseConnection()
1032 pb.setUnjellyableForClass(ChatAvatar, AvatarReference)
1035 class WordsRealm(object):
1036 implements(portal.IRealm, iwords.IChatService)
1040 def __init__(self, name):
1044 def userFactory(self, name):
1048 def groupFactory(self, name):
1052 def logoutFactory(self, avatar, facet):
1054 # XXX Deferred support here
1055 getattr(facet, 'logout', lambda: None)()
1056 avatar.realm = avatar.mind = None
1060 def requestAvatar(self, avatarId, mind, *interfaces):
1061 if isinstance(avatarId, str):
1062 avatarId = avatarId.decode(self._encoding)
1064 def gotAvatar(avatar):
1065 if avatar.realm is not None:
1066 raise ewords.AlreadyLoggedIn()
1067 for iface in interfaces:
1068 facet = iface(avatar, None)
1069 if facet is not None:
1070 avatar.loggedIn(self, mind)
1071 mind.name = avatarId
1073 mind.avatar = avatar
1074 return iface, facet, self.logoutFactory(avatar, facet)
1075 raise NotImplementedError(self, interfaces)
1077 return self.getUser(avatarId).addCallback(gotAvatar)
1080 # IChatService, mostly.
1081 createGroupOnRequest = False
1082 createUserOnRequest = True
1084 def lookupUser(self, name):
1085 raise NotImplementedError
1088 def lookupGroup(self, group):
1089 raise NotImplementedError
1092 def addUser(self, user):
1093 """Add the given user to this service.
1095 This is an internal method intented to be overridden by
1096 L{WordsRealm} subclasses, not called by external code.
1098 @type user: L{IUser}
1100 @rtype: L{twisted.internet.defer.Deferred}
1101 @return: A Deferred which fires with C{None} when the user is
1102 added, or which fails with
1103 L{twisted.words.ewords.DuplicateUser} if a user with the
1104 same name exists already.
1106 raise NotImplementedError
1109 def addGroup(self, group):
1110 """Add the given group to this service.
1112 @type group: L{IGroup}
1114 @rtype: L{twisted.internet.defer.Deferred}
1115 @return: A Deferred which fires with C{None} when the group is
1116 added, or which fails with
1117 L{twisted.words.ewords.DuplicateGroup} if a group with the
1118 same name exists already.
1120 raise NotImplementedError
1123 def getGroup(self, name):
1124 assert isinstance(name, unicode)
1125 if self.createGroupOnRequest:
1127 err.trap(ewords.DuplicateGroup)
1128 return self.lookupGroup(name)
1129 return self.createGroup(name).addErrback(ebGroup)
1130 return self.lookupGroup(name)
1133 def getUser(self, name):
1134 assert isinstance(name, unicode)
1135 if self.createUserOnRequest:
1137 err.trap(ewords.DuplicateUser)
1138 return self.lookupUser(name)
1139 return self.createUser(name).addErrback(ebUser)
1140 return self.lookupUser(name)
1143 def createUser(self, name):
1144 assert isinstance(name, unicode)
1146 return failure.Failure(ewords.DuplicateUser(name))
1148 err.trap(ewords.NoSuchUser)
1149 return self.userFactory(name)
1152 d = self.lookupUser(name)
1153 d.addCallbacks(cbLookup, ebLookup)
1154 d.addCallback(self.addUser)
1158 def createGroup(self, name):
1159 assert isinstance(name, unicode)
1160 def cbLookup(group):
1161 return failure.Failure(ewords.DuplicateGroup(name))
1163 err.trap(ewords.NoSuchGroup)
1164 return self.groupFactory(name)
1167 d = self.lookupGroup(name)
1168 d.addCallbacks(cbLookup, ebLookup)
1169 d.addCallback(self.addGroup)
1173 class InMemoryWordsRealm(WordsRealm):
1174 def __init__(self, *a, **kw):
1175 super(InMemoryWordsRealm, self).__init__(*a, **kw)
1180 def itergroups(self):
1181 return defer.succeed(self.groups.itervalues())
1184 def addUser(self, user):
1185 if user.name in self.users:
1186 return defer.fail(failure.Failure(ewords.DuplicateUser()))
1187 self.users[user.name] = user
1188 return defer.succeed(user)
1191 def addGroup(self, group):
1192 if group.name in self.groups:
1193 return defer.fail(failure.Failure(ewords.DuplicateGroup()))
1194 self.groups[group.name] = group
1195 return defer.succeed(group)
1198 def lookupUser(self, name):
1199 assert isinstance(name, unicode)
1202 user = self.users[name]
1204 return defer.fail(failure.Failure(ewords.NoSuchUser(name)))
1206 return defer.succeed(user)
1209 def lookupGroup(self, name):
1210 assert isinstance(name, unicode)
1213 group = self.groups[name]
1215 return defer.fail(failure.Failure(ewords.NoSuchGroup(name)))
1217 return defer.succeed(group)
1222 'WordsRealm', 'InMemoryWordsRealm',