home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 February / maximum-cd-2011-02.iso / DiscContents / digsby_setup85.exe / lib / common / notifications.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-11-24  |  16.4 KB  |  537 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.             return (None, None)
  222.  
  223.         mapping.update(soundfile = sound_filename)
  224.     
  225.     return (reaction, mapping)
  226.  
  227.  
  228. class Reaction(object):
  229.     
  230.     def preview(self):
  231.         pass
  232.  
  233.     
  234.     def allowed(self):
  235.         cname = type(self).__name__.lower()
  236.         
  237.         try:
  238.             away = profile.status.away
  239.         except AttributeError:
  240.             return True
  241.  
  242.         if pref('notifications.enable_%s' % cname, True):
  243.             if away:
  244.                 pass
  245.         return bool(not pref('messaging.when_away.disable_%s' % cname, False))
  246.  
  247.     allowed = property(allowed)
  248.  
  249.  
  250. class Sound(Reaction):
  251.     desc = 'Play sound %(filename)s'
  252.     
  253.     def __init__(self, soundfile):
  254.         self.soundfile = soundfile
  255.  
  256.     
  257.     def __call__(self, **k):
  258.         if not self.allowed:
  259.             return None
  260.         import gui.native.helpers as helpers
  261.         if pref('fullscreen.disable_sounds', False) and helpers.FullscreenApp():
  262.             return None
  263.  
  264.     
  265.     def preview(self):
  266.         self()
  267.  
  268.     
  269.     def __repr__(self):
  270.         return '<Sound %s>' % path(self.filename).basename()
  271.  
  272.  
  273.  
  274. class Alert(Reaction):
  275.     desc = 'Show alert "%(msg)s"'
  276.     
  277.     def __init__(self, msg):
  278.         self.msg = msg
  279.  
  280.     
  281.     def __call__(self, **info):
  282.         if not self.allowed:
  283.             return None
  284.         wx.MessageBox(self.msg)
  285.  
  286.  
  287.  
  288. class Popup(Reaction):
  289.     
  290.     def desc(cls, info):
  291.         return 'Show a popup notification'
  292.  
  293.     desc = classmethod(desc)
  294.     
  295.     def __init__(self, sticky = False):
  296.         self.sticky = sticky
  297.  
  298.     
  299.     def __call__(self, **info):
  300.         import gui.native.helpers as helpers
  301.         if pref('fullscreen.disable_popups', True) and helpers.FullscreenApp():
  302.             return None
  303.         if self.allowed or info.get('always_show', False):
  304.             popup = popup
  305.             import gui.toast
  306.             cpy = vars(self).copy()
  307.             cpy.update(info)
  308.             return popup(**cpy)
  309.  
  310.  
  311.  
  312. class ShowContactList(Reaction):
  313.     DEFAULT_DURATION_SEC = 3
  314.     desc = 'Show the contact list for %(duration)s seconds'
  315.     
  316.     def __init__(self, duration):
  317.         self.duration = duration
  318.  
  319.     
  320.     def __call__(self):
  321.         print 'do buddylist stuff for', self.duration, 'sec'
  322.  
  323.  
  324.  
  325. class LogMessage(Reaction):
  326.     
  327.     def __init__(self, msg):
  328.         self.msg = msg
  329.  
  330.     
  331.     def __call__(self, **info):
  332.         if self.msg.find('%') != -1 and info.get('buddy', None) is not None:
  333.             log.info(self.msg, info['buddy'])
  334.         else:
  335.             log.info(self.msg)
  336.  
  337.  
  338.  
  339. class StartCommand(Reaction):
  340.     desc = 'Start external command "%(path)s"'
  341.     
  342.     def __init__(self, path):
  343.         self.path = path
  344.  
  345.     
  346.     def __call__(self, **info):
  347.         os.startfile(self.path)
  348.  
  349.     
  350.     def preview(self):
  351.         self()
  352.  
  353.  
  354.  
  355. def get_notification_info(_cache = []):
  356.     
  357.     try:
  358.         nots = _cache[0]
  359.     except IndexError:
  360.         skin = skin
  361.         import gui
  362.         mod = importer.yaml_import('notificationview', loadpath = [
  363.             skin.resourcedir()])
  364.         nots = _process_notifications_list(mod.__content__)
  365.         _cache.append(nots)
  366.  
  367.     import hooks
  368.     hooks.notify('digsby.notifications.get_topics', nots)
  369.     return nots
  370.  
  371.  
  372. def _process_notifications_list(nots_list):
  373.     odict_from_dictlist = odict_from_dictlist
  374.     import util.primitives.mapping
  375.     nots = odict_from_dictlist(nots_list)
  376.     ordered_underscores_to_dots(nots)
  377.     update_always_fire(nots)
  378.     return nots
  379.  
  380.  
  381. def add_notifications(nots_list):
  382.     to_update = get_notification_info()
  383.     mynots = _process_notifications_list(nots_list)
  384.     for k in mynots:
  385.         to_update[k] = mynots[k]
  386.     
  387.     error = to_update.pop('error', None)
  388.     if error is not None:
  389.         to_update['error'] = error
  390.     
  391.  
  392.  
  393. def add_default_notifications(not_defaults):
  394.     default_notifications[None].update(not_defaults)
  395.  
  396. always_fire = { }
  397.  
  398. def update_always_fire(nots):
  399.     always_fire.clear()
  400.     for name, info in nots.iteritems():
  401.         if 'always_show' in info:
  402.             reactions = info.get('always_show')
  403.             if isinstance(reactions, basestring):
  404.                 reactions = [
  405.                     reactions]
  406.             
  407.             always_fire[name] = reactions
  408.             continue
  409.     
  410.  
  411.  
  412. def ordered_underscores_to_dots(d):
  413.     ordered_keys = d._keys[:]
  414.     for i, key in enumerate(list(ordered_keys)):
  415.         if key and '_' in key:
  416.             new_key = key.replace('_', '.')
  417.             d[new_key] = d.pop(key)
  418.             ordered_keys[i] = new_key
  419.             continue
  420.     
  421.     d._keys = ordered_keys
  422.  
  423. from common.message import StatusUpdateMessage
  424.  
  425. class IMWinStatusUpdate(Reaction):
  426.     
  427.     def __init__(self, **info):
  428.         self.info = info
  429.  
  430.     
  431.     def __call__(self, **info):
  432.         self.info.update(info)
  433.         on_done = info.pop('on_done', (lambda : pass))
  434.         on_status = on_status
  435.         import gui.imwin.imhub
  436.         wx.CallAfter(on_status, StatusUpdateMessage(**self.info), on_done)
  437.  
  438.  
  439. reactions = [
  440.     Popup,
  441.     Alert,
  442.     Sound,
  443.     ShowContactList,
  444.     StartCommand,
  445.     IMWinStatusUpdate]
  446. if 'wxMac' in wx.PlatformInfo:
  447.     
  448.     class BounceDockIcon(Reaction):
  449.         
  450.         def __call__(self, **info):
  451.             tlw = wx.GetApp().GetTopWindow()
  452.             if tlw:
  453.                 tlw.RequestUserAttention(wx.USER_ATTENTION_ERROR)
  454.             
  455.  
  456.  
  457.     reactions.extend([
  458.         BounceDockIcon])
  459.  
  460. reactions_set = set(reactions)
  461. default_notifications = {
  462.     None: {
  463.         'contact.returnsfromidle': [],
  464.         'email.new': [
  465.             {
  466.                 'reaction': 'Popup' }],
  467.         'error': [
  468.             {
  469.                 'reaction': 'Popup' }],
  470.         'filetransfer.ends': [
  471.             {
  472.                 'reaction': 'Popup' }],
  473.         'filetransfer.error': [
  474.             {
  475.                 'reaction': 'Popup' }],
  476.         'filetransfer.request': [
  477.             {
  478.                 'reaction': 'Popup' }],
  479.         'message.received.background': [
  480.             {
  481.                 'reaction': 'Popup' }],
  482.         'message.received.initial': [
  483.             {
  484.                 'reaction': 'Sound' }],
  485.         'message.received.hidden': [
  486.             {
  487.                 'reaction': 'Popup' }],
  488.         'myspace.alert': [
  489.             {
  490.                 'reaction': 'Popup' }],
  491.         'myspace.newsfeed': [
  492.             {
  493.                 'reaction': 'Popup' }],
  494.         'twitter.newdirect': [
  495.             {
  496.                 'reaction': 'Popup' }],
  497.         'twitter.newtweet': [
  498.             {
  499.                 'reaction': 'Popup' }] } }
  500. for topic in [
  501.     'contact.signon',
  502.     'contact.signoff',
  503.     'contact.available',
  504.     'contact.away',
  505.     'contact.returnsfromidle',
  506.     'contact.idle']:
  507.     seq = default_notifications[None].setdefault(topic, [])
  508.     seq += [
  509.         {
  510.             'reaction': 'IMWinStatusUpdate' }]
  511.  
  512.  
  513. class Notification(SlotsSavable):
  514.     pass
  515.  
  516. TOPIC_SEP = '.'
  517.  
  518. def _topics_from_string(topic):
  519.     _check_topic_string(topic)
  520.     topiclist = topic.split(TOPIC_SEP)
  521.     topics = []([ TOPIC_SEP.join(topiclist[:x]) for x in xrange(1, len(topiclist) + 1) ])
  522.     return list(topics)
  523.  
  524. _topics_from_string = memoize(_topics_from_string)
  525.  
  526. def _check_topic_string(topic):
  527.     if not isinstance(topic, basestring):
  528.         raise TypeError('topic must be a string')
  529.     isinstance(topic, basestring)
  530.     if topic.find('..') != -1:
  531.         raise ValueError('consecutive periods not allowed in topic')
  532.     topic.find('..') != -1
  533.     if topic.startswith('.') or topic.endswith('.'):
  534.         raise ValueError('topic cannot start or end with a topic')
  535.     topic.endswith('.')
  536.  
  537.