1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
6 """Instance Messenger base classes for protocol support.
8 You will find these useful if you're adding a new protocol to IM.
11 # Abstract representation of chat "model" classes
13 from twisted.words.im.locals import ONLINE, OFFLINE, OfflineError
14 from twisted.words.im import interfaces
16 from twisted.internet.protocol import Protocol
18 from twisted.python.reflect import prefixedMethods
19 from twisted.persisted import styles
21 from twisted.internet import error
24 def __init__(self, name, account):
26 self.account = account
28 def getGroupCommands(self):
29 """finds group commands
31 these commands are methods on me that start with imgroup_; they are
32 called with no arguments
34 return prefixedMethods(self, "imgroup_")
36 def getTargetCommands(self, target):
37 """finds group commands
39 these commands are methods on me that start with imgroup_; they are
40 called with a user present within this room as an argument
42 you may want to override this in your group in order to filter for
43 appropriate commands on the given user
45 return prefixedMethods(self, "imtarget_")
48 if not self.account.client:
50 self.account.client.joinGroup(self.name)
53 if not self.account.client:
55 self.account.client.leaveGroup(self.name)
58 return '<%s %r>' % (self.__class__, self.name)
61 return '%s@%s' % (self.name, self.account.accountName)
64 def __init__(self, name, baseAccount):
66 self.account = baseAccount
69 def getPersonCommands(self):
70 """finds person commands
72 these commands are methods on me that start with imperson_; they are
73 called with no arguments
75 return prefixedMethods(self, "imperson_")
77 def getIdleTime(self):
84 return '<%s %r/%s>' % (self.__class__, self.name, self.status)
87 return '%s@%s' % (self.name, self.account.accountName)
89 class AbstractClientMixin:
90 """Designed to be mixed in to a Protocol implementing class.
92 Inherit from me first.
94 @ivar _logonDeferred: Fired when I am done logging in.
96 def __init__(self, account, chatui, logonDeferred):
97 for base in self.__class__.__bases__:
98 if issubclass(base, Protocol):
99 self.__class__._protoBase = base
103 self.account = account
105 self._logonDeferred = logonDeferred
107 def connectionMade(self):
108 self._protoBase.connectionMade(self)
110 def connectionLost(self, reason):
111 self.account._clientLost(self, reason)
112 self.unregisterAsAccountClient()
113 return self._protoBase.connectionLost(self, reason)
115 def unregisterAsAccountClient(self):
116 """Tell the chat UI that I have `signed off'.
118 self.chat.unregisterAccountClient(self)
121 class AbstractAccount(styles.Versioned):
122 """Base class for Accounts.
124 I am the start of an implementation of L{IAccount<interfaces.IAccount>}, I
125 implement L{isOnline} and most of L{logOn}, though you'll need to implement
126 L{_startLogOn} in a subclass.
128 @cvar _groupFactory: A Callable that will return a L{IGroup} appropriate
129 for this account type.
130 @cvar _personFactory: A Callable that will return a L{IPerson} appropriate
131 for this account type.
133 @type _isConnecting: boolean
134 @ivar _isConnecting: Whether I am in the process of establishing a
135 connection to the server.
136 @type _isOnline: boolean
137 @ivar _isOnline: Whether I am currently on-line with the server.
151 _groupFactory = AbstractGroup
152 _personFactory = AbstractPerson
154 persistanceVersion = 2
156 def __init__(self, accountName, autoLogin, username, password, host, port):
157 self.accountName = accountName
158 self.autoLogin = autoLogin
159 self.username = username
160 self.password = password
167 def upgrateToVersion2(self):
168 # Added in CVS revision 1.16.
169 for k in ('_groups', '_persons'):
170 if not hasattr(self, k):
173 def __getstate__(self):
174 state = styles.Versioned.__getstate__(self)
175 for k in ('client', '_isOnline', '_isConnecting'):
183 return self._isOnline
185 def logOn(self, chatui):
186 """Log on to this account.
188 Takes care to not start a connection if a connection is
189 already in progress. You will need to implement
190 L{_startLogOn} for this to work, and it would be a good idea
191 to override L{_loginFailed} too.
193 @returntype: Deferred L{interfaces.IClient}
195 if (not self._isConnecting) and (not self._isOnline):
196 self._isConnecting = 1
197 d = self._startLogOn(chatui)
198 d.addCallback(self._cb_logOn)
199 # if chatui is not None:
200 # (I don't particularly like having to pass chatUI to this function,
201 # but we haven't factored it out yet.)
202 d.addCallback(chatui.registerAccountClient)
203 d.addErrback(self._loginFailed)
206 raise error.ConnectError("Connection in progress")
208 def getGroup(self, name):
211 @param name: Name of the group on this account.
214 group = self._groups.get(name)
216 group = self._groupFactory(name, self)
217 self._groups[name] = group
220 def getPerson(self, name):
223 @param name: Name of the person on this account.
226 person = self._persons.get(name)
228 person = self._personFactory(name, self)
229 self._persons[name] = person
232 def _startLogOn(self, chatui):
233 """Start the sign on process.
235 Factored out of L{logOn}.
237 @returntype: Deferred L{interfaces.IClient}
239 raise NotImplementedError()
241 def _cb_logOn(self, client):
242 self._isConnecting = 0
247 def _loginFailed(self, reason):
248 """Errorback for L{logOn}.
250 @type reason: Failure
252 @returns: I{reason}, for further processing in the callback chain.
255 self._isConnecting = 0
256 self._isOnline = 0 # just in case
259 def _clientLost(self, client, reason):
261 self._isConnecting = 0
266 return "<%s: %s (%s@%s:%s)>" % (self.__class__,