Only auto-unread for conversations
[theonering] / src / autogv.py
1 import logging
2
3 import dbus
4 import telepathy
5
6 try:
7         import conic as _conic
8         conic = _conic
9 except (ImportError, OSError):
10         conic = None
11
12 try:
13         import osso as _osso
14         osso = _osso
15 except (ImportError, OSError):
16         osso = None
17
18 import constants
19 import util.coroutines as coroutines
20 import util.go_utils as gobject_utils
21 import util.tp_utils as telepathy_utils
22 import util.misc as misc_utils
23 import gvoice
24
25
26 _moduleLogger = logging.getLogger(__name__)
27
28
29 class NewGVConversations(object):
30
31         def __init__(self, connRef):
32                 self._connRef = connRef
33                 self.__callback = None
34
35         def start(self):
36                 self.__callback = coroutines.func_sink(
37                         coroutines.expand_positional(
38                                 self._on_conversations_updated
39                         )
40                 )
41                 self._connRef().session.voicemails.updateSignalHandler.register_sink(
42                         self.__callback
43                 )
44                 self._connRef().session.texts.updateSignalHandler.register_sink(
45                         self.__callback
46                 )
47
48         def stop(self):
49                 if self.__callback is None:
50                         _moduleLogger.info("New conversation monitor stopped without starting")
51                         return
52                 self._connRef().session.voicemails.updateSignalHandler.unregister_sink(
53                         self.__callback
54                 )
55                 self._connRef().session.texts.updateSignalHandler.unregister_sink(
56                         self.__callback
57                 )
58                 self.__callback = None
59
60         @misc_utils.log_exception(_moduleLogger)
61         def _on_conversations_updated(self, conv, conversationIds):
62                 _moduleLogger.debug("Incoming messages from: %r" % (conversationIds, ))
63                 for phoneNumber in conversationIds:
64                         h = self._connRef().get_handle_by_name(telepathy.HANDLE_TYPE_CONTACT, phoneNumber)
65                         # Just let the TextChannel decide whether it should be reported to the user or not
66                         props = self._connRef().generate_props(telepathy.CHANNEL_TYPE_TEXT, h, False)
67                         if self._connRef()._channel_manager.channel_exists(props):
68                                 _moduleLogger.debug("Chat box already open for texting conversation with %s" % phoneNumber)
69                                 continue
70
71                         # Maemo 4.1's RTComm opens a window for a chat regardless if a
72                         # message is received or not, so we need to do some filtering here
73                         mergedConv = conv.get_conversation(phoneNumber)
74                         newConversations = mergedConv.conversations
75                         newConversations = gvoice.conversations.filter_out_read(newConversations)
76                         newConversations = gvoice.conversations.filter_out_self(newConversations)
77                         newConversations = list(newConversations)
78                         if not newConversations:
79                                 _moduleLogger.debug("Not opening chat box for %s, all new messages are either read or from yourself" % phoneNumber)
80                                 continue
81
82                         chan = self._connRef()._channel_manager.channel_for_props(props, signal=True)
83
84
85 class RefreshVoicemail(object):
86
87         def __init__(self, connRef):
88                 self._isStarted = False
89                 self._connRef = connRef
90
91                 self._newChannelSignaller = telepathy_utils.NewChannelSignaller(self._on_new_channel)
92                 self._outstandingRequests = []
93
94         def start(self):
95                 self._newChannelSignaller.start()
96                 self._isStarted = True
97
98         def stop(self):
99                 if not self._isStarted:
100                         _moduleLogger.info("voicemail monitor stopped without starting")
101                         return
102                 _moduleLogger.info("Stopping voicemail refresh")
103                 self._newChannelSignaller.stop()
104
105                 # I don't want to trust whether the cancel happens within the current
106                 # callback or not which could be the deciding factor between invalid
107                 # iterators or infinite loops
108                 localRequests = [r for r in self._outstandingRequests]
109                 for request in localRequests:
110                         localRequests.cancel()
111
112                 self._isStarted = False
113
114         @misc_utils.log_exception(_moduleLogger)
115         def _on_new_channel(self, bus, serviceName, connObjectPath, channelObjectPath, channelType):
116                 if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
117                         return
118
119                 cmName = telepathy_utils.cm_from_path(connObjectPath)
120                 if cmName == constants._telepathy_implementation_name_:
121                         _moduleLogger.debug("Ignoring channels from self to prevent deadlock")
122                         return
123
124                 conn = telepathy.client.Connection(serviceName, connObjectPath)
125                 try:
126                         chan = telepathy.client.Channel(serviceName, channelObjectPath)
127                 except dbus.exceptions.UnknownMethodException:
128                         _moduleLogger.exception("Client might not have implemented a deprecated method")
129                         return
130                 missDetection = telepathy_utils.WasMissedCall(
131                         bus, conn, chan, self._on_missed_call, self._on_error_for_missed
132                 )
133                 self._outstandingRequests.append(missDetection)
134
135         @misc_utils.log_exception(_moduleLogger)
136         def _on_missed_call(self, missDetection):
137                 _moduleLogger.info("Missed a call")
138                 self._connRef().session.voicemailsStateMachine.reset_timers()
139                 self._outstandingRequests.remove(missDetection)
140
141         @misc_utils.log_exception(_moduleLogger)
142         def _on_error_for_missed(self, missDetection, reason):
143                 _moduleLogger.debug("Error: %r claims %r" % (missDetection, reason))
144                 self._outstandingRequests.remove(missDetection)
145
146
147 class AutoAcceptGVCall(object):
148
149         def __init__(self, connRef):
150                 self._connRef = connRef
151                 self._isStarted = False
152                 self._incomingCall = False
153                 self._incomingChannel = False
154
155                 self._newChannelSignaller = telepathy_utils.NewChannelSignaller(self._on_new_channel)
156
157                 self._bus = dbus.SystemBus()
158                 self._bus.add_signal_receiver(
159                         self._on_incoming,
160                         path='/com/nokia/csd/call',
161                         dbus_interface='com.nokia.csd.Call',
162                         signal_name='Coming'
163                 )
164                 self._callObject = self._bus.get_object('com.nokia.csd.Call', '/com/nokia/csd/call/1')
165                 self._callInstance = dbus.Interface(self._callObject, 'com.nokia.csd.Call.Instance')
166
167         def start(self):
168                 self._newChannelSignaller.start()
169                 self._isStarted = True
170
171         def stop(self):
172                 if not self._isStarted:
173                         _moduleLogger.info("auto-accept monitor stopped without starting")
174                         return
175                 _moduleLogger.info("Stopping auto-accepting")
176                 self._newChannelSignaller.stop()
177
178                 self._incomingCall = False
179                 self._incomingChannel = False
180                 self._isStarted = False
181
182         @misc_utils.log_exception(_moduleLogger)
183         def _on_new_channel(self, bus, serviceName, connObjectPath, channelObjectPath, channelType):
184                 if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
185                         return
186
187                 cmName = telepathy_utils.cm_from_path(connObjectPath)
188                 if cmName == constants._telepathy_implementation_name_:
189                         _moduleLogger.debug("Ignoring channels from self to prevent deadlock")
190                         return
191
192                 conn = telepathy.client.Connection(serviceName, connObjectPath)
193                 try:
194                         chan = telepathy.client.Channel(serviceName, channelObjectPath)
195                 except dbus.exceptions.UnknownMethodException:
196                         _moduleLogger.exception("Client might not have implemented a deprecated method")
197                         return
198
199                 chan[telepathy.interfaces.CHANNEL].connect_to_signal(
200                         "Closed",
201                         self._on_closed,
202                 )
203
204                 self._incomingChannel = True
205                 self._accept_if_ready()
206
207         @misc_utils.log_exception(_moduleLogger)
208         def _on_incoming(self, objPath, callerNumber):
209                 if self._isStarted:
210                         self._incomingCall = True
211                         self._accept_if_ready()
212
213         @misc_utils.log_exception(_moduleLogger)
214         def _on_closed(self):
215                 self._incomingCall = False
216                 self._incomingChannel = False
217
218
219 class TimedDisconnect(object):
220
221         def __init__(self, connRef):
222                 self._connRef = connRef
223                 self.__delayedDisconnect = gobject_utils.Timeout(self._on_delayed_disconnect)
224
225         def start(self):
226                 self.__delayedDisconnect.start(seconds=60)
227
228         def stop(self):
229                 self.__delayedDisconnect.cancel()
230
231         @misc_utils.log_exception(_moduleLogger)
232         def _on_delayed_disconnect(self):
233                 _moduleLogger.info("Timed disconnect occurred")
234                 self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
235
236
237 class AutoDisconnect(object):
238
239         def __init__(self, connRef):
240                 self._connRef = connRef
241                 if conic is not None:
242                         self.__connection = conic.Connection()
243                 else:
244                         self.__connection = None
245
246                 self.__connectionEventId = None
247                 self.__delayedDisconnect = gobject_utils.Timeout(self._on_delayed_disconnect)
248
249         def start(self):
250                 if self.__connection is not None:
251                         self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
252
253         def stop(self):
254                 self._cancel_delayed_disconnect()
255
256         @misc_utils.log_exception(_moduleLogger)
257         def _on_connection_change(self, connection, event):
258                 """
259                 @note Maemo specific
260                 """
261                 status = event.get_status()
262                 error = event.get_error()
263                 iap_id = event.get_iap_id()
264                 bearer = event.get_bearer_type()
265
266                 if status == conic.STATUS_DISCONNECTED:
267                         _moduleLogger.info("Disconnected from network, starting countdown to logoff")
268                         self.__delayedDisconnect.start(seconds=5)
269                 elif status == conic.STATUS_CONNECTED:
270                         _moduleLogger.info("Connected to network")
271                         self._cancel_delayed_disconnect()
272                 else:
273                         _moduleLogger.info("Other status: %r" % (status, ))
274
275         @misc_utils.log_exception(_moduleLogger)
276         def _cancel_delayed_disconnect(self):
277                 _moduleLogger.info("Cancelling auto-log off")
278                 self.__delayedDisconnect.cancel()
279
280         @misc_utils.log_exception(_moduleLogger)
281         def _on_delayed_disconnect(self):
282                 if not self._connRef().session.is_logged_in():
283                         _moduleLogger.info("Received connection change event when not logged in")
284                         return
285                 try:
286                         self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
287                 except Exception:
288                         _moduleLogger.exception("Error durring disconnect")
289
290
291 class DisconnectOnShutdown(object):
292         """
293         I'm unsure when I get notified of shutdown or if I have enough time to do
294         anything about it, but thought this might help
295         """
296
297         def __init__(self, connRef):
298                 self._connRef = connRef
299
300                 self._osso = None
301                 self._deviceState = None
302
303         def start(self):
304                 if osso is not None:
305                         self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
306                         self._deviceState = osso.DeviceState(self._osso)
307                         self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
308                 else:
309                         _moduleLogger.warning("No device state support")
310
311         def stop(self):
312                 try:
313                         self._deviceState.close()
314                 except AttributeError:
315                         pass # Either None or close was removed (in Fremantle)
316                 self._deviceState = None
317                 try:
318                         self._osso.close()
319                 except AttributeError:
320                         pass # Either None or close was removed (in Fremantle)
321                 self._osso = None
322
323         @misc_utils.log_exception(_moduleLogger)
324         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
325                 """
326                 @note Hildon specific
327                 """
328                 try:
329                         self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_REQUESTED)
330                 except Exception:
331                         _moduleLogger.exception("Error durring disconnect")
332
333
334 class DelayEnableContactIntegration(object):
335
336         def __init__(self, protocolName):
337                 self.__enableSystemContactSupport = telepathy_utils.EnableSystemContactIntegration(
338                         protocolName
339                 )
340                 self.__delayedEnable = gobject_utils.Async(self._on_delayed_enable)
341
342         def start(self):
343                 self.__delayedEnable.start()
344
345         def stop(self):
346                 self.__delayedEnable.cancel()
347
348         @misc_utils.log_exception(_moduleLogger)
349         def _on_delayed_enable(self):
350                 try:
351                         self.__enableSystemContactSupport.start()
352                 except dbus.DBusException, e:
353                         _moduleLogger.info("Contact integration seems to not be supported (%s)" % e)