basesupport.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. #
  4. """Instance Messenger base classes for protocol support.
  5. You will find these useful if you're adding a new protocol to IM.
  6. """
  7. # Abstract representation of chat "model" classes
  8. from twisted.words.im.locals import OFFLINE, OfflineError
  9. from twisted.internet.protocol import Protocol
  10. from twisted.python.reflect import prefixedMethods
  11. from twisted.persisted import styles
  12. from twisted.internet import error
  13. class AbstractGroup:
  14. def __init__(self, name, account):
  15. self.name = name
  16. self.account = account
  17. def getGroupCommands(self):
  18. """finds group commands
  19. these commands are methods on me that start with imgroup_; they are
  20. called with no arguments
  21. """
  22. return prefixedMethods(self, "imgroup_")
  23. def getTargetCommands(self, target):
  24. """finds group commands
  25. these commands are methods on me that start with imgroup_; they are
  26. called with a user present within this room as an argument
  27. you may want to override this in your group in order to filter for
  28. appropriate commands on the given user
  29. """
  30. return prefixedMethods(self, "imtarget_")
  31. def join(self):
  32. if not self.account.client:
  33. raise OfflineError
  34. self.account.client.joinGroup(self.name)
  35. def leave(self):
  36. if not self.account.client:
  37. raise OfflineError
  38. self.account.client.leaveGroup(self.name)
  39. def __repr__(self):
  40. return '<%s %r>' % (self.__class__, self.name)
  41. def __str__(self):
  42. return '%s@%s' % (self.name, self.account.accountName)
  43. class AbstractPerson:
  44. def __init__(self, name, baseAccount):
  45. self.name = name
  46. self.account = baseAccount
  47. self.status = OFFLINE
  48. def getPersonCommands(self):
  49. """finds person commands
  50. these commands are methods on me that start with imperson_; they are
  51. called with no arguments
  52. """
  53. return prefixedMethods(self, "imperson_")
  54. def getIdleTime(self):
  55. """
  56. Returns a string.
  57. """
  58. return '--'
  59. def __repr__(self):
  60. return '<%s %r/%s>' % (self.__class__, self.name, self.status)
  61. def __str__(self):
  62. return '%s@%s' % (self.name, self.account.accountName)
  63. class AbstractClientMixin:
  64. """Designed to be mixed in to a Protocol implementing class.
  65. Inherit from me first.
  66. @ivar _logonDeferred: Fired when I am done logging in.
  67. """
  68. def __init__(self, account, chatui, logonDeferred):
  69. for base in self.__class__.__bases__:
  70. if issubclass(base, Protocol):
  71. self.__class__._protoBase = base
  72. break
  73. else:
  74. pass
  75. self.account = account
  76. self.chat = chatui
  77. self._logonDeferred = logonDeferred
  78. def connectionMade(self):
  79. self._protoBase.connectionMade(self)
  80. def connectionLost(self, reason):
  81. self.account._clientLost(self, reason)
  82. self.unregisterAsAccountClient()
  83. return self._protoBase.connectionLost(self, reason)
  84. def unregisterAsAccountClient(self):
  85. """Tell the chat UI that I have `signed off'.
  86. """
  87. self.chat.unregisterAccountClient(self)
  88. class AbstractAccount(styles.Versioned):
  89. """Base class for Accounts.
  90. I am the start of an implementation of L{IAccount<interfaces.IAccount>}, I
  91. implement L{isOnline} and most of L{logOn}, though you'll need to implement
  92. L{_startLogOn} in a subclass.
  93. @cvar _groupFactory: A Callable that will return a L{IGroup} appropriate
  94. for this account type.
  95. @cvar _personFactory: A Callable that will return a L{IPerson} appropriate
  96. for this account type.
  97. @type _isConnecting: boolean
  98. @ivar _isConnecting: Whether I am in the process of establishing a
  99. connection to the server.
  100. @type _isOnline: boolean
  101. @ivar _isOnline: Whether I am currently on-line with the server.
  102. @ivar accountName:
  103. @ivar autoLogin:
  104. @ivar username:
  105. @ivar password:
  106. @ivar host:
  107. @ivar port:
  108. """
  109. _isOnline = 0
  110. _isConnecting = 0
  111. client = None
  112. _groupFactory = AbstractGroup
  113. _personFactory = AbstractPerson
  114. persistanceVersion = 2
  115. def __init__(self, accountName, autoLogin, username, password, host, port):
  116. self.accountName = accountName
  117. self.autoLogin = autoLogin
  118. self.username = username
  119. self.password = password
  120. self.host = host
  121. self.port = port
  122. self._groups = {}
  123. self._persons = {}
  124. def upgrateToVersion2(self):
  125. # Added in CVS revision 1.16.
  126. for k in ('_groups', '_persons'):
  127. if not hasattr(self, k):
  128. setattr(self, k, {})
  129. def __getstate__(self):
  130. state = styles.Versioned.__getstate__(self)
  131. for k in ('client', '_isOnline', '_isConnecting'):
  132. try:
  133. del state[k]
  134. except KeyError:
  135. pass
  136. return state
  137. def isOnline(self):
  138. return self._isOnline
  139. def logOn(self, chatui):
  140. """Log on to this account.
  141. Takes care to not start a connection if a connection is
  142. already in progress. You will need to implement
  143. L{_startLogOn} for this to work, and it would be a good idea
  144. to override L{_loginFailed} too.
  145. @returntype: Deferred L{interfaces.IClient}
  146. """
  147. if (not self._isConnecting) and (not self._isOnline):
  148. self._isConnecting = 1
  149. d = self._startLogOn(chatui)
  150. d.addCallback(self._cb_logOn)
  151. # if chatui is not None:
  152. # (I don't particularly like having to pass chatUI to this function,
  153. # but we haven't factored it out yet.)
  154. d.addCallback(chatui.registerAccountClient)
  155. d.addErrback(self._loginFailed)
  156. return d
  157. else:
  158. raise error.ConnectError("Connection in progress")
  159. def getGroup(self, name):
  160. """Group factory.
  161. @param name: Name of the group on this account.
  162. @type name: string
  163. """
  164. group = self._groups.get(name)
  165. if group is None:
  166. group = self._groupFactory(name, self)
  167. self._groups[name] = group
  168. return group
  169. def getPerson(self, name):
  170. """Person factory.
  171. @param name: Name of the person on this account.
  172. @type name: string
  173. """
  174. person = self._persons.get(name)
  175. if person is None:
  176. person = self._personFactory(name, self)
  177. self._persons[name] = person
  178. return person
  179. def _startLogOn(self, chatui):
  180. """Start the sign on process.
  181. Factored out of L{logOn}.
  182. @returntype: Deferred L{interfaces.IClient}
  183. """
  184. raise NotImplementedError()
  185. def _cb_logOn(self, client):
  186. self._isConnecting = 0
  187. self._isOnline = 1
  188. self.client = client
  189. return client
  190. def _loginFailed(self, reason):
  191. """Errorback for L{logOn}.
  192. @type reason: Failure
  193. @returns: I{reason}, for further processing in the callback chain.
  194. @returntype: Failure
  195. """
  196. self._isConnecting = 0
  197. self._isOnline = 0 # just in case
  198. return reason
  199. def _clientLost(self, client, reason):
  200. self.client = None
  201. self._isConnecting = 0
  202. self._isOnline = 0
  203. return reason
  204. def __repr__(self):
  205. return "<%s: %s (%s@%s:%s)>" % (self.__class__,
  206. self.accountName,
  207. self.username,
  208. self.host,
  209. self.port)