BROKEN: Moved everything
[gc-dialer] / dialcentral / alarm_handler.py
diff --git a/dialcentral/alarm_handler.py b/dialcentral/alarm_handler.py
new file mode 100644 (file)
index 0000000..a79f992
--- /dev/null
@@ -0,0 +1,460 @@
+#!/usr/bin/env python
+
+import os
+import time
+import datetime
+import ConfigParser
+import logging
+
+import util.qt_compat as qt_compat
+QtCore = qt_compat.QtCore
+import dbus
+
+
+_FREMANTLE_ALARM = "Fremantle"
+_DIABLO_ALARM = "Diablo"
+_NO_ALARM = "None"
+
+
+try:
+       import alarm
+       ALARM_TYPE = _FREMANTLE_ALARM
+except (ImportError, OSError):
+       try:
+               import osso.alarmd as alarmd
+               ALARM_TYPE = _DIABLO_ALARM
+       except (ImportError, OSError):
+               ALARM_TYPE = _NO_ALARM
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+def _get_start_time(recurrence):
+       now = datetime.datetime.now()
+       startTimeMinute = now.minute + max(recurrence, 5) # being safe
+       startTimeHour = now.hour + int(startTimeMinute / 60)
+       startTimeMinute = startTimeMinute % 59
+       now.replace(minute=startTimeMinute)
+       timestamp = int(time.mktime(now.timetuple()))
+       return timestamp
+
+
+def _create_recurrence_mask(recurrence, base):
+       """
+       >>> bin(_create_recurrence_mask(60, 60))
+       '0b1'
+       >>> bin(_create_recurrence_mask(30, 60))
+       '0b1000000000000000000000000000001'
+       >>> bin(_create_recurrence_mask(2, 60))
+       '0b10101010101010101010101010101010101010101010101010101010101'
+       >>> bin(_create_recurrence_mask(1, 60))
+       '0b111111111111111111111111111111111111111111111111111111111111'
+       """
+       mask = 0
+       for i in xrange(base / recurrence):
+               mask |= 1 << (recurrence * i)
+       return mask
+
+
+def _unpack_minutes(recurrence):
+       """
+       >>> _unpack_minutes(0)
+       (0, 0, 0)
+       >>> _unpack_minutes(1)
+       (0, 0, 1)
+       >>> _unpack_minutes(59)
+       (0, 0, 59)
+       >>> _unpack_minutes(60)
+       (0, 1, 0)
+       >>> _unpack_minutes(129)
+       (0, 2, 9)
+       >>> _unpack_minutes(5 * 60 * 24 + 3 * 60 + 2)
+       (5, 3, 2)
+       >>> _unpack_minutes(12 * 60 * 24 + 3 * 60 + 2)
+       (5, 3, 2)
+       """
+       minutesInAnHour = 60
+       minutesInDay = 24 * minutesInAnHour
+       minutesInAWeek = minutesInDay * 7
+
+       days = recurrence / minutesInDay
+       daysOfWeek = days % 7
+       recurrence -= days * minutesInDay
+       hours = recurrence / minutesInAnHour
+       recurrence -= hours * minutesInAnHour
+       mins = recurrence % minutesInAnHour
+       recurrence -= mins
+       assert recurrence == 0, "Recurrence %d" % recurrence
+       return daysOfWeek, hours, mins
+
+
+class _FremantleAlarmHandler(object):
+
+       _INVALID_COOKIE = -1
+       _REPEAT_FOREVER = -1
+       _TITLE = "Dialcentral Notifications"
+       _LAUNCHER = os.path.abspath(os.path.join(os.path.dirname(__file__), "alarm_notify.py"))
+
+       def __init__(self):
+               self._recurrence = 5
+
+               self._alarmCookie = self._INVALID_COOKIE
+               self._launcher = self._LAUNCHER
+
+       def load_settings(self, config, sectionName):
+               try:
+                       self._recurrence = config.getint(sectionName, "recurrence")
+                       self._alarmCookie = config.getint(sectionName, "alarmCookie")
+                       launcher = config.get(sectionName, "notifier")
+                       if launcher:
+                               self._launcher = launcher
+               except ConfigParser.NoOptionError:
+                       pass
+               except ConfigParser.NoSectionError:
+                       pass
+
+       def save_settings(self, config, sectionName):
+               try:
+                       config.set(sectionName, "recurrence", str(self._recurrence))
+                       config.set(sectionName, "alarmCookie", str(self._alarmCookie))
+                       launcher = self._launcher if self._launcher != self._LAUNCHER else ""
+                       config.set(sectionName, "notifier", launcher)
+               except ConfigParser.NoOptionError:
+                       pass
+               except ConfigParser.NoSectionError:
+                       pass
+
+       def apply_settings(self, enabled, recurrence):
+               if recurrence != self._recurrence or enabled != self.isEnabled:
+                       if self.isEnabled:
+                               self._clear_alarm()
+                       if enabled:
+                               self._set_alarm(recurrence)
+               self._recurrence = int(recurrence)
+
+       @property
+       def recurrence(self):
+               return self._recurrence
+
+       @property
+       def isEnabled(self):
+               return self._alarmCookie != self._INVALID_COOKIE
+
+       def _set_alarm(self, recurrenceMins):
+               assert 1 <= recurrenceMins, "Notifications set to occur too frequently: %d" % recurrenceMins
+               alarmTime = _get_start_time(recurrenceMins)
+
+               event = alarm.Event()
+               event.appid = self._TITLE
+               event.alarm_time = alarmTime
+               event.recurrences_left = self._REPEAT_FOREVER
+
+               action = event.add_actions(1)[0]
+               action.flags |= alarm.ACTION_TYPE_EXEC | alarm.ACTION_WHEN_TRIGGERED
+               action.command = self._launcher
+
+               recurrence = event.add_recurrences(1)[0]
+               recurrence.mask_min |= _create_recurrence_mask(recurrenceMins, 60)
+               recurrence.mask_hour |= alarm.RECUR_HOUR_DONTCARE
+               recurrence.mask_mday |= alarm.RECUR_MDAY_DONTCARE
+               recurrence.mask_wday |= alarm.RECUR_WDAY_DONTCARE
+               recurrence.mask_mon |= alarm.RECUR_MON_DONTCARE
+               recurrence.special |= alarm.RECUR_SPECIAL_NONE
+
+               assert event.is_sane()
+               self._alarmCookie = alarm.add_event(event)
+
+       def _clear_alarm(self):
+               if self._alarmCookie == self._INVALID_COOKIE:
+                       return
+               alarm.delete_event(self._alarmCookie)
+               self._alarmCookie = self._INVALID_COOKIE
+
+
+class _DiabloAlarmHandler(object):
+
+       _INVALID_COOKIE = -1
+       _TITLE = "Dialcentral Notifications"
+       _LAUNCHER = os.path.abspath(os.path.join(os.path.dirname(__file__), "alarm_notify.py"))
+       _REPEAT_FOREVER = -1
+
+       def __init__(self):
+               self._recurrence = 5
+
+               bus = dbus.SystemBus()
+               self._alarmdDBus = bus.get_object("com.nokia.alarmd", "/com/nokia/alarmd");
+               self._alarmCookie = self._INVALID_COOKIE
+               self._launcher = self._LAUNCHER
+
+       def load_settings(self, config, sectionName):
+               try:
+                       self._recurrence = config.getint(sectionName, "recurrence")
+                       self._alarmCookie = config.getint(sectionName, "alarmCookie")
+                       launcher = config.get(sectionName, "notifier")
+                       if launcher:
+                               self._launcher = launcher
+               except ConfigParser.NoOptionError:
+                       pass
+               except ConfigParser.NoSectionError:
+                       pass
+
+       def save_settings(self, config, sectionName):
+               config.set(sectionName, "recurrence", str(self._recurrence))
+               config.set(sectionName, "alarmCookie", str(self._alarmCookie))
+               launcher = self._launcher if self._launcher != self._LAUNCHER else ""
+               config.set(sectionName, "notifier", launcher)
+
+       def apply_settings(self, enabled, recurrence):
+               if recurrence != self._recurrence or enabled != self.isEnabled:
+                       if self.isEnabled:
+                               self._clear_alarm()
+                       if enabled:
+                               self._set_alarm(recurrence)
+               self._recurrence = int(recurrence)
+
+       @property
+       def recurrence(self):
+               return self._recurrence
+
+       @property
+       def isEnabled(self):
+               return self._alarmCookie != self._INVALID_COOKIE
+
+       def _set_alarm(self, recurrence):
+               assert 1 <= recurrence, "Notifications set to occur too frequently: %d" % recurrence
+               alarmTime = _get_start_time(recurrence)
+
+               #Setup the alarm arguments so that they can be passed to the D-Bus add_event method
+               _DEFAULT_FLAGS = (
+                       alarmd.ALARM_EVENT_NO_DIALOG |
+                       alarmd.ALARM_EVENT_NO_SNOOZE |
+                       alarmd.ALARM_EVENT_CONNECTED
+               )
+               action = []
+               action.extend(['flags', _DEFAULT_FLAGS])
+               action.extend(['title', self._TITLE])
+               action.extend(['path', self._launcher])
+               action.extend([
+                       'arguments',
+                       dbus.Array(
+                               [alarmTime, int(27)],
+                               signature=dbus.Signature('v')
+                       )
+               ])  #int(27) used in place of alarm_index
+
+               event = []
+               event.extend([dbus.ObjectPath('/AlarmdEventRecurring'), dbus.UInt32(4)])
+               event.extend(['action', dbus.ObjectPath('/AlarmdActionExec')])  #use AlarmdActionExec instead of AlarmdActionDbus
+               event.append(dbus.UInt32(len(action) / 2))
+               event.extend(action)
+               event.extend(['time', dbus.Int64(alarmTime)])
+               event.extend(['recurr_interval', dbus.UInt32(recurrence)])
+               event.extend(['recurr_count', dbus.Int32(self._REPEAT_FOREVER)])
+
+               self._alarmCookie = self._alarmdDBus.add_event(*event);
+
+       def _clear_alarm(self):
+               if self._alarmCookie == self._INVALID_COOKIE:
+                       return
+               deleteResult = self._alarmdDBus.del_event(dbus.Int32(self._alarmCookie))
+               self._alarmCookie = self._INVALID_COOKIE
+               assert deleteResult != -1, "Deleting of alarm event failed"
+
+
+class _ApplicationAlarmHandler(object):
+
+       _REPEAT_FOREVER = -1
+       _MIN_TO_MS_FACTORY = 1000 * 60
+
+       def __init__(self):
+               self._timer = QtCore.QTimer()
+               self._timer.setSingleShot(False)
+               self._timer.setInterval(5 * self._MIN_TO_MS_FACTORY)
+
+       def load_settings(self, config, sectionName):
+               try:
+                       self._timer.setInterval(config.getint(sectionName, "recurrence") * self._MIN_TO_MS_FACTORY)
+               except ConfigParser.NoOptionError:
+                       pass
+               except ConfigParser.NoSectionError:
+                       pass
+               self._timer.start()
+
+       def save_settings(self, config, sectionName):
+               config.set(sectionName, "recurrence", str(self.recurrence))
+
+       def apply_settings(self, enabled, recurrence):
+               self._timer.setInterval(recurrence * self._MIN_TO_MS_FACTORY)
+               if enabled:
+                       self._timer.start()
+               else:
+                       self._timer.stop()
+
+       @property
+       def notifySignal(self):
+               return self._timer.timeout
+
+       @property
+       def recurrence(self):
+               return int(self._timer.interval() / self._MIN_TO_MS_FACTORY)
+
+       @property
+       def isEnabled(self):
+               return self._timer.isActive()
+
+
+class _NoneAlarmHandler(object):
+
+       def __init__(self):
+               self._enabled = False
+               self._recurrence = 5
+
+       def load_settings(self, config, sectionName):
+               try:
+                       self._recurrence = config.getint(sectionName, "recurrence")
+                       self._enabled = True
+               except ConfigParser.NoOptionError:
+                       pass
+               except ConfigParser.NoSectionError:
+                       pass
+
+       def save_settings(self, config, sectionName):
+               config.set(sectionName, "recurrence", str(self.recurrence))
+
+       def apply_settings(self, enabled, recurrence):
+               self._enabled = enabled
+
+       @property
+       def recurrence(self):
+               return self._recurrence
+
+       @property
+       def isEnabled(self):
+               return self._enabled
+
+
+_BACKGROUND_ALARM_FACTORY = {
+       _FREMANTLE_ALARM: _FremantleAlarmHandler,
+       _DIABLO_ALARM: _DiabloAlarmHandler,
+       _NO_ALARM: None,
+}[ALARM_TYPE]
+
+
+class AlarmHandler(object):
+
+       ALARM_NONE = "No Alert"
+       ALARM_BACKGROUND = "Background Alert"
+       ALARM_APPLICATION = "Application Alert"
+       ALARM_TYPES = [ALARM_NONE, ALARM_BACKGROUND, ALARM_APPLICATION]
+
+       ALARM_FACTORY = {
+               ALARM_NONE: _NoneAlarmHandler,
+               ALARM_BACKGROUND: _BACKGROUND_ALARM_FACTORY,
+               ALARM_APPLICATION: _ApplicationAlarmHandler,
+       }
+
+       def __init__(self):
+               self._alarms = {self.ALARM_NONE: _NoneAlarmHandler()}
+               self._currentAlarmType = self.ALARM_NONE
+
+       def load_settings(self, config, sectionName):
+               try:
+                       self._currentAlarmType = config.get(sectionName, "alarm")
+               except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+                       _moduleLogger.exception("Falling back to old style")
+                       self._currentAlarmType = self.ALARM_BACKGROUND
+               if self._currentAlarmType not in self.ALARM_TYPES:
+                       self._currentAlarmType = self.ALARM_NONE
+
+               self._init_alarm(self._currentAlarmType)
+               if self._currentAlarmType in self._alarms:
+                       self._alarms[self._currentAlarmType].load_settings(config, sectionName)
+                       if not self._alarms[self._currentAlarmType].isEnabled:
+                               _moduleLogger.info("Config file lied, not actually enabled")
+                               self._currentAlarmType = self.ALARM_NONE
+               else:
+                       _moduleLogger.info("Background alerts not supported")
+                       self._currentAlarmType = self.ALARM_NONE
+
+       def save_settings(self, config, sectionName):
+               config.set(sectionName, "alarm", self._currentAlarmType)
+               self._alarms[self._currentAlarmType].save_settings(config, sectionName)
+
+       def apply_settings(self, t, recurrence):
+               self._init_alarm(t)
+               newHandler = self._alarms[t]
+               oldHandler = self._alarms[self._currentAlarmType]
+               if newHandler != oldHandler:
+                       oldHandler.apply_settings(False, 0)
+               newHandler.apply_settings(True, recurrence)
+               self._currentAlarmType = t
+
+       @property
+       def alarmType(self):
+               return self._currentAlarmType
+
+       @property
+       def backgroundNotificationsSupported(self):
+               return self.ALARM_FACTORY[self.ALARM_BACKGROUND] is not None
+
+       @property
+       def applicationNotifySignal(self):
+               self._init_alarm(self.ALARM_APPLICATION)
+               return self._alarms[self.ALARM_APPLICATION].notifySignal
+
+       @property
+       def recurrence(self):
+               return self._alarms[self._currentAlarmType].recurrence
+
+       @property
+       def isEnabled(self):
+               return self._currentAlarmType != self.ALARM_NONE
+
+       def _init_alarm(self, t):
+               if t not in self._alarms and self.ALARM_FACTORY[t] is not None:
+                       self._alarms[t] = self.ALARM_FACTORY[t]()
+
+
+def main():
+       logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
+       logging.basicConfig(level=logging.DEBUG, format=logFormat)
+       import constants
+       try:
+               import optparse
+       except ImportError:
+               return
+
+       parser = optparse.OptionParser()
+       parser.add_option("-x", "--display", action="store_true", dest="display", help="Display data")
+       parser.add_option("-e", "--enable", action="store_true", dest="enabled", help="Whether the alarm should be enabled or not", default=False)
+       parser.add_option("-d", "--disable", action="store_false", dest="enabled", help="Whether the alarm should be enabled or not", default=False)
+       parser.add_option("-r", "--recurrence", action="store", type="int", dest="recurrence", help="How often the alarm occurs", default=5)
+       (commandOptions, commandArgs) = parser.parse_args()
+
+       alarmHandler = AlarmHandler()
+       config = ConfigParser.SafeConfigParser()
+       config.read(constants._user_settings_)
+       alarmHandler.load_settings(config, "alarm")
+
+       if commandOptions.display:
+               print "Alarm (%s) is %s for every %d minutes" % (
+                       alarmHandler._alarmCookie,
+                       "enabled" if alarmHandler.isEnabled else "disabled",
+                       alarmHandler.recurrence,
+               )
+       else:
+               isEnabled = commandOptions.enabled
+               recurrence = commandOptions.recurrence
+               alarmHandler.apply_settings(isEnabled, recurrence)
+
+               alarmHandler.save_settings(config, "alarm")
+               configFile = open(constants._user_settings_, "wb")
+               try:
+                       config.write(configFile)
+               finally:
+                       configFile.close()
+
+
+if __name__ == "__main__":
+       main()