Added detection of missed calls. As part of this I moved some of the connections...
[theonering] / src / autogv.py
1 import logging
2
3 import gobject
4 import telepathy
5
6 try:
7         import conic as _conic
8         conic = _conic
9 except (ImportError, OSError):
10         conic = None
11
12 import constants
13 import util.coroutines as coroutines
14 import util.go_utils as gobject_utils
15 import util.tp_utils as telepathy_utils
16 import gtk_toolbox
17
18
19 _moduleLogger = logging.getLogger("autogv")
20
21
22 class NewGVConversations(object):
23
24         def __init__(self, connRef):
25                 self._connRef = connRef
26                 self.__callback = None
27
28         def start(self):
29                 self.__callback = coroutines.func_sink(
30                         coroutines.expand_positional(
31                                 self._on_conversations_updated
32                         )
33                 )
34                 self._connRef().session.voicemails.updateSignalHandler.register_sink(
35                         self.__callback
36                 )
37                 self._connRef().session.texts.updateSignalHandler.register_sink(
38                         self.__callback
39                 )
40
41         def stop(self):
42                 self._connRef().session.voicemails.updateSignalHandler.unregister_sink(
43                         self.__callback
44                 )
45                 self._connRef().session.texts.updateSignalHandler.unregister_sink(
46                         self.__callback
47                 )
48                 self.__callback = None
49
50         @gtk_toolbox.log_exception(_moduleLogger)
51         def _on_conversations_updated(self, conv, conversationIds):
52                 _moduleLogger.debug("Incoming messages from: %r" % (conversationIds, ))
53                 for phoneNumber in conversationIds:
54                         h = self._connRef().get_handle_by_name(telepathy.HANDLE_TYPE_CONTACT, phoneNumber)
55                         # Just let the TextChannel decide whether it should be reported to the user or not
56                         props = self._connRef().generate_props(telepathy.CHANNEL_TYPE_TEXT, h, False)
57                         if self._channel_manager.channel_exists(props):
58                                 continue
59
60                         # Maemo 4.1's RTComm opens a window for a chat regardless if a
61                         # message is received or not, so we need to do some filtering here
62                         mergedConv = conv.get_conversation(phoneNumber)
63                         unreadConvs = [
64                                 conversation
65                                 for conversation in mergedConv.conversations
66                                 if not conversation.isRead and not conversation.isArchived
67                         ]
68                         if not unreadConvs:
69                                 continue
70
71                         chan = self._channel_manager.channel_for_props(props, signal=True)
72
73
74 class RefreshVoicemail(object):
75
76         def __init__(self, connRef):
77                 self._connRef = connRef
78                 self._newChannelSignaller = telepathy_utils.NewChannelSignaller(self._on_new_channel)
79                 self._outstandingRequests = []
80
81         def start(self):
82                 self._newChannelSignaller.start()
83
84         def stop(self):
85                 _moduleLogger.info("Stopping voicemail refresh")
86                 self._newChannelSignaller.stop()
87
88                 # I don't want to trust whether the cancel happens within the current
89                 # callback or not which could be the deciding factor between invalid
90                 # iterators or infinite loops
91                 localRequests = [r for r in self._outstandingRequests]
92                 for request in localRequests:
93                         localRequests.cancel()
94
95         @gtk_toolbox.log_exception(_moduleLogger)
96         def _on_new_channel(self, bus, serviceName, connObjectPath, channelObjectPath, channelType):
97                 if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
98                         return
99
100                 cmName = telepathy_utils.cm_from_path(connObjectPath)
101                 if cmName == constants._telepathy_implementation_name_:
102                         _moduleLogger.debug("Ignoring channels from self to prevent deadlock")
103                         return
104
105                 conn = telepathy.client.Connection(serviceName, connObjectPath)
106                 chan = telepathy.client.Channel(serviceName, channelObjectPath)
107                 missDetection = telepathy_utils.WasMissedCall(
108                         bus, conn, chan, self._on_missed_call, self._on_error_for_missed
109                 )
110                 self._outstandingRequests.append(missDetection)
111
112         @gtk_toolbox.log_exception(_moduleLogger)
113         def _on_missed_call(self, missDetection):
114                 _moduleLogger.info("Missed a call")
115                 self._connRef().session.voicemailsStateMachine.reset_timers()
116                 self._outstandingRequests.remove(missDetection)
117
118         @gtk_toolbox.log_exception(_moduleLogger)
119         def _on_error_for_missed(self, missDetection, reason):
120                 _moduleLogger.debug("Error: %r claims %r" % (missDetection, reason))
121                 self._outstandingRequests.remove(missDetection)
122
123
124 class AutoDisconnect(object):
125
126         def __init__(self, connRef):
127                 self._connRef = connRef
128                 if conic is not None:
129                         self.__connection = conic.Connection()
130                 else:
131                         self.__connection = None
132
133                 self.__connectionEventId = None
134                 self.__delayedDisconnectEventId = None
135
136         def start(self):
137                 if self.__connection is not None:
138                         self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
139
140         def stop(self):
141                 self._cancel_delayed_disconnect()
142
143         @gtk_toolbox.log_exception(_moduleLogger)
144         def _on_connection_change(self, connection, event):
145                 """
146                 @note Maemo specific
147                 """
148                 status = event.get_status()
149                 error = event.get_error()
150                 iap_id = event.get_iap_id()
151                 bearer = event.get_bearer_type()
152
153                 if status == conic.STATUS_DISCONNECTED:
154                         _moduleLogger.info("Disconnected from network, starting countdown to logoff")
155                         self.__delayedDisconnectEventId = gobject_utils.timeout_add_seconds(
156                                 5, self._on_delayed_disconnect
157                         )
158                 elif status == conic.STATUS_CONNECTED:
159                         _moduleLogger.info("Connected to network")
160                         self._cancel_delayed_disconnect()
161                 else:
162                         _moduleLogger.info("Other status: %r" % (status, ))
163
164         def _cancel_delayed_disconnect(self):
165                 if self.__delayedDisconnectEventId is None:
166                         return
167                 _moduleLogger.info("Cancelling auto-log off")
168                 gobject.source_reove(self.__delayedDisconnectEventId)
169                 self.__delayedDisconnectEventId = None
170
171         @gtk_toolbox.log_exception(_moduleLogger)
172         def _on_delayed_disconnect(self):
173                 if not self.session.is_logged_in():
174                         _moduleLogger.info("Received connection change event when not logged in")
175                         return
176                 try:
177                         self._connRef().disconnect()
178                 except Exception:
179                         _moduleLogger.exception("Error durring disconnect")
180                 self._connRef().StatusChanged(
181                         telepathy.CONNECTION_STATUS_DISCONNECTED,
182                         telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
183                 )
184                 self.__delayedDisconnectEventId = None
185                 return False
186