Protecting against stopping without starting
[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                 if self.__callback is None:
43                         _moduleLogger.info("New conversation monitor stopped without starting")
44                         return
45                 self._connRef().session.voicemails.updateSignalHandler.unregister_sink(
46                         self.__callback
47                 )
48                 self._connRef().session.texts.updateSignalHandler.unregister_sink(
49                         self.__callback
50                 )
51                 self.__callback = None
52
53         @gtk_toolbox.log_exception(_moduleLogger)
54         def _on_conversations_updated(self, conv, conversationIds):
55                 _moduleLogger.debug("Incoming messages from: %r" % (conversationIds, ))
56                 for phoneNumber in conversationIds:
57                         h = self._connRef().get_handle_by_name(telepathy.HANDLE_TYPE_CONTACT, phoneNumber)
58                         # Just let the TextChannel decide whether it should be reported to the user or not
59                         props = self._connRef().generate_props(telepathy.CHANNEL_TYPE_TEXT, h, False)
60                         if self._connRef()._channel_manager.channel_exists(props):
61                                 continue
62
63                         # Maemo 4.1's RTComm opens a window for a chat regardless if a
64                         # message is received or not, so we need to do some filtering here
65                         mergedConv = conv.get_conversation(phoneNumber)
66                         unreadConvs = [
67                                 conversation
68                                 for conversation in mergedConv.conversations
69                                 if not conversation.isRead and not conversation.isArchived
70                         ]
71                         if not unreadConvs:
72                                 continue
73
74                         chan = self._connRef()._channel_manager.channel_for_props(props, signal=True)
75
76
77 class RefreshVoicemail(object):
78
79         def __init__(self, connRef):
80                 self._connRef = connRef
81                 self._newChannelSignaller = telepathy_utils.NewChannelSignaller(self._on_new_channel)
82                 self._outstandingRequests = []
83                 self._isStarted = False
84
85         def start(self):
86                 self._newChannelSignaller.start()
87                 self._isStarted = True
88
89         def stop(self):
90                 if not self._isStarted:
91                         _moduleLogger.info("voicemail monitor stopped without starting")
92                         return
93                 _moduleLogger.info("Stopping voicemail refresh")
94                 self._newChannelSignaller.stop()
95
96                 # I don't want to trust whether the cancel happens within the current
97                 # callback or not which could be the deciding factor between invalid
98                 # iterators or infinite loops
99                 localRequests = [r for r in self._outstandingRequests]
100                 for request in localRequests:
101                         localRequests.cancel()
102
103                 self._isStarted = False
104
105         @gtk_toolbox.log_exception(_moduleLogger)
106         def _on_new_channel(self, bus, serviceName, connObjectPath, channelObjectPath, channelType):
107                 if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
108                         return
109
110                 cmName = telepathy_utils.cm_from_path(connObjectPath)
111                 if cmName == constants._telepathy_implementation_name_:
112                         _moduleLogger.debug("Ignoring channels from self to prevent deadlock")
113                         return
114
115                 conn = telepathy.client.Connection(serviceName, connObjectPath)
116                 chan = telepathy.client.Channel(serviceName, channelObjectPath)
117                 missDetection = telepathy_utils.WasMissedCall(
118                         bus, conn, chan, self._on_missed_call, self._on_error_for_missed
119                 )
120                 self._outstandingRequests.append(missDetection)
121
122         @gtk_toolbox.log_exception(_moduleLogger)
123         def _on_missed_call(self, missDetection):
124                 _moduleLogger.info("Missed a call")
125                 self._connRef().session.voicemailsStateMachine.reset_timers()
126                 self._outstandingRequests.remove(missDetection)
127
128         @gtk_toolbox.log_exception(_moduleLogger)
129         def _on_error_for_missed(self, missDetection, reason):
130                 _moduleLogger.debug("Error: %r claims %r" % (missDetection, reason))
131                 self._outstandingRequests.remove(missDetection)
132
133
134 class AutoDisconnect(object):
135
136         def __init__(self, connRef):
137                 self._connRef = connRef
138                 if conic is not None:
139                         self.__connection = conic.Connection()
140                 else:
141                         self.__connection = None
142
143                 self.__connectionEventId = None
144                 self.__delayedDisconnectEventId = None
145
146         def start(self):
147                 if self.__connection is not None:
148                         self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
149
150         def stop(self):
151                 self._cancel_delayed_disconnect()
152
153         @gtk_toolbox.log_exception(_moduleLogger)
154         def _on_connection_change(self, connection, event):
155                 """
156                 @note Maemo specific
157                 """
158                 status = event.get_status()
159                 error = event.get_error()
160                 iap_id = event.get_iap_id()
161                 bearer = event.get_bearer_type()
162
163                 if status == conic.STATUS_DISCONNECTED:
164                         _moduleLogger.info("Disconnected from network, starting countdown to logoff")
165                         self.__delayedDisconnectEventId = gobject_utils.timeout_add_seconds(
166                                 5, self._on_delayed_disconnect
167                         )
168                 elif status == conic.STATUS_CONNECTED:
169                         _moduleLogger.info("Connected to network")
170                         self._cancel_delayed_disconnect()
171                 else:
172                         _moduleLogger.info("Other status: %r" % (status, ))
173
174         def _cancel_delayed_disconnect(self):
175                 if self.__delayedDisconnectEventId is None:
176                         return
177                 _moduleLogger.info("Cancelling auto-log off")
178                 gobject.source_reove(self.__delayedDisconnectEventId)
179                 self.__delayedDisconnectEventId = None
180
181         @gtk_toolbox.log_exception(_moduleLogger)
182         def _on_delayed_disconnect(self):
183                 if not self.session.is_logged_in():
184                         _moduleLogger.info("Received connection change event when not logged in")
185                         return
186                 try:
187                         self._connRef().disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
188                 except Exception:
189                         _moduleLogger.exception("Error durring disconnect")
190                 self.__delayedDisconnectEventId = None
191                 return False
192