BROKEN: Moved everything
[gc-dialer] / dialcentral / alarm_handler.py
1 #!/usr/bin/env python
2
3 import os
4 import time
5 import datetime
6 import ConfigParser
7 import logging
8
9 import util.qt_compat as qt_compat
10 QtCore = qt_compat.QtCore
11 import dbus
12
13
14 _FREMANTLE_ALARM = "Fremantle"
15 _DIABLO_ALARM = "Diablo"
16 _NO_ALARM = "None"
17
18
19 try:
20         import alarm
21         ALARM_TYPE = _FREMANTLE_ALARM
22 except (ImportError, OSError):
23         try:
24                 import osso.alarmd as alarmd
25                 ALARM_TYPE = _DIABLO_ALARM
26         except (ImportError, OSError):
27                 ALARM_TYPE = _NO_ALARM
28
29
30 _moduleLogger = logging.getLogger(__name__)
31
32
33 def _get_start_time(recurrence):
34         now = datetime.datetime.now()
35         startTimeMinute = now.minute + max(recurrence, 5) # being safe
36         startTimeHour = now.hour + int(startTimeMinute / 60)
37         startTimeMinute = startTimeMinute % 59
38         now.replace(minute=startTimeMinute)
39         timestamp = int(time.mktime(now.timetuple()))
40         return timestamp
41
42
43 def _create_recurrence_mask(recurrence, base):
44         """
45         >>> bin(_create_recurrence_mask(60, 60))
46         '0b1'
47         >>> bin(_create_recurrence_mask(30, 60))
48         '0b1000000000000000000000000000001'
49         >>> bin(_create_recurrence_mask(2, 60))
50         '0b10101010101010101010101010101010101010101010101010101010101'
51         >>> bin(_create_recurrence_mask(1, 60))
52         '0b111111111111111111111111111111111111111111111111111111111111'
53         """
54         mask = 0
55         for i in xrange(base / recurrence):
56                 mask |= 1 << (recurrence * i)
57         return mask
58
59
60 def _unpack_minutes(recurrence):
61         """
62         >>> _unpack_minutes(0)
63         (0, 0, 0)
64         >>> _unpack_minutes(1)
65         (0, 0, 1)
66         >>> _unpack_minutes(59)
67         (0, 0, 59)
68         >>> _unpack_minutes(60)
69         (0, 1, 0)
70         >>> _unpack_minutes(129)
71         (0, 2, 9)
72         >>> _unpack_minutes(5 * 60 * 24 + 3 * 60 + 2)
73         (5, 3, 2)
74         >>> _unpack_minutes(12 * 60 * 24 + 3 * 60 + 2)
75         (5, 3, 2)
76         """
77         minutesInAnHour = 60
78         minutesInDay = 24 * minutesInAnHour
79         minutesInAWeek = minutesInDay * 7
80
81         days = recurrence / minutesInDay
82         daysOfWeek = days % 7
83         recurrence -= days * minutesInDay
84         hours = recurrence / minutesInAnHour
85         recurrence -= hours * minutesInAnHour
86         mins = recurrence % minutesInAnHour
87         recurrence -= mins
88         assert recurrence == 0, "Recurrence %d" % recurrence
89         return daysOfWeek, hours, mins
90
91
92 class _FremantleAlarmHandler(object):
93
94         _INVALID_COOKIE = -1
95         _REPEAT_FOREVER = -1
96         _TITLE = "Dialcentral Notifications"
97         _LAUNCHER = os.path.abspath(os.path.join(os.path.dirname(__file__), "alarm_notify.py"))
98
99         def __init__(self):
100                 self._recurrence = 5
101
102                 self._alarmCookie = self._INVALID_COOKIE
103                 self._launcher = self._LAUNCHER
104
105         def load_settings(self, config, sectionName):
106                 try:
107                         self._recurrence = config.getint(sectionName, "recurrence")
108                         self._alarmCookie = config.getint(sectionName, "alarmCookie")
109                         launcher = config.get(sectionName, "notifier")
110                         if launcher:
111                                 self._launcher = launcher
112                 except ConfigParser.NoOptionError:
113                         pass
114                 except ConfigParser.NoSectionError:
115                         pass
116
117         def save_settings(self, config, sectionName):
118                 try:
119                         config.set(sectionName, "recurrence", str(self._recurrence))
120                         config.set(sectionName, "alarmCookie", str(self._alarmCookie))
121                         launcher = self._launcher if self._launcher != self._LAUNCHER else ""
122                         config.set(sectionName, "notifier", launcher)
123                 except ConfigParser.NoOptionError:
124                         pass
125                 except ConfigParser.NoSectionError:
126                         pass
127
128         def apply_settings(self, enabled, recurrence):
129                 if recurrence != self._recurrence or enabled != self.isEnabled:
130                         if self.isEnabled:
131                                 self._clear_alarm()
132                         if enabled:
133                                 self._set_alarm(recurrence)
134                 self._recurrence = int(recurrence)
135
136         @property
137         def recurrence(self):
138                 return self._recurrence
139
140         @property
141         def isEnabled(self):
142                 return self._alarmCookie != self._INVALID_COOKIE
143
144         def _set_alarm(self, recurrenceMins):
145                 assert 1 <= recurrenceMins, "Notifications set to occur too frequently: %d" % recurrenceMins
146                 alarmTime = _get_start_time(recurrenceMins)
147
148                 event = alarm.Event()
149                 event.appid = self._TITLE
150                 event.alarm_time = alarmTime
151                 event.recurrences_left = self._REPEAT_FOREVER
152
153                 action = event.add_actions(1)[0]
154                 action.flags |= alarm.ACTION_TYPE_EXEC | alarm.ACTION_WHEN_TRIGGERED
155                 action.command = self._launcher
156
157                 recurrence = event.add_recurrences(1)[0]
158                 recurrence.mask_min |= _create_recurrence_mask(recurrenceMins, 60)
159                 recurrence.mask_hour |= alarm.RECUR_HOUR_DONTCARE
160                 recurrence.mask_mday |= alarm.RECUR_MDAY_DONTCARE
161                 recurrence.mask_wday |= alarm.RECUR_WDAY_DONTCARE
162                 recurrence.mask_mon |= alarm.RECUR_MON_DONTCARE
163                 recurrence.special |= alarm.RECUR_SPECIAL_NONE
164
165                 assert event.is_sane()
166                 self._alarmCookie = alarm.add_event(event)
167
168         def _clear_alarm(self):
169                 if self._alarmCookie == self._INVALID_COOKIE:
170                         return
171                 alarm.delete_event(self._alarmCookie)
172                 self._alarmCookie = self._INVALID_COOKIE
173
174
175 class _DiabloAlarmHandler(object):
176
177         _INVALID_COOKIE = -1
178         _TITLE = "Dialcentral Notifications"
179         _LAUNCHER = os.path.abspath(os.path.join(os.path.dirname(__file__), "alarm_notify.py"))
180         _REPEAT_FOREVER = -1
181
182         def __init__(self):
183                 self._recurrence = 5
184
185                 bus = dbus.SystemBus()
186                 self._alarmdDBus = bus.get_object("com.nokia.alarmd", "/com/nokia/alarmd");
187                 self._alarmCookie = self._INVALID_COOKIE
188                 self._launcher = self._LAUNCHER
189
190         def load_settings(self, config, sectionName):
191                 try:
192                         self._recurrence = config.getint(sectionName, "recurrence")
193                         self._alarmCookie = config.getint(sectionName, "alarmCookie")
194                         launcher = config.get(sectionName, "notifier")
195                         if launcher:
196                                 self._launcher = launcher
197                 except ConfigParser.NoOptionError:
198                         pass
199                 except ConfigParser.NoSectionError:
200                         pass
201
202         def save_settings(self, config, sectionName):
203                 config.set(sectionName, "recurrence", str(self._recurrence))
204                 config.set(sectionName, "alarmCookie", str(self._alarmCookie))
205                 launcher = self._launcher if self._launcher != self._LAUNCHER else ""
206                 config.set(sectionName, "notifier", launcher)
207
208         def apply_settings(self, enabled, recurrence):
209                 if recurrence != self._recurrence or enabled != self.isEnabled:
210                         if self.isEnabled:
211                                 self._clear_alarm()
212                         if enabled:
213                                 self._set_alarm(recurrence)
214                 self._recurrence = int(recurrence)
215
216         @property
217         def recurrence(self):
218                 return self._recurrence
219
220         @property
221         def isEnabled(self):
222                 return self._alarmCookie != self._INVALID_COOKIE
223
224         def _set_alarm(self, recurrence):
225                 assert 1 <= recurrence, "Notifications set to occur too frequently: %d" % recurrence
226                 alarmTime = _get_start_time(recurrence)
227
228                 #Setup the alarm arguments so that they can be passed to the D-Bus add_event method
229                 _DEFAULT_FLAGS = (
230                         alarmd.ALARM_EVENT_NO_DIALOG |
231                         alarmd.ALARM_EVENT_NO_SNOOZE |
232                         alarmd.ALARM_EVENT_CONNECTED
233                 )
234                 action = []
235                 action.extend(['flags', _DEFAULT_FLAGS])
236                 action.extend(['title', self._TITLE])
237                 action.extend(['path', self._launcher])
238                 action.extend([
239                         'arguments',
240                         dbus.Array(
241                                 [alarmTime, int(27)],
242                                 signature=dbus.Signature('v')
243                         )
244                 ])  #int(27) used in place of alarm_index
245
246                 event = []
247                 event.extend([dbus.ObjectPath('/AlarmdEventRecurring'), dbus.UInt32(4)])
248                 event.extend(['action', dbus.ObjectPath('/AlarmdActionExec')])  #use AlarmdActionExec instead of AlarmdActionDbus
249                 event.append(dbus.UInt32(len(action) / 2))
250                 event.extend(action)
251                 event.extend(['time', dbus.Int64(alarmTime)])
252                 event.extend(['recurr_interval', dbus.UInt32(recurrence)])
253                 event.extend(['recurr_count', dbus.Int32(self._REPEAT_FOREVER)])
254
255                 self._alarmCookie = self._alarmdDBus.add_event(*event);
256
257         def _clear_alarm(self):
258                 if self._alarmCookie == self._INVALID_COOKIE:
259                         return
260                 deleteResult = self._alarmdDBus.del_event(dbus.Int32(self._alarmCookie))
261                 self._alarmCookie = self._INVALID_COOKIE
262                 assert deleteResult != -1, "Deleting of alarm event failed"
263
264
265 class _ApplicationAlarmHandler(object):
266
267         _REPEAT_FOREVER = -1
268         _MIN_TO_MS_FACTORY = 1000 * 60
269
270         def __init__(self):
271                 self._timer = QtCore.QTimer()
272                 self._timer.setSingleShot(False)
273                 self._timer.setInterval(5 * self._MIN_TO_MS_FACTORY)
274
275         def load_settings(self, config, sectionName):
276                 try:
277                         self._timer.setInterval(config.getint(sectionName, "recurrence") * self._MIN_TO_MS_FACTORY)
278                 except ConfigParser.NoOptionError:
279                         pass
280                 except ConfigParser.NoSectionError:
281                         pass
282                 self._timer.start()
283
284         def save_settings(self, config, sectionName):
285                 config.set(sectionName, "recurrence", str(self.recurrence))
286
287         def apply_settings(self, enabled, recurrence):
288                 self._timer.setInterval(recurrence * self._MIN_TO_MS_FACTORY)
289                 if enabled:
290                         self._timer.start()
291                 else:
292                         self._timer.stop()
293
294         @property
295         def notifySignal(self):
296                 return self._timer.timeout
297
298         @property
299         def recurrence(self):
300                 return int(self._timer.interval() / self._MIN_TO_MS_FACTORY)
301
302         @property
303         def isEnabled(self):
304                 return self._timer.isActive()
305
306
307 class _NoneAlarmHandler(object):
308
309         def __init__(self):
310                 self._enabled = False
311                 self._recurrence = 5
312
313         def load_settings(self, config, sectionName):
314                 try:
315                         self._recurrence = config.getint(sectionName, "recurrence")
316                         self._enabled = True
317                 except ConfigParser.NoOptionError:
318                         pass
319                 except ConfigParser.NoSectionError:
320                         pass
321
322         def save_settings(self, config, sectionName):
323                 config.set(sectionName, "recurrence", str(self.recurrence))
324
325         def apply_settings(self, enabled, recurrence):
326                 self._enabled = enabled
327
328         @property
329         def recurrence(self):
330                 return self._recurrence
331
332         @property
333         def isEnabled(self):
334                 return self._enabled
335
336
337 _BACKGROUND_ALARM_FACTORY = {
338         _FREMANTLE_ALARM: _FremantleAlarmHandler,
339         _DIABLO_ALARM: _DiabloAlarmHandler,
340         _NO_ALARM: None,
341 }[ALARM_TYPE]
342
343
344 class AlarmHandler(object):
345
346         ALARM_NONE = "No Alert"
347         ALARM_BACKGROUND = "Background Alert"
348         ALARM_APPLICATION = "Application Alert"
349         ALARM_TYPES = [ALARM_NONE, ALARM_BACKGROUND, ALARM_APPLICATION]
350
351         ALARM_FACTORY = {
352                 ALARM_NONE: _NoneAlarmHandler,
353                 ALARM_BACKGROUND: _BACKGROUND_ALARM_FACTORY,
354                 ALARM_APPLICATION: _ApplicationAlarmHandler,
355         }
356
357         def __init__(self):
358                 self._alarms = {self.ALARM_NONE: _NoneAlarmHandler()}
359                 self._currentAlarmType = self.ALARM_NONE
360
361         def load_settings(self, config, sectionName):
362                 try:
363                         self._currentAlarmType = config.get(sectionName, "alarm")
364                 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
365                         _moduleLogger.exception("Falling back to old style")
366                         self._currentAlarmType = self.ALARM_BACKGROUND
367                 if self._currentAlarmType not in self.ALARM_TYPES:
368                         self._currentAlarmType = self.ALARM_NONE
369
370                 self._init_alarm(self._currentAlarmType)
371                 if self._currentAlarmType in self._alarms:
372                         self._alarms[self._currentAlarmType].load_settings(config, sectionName)
373                         if not self._alarms[self._currentAlarmType].isEnabled:
374                                 _moduleLogger.info("Config file lied, not actually enabled")
375                                 self._currentAlarmType = self.ALARM_NONE
376                 else:
377                         _moduleLogger.info("Background alerts not supported")
378                         self._currentAlarmType = self.ALARM_NONE
379
380         def save_settings(self, config, sectionName):
381                 config.set(sectionName, "alarm", self._currentAlarmType)
382                 self._alarms[self._currentAlarmType].save_settings(config, sectionName)
383
384         def apply_settings(self, t, recurrence):
385                 self._init_alarm(t)
386                 newHandler = self._alarms[t]
387                 oldHandler = self._alarms[self._currentAlarmType]
388                 if newHandler != oldHandler:
389                         oldHandler.apply_settings(False, 0)
390                 newHandler.apply_settings(True, recurrence)
391                 self._currentAlarmType = t
392
393         @property
394         def alarmType(self):
395                 return self._currentAlarmType
396
397         @property
398         def backgroundNotificationsSupported(self):
399                 return self.ALARM_FACTORY[self.ALARM_BACKGROUND] is not None
400
401         @property
402         def applicationNotifySignal(self):
403                 self._init_alarm(self.ALARM_APPLICATION)
404                 return self._alarms[self.ALARM_APPLICATION].notifySignal
405
406         @property
407         def recurrence(self):
408                 return self._alarms[self._currentAlarmType].recurrence
409
410         @property
411         def isEnabled(self):
412                 return self._currentAlarmType != self.ALARM_NONE
413
414         def _init_alarm(self, t):
415                 if t not in self._alarms and self.ALARM_FACTORY[t] is not None:
416                         self._alarms[t] = self.ALARM_FACTORY[t]()
417
418
419 def main():
420         logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
421         logging.basicConfig(level=logging.DEBUG, format=logFormat)
422         import constants
423         try:
424                 import optparse
425         except ImportError:
426                 return
427
428         parser = optparse.OptionParser()
429         parser.add_option("-x", "--display", action="store_true", dest="display", help="Display data")
430         parser.add_option("-e", "--enable", action="store_true", dest="enabled", help="Whether the alarm should be enabled or not", default=False)
431         parser.add_option("-d", "--disable", action="store_false", dest="enabled", help="Whether the alarm should be enabled or not", default=False)
432         parser.add_option("-r", "--recurrence", action="store", type="int", dest="recurrence", help="How often the alarm occurs", default=5)
433         (commandOptions, commandArgs) = parser.parse_args()
434
435         alarmHandler = AlarmHandler()
436         config = ConfigParser.SafeConfigParser()
437         config.read(constants._user_settings_)
438         alarmHandler.load_settings(config, "alarm")
439
440         if commandOptions.display:
441                 print "Alarm (%s) is %s for every %d minutes" % (
442                         alarmHandler._alarmCookie,
443                         "enabled" if alarmHandler.isEnabled else "disabled",
444                         alarmHandler.recurrence,
445                 )
446         else:
447                 isEnabled = commandOptions.enabled
448                 recurrence = commandOptions.recurrence
449                 alarmHandler.apply_settings(isEnabled, recurrence)
450
451                 alarmHandler.save_settings(config, "alarm")
452                 configFile = open(constants._user_settings_, "wb")
453                 try:
454                         config.write(configFile)
455                 finally:
456                         configFile.close()
457
458
459 if __name__ == "__main__":
460         main()