d18b28b221aa629398eaa767b6264c9bb509f308
[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 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 gtk_toolbox
23
24
25 _moduleLogger = logging.getLogger("autogv")
26
27
28 class NewGVConversations(object):
29
30         def __init__(self, connRef):
31                 self._connRef = connRef
32                 self.__callback = None
33
34         def start(self):
35                 self.__callback = coroutines.func_sink(
36                         coroutines.expand_positional(
37                                 self._on_conversations_updated
38                         )
39                 )
40                 self._connRef().session.voicemails.updateSignalHandler.register_sink(
41                         self.__callback
42                 )
43                 self._connRef().session.texts.updateSignalHandler.register_sink(
44                         self.__callback
45                 )
46
47         def stop(self):
48                 if self.__callback is None:
49                         _moduleLogger.info("New conversation monitor stopped without starting")
50                         return
51                 self._connRef().session.voicemails.updateSignalHandler.unregister_sink(
52                         self.__callback
53                 )
54                 self._connRef().session.texts.updateSignalHandler.unregister_sink(
55                         self.__callback
56                 )
57                 self.__callback = None
58
59         @gtk_toolbox.log_exception(_moduleLogger)
60         def _on_conversations_updated(self, conv, conversationIds):
61                 _moduleLogger.debug("Incoming messages from: %r" % (conversationIds, ))
62                 for phoneNumber in conversationIds:
63                         h = self._connRef().get_handle_by_name(telepathy.HANDLE_TYPE_CONTACT, phoneNumber)
64                         # Just let the TextChannel decide whether it should be reported to the user or not
65                         props = self._connRef().generate_props(telepathy.CHANNEL_TYPE_TEXT, h, False)
66                         if self._connRef()._channel_manager.channel_exists(props):
67                                 continue
68
69                         # Maemo 4.1's RTComm opens a window for a chat regardless if a
70                         # message is received or not, so we need to do some filtering here
71                         mergedConv = conv.get_conversation(phoneNumber)
72                         unreadConvs = [
73                                 conversation
74                                 for conversation in mergedConv.conversations
75                                 if not conversation.isRead and not conversation.isArchived
76                         ]
77                         if not unreadConvs:
78                                 continue
79
80                         chan = self._connRef()._channel_manager.channel_for_props(props, signal=True)
81
82
83 class RefreshVoicemail(object):
84
85         def __init__(self, connRef):
86                 self._connRef = connRef
87                 self._newChannelSignaller = telepathy_utils.NewChannelSignaller(self._on_new_channel)
88                 self._outstandingRequests = []
89                 self._isStarted = False
90
91         def start(self):
92                 self._newChannelSignaller.start()
93                 self._isStarted = True
94
95         def stop(self):
96                 if not self._isStarted:
97                         _moduleLogger.info("voicemail monitor stopped without starting")
98                         return
99                 _moduleLogger.info("Stopping voicemail refresh")
100                 self._newChannelSignaller.stop()
101
102                 # I don't want to trust whether the cancel happens within the current
103                 # callback or not which could be the deciding factor between invalid
104                 # iterators or infinite loops
105                 localRequests = [r for r in self._outstandingRequests]
106                 for request in localRequests:
107                         localRequests.cancel()
108
109                 self._isStarted = False
110
111         @gtk_toolbox.log_exception(_moduleLogger)
112         def _on_new_channel(self, bus, serviceName, connObjectPath, channelObjectPath, channelType):
113                 if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
114                         return
115
116                 cmName = telepathy_utils.cm_from_path(connObjectPath)
117                 if cmName == constants._telepathy_implementation_name_:
118                         _moduleLogger.debug("Ignoring channels from self to prevent deadlock")
119                         return
120
121                 conn = telepathy.client.Connection(serviceName, connObjectPath)
122                 chan = telepathy.client.Channel(serviceName, channelObjectPath)
123                 missDetection = telepathy_utils.WasMissedCall(
124                         bus, conn, chan, self._on_missed_call, self._on_error_for_missed
125                 )
126                 self._outstandingRequests.append(missDetection)
127
128         @gtk_toolbox.log_exception(_moduleLogger)
129         def _on_missed_call(self, missDetection):
130                 _moduleLogger.info("Missed a call")
131                 self._connRef().session.voicemailsStateMachine.reset_timers()
132                 self._outstandingRequests.remove(missDetection)
133
134         @gtk_toolbox.log_exception(_moduleLogger)
135         def _on_error_for_missed(self, missDetection, reason):
136                 _moduleLogger.debug("Error: %r claims %r" % (missDetection, reason))
137                 self._outstandingRequests.remove(missDetection)
138
139
140 class AutoDisconnect(object):
141
142         def __init__(self, connRef):
143                 self._connRef = connRef
144                 if conic is not None:
145                         self.__connection = conic.Connection()
146                 else:
147                         self.__connection = None
148
149                 self.__connectionEventId = None
150                 self.__delayedDisconnectEventId = None
151
152         def start(self):
153                 if self.__connection is not None:
154                         self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
155
156         def stop(self):
157                 self._cancel_delayed_disconnect()
158
159         @gtk_toolbox.log_exception(_moduleLogger)
160         def _on_connection_change(self, connection, event):
161                 """
162                 @note Maemo specific
163                 """
164                 status = event.get_status()
165                 error = event.get_error()
166                 iap_id = event.get_iap_id()
167                 bearer = event.get_bearer_type()
168
169                 if status == conic.STATUS_DISCONNECTED:
170                         _moduleLogger.info("Disconnected from network, starting countdown to logoff")
171                         self.__delayedDisconnectEventId = gobject_utils.timeout_add_seconds(
172                                 5, self._on_delayed_disconnect
173                         )
174                 elif status == conic.STATUS_CONNECTED:
175                         _moduleLogger.info("Connected to network")
176                         self._cancel_delayed_disconnect()
177                 else:
178                         _moduleLogger.info("Other status: %r" % (status, ))
179
180         def _cancel_delayed_disconnect(self):
181                 if self.__delayedDisconnectEventId is None:
182                         return
183                 _moduleLogger.info("Cancelling auto-log off")
184                 gobject.source_reove(self.__delayedDisconnectEventId)
185                 self.__delayedDisconnectEventId = None
186
187         @gtk_toolbox.log_exception(_moduleLogger)
188         def _on_delayed_disconnect(self):
189                 if not self.session.is_logged_in():
190                         _moduleLogger.info("Received connection change event when not logged in")
191                         return
192                 try:
193                         self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
194                 except Exception:
195                         _moduleLogger.exception("Error durring disconnect")
196                 self.__delayedDisconnectEventId = None
197                 return False
198
199
200 class DisconnectOnShutdown(object):
201         """
202         I'm unsure when I get notified of shutdown or if I have enough time to do
203         anything about it, but thought this might help
204         """
205
206         def __init__(self, connRef):
207                 self._connRef = connRef
208
209                 self._osso = None
210                 self._deviceState = None
211
212         def start(self):
213                 if osso is not None:
214                         self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
215                         self._deviceState = osso.DeviceState(self._osso)
216                         self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
217                 else:
218                         _moduleLogger.warning("No device state support")
219
220         def stop(self):
221                 try:
222                         self._deviceState.close()
223                 except AttributeError:
224                         pass # Either None or close was removed (in Fremantle)
225                 self._deviceState = None
226                 try:
227                         self._osso.close()
228                 except AttributeError:
229                         pass # Either None or close was removed (in Fremantle)
230                 self._osso = None
231
232         @gtk_toolbox.log_exception(_moduleLogger)
233         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
234                 """
235                 @note Hildon specific
236                 """
237                 try:
238                         self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_REQUEST)
239                 except Exception:
240                         _moduleLogger.exception("Error durring disconnect")