Unsure when this got introduced, how strange
[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                                 continue
69
70                         # Maemo 4.1's RTComm opens a window for a chat regardless if a
71                         # message is received or not, so we need to do some filtering here
72                         mergedConv = conv.get_conversation(phoneNumber)
73                         newConversations = mergedConv.conversations
74                         newConversations = gvoice.conversations.filter_out_read(newConversations)
75                         newConversations = gvoice.conversations.filter_out_self(newConversations)
76                         newConversations = list(newConversations)
77                         if not newConversations:
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         @misc_utils.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                 try:
123                         chan = telepathy.client.Channel(serviceName, channelObjectPath)
124                 except dbus.exceptions.UnknownMethodException:
125                         _moduleLogger.exception("Client might not have implemented a deprecated method")
126                         return
127                 missDetection = telepathy_utils.WasMissedCall(
128                         bus, conn, chan, self._on_missed_call, self._on_error_for_missed
129                 )
130                 self._outstandingRequests.append(missDetection)
131
132         @misc_utils.log_exception(_moduleLogger)
133         def _on_missed_call(self, missDetection):
134                 _moduleLogger.info("Missed a call")
135                 self._connRef().session.voicemailsStateMachine.reset_timers()
136                 self._outstandingRequests.remove(missDetection)
137
138         @misc_utils.log_exception(_moduleLogger)
139         def _on_error_for_missed(self, missDetection, reason):
140                 _moduleLogger.debug("Error: %r claims %r" % (missDetection, reason))
141                 self._outstandingRequests.remove(missDetection)
142
143
144 class TimedDisconnect(object):
145
146         def __init__(self, connRef):
147                 self._connRef = connRef
148                 self.__delayedDisconnect = gobject_utils.Timeout(self._on_delayed_disconnect)
149
150         def start(self):
151                 self.__delayedDisconnect.start(seconds=20)
152
153         def stop(self):
154                 self.__delayedDisconnect.cancel()
155
156         @misc_utils.log_exception(_moduleLogger)
157         def _on_delayed_disconnect(self):
158                 _moduleLogger.info("Timed disconnect occurred")
159                 self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
160
161
162 class AutoDisconnect(object):
163
164         def __init__(self, connRef):
165                 self._connRef = connRef
166                 if conic is not None:
167                         self.__connection = conic.Connection()
168                 else:
169                         self.__connection = None
170
171                 self.__connectionEventId = None
172                 self.__delayedDisconnect = gobject_utils.Timeout(self._on_delayed_disconnect)
173
174         def start(self):
175                 if self.__connection is not None:
176                         self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
177
178         def stop(self):
179                 self._cancel_delayed_disconnect()
180
181         @misc_utils.log_exception(_moduleLogger)
182         def _on_connection_change(self, connection, event):
183                 """
184                 @note Maemo specific
185                 """
186                 status = event.get_status()
187                 error = event.get_error()
188                 iap_id = event.get_iap_id()
189                 bearer = event.get_bearer_type()
190
191                 if status == conic.STATUS_DISCONNECTED:
192                         _moduleLogger.info("Disconnected from network, starting countdown to logoff")
193                         self.__delayedDisconnect.start(seconds=5)
194                 elif status == conic.STATUS_CONNECTED:
195                         _moduleLogger.info("Connected to network")
196                         self._cancel_delayed_disconnect()
197                 else:
198                         _moduleLogger.info("Other status: %r" % (status, ))
199
200         @misc_utils.log_exception(_moduleLogger)
201         def _cancel_delayed_disconnect(self):
202                 _moduleLogger.info("Cancelling auto-log off")
203                 self.__delayedDisconnect.cancel()
204
205         @misc_utils.log_exception(_moduleLogger)
206         def _on_delayed_disconnect(self):
207                 if not self._connRef().session.is_logged_in():
208                         _moduleLogger.info("Received connection change event when not logged in")
209                         return
210                 try:
211                         self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
212                 except Exception:
213                         _moduleLogger.exception("Error durring disconnect")
214
215
216 class DisconnectOnShutdown(object):
217         """
218         I'm unsure when I get notified of shutdown or if I have enough time to do
219         anything about it, but thought this might help
220         """
221
222         def __init__(self, connRef):
223                 self._connRef = connRef
224
225                 self._osso = None
226                 self._deviceState = None
227
228         def start(self):
229                 if osso is not None:
230                         self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
231                         self._deviceState = osso.DeviceState(self._osso)
232                         self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
233                 else:
234                         _moduleLogger.warning("No device state support")
235
236         def stop(self):
237                 try:
238                         self._deviceState.close()
239                 except AttributeError:
240                         pass # Either None or close was removed (in Fremantle)
241                 self._deviceState = None
242                 try:
243                         self._osso.close()
244                 except AttributeError:
245                         pass # Either None or close was removed (in Fremantle)
246                 self._osso = None
247
248         @misc_utils.log_exception(_moduleLogger)
249         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
250                 """
251                 @note Hildon specific
252                 """
253                 try:
254                         self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_REQUESTED)
255                 except Exception:
256                         _moduleLogger.exception("Error durring disconnect")
257
258
259 class DelayEnableContactIntegration(object):
260
261         def __init__(self, protocolName):
262                 self.__enableSystemContactSupport = telepathy_utils.EnableSystemContactIntegration(
263                         protocolName
264                 )
265                 self.__delayedEnable = gobject_utils.Async(self._on_delayed_enable)
266
267         def start(self):
268                 self.__delayedEnable.start()
269
270         def stop(self):
271                 self.__delayedEnable.cancel()
272
273         @misc_utils.log_exception(_moduleLogger)
274         def _on_delayed_enable(self):
275                 self.__enableSystemContactSupport.start()