home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 October / maximum-cd-2011-10.iso / DiscContents / digsby_setup.exe / lib / common / notifications.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2011-06-22  |  16.6 KB  |  547 lines

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