home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 June / maximum-cd-2009-06.iso / DiscContents / digsby_setup.exe / lib / common / notifications.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-02-26  |  14.9 KB  |  494 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. from __future__ import with_statement
  5. import wx
  6. import os.path as os
  7. from collections import defaultdict
  8. from util import traceguard, dictadd, dictsub, do, try_this, memoize
  9. import util.data_importer as importer
  10. from path import path
  11. from common.slotssavable import SlotsSavable
  12. from os.path import exists as path_exists
  13. from common import profile, pref
  14. from pprint import pformat, pprint
  15. from logging import getLogger
  16. log = getLogger('notifications')
  17. from wx import PyDeadObjectError
  18. PlaySound = wx.Sound.PlaySound
  19. from Queue import Queue, Empty
  20. from weakref import ref
  21.  
  22. class cancellables(Queue):
  23.     
  24.     def __init__(self):
  25.         Queue.__init__(self)
  26.  
  27.     
  28.     def cancel(self):
  29.         n = 0
  30.         
  31.         try:
  32.             while True:
  33.                 item = self.get(block = False)
  34.                 if isinstance(item, ref):
  35.                     item = item()
  36.                 
  37.                 if item is not None:
  38.                     
  39.                     try:
  40.                         wx.CallAfter(item.cancel)
  41.                     except PyDeadObjectError:
  42.                         pass
  43.                     except Exception:
  44.                         print_exc = print_exc
  45.                         import traceback
  46.                         print_exc()
  47.  
  48.                     n += 1
  49.                     continue
  50.         except Empty:
  51.             pass
  52.  
  53.         log.info('cancelled %d notification reactions', n)
  54.  
  55.  
  56.  
  57. def fire(topic, **info):
  58.     log.debug('fired topic %r', topic)
  59.     topics = _topics_from_string(topic)
  60.     types = set()
  61.     cancels = cancellables()
  62.     fired = False
  63.     
  64.     try:
  65.         notifications = profile.notifications
  66.     except AttributeError:
  67.         return log.warning('no notifications yet')
  68.  
  69.     always_show = info.get('always_show', [])
  70.     always_show = None if always_show is not None else set()
  71.     always_show.update(always_fire.get(topic, []))
  72.     if 'buddy' in info:
  73.         
  74.         try:
  75.             idstr = info['buddy'].idstr()
  76.             buddy_events = notifications.get(idstr, [])
  77.         except:
  78.             buddy_events = []
  79.  
  80.         log.debug('found %d buddy specific events', len(buddy_events))
  81.         if buddy_events:
  82.             fired = True
  83.             firetopics(topics, buddy_events, types = types, cancels = cancels, **info)
  84.         
  85.     
  86.     generic_events = notifications.get(None, [])
  87.     if generic_events:
  88.         fired = True
  89.         firetopics(topics, generic_events, types = types, cancels = cancels, **info)
  90.     
  91.     if always_show:
  92.         import gui.notificationview as nview
  93.         G = globals()
  94.         reactions = (set,)((lambda .0: for name in .0:
  95. G[name])(always_show))
  96.         ninfo = nview.get_notification_info()
  97.         for reaction in reactions - types:
  98.             args = dictadd(ninfo.get(topic, { }), info)
  99.             
  100.             def doit(cancels = cancels, reaction = reaction, args = args):
  101.                 cancellable = reaction()(**args)
  102.                 if hasattr(cancellable, 'cancel'):
  103.                     cancels.put(ref(cancellable))
  104.                 
  105.  
  106.             wx.CallAfter(doit)
  107.             fired = True
  108.         
  109.     
  110.     
  111.     try:
  112.         on_done = info['on_done']
  113.     except KeyError:
  114.         pass
  115.  
  116.     if not fired:
  117.         log.info('Calling on_done callback')
  118.         traceguard.__enter__()
  119.         
  120.         try:
  121.             on_done()
  122.         finally:
  123.             pass
  124.  
  125.     
  126.     return cancels
  127.  
  128.  
  129. def firetopics(topics, events, types, cancels, **info):
  130.     import gui.notificationview as nview
  131.     if types is None:
  132.         types = set()
  133.     
  134.     ninfo = nview.get_notification_info()
  135.     for topic in topics:
  136.         for event in events.get(topic, []):
  137.             (reaction, eventinfo) = getReaction(event, topic)
  138.             if reaction in types or reaction is None:
  139.                 continue
  140.             
  141.             template_info = ninfo.get(topic, { })
  142.             
  143.             def doit(cancels = cancels, reaction = reaction, args = dictadd(template_info, info), eventinfo = eventinfo):
  144.                 cancellable = reaction(**eventinfo)(**args)
  145.                 if hasattr(cancellable, 'cancel'):
  146.                     cancels.put(ref(cancellable))
  147.                 
  148.  
  149.             wx.CallAfter(doit)
  150.             types.add(reaction)
  151.         
  152.     
  153.     return types
  154.  
  155.  
  156. def getReaction(mapping, topic):
  157.     mapping = dict(mapping)
  158.     reaction = mapping.pop('reaction')
  159.     if isinstance(reaction, basestring):
  160.         reaction = globals().get(reaction, None)
  161.     
  162.     if reaction not in reactions_set:
  163.         return (None, None)
  164.     
  165.     if reaction is Sound:
  166.         active_soundset = active_soundset
  167.         SoundsetException = SoundsetException
  168.         import gui.notifications.sounds
  169.         
  170.         try:
  171.             soundset = active_soundset()
  172.         except SoundsetException:
  173.             soundset = { }
  174.  
  175.         
  176.         try:
  177.             sound_filename = soundset[topic]
  178.         except KeyError:
  179.             return (None, None)
  180.  
  181.         mapping.update(soundfile = sound_filename)
  182.     
  183.     return (reaction, mapping)
  184.  
  185.  
  186. class Reaction(object):
  187.     
  188.     def preview(self):
  189.         pass
  190.  
  191.     
  192.     def allowed(self):
  193.         cname = type(self).__name__.lower()
  194.         away = profile.status.away
  195.         if pref('notifications.enable_%s' % cname, True):
  196.             if away:
  197.                 pass
  198.         return bool(not pref('messaging.when_away.disable_%s' % cname, False))
  199.  
  200.     allowed = property(allowed)
  201.  
  202.  
  203. class Sound(Reaction):
  204.     desc = 'Play sound %(filename)s'
  205.     
  206.     def __init__(self, soundfile):
  207.         self.soundfile = soundfile
  208.  
  209.     
  210.     def __call__(self, **k):
  211.         if not self.allowed:
  212.             return None
  213.         
  214.         import gui.native.helpers as helpers
  215.         if pref('fullscreen.disable_sounds', False) and helpers.FullscreenApp():
  216.             return None
  217.         
  218.         if path_exists(self.soundfile):
  219.             PlaySound(self.soundfile)
  220.         
  221.  
  222.     
  223.     def preview(self):
  224.         self()
  225.  
  226.     
  227.     def __repr__(self):
  228.         return '<Sound %s>' % path(self.filename).basename()
  229.  
  230.  
  231.  
  232. class Alert(Reaction):
  233.     desc = 'Show alert "%(msg)s"'
  234.     
  235.     def __init__(self, msg):
  236.         self.msg = msg
  237.  
  238.     
  239.     def __call__(self, **info):
  240.         if not self.allowed:
  241.             return None
  242.         
  243.         wx.MessageBox(self.msg)
  244.  
  245.  
  246.  
  247. class Popup(Reaction):
  248.     
  249.     def desc(cls, info):
  250.         return 'Show a popup notification'
  251.  
  252.     desc = classmethod(desc)
  253.     
  254.     def __init__(self, sticky = False):
  255.         self.sticky = sticky
  256.  
  257.     
  258.     def __call__(self, **info):
  259.         import gui.native.helpers as helpers
  260.         if pref('fullscreen.disable_popups', True) and helpers.FullscreenApp():
  261.             return None
  262.         
  263.         if self.allowed or info.get('always_show', False):
  264.             popup = popup
  265.             import gui.popup
  266.             cpy = vars(self).copy()
  267.             cpy.update(info)
  268.             return popup(**cpy)
  269.         
  270.  
  271.  
  272.  
  273. class ShowContactList(Reaction):
  274.     DEFAULT_DURATION_SEC = 3
  275.     desc = 'Show the contact list for %(duration)s seconds'
  276.     
  277.     def __init__(self, duration):
  278.         self.duration = duration
  279.  
  280.     
  281.     def __call__(self):
  282.         print 'do buddylist stuff for', self.duration, 'sec'
  283.  
  284.  
  285.  
  286. class LogMessage(Reaction):
  287.     
  288.     def __init__(self, msg):
  289.         self.msg = msg
  290.  
  291.     
  292.     def __call__(self, **info):
  293.         if self.msg.find('%') != -1 and info.get('buddy', None) is not None:
  294.             log.info(self.msg, info['buddy'])
  295.         else:
  296.             log.info(self.msg)
  297.  
  298.  
  299.  
  300. class StartCommand(Reaction):
  301.     desc = 'Start external command "%(path)s"'
  302.     
  303.     def __init__(self, path):
  304.         self.path = path
  305.  
  306.     
  307.     def __call__(self, **info):
  308.         os.startfile(self.path)
  309.  
  310.     
  311.     def preview(self):
  312.         self()
  313.  
  314.  
  315.  
  316. def get_notification_info(_cache = []):
  317.     
  318.     try:
  319.         return _cache[0]
  320.     except:
  321.         pass
  322.  
  323.     skin = skin
  324.     import gui
  325.     mod = importer.yaml_import('notificationview', loadpath = [
  326.         skin.resourcedir()])
  327.     nots = _process_notifications_list(mod.__content__)
  328.     _cache.append(nots)
  329.     return nots
  330.  
  331.  
  332. def _process_notifications_list(nots_list):
  333.     odict_from_dictlist = odict_from_dictlist
  334.     import util
  335.     nots = odict_from_dictlist(nots_list)
  336.     ordered_underscores_to_dots(nots)
  337.     update_always_fire(nots)
  338.     return nots
  339.  
  340.  
  341. def add_notifications(nots_list):
  342.     to_update = get_notification_info()
  343.     mynots = _process_notifications_list(nots_list)
  344.     for k in mynots:
  345.         to_update[k] = mynots[k]
  346.     
  347.     error = to_update.pop('error', None)
  348.     if error is not None:
  349.         to_update['error'] = error
  350.     
  351.  
  352.  
  353. def add_default_notifications(not_defaults):
  354.     default_notifications[None].update(not_defaults)
  355.  
  356. always_fire = { }
  357.  
  358. def update_always_fire(nots):
  359.     always_fire.clear()
  360.     for name, info in nots.iteritems():
  361.         if 'always_show' in info:
  362.             reactions = info.get('always_show')
  363.             if isinstance(reactions, basestring):
  364.                 reactions = [
  365.                     reactions]
  366.             
  367.             always_fire[name] = reactions
  368.             continue
  369.     
  370.  
  371.  
  372. def ordered_underscores_to_dots(d):
  373.     ordered_keys = d._keys[:]
  374.     for i, key in enumerate(list(ordered_keys)):
  375.         if key and '_' in key:
  376.             new_key = key.replace('_', '.')
  377.             d[new_key] = d.pop(key)
  378.             ordered_keys[i] = new_key
  379.             continue
  380.     
  381.     d._keys = ordered_keys
  382.  
  383. from common.message import StatusUpdateMessage
  384.  
  385. class IMWinStatusUpdate(Reaction):
  386.     
  387.     def __init__(self, **info):
  388.         self.info = info
  389.  
  390.     
  391.     def __call__(self, **info):
  392.         self.info.update(info)
  393.         on_done = info.pop('on_done', (lambda : pass))
  394.         on_status = on_status
  395.         import gui.imwin.imhub
  396.         wx.CallAfter(on_status, StatusUpdateMessage(**self.info), on_done)
  397.  
  398.  
  399. reactions = [
  400.     Popup,
  401.     Alert,
  402.     Sound,
  403.     ShowContactList,
  404.     StartCommand,
  405.     IMWinStatusUpdate]
  406. if 'wxMac' in wx.PlatformInfo:
  407.     
  408.     class BounceDockIcon(Reaction):
  409.         
  410.         def __call__(self, **info):
  411.             wx.GetApp().MacRequestUserAttention(wx.NOTIFY_REPEAT)
  412.  
  413.  
  414.     reactions.extend([
  415.         BounceDockIcon])
  416.  
  417. reactions_set = set(reactions)
  418. default_notifications = {
  419.     None: {
  420.         'contact.returnsfromidle': [],
  421.         'email.new': [
  422.             {
  423.                 'reaction': 'Popup' }],
  424.         'error': [
  425.             {
  426.                 'reaction': 'Popup' }],
  427.         'facebook.alert': [
  428.             {
  429.                 'reaction': 'Popup' }],
  430.         'facebook.newsfeed': [
  431.             {
  432.                 'reaction': 'Popup' }],
  433.         'filetransfer.ends': [
  434.             {
  435.                 'reaction': 'Popup' }],
  436.         'filetransfer.error': [
  437.             {
  438.                 'reaction': 'Popup' }],
  439.         'filetransfer.request': [
  440.             {
  441.                 'reaction': 'Popup' }],
  442.         'message.received.background': [
  443.             {
  444.                 'reaction': 'Popup' }],
  445.         'message.received.initial': [
  446.             {
  447.                 'reaction': 'Sound' }],
  448.         'myspace.alert': [
  449.             {
  450.                 'reaction': 'Popup' }],
  451.         'twitter.newdirect': [
  452.             {
  453.                 'reaction': 'Popup' }],
  454.         'twitter.newtweet': [
  455.             {
  456.                 'reaction': 'Popup' }] } }
  457. for topic in [
  458.     'contact.signon',
  459.     'contact.signoff',
  460.     'contact.available',
  461.     'contact.away',
  462.     'contact.returnsfromidle',
  463.     'contact.idle']:
  464.     seq = default_notifications[None].setdefault(topic, [])
  465.     seq += [
  466.         {
  467.             'reaction': 'IMWinStatusUpdate' }]
  468.  
  469.  
  470. class Notification(SlotsSavable):
  471.     pass
  472.  
  473. TOPIC_SEP = '.'
  474.  
  475. def _topics_from_string(topic):
  476.     _check_topic_string(topic)
  477.     topiclist = topic.split(TOPIC_SEP)
  478.     topics = []([ TOPIC_SEP.join(topiclist[:x]) for x in xrange(1, len(topiclist) + 1) ])
  479.     return list(topics)
  480.  
  481. _topics_from_string = memoize(_topics_from_string)
  482.  
  483. def _check_topic_string(topic):
  484.     if not isinstance(topic, basestring):
  485.         raise TypeError('topic must be a string')
  486.     
  487.     if topic.find('..') != -1:
  488.         raise ValueError('consecutive periods not allowed in topic')
  489.     
  490.     if topic.startswith('.') or topic.endswith('.'):
  491.         raise ValueError('topic cannot start or end with a topic')
  492.     
  493.  
  494.