Example #1
0
    def __init__(self, win, autohide=False, enabled=True):
        self.Animate = True
        self.autohidden = False
        self.AutoHide = False
        self.docked = False
        self.docking = False
        self.LinkedWindows = []
        self.manualMove = False
        self.motiontrigger = False
        self.pixelsdragged = 0
        self.ShouldShowInTaskbar = lambda: True
        self.timer = wx.PyTimer(self.OnTimer)
        self.spookyGhostWindow = None

        self.win = win
        self.win.Bind(wx.EVT_ACTIVATE, self.OnActivateWin)
        self.win.Bind(wx.EVT_MOVE, self.OnMoving)

        self.lastrect = None

        self.SetAutoHide(autohide)

        self.OnDock = Delegate()
        self.OnHide = Delegate()

        publisher = Publisher()
        publisher.subscribe(self.OnActivateApp, 'app.activestate.changed')
Example #2
0
    def retrieved_blob(self, stanza):
        '''
        The answer from server_retrieve.
        Sends blob to the profile, either from the (in memory, pre-loaded) cache,
        or from the response from the server.  If the local copy was newer,
        this initiates a profile.save for the blob, after sending it to the profile.
        '''
        ns = stanza.get_query_ns()
        info('retrieved blob w/ ns:"%r"', ns)
        blob = ns_to_obj[ns](stanza.get_query())
        name = ns_to_name[ns]
        calls = Delegate()
        if blob.update_needed is True:
            assert blob._data is None
            info('%s: server-side older, sending our copy', name)
            calls.append(lambda: self.profile.save(name, force=True))
        if blob._data is None:
            info('%s: server-side matched cache', name)
            blob._data = self.from_cache.pop(name)  #default?
        else:
            info('%s: server-side newer', name)
            self.blob_cache(blob)

        useful_data = blob.data
        old_loading = self.loading
        self.waiting_blobs.discard(name)
        self.profile.update_blob(name, useful_data)
        self.notify('loading', old_loading, self.loading)
        calls()
Example #3
0
    def retrieved_blob(self, stanza):
        '''
        The answer from server_retrieve.
        Sends blob to the profile, either from the (in memory, pre-loaded) cache,
        or from the response from the server.  If the local copy was newer,
        this initiates a profile.save for the blob, after sending it to the profile.
        '''
        ns = stanza.get_query_ns()
        info('retrieved blob w/ ns:"%r"', ns)
        blob = ns_to_obj[ns](stanza.get_query())
        name = ns_to_name[ns]
        calls = Delegate()
        if blob.update_needed is True:
            assert blob._data is None
            info('%s: server-side older, sending our copy', name)
            calls.append(lambda: self.profile.save(name, force = True))
        if blob._data is None:
            info('%s: server-side matched cache', name)
            blob._data = self.from_cache.pop(name) #default?
        else:
            info('%s: server-side newer', name)
            self.blob_cache(blob)

        useful_data = blob.data
        old_loading = self.loading
        self.waiting_blobs.discard(name)
        self.profile.update_blob(name, useful_data)
        self.notify('loading', old_loading, self.loading)
        calls()
Example #4
0
    def __init__(self,
                 parent,
                 text,
                 on_click=None,
                 should_be_active=None,
                 align='right'):

        self._on_click = Delegate()

        if on_click:
            self._on_click += on_click

        ClearLink.__init__(self,
                           parent,
                           -1,
                           text,
                           on_click,
                           style=wx.NO_BORDER
                           | getattr(wx, 'HL_ALIGN_%s' % align.upper()))
        linkfont = skin.get('filetransfers.fonts.link', default_font)

        self.SetFont(linkfont)

        self._is_active = Delegate(collect_values=True)
        if should_be_active:
            self._is_active += should_be_active
Example #5
0
    def __init__(self, jabber):
        self.jabber = jabber

        self.on_incoming_node = Delegate()
        self.on_outgoing_node = Delegate()

        jabber.add_observer(self.state_changed, 'state')
        self.state_changed(jabber)
Example #6
0
    def __init__(self, source, time_secs):
        self.source = source

        self.request_callbacks = Delegate()
        self.requesting = False
        self.ads = None

        self.ad_index = -1

        self.append_short_links = SHORTEN_AD_URLS
Example #7
0
    def __init__(self, parent):
        SimplePanel.__init__(self, parent)

        self.OnEditEmail = Delegate()
        self.OnSendEmail = Delegate()

        self.gui_constructed = False

        self.UpdateSkin()

        self.construct_gui()
Example #8
0
    def __init__(self, profile):
        Observable.__init__(self)

        self.accounts_loaded = False

        self.profile = profile
        self.connected_accounts = ObservableList()
        self.reconnect_timers   = {}

        # holds "cancel" objects from Popups
        self.cancellers = {}

        self.profile.add_observer(self.on_state_change,   'state')
        self.profile.add_observer(self.on_offline_change, 'offline_reason')
        self.profile.add_observer(self.profile_state_changed, 'state')

        import wx
        wx.GetApp().OnBuddyListShown.append(lambda *a, **k: Timer(.25,
            threaded(self.release_accounts), *a, **k).start())

        self._hash = sentinel

        self.got_accounts = False

        self.acct_calls = Delegate()
        self.delay_accounts = True
        self.acct_delay_lock = RLock()

        self._all_acct_hash = {}
        self.last_server_order = None

        self._all_accounts = Storage()
        for type_ in ('im', 'em', 'so'):

            s = Storage(accounts = ObservableList(),
                        old = [])

            setattr(self._all_accounts, type_, s)

            # when the order of accounts changes, or accounts are added or deleted,
            # calls profile.accounts_changed('im', list)
            s.accounts.add_observer(getattr(self, type_ + '_accounts_changed'))

        self.accounts = self._all_accounts.im.accounts
        self.emailaccounts = self._all_accounts.em.accounts
        self.socialaccounts = self._all_accounts.so.accounts

        self.buddywatcher = BuddyWatcher()

        import services.service_provider as sp
        container = sp.ServiceProviderContainer(self.profile)
        container.on_order_changed += self._set_order
Example #9
0
 def __init__(self, updatefreq=None, *a, **k):
     StateMixin.__init__(self, *a, **k)
     if updatefreq is not None:
         try:
             updatefreq = int(updatefreq)
         except ValueError:
             pass
         else:
             if updatefreq < 1:
                 updatefreq = 60
             self.updatefreq = max(updatefreq, 15)
     self.on_connect = Delegate()
     self.on_disable = Delegate()
Example #10
0
    def __init__(self,
                 timer_secs,
                 time_mode,
                 trigger_mode,
                 current_time_func=time):
        assert isinstance(timer_secs, int)
        assert trigger_mode in allowed_trigger_modes
        assert time_mode in allowed_time_modes

        self.has_focus = False

        self.timer_secs = timer_secs
        self.time_mode = time_mode
        self.trigger_mode = trigger_mode

        self.scenario_identifier = '%s_%s_%s' % (timer_secs, time_mode,
                                                 trigger_mode)

        assert hasattr(current_time_func, '__call__')
        self._get_time = current_time_func

        self._reset_time(
            start=False)  # the last UNIX time we showed an ad ( = now).
        self.on_reload = Delegate()

        if self.trigger_mode == 'focus':
            self.wx_timer = wx.PyTimer(self._on_wxtimer)
            self.wx_timer.StartRepeating(1000)
Example #11
0
    def __init__(self, frame):
        frame.Bind(wx.EVT_MENU, self._oncommandevent)

        self.frame = frame
        self.cbs = defaultdict(
            lambda: Delegate(ignore_exceptions=wx.PyDeadObjectError))
        self.idcbs = {}
Example #12
0
    def __init__(self, tocombo, fromcombo):
        BInfoControl.__init__(self, tocombo, fromcombo, 'email',
                              email_menu_content)

        self._obs_link = None
        self.setup_from_accts()
        self.OnEmailAccountChanged = Delegate()
Example #13
0
 def new_done(func=None):
     done = Delegate()
     if func is not None:
         done += func
     done += set_order
     done += self.save_server_info
     return done
Example #14
0
    def __init__(self, username, password):
        self.username = username
        self.password = password

        self.recent_timeline = []
        self.self_tweet = None
        self.trends = {}

        self.feeds = []
        self.feeds_by_name = {}
        self.unread_counts = []

        e = self.events = Storage(
            (name, Delegate()) for name in self.event_names)

        e.following += self.on_following
        e.trends += self.on_trends
        e.on_unread_counts += self.on_unread_counts
        e.recent_timeline += self.on_recent_timeline
        e.self_tweet += self.on_self_tweet
        e.on_feeds += self.on_feeds
        e.on_change_view += self.on_change_view
        e.on_view += self.on_view_changed

        def render_tweets(tweets, render_context):
            return htmlize_tweets(self, tweets)

        self.social_feed = SocialFeed('twitter_' + self.username,
                                      'twitter_' + self.username,
                                      self.get_tweet_feed, render_tweets,
                                      lambda: self.account.set_infobox_dirty)
Example #15
0
 def conversation_reconnected(self):
     try:
         d = self._conversation_reconnected
     except AttributeError:
         from util.primitives.funcs import Delegate
         d = self._conversation_reconnected = Delegate()
     return d
Example #16
0
    def __init__(self,
                 parent,
                 label='',
                 id=None,
                 onshow=None,
                 windowless=None):

        if not isinstance(parent, wx.WindowClass):
            raise TypeError('UMenu parent must be a wx.Window')

        wx.Menu.__init__(self, label)

        InstanceTracker.track(self)

        if not isinstance(id, (int, type(None))):
            raise TypeError

        self._parentmenu = self._childmenu = None
        self.Id = wx.NewId() if id is None else id
        self._window = ref(parent)
        self.OnDismiss = Delegate()
        self.cbs = {}

        #self.Bind(wx.EVT_MENU, lambda e: menuEventHandler(self.InvokingWindow).ProcessEvent(e))

        if onshow is not None:
            self.Handler.AddShowCallback(self.Id,
                                         lambda menu=ref(self): onshow(menu()))

        if wxMSW:
            self.Handler.hwndMap[self.HMenu] = self

        self.Windowless = windowless

        self.UpdateSkin()
Example #17
0
    def __init__(self, **options):
        for key in self.protocol_info()['defaults'].iterkeys():
            try:
                val = options[key]
            except KeyError:
                val = self.default(key)
            setattr(self, key, val)

        self.oauth_token = options.pop('oauth_token', None)

        self._on_online = Delegate()

        self.count = None
        self.twitter_protocol = None

        if False:
            # TODO: this will take an hour to come back from idle
            @guithread
            def later():
                self.idle_timer = wx.PyTimer(self.on_idle_timer)
                MINUTE_MS = 60 * 1000 * 60
                self.idle_timer.StartRepeating(30 * MINUTE_MS)

        social.network.__init__(self, **options)

        self.header_funcs = [
            (_('Home'), 'http://twitter.com'),
            (_('Profile'), lambda: wx.LaunchDefaultBrowser(
                'http://twitter.com/' + self.twitter_username)),
            (_('Followers'), 'http://twitter.com/followers'),
            (_('Following'), 'http://twitter.com/following'),
        ]

        import twitter_notifications as twitter_notifications
        twitter_notifications._register_hooks()

        # options that affect first creation of the account.
        # account dialog will call onCreate
        self.do_follow_digsby = options.pop('do_follow_digsby', False)
        #        self.do_tweet_about_digsby = options.pop('do_tweet_about_digsby', False)

        for twitter_pref, digsby_pref, default in self.account_prefs():
            common.profile.prefs.link(digsby_pref, self.on_pref_change)

        #self.extra_header_func = (_('Invite Friends'), self.on_invite_friends)

        self.api_server = options.get('api_server', None)
Example #18
0
 def __init__(self, webview):
     self.id_count = 0
     self.callbacks = {}  # TODO: expire timeouts
     self.webview = webview
     self.webview.Bind(wx.webview.EVT_WEBVIEW_RECEIVED_TITLE,
                       self.on_before_load)
     self.specifiers = {}
     self.on_call = Delegate()
Example #19
0
    def __init__(self, win, autohide = False, enabled = True):
        self.win = win
        self._side = None#ABE_LEFT
        self.DockMargin = 30
        self.ShouldShowInTaskbar = lambda: True  #must be set by window
        self.ShouldAlwaysStayOnTop = None
        self.RevealDurationMs = 300
        self.Animate = True
        self.autohidden = self.docking = self.docked = False
        self._enabled = enabled
        self._autohide = autohide

        self.bypassSizeEvents = False
        self.bypassMoveEvents = False
        self.reReserveTimer = None

        Bind = win.Bind
        BindWin32 = win.BindWin32

        Bind(wx.EVT_MOVING, self.OnMoving)
        Bind(wx.EVT_CLOSE, self.OnClose)
        Bind(wx.EVT_ACTIVATE, self.OnActivate)
        Bind(wx.EVT_DISPLAY_CHANGED, self.OnDisplayChanged)
        Bind(wx.EVT_SHOW, self.OnShow)


        BindWin32(WM_WINDOWPOSCHANGING, self.OnWindowPosChanging)

        BindWin32(WM_EXITSIZEMOVE, self.OnExitSizeMove)
        BindWin32(WM_SIZING, self.OnSizing)

        BindWin32(WM_NCHITTEST, self.OnNCHitTest)
        BindWin32(WM_SYSCOMMAND, self.OnSysCommand)

        self.appbar_cb_id = WM_USER + 100
        BindWin32(self.appbar_cb_id, self.AppBarCallback)

        self.firstShow = True
        self.wasDocked = False
        self.oldSize = None
        self.motiontrigger = False
        self.timer = PyTimer(self.OnTimer)

        self.OnDock = Delegate()
        self.OnHide = Delegate()
        self.LinkedWindows = []
Example #20
0
    def __init__(self, imwin, capsbar, tocombo, fromcombo, onselection=None):

        self.imwin = imwin
        self.capsbar = capsbar
        self.tocombo = tocombo
        self.fromcombo = fromcombo

        self.contact = None
        self.blist = profile.blist

        self.register_observers()

        self.OnSelection = Delegate()
        self.OnSwitchContact = Delegate()

        self.msgarea = None
        self.ischat = False
Example #21
0
        def __init__(self,
                     parent,
                     initialContents='',
                     contentPath='file:///c:/',
                     url=None,
                     simple_events=False,
                     external_links=True,
                     **opts):
            super(WebKitWindow, self).__init__(parent,
                                               size=wx.Size(200, 200),
                                               **opts)

            self._jsqueue_enabled = True
            self.jsqueue = []
            self.js_to_stderr = False

            self.ExternalLinks = external_links

            self.OnNav = Delegate()  # Called for NavigateComplete2 events
            self.OnDoc = Delegate()  # Called for DocumentComplete events
            self.OnTitleChanged = Delegate()

            Bind = self.Bind
            Bind(wx.EVT_CONTEXT_MENU, self.__OnContextMenu)
            Bind(webview.EVT_WEBVIEW_LOAD, self.OnStateChanged)
            Bind(webview.EVT_WEBVIEW_BEFORE_LOAD, self.OnBeforeLoad)
            Bind(webview.EVT_WEBVIEW_RECEIVED_TITLE, self.OnTitleChanged)
            from gui.browser.webkit import setup_webview_logging
            setup_webview_logging(self, 'webview')

            self.urltriggers = defaultdict(list)

            if initialContents and url is not None:
                raise ValueError(
                    "please specify initialContents or url, but not both")

            # some APIs call LoadUrl
            self.LoadUrl = self.LoadURL

            if url is not None:
                self.LoadURL(url)
            else:
                self.SetPageSource(initialContents, 'file:///')

            self.BlockWebKitMenu = True
Example #22
0
 def connection_closed(self, socket=None):
     if self.state == self.Statuses.OFFLINE:
         log.info('socket closed normally')
         reason = self.Reasons.NONE
     else:
         log.info('socket closed unexpectedly (-> CONN_LOST)')
         reason = self.Reasons.CONN_LOST
     self.Disconnect(reason)
     self.on_connect = Delegate()
Example #23
0
 def __init__(self, subject):
     self.profile = subject
     self.existing_sps = {}
     self.order = None
     self.on_order_changed = Delegate()
     self.lock = threading.RLock()
     self.rebuild_count = 0
     self.accounts_to_rebuild = None
     self.new = set()
Example #24
0
    def __init__(self, source, time_secs):
        self.source = source

        self.request_callbacks = Delegate()
        self.requesting = False
        self.ads = None

        self.ad_index = -1

        self.append_short_links = SHORTEN_AD_URLS
Example #25
0
    def watch_status(self, buddy, callback):
        assert callable(callback)
        bid = key(buddy)

        try:
            watchers = self.watchers[bid]
        except KeyError:
            watchers = self.watchers.setdefault(bid, Delegate())

        watchers += callback
Example #26
0
    def __init__(self, username, password, follow_ids):
        self.follow_ids = follow_ids
        self.httpmaster = asynchttp.HttpMaster()

        uris = ['stream.twitter.com', 'twitter.com']
        realm = 'Firehose'
        self.httpmaster.add_password(realm, uris, username, password)

        self.post_data = 'follow=' + ','.join(str(i) for i in follow_ids)

        self.on_tweet = Delegate()
Example #27
0
    def __init__(self, tocombo, fromcombo, info_attr, content_cb):
        self.tocombo, self.fromcombo = tocombo, fromcombo
        self._editing = False

        assert isinstance(info_attr, str)
        self.info_attr = info_attr

        self.OnLoseFocus = Delegate()
        e = self.to_editor = ComboListEditor(tocombo, self.OnLoseFocus)
        e.SetContentCallback(content_cb)

        self.blist = profile.blist
Example #28
0
    def __init__(self, parent, key):
        wx.Panel.__init__(self,
                          parent,
                          style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE)

        self.SetSkinKey(key, True)

        Bind = self.Bind
        Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
        Bind(wx.EVT_PAINT, self.OnPaint)

        self.ChildPaints = Delegate()
Example #29
0
    def __init__(self, parent, skinkey='MenuBar'):
        wx.MenuBar.__init__(self)
        self.toptitles = {}
        self.skinkey = skinkey
        self.accelremoves = Delegate()

        self.panel = SkinnedPanel(
            parent,
            'MenuBar',
        )
        self.panel.UpdateSkin = self.UpdateSkin
        self.panel.Hide()
        self.UpdateSkin()
Example #30
0
    def __init__(self, parent, account = None, protocol_name = None):
        "Please do not call directly. See classmethods create_new and edit_account."

        # Editing an existing account
        if account is not None:
            self.new = False
            assert protocol_name is None
            protocolinfo = account.protocol_info()
            self.protocol_name = account.protocol
            title = '%s - %s Settings' % (account.name, protocolinfo.name)

        # Creating a new account
        if account is None:
            self.new = True
            protocolinfo = protocols[protocol_name]
            self.protocol_name = protocol_name
            title = '%s Account' % protocolinfo.name

        # What to call the username (screenname, username, Jabber ID, etc.)
        self.screenname_name = protocolinfo.username_desc

        wx.Dialog.__init__(self, parent, title=title, size=(400,300))
        self.account = account if account is not None else emptystringer(getattr(protocolinfo, 'defaults', None))
        self.new = account is None
        self.protocolinfo = protocolinfo

        # Set the account type icon
        from gui import skin
        self.SetFrameIcon(skin.get('serviceicons.%s' % self.protocol_name))

        self.formtype  = getattr(protocolinfo, 'form', 'default')
        self.info_callbacks = Delegate()

        if self.new:
            self._allaccts = [acctid(a.protocol, a.name) for a in profile.account_manager]

        self.construct(account is None)
        self.layout()

        # enable or disable the save button as necessary.
        self.check_warnings()
        self.Fit()

        # focus the first enabled text control.
        for c in self.Children:
            if isinstance(c, TextCtrl) and c.IsEnabled() and c.IsEditable():
                if c is get(self, 'password', None):
                    c.SetSelection(-1, -1) # only makes sense to select all on a password field :)

                wx.CallAfter(c.SetFocus)
                break
Example #31
0
    def AddActionCallback(self, actionname, callback):
        'Associates a callable with an action.'

        actionname = actionname.lower()

        if isinstance(callback, basestring):
            callback = LazyStringImport(callback)

        if not actionname in self.handlers:
            self.handlers[actionname] = Delegate([callback])
        else:
            self.handlers[actionname] += callback

        self.resolve_actions()
Example #32
0
    def __init__(self, parent, initial_tab=default_selected_tab):
        wx.Frame.__init__(self,
                          parent,
                          title=_('Digsby Preferences'),
                          size=self.default_size,
                          style=prefs_dialog_style,
                          name='Preferences Window')

        self.loaded_panels = {}
        self.SetMinSize(self.default_size)

        metrics.event('Prefs Dialog Opened')

        self.create_gui()
        self.bind_events()
        self.layout_gui()
        self.exithooks = Delegate()

        with traceguard:
            from gui import skin
            self.SetFrameIcon(skin.get('AppDefaults.TaskbarIcon'))

        if not wxMac:
            self.BackgroundColour = wx.WHITE
            self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)

        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

        # Fake a first selection
        self.tabs.SetSelection(initial_tab)
        self.on_tab_selected(initial_tab)

        self.tabnames = names = [
            module_name for module_name, nice_name in tabnames
        ]

        self.Bind(wx.EVT_CLOSE, self.on_close)
        self._loaded = 0

        # Obey the windows.sticky prreference
        snap_pref(self)

        profile.prefs.add_observer(self.incoming_network_prefs)

        from gui.uberwidgets.keycatcher import KeyCatcher
        k = self._keycatcher = KeyCatcher(self)
        k.OnDown('ctrl+w', self.Close)
        k.OnDown('escape', self.Close)
Example #33
0
    def __init__(self, **options):
        for key in self.protocol_info()['defaults'].iterkeys():
            try: val = options[key]
            except KeyError: val = self.default(key)
            setattr(self, key, val)

        self.oauth_token = options.pop('oauth_token', None)

        self._on_online = Delegate()

        self.count = None
        self.twitter_protocol = None

        if False:
            # TODO: this will take an hour to come back from idle
            @guithread
            def later():
                self.idle_timer = wx.PyTimer(self.on_idle_timer)
                MINUTE_MS = 60 * 1000 * 60
                self.idle_timer.StartRepeating(30 * MINUTE_MS)

        social.network.__init__(self, **options)

        self.header_funcs = [
            (_('Home'), 'http://twitter.com'),
            (_('Profile'), lambda: wx.LaunchDefaultBrowser('http://twitter.com/' + self.twitter_username)),
            (_('Followers'), 'http://twitter.com/followers'),
            (_('Following'), 'http://twitter.com/following'),
        ]

        import twitter_notifications as twitter_notifications
        twitter_notifications._register_hooks()

        # options that affect first creation of the account.
        # account dialog will call onCreate
        self.do_follow_digsby = options.pop('do_follow_digsby', False)
#        self.do_tweet_about_digsby = options.pop('do_tweet_about_digsby', False)

        for twitter_pref, digsby_pref, default in self.account_prefs():
            common.profile.prefs.link(digsby_pref, self.on_pref_change)

        #self.extra_header_func = (_('Invite Friends'), self.on_invite_friends)

        self.api_server = options.get('api_server', None)
Example #34
0
class AdQueue(object):

    def __init__(self, source, time_secs):
        self.source = source

        self.request_callbacks = Delegate()
        self.requesting = False
        self.ads = None

        self.ad_index = -1

        self.append_short_links = SHORTEN_AD_URLS

    def needs_request(self):
        return self.ads is None or self.ad_index >= len(self.ads) - 2

    def next_ad(self, cb):
        def _have_ads():
            if not self.ads:
                return cb(None)
            else:
                self.ad_index += 1
                ad = self.ads[self.ad_index % len(self.ads)]
                cb(ad)

                @wx.CallAfter
                def after():
                    win = FeedTrendsDebugWindow.RaiseExisting()
                    if win is not None:
                        win.update_arrow(self.ad_index)

        self.once_have_ads(_have_ads)

    def once_have_ads(self, cb):
        if self.requesting:
            if cb is not None:
                self.request_callbacks.append(cb)
        elif self.needs_request():
            if cb is not None:
                self.request_callbacks.append(cb)
            self.request_new_ads()
        else:
            if cb is not None:
                cb()

    def request_new_ads(self, cb=None):
        self.requesting = True

        def on_ads(ads):
            if SHUFFLE_ADS:
                random.shuffle(ads)

            self.requesting = False

            if KEEP_OLD_ADS:
                if self.ads is not None:
                    ads.extend(self.ads)
                ads[:] = ads[:MAX_ADS]

            self.ads = ads
            self.ad_index = -1

            @wx.CallAfter
            def after():
                win = FeedTrendsDebugWindow.RaiseExisting()
                if win is not None:
                    log.info('updated feed trends debug window')
                    win.received_ads(feed_ads())

            self.request_callbacks.call_and_clear()

        def on_ads_error(req = None, resp=None):
            self.requesting = False
            self.request_callbacks.call_and_clear()

        log.info('requesting new ad pool')
        self.source.request_ads(on_ads, on_ads_error)
Example #35
0
class TwitterAccount(social.network):
    service = protocol = 'twitter'
    _dirty = True
    update_mixin_timer = False

    @callbacks.callsback
    def SetStatusMessage(self, message, reply_to=None, callback=None, **k):
        @wx.CallAfter
        def after():
            def error(err):
                callback.error(Exception('Error sending tweet'))

            self.twitter_protocol.on_status(message,
                    reply_id=reply_to,
                    success=callback.success,
                    error=error)

    # console shortcuts

    @property
    def j(self):
        return self.connection.webkitcontroller.evaljs

    @property
    def w(self):
        return self.connection.webkitcontroller.webview

    def update_now(self):
        # if we're in "failed to connect" then just try reconnecting.
        if self.state == self.Statuses.OFFLINE and \
            self.offline_reason == self.Reasons.CONN_FAIL:
                self.Connect()
        else:
            self.twitter_protocol.update()

    @classmethod
    def tray_icon_class(cls):
        from .twitter_gui import TwitterTrayIcon
        return TwitterTrayIcon

    def menu_actions(self, menu):
        from .twitter_gui import menu_actions
        menu_actions(self, menu)

    @property
    def connection(self):
        return self.twitter_protocol

    def update_info(self, **info):
        '''new account info arrives from network/account dialog'''
        for item in ['do_follow_digsby', 'do_tweet_about_digsby']:
            info.pop(item, None)

        # if the user changes the password at runtime, then clear the oauth token
        if info.get('password', None) and self.password and info['password'] != self.password:
            log.critical('clearing oauth token')
            info['oauth_token'] = None

        super(TwitterAccount, self).update_info(**info)
        self.set_account_opts()

    def get_options(self):
        '''return the set of values to be serialized to the server'''

        opts = super(TwitterAccount, self).get_options()
        opts.update({'informed_ach': True, 'post_ach_all': False})
        for k in self.protocol_info()['defaults'].iterkeys():
            v = getattr(self, k)
            if v != self.default(k):
                opts[k] = v

        if self.oauth_token is not None:
            opts['oauth_token'] = self.oauth_token

        api_server = getattr(self, 'api_server', None)
        if api_server is not None:
            opts['api_server'] = api_server

        return opts

    def account_prefs(self):
        return [('autoscroll_when_at_bottom', 'twitter.autoscroll.when_at_bottom', True), ]

    def set_account_opts(self):
        if self.twitter_protocol is not None:
            opts = self.get_account_opts()
            self.twitter_protocol.set_options(opts)

    def on_pref_change(self, *a, **k):
        @wx.CallAfter
        def after():
            try:
                timer = self._preftimer
            except AttributeError:
                timer = self._preftimer = wx.PyTimer(self.set_account_opts)

            timer.StartOneShot(500)

    @property
    def update_frequencies(self):
        return dict((a, getattr(self, a)) for a in
                    ('friends_timeline',
                     'direct_messages',
                     'replies',
                     'search_updatefreq'))

    def __init__(self, **options):
        for key in self.protocol_info()['defaults'].iterkeys():
            try: val = options[key]
            except KeyError: val = self.default(key)
            setattr(self, key, val)

        self.oauth_token = options.pop('oauth_token', None)

        self._on_online = Delegate()

        self.count = None
        self.twitter_protocol = None

        if False:
            # TODO: this will take an hour to come back from idle
            @guithread
            def later():
                self.idle_timer = wx.PyTimer(self.on_idle_timer)
                MINUTE_MS = 60 * 1000 * 60
                self.idle_timer.StartRepeating(30 * MINUTE_MS)

        social.network.__init__(self, **options)

        self.header_funcs = [
            (_('Home'), 'http://twitter.com'),
            (_('Profile'), lambda: wx.LaunchDefaultBrowser('http://twitter.com/' + self.twitter_username)),
            (_('Followers'), 'http://twitter.com/followers'),
            (_('Following'), 'http://twitter.com/following'),
        ]

        import twitter_notifications as twitter_notifications
        twitter_notifications._register_hooks()

        # options that affect first creation of the account.
        # account dialog will call onCreate
        self.do_follow_digsby = options.pop('do_follow_digsby', False)
#        self.do_tweet_about_digsby = options.pop('do_tweet_about_digsby', False)

        for twitter_pref, digsby_pref, default in self.account_prefs():
            common.profile.prefs.link(digsby_pref, self.on_pref_change)

        #self.extra_header_func = (_('Invite Friends'), self.on_invite_friends)

        self.api_server = options.get('api_server', None)

    @property
    def twitter_username(self):
        assert wx.IsMainThread()
        return self.j('account.selfScreenNameLower')

    def on_invite_friends(self):
        '''show a dialog asking ot direct message followers, inviting them to digsby'''

        from .twitter_gui import show_acheivements_dialog
        show_acheivements_dialog(lambda: self.j('inviteFollowers();'))

    def on_idle_timer(self):
        if self.twitter_protocol is not None:
            import gui.native.helpers
            from common import pref
            idle = gui.native.helpers.GetUserIdleTime() > pref('twitter.idle_time', type=int, default=(10 * 60 * 1000))
            val = 'true' if idle else 'false'
            self.j('window.userIdle = %s;' % val)

    def onCreate(self):
        '''called just after this account type is created by the user'''

        if self.do_follow_digsby:
            self._on_online += lambda: self.twitter_protocol.webkitcontroller.JSCall('follow', screen_name='digsby')
#        if self.do_tweet_about_digsby:
#            self._on_online += lambda: self.twitter_protocol.on_status(DIGSBY_TWEET_MESSAGE())
        if SHOW_INVITE_DM_DIALOG_ON_CREATE:
            wx.CallAfter(self.on_invite_friends)

    def get_account_opts(self):
        opts = dict((a, getattr(self, a))
                for a in self.protocol_info()['defaults'])

        for twitter_pref, digsby_pref, default in self.account_prefs():
            opts[twitter_pref] = common.pref(digsby_pref, default)

        opts['demovideo_link'] = DEMO_VIDEO_LINK()

        api_server = getattr(self, 'api_server', None)
        log.warning('api_server: %r', api_server)
        if api_server is not None:
            opts['apiRoot'] = api_server

        return opts

    def on_state_change(self, state):
        log.info('on_state_change: %r', state)

        if state == 'online':
            self.change_state(self.Statuses.ONLINE)
            self._on_online.call_and_clear()

        elif state == 'autherror':
            self.set_offline(self.Reasons.BAD_PASSWORD)

        elif state == 'oautherror':
            if self._should_retry_oauth():
                log.warning('negotiating new OAuth token')
                metrics.event('Twitter OAuth Refresh Token')
                self.Disconnect(set_state=False)
                self.oauth_token = None
                self.Connect()
            else:
                self.set_offline(self.Reasons.BAD_PASSWORD)

        elif state == 'connfail':
            self.set_offline(self.Reasons.CONN_FAIL)
            self.Disconnect(set_state=False)

    _last_oauth_retry_time = 0

    def _should_retry_oauth(self):
        now = time()
        if now - self._last_oauth_retry_time > 60*2:
            self._last_oauth_retry_time = now
            return True

    def Connect(self):
        @guithread
        def _connect():
            log.warning('twitter Connect')
            self.change_state(self.Statuses.CONNECTING)

            if self.twitter_protocol is not None:
                self.twitter_protocol.disconnect()

            self.twitter_protocol = TwitterProtocol(self.username, self._decryptedpw())
            self.json = self.twitter_protocol.json
            self.twitter_protocol.account = self
            self.twitter_protocol.connect(self.get_account_opts())
            e = self.twitter_protocol.events
            e.state_changed += self.on_state_change
            e.on_unread_counts += self.on_unread_counts
            e.on_feeds += self.on_feeds
            e.recent_timeline += self.invalidate_infobox
            e.self_tweet += self.invalidate_infobox
            e.status_update_clicked += self.update_status_window_needed
            e.on_corrupted_database += self.on_corrupted_database
            e.update_social_ids += self.on_update_social_ids
            e.received_whole_update += self.on_received_whole_update

    def _get_database_path(self):
        return self.connection._get_database_path()

    def on_corrupted_database(self):
        '''
        The webkit control window signals this method via "D.rpc('on_corrupted_database');"
        when sqlite indicates that the database is corrupted, or if the openDatabase call
        returns undefined.

        We try to remove the database file entirely, and then reconnect.
        '''

        if getattr(self, 'did_attempt_recovery', False):
            log.info('skipping on_corrupted_database, already done once')
            return

        log.info('corrupted_database detected')
        log.info('free disk space: %r', fileutil.free_disk_space())

        if self.connection:
            dbpath = self._get_database_path()
            log.info('  path to database: %r', dbpath)
            if dbpath:
                result = try_opening_tempfile(os.path.dirname(dbpath))
                log.info('opening tempfile: %r', result)

                self.Disconnect()

                def disconnected():
                    try:
                        log.info('  attempting delete')
                        os.remove(dbpath)
                    except Exception:
                        traceback.print_exc()
                    else:
                        log.info('success! reconnecting')
                        self.Connect()

                    self.did_attempt_recovery = True

                wx.CallLater(1000, disconnected)


    def on_feeds(self, feeds):
        self.invalidate_infobox()

    def observe_count(self, callback):
        self.add_gui_observer(callback, 'count')

    def unobserve_count(self, callback):
        self.remove_gui_observer(callback, 'count')

    def on_unread_counts(self, opts):
        self.setnotify('count', opts.get('total'))
        self.invalidate_infobox()

    def invalidate_infobox(self, *a, **k):
        self.on_update_social_ids()
        self.set_infobox_dirty()

    def on_received_whole_update(self):
        self.did_receive_whole_update = True

    def on_update_social_ids(self):
        if self.state == self.Statuses.ONLINE and getattr(self, 'did_receive_whole_update', False):
            self.twitter_protocol.update_social_ids()

    def set_infobox_dirty(self):
        self._dirty = True
        self.notify('dirty')

    def disconnect(self):
        self.Disconnect()

    def Disconnect(self, *a, **k):
        log.warning('twitter Disconnect')
        if self.twitter_protocol is not None:
            @guithread
            def after():
                p, self.twitter_protocol = self.twitter_protocol, None
                e = p.events
                e.state_changed -= self.on_state_change
                e.on_unread_counts -= self.on_unread_counts
                e.recent_timeline -= self.invalidate_infobox
                e.self_tweet -= self.invalidate_infobox
                e.on_feeds -= self.on_feeds
                e.status_update_clicked -= self.update_status_window_needed
                e.on_corrupted_database -= self.on_corrupted_database
                e.update_social_ids -= self.on_update_social_ids
                e.received_whole_update -= self.on_received_whole_update
                p.disconnect()

        @guithread
        def after2():
            set_state = k.pop('set_state', True)
            if set_state:
                self.set_offline(self.Reasons.NONE)

            self.did_receive_whole_update = False

            success = k.pop('success', None)
            if success is not None:
                success()

    def DefaultAction(self):
        if self.twitter_protocol is not None and self.state == self.Statuses.ONLINE:
            self.twitter_protocol.open_timeline_window()

    def update_status_window_needed(self):
        if common.pref('social.use_global_status', default=False, type=bool):
            wx.GetApp().SetStatusPrompt([self])
        else:
            self.twitter_protocol.open_timeline_window()

    def _enable_unread_counts(self):
        self.connection.set_account_pref('show_unread_count', True)
        self.on_unread_counts({'total':self.count})

    def _disable_unread_counts(self):
        self.connection.set_account_pref('show_unread_count', False)
        self.on_unread_counts({'total':self.count})

    def should_show_unread_counts(self):
        return _get_account_pref(self.username, 'show_unread_count', True)

    def count_text_callback(self, txt):
        if self.should_show_unread_counts() and self.count is not None:
            return txt + (' (%s)' % self.count)
        else:
            return txt

    def mark_all_as_read(self):
        self.connection.mark_all_as_read()
Example #36
0
class AccountManager(Observable, HashedAccounts):

    def __init__(self, profile):
        Observable.__init__(self)

        self.accounts_loaded = False

        self.profile = profile
        self.connected_accounts = ObservableList()
        self.reconnect_timers   = {}

        # holds "cancel" objects from Popups
        self.cancellers = {}

        self.profile.add_observer(self.on_state_change,   'state')
        self.profile.add_observer(self.on_offline_change, 'offline_reason')
        self.profile.add_observer(self.profile_state_changed, 'state')

        import wx
        wx.GetApp().OnBuddyListShown.append(lambda *a, **k: Timer(.25,
            threaded(self.release_accounts), *a, **k).start())

        self._hash = sentinel

        self.got_accounts = False

        self.acct_calls = Delegate()
        self.delay_accounts = True
        self.acct_delay_lock = RLock()

        self._all_acct_hash = {}
        self.last_server_order = None

        self._all_accounts = Storage()
        for type_ in ('im', 'em', 'so'):

            s = Storage(accounts = ObservableList(),
                        old = [])

            setattr(self._all_accounts, type_, s)

            # when the order of accounts changes, or accounts are added or deleted,
            # calls profile.accounts_changed('im', list)
            s.accounts.add_observer(getattr(self, type_ + '_accounts_changed'))

        self.accounts = self._all_accounts.im.accounts
        self.emailaccounts = self._all_accounts.em.accounts
        self.socialaccounts = self._all_accounts.so.accounts

        self.buddywatcher = BuddyWatcher()

        import services.service_provider as sp
        container = sp.ServiceProviderContainer(self.profile)
        container.on_order_changed += self._set_order

    def get_account_for_protocol(self, proto):
        for account in self.connected_im_accounts:
            if getattr(account, 'connection', None) is proto:
                return account

        return None

    def find_account(self, username, protocol):
        for acct in self.all_accounts:
            if acct.username == username and acct.protocol == protocol:
                return acct

        return None

    @property
    def connected_im_accounts(self):
        'A list of all connected IM accounts.'

        accts = list(self.connected_accounts)
        if self.profile in accts:
            accts.remove(self.profile)

        return accts

    def get_im_account(self, username, service):
        for acct in self.connected_accounts:
            conn = acct.connection
            if conn is not None:
                if conn.name == service and conn.username == username:
                    return acct

    @property
    def all_accounts(self):
        'Returns all IM, social, and email accounts in one list.'

        return self.accounts + self.emailaccounts + self.socialaccounts

    def profile_state_changed(self, src, attr, old, new):
        '''
        notify target.
        used to initiate retrieval of accounts from the server
        when the profile goes online
        '''
        assert src == self.profile
        assert attr == 'state'
        from digsby import protocol as digsby_protocol
        if old == new: return


        if new == digsby_protocol.Statuses.SYNC_PREFS:
            with traceguard:
                self.profile.connection.get_accounts(success=lambda stanza:
                                                     self.finished_get(
                                    digsby.accounts.Accounts(stanza.get_query())))

    def _xfrm_sort(self, src, cls, pred):
        '''
        Filters sequence src by predicate pred, and sorts src by its 'order' attribute. Transforms elements
        by calling cls.from_net on each.
        '''
        from common.protocolmeta import protocols, proto_init
        accounts = []
        for x in src:
            if pred(x):
                with traceguard:
                    if x.protocol not in protocols:
                        log.info('don\'t know what kind of account %r is: %r', x.protocol, x)
                        continue

                    if protocols.get(x.protocol, {}).get('smtp_pw_type', False):
                        cls2 = proto_init(x.protocol)
                        acct = cls2.from_net(x)
                    else:
                        acct = cls.from_net(x)

                    accounts.append(acct)

        return sorted(accounts, key=lambda x: src.order.index(x.id) if x.id in src.order else len(src))

    def maybe_delay_accounts(self, cb):
        '''
        if self.delay_accounts is True, calls cb later and returns True.
        '''
        with self.acct_delay_lock:
            if self.delay_accounts:
                self.acct_calls.append(cb)
                return True

    def load_from_identity(self, identity = None):
        '''
        isolating the behavior required to support identities without
        completely trashing the rest of the existing code.
        '''
        self.setnotify('got_accounts', True)
        accounts = identity.load_data('accounts')
        self.replace_local(accounts, do_save=False)
        self.setnotify('accounts_loaded', True)

    def finished_get(self, accounts):
        '''
        this is the response to a get request to the server.
        since we currently only request the entire list,
        the value we get here should be a complete
        representation of what the server has at this moment
        '''
        self.setnotify('got_accounts', True)

        if self.maybe_delay_accounts(lambda: self.finished_get(accounts)):
            return

        if self._all_acct_hash != accounts.calc_hash():
            accounts_debug("!!!!! last known server list is not the same as the server")
            accounts_debug('all_acct_hash: %r', self._all_acct_hash)
            accounts_debug('calc_hash(): %r', accounts.calc_hash())
            self.replace_local(accounts)
        #or if the last recorded server order is not the same as the server now.
        elif self.last_server_order != accounts.order:
            accounts_debug("!!!!! the last recorded server order is not the same as the server now.")
            accounts_debug('last_server_order: %r', self.last_server_order)
            accounts_debug('accounts.order(): %r', accounts.order)
            self.replace_local(accounts)
        #if we made it this far, the server hasn't changed since we last heard
        #from it, therefore, push changes (if any) to the server.
        else:
            accounts_debug('!!!!! update_server')
            self.update_server(accounts)

        self.setnotify('accounts_loaded', True)

    def load_from_local(self, accounts, last_known_server_hash, last_server_order):
        self.setnotify('got_accounts', True)

        if self.maybe_delay_accounts(lambda: self.load_from_local(accounts, last_known_server_hash, last_server_order)):
            return

        self.replace_local(accounts, do_save=False)
        accounts_debug('_all_acct_hash: %r,\nlast_known_server_hash:%r', self._all_acct_hash, last_known_server_hash)
        self._all_acct_hash = last_known_server_hash
        accounts_debug('last_server_order: %r,\nlast_server_order:%r', self.last_server_order, last_server_order)
        self.last_server_order = last_server_order
        self.setnotify('accounts_loaded', True)

    def do_load_local_notification(self):
        if sys.opts.start_offline:
            return # if --online was passed on the command line, don't show a popup

        if self.maybe_delay_accounts(self.do_load_local_notification):
            return

        log.debug('local mode popup')
        fire('error',
             title = _('Digsby is running in "Local Mode"'),
             major =  '',
             minor = _('Changes to Digsby preferences may not synchronize to your other PCs right away'),
             onclick = LOCAL_MODE_URL)

    def replace_local(self, accounts, do_save=True):
        '''
        This function should replace the local list with the server list
        '''
        accounts_debug('replace local')
        # find common simple hashes
        server_list = dict((a.min_hash(), a) for a in accounts)
        accounts_debug('server_list: %r, %r', server_list, accounts)
        local_list = dict((a.min_hash(), a) for a in self)
        accounts_debug('local_list: %r, %r', local_list, list(self))
        common = set(server_list.keys()).intersection(set(local_list.keys()))
        accounts_debug('common: %r', common)

        # update
        update = [server_list[k] for k in common]
        accounts_debug('update: %r', update)

        # delete remainder of local list
        local_del = set(local_list.keys()) - common
        accounts_debug('local_del: %r', local_del)
        delete = [local_list[k] for k in local_del]
        accounts_debug('delete: %r', delete)

        # add remainder of new list
        remote_add = set(server_list.keys()) - common
        accounts_debug('remote_add: %r', remote_add)
        add = [server_list[k] for k in remote_add]
        accounts_debug('add: %r', add)

        # get rid of hashes for things that don't exist anymore.
        # can happen between logins when the server has changed,
        # though it should also require something to have happened locally.
        disappeared = (set(self._all_acct_hash.keys()) - set(a.id for a in accounts)) - set(a.id for a in self)
        for k in disappeared:
            self._all_acct_hash.pop(k, None)

        from digsby.accounts.accounts import Accounts
        add = Accounts(add, order = accounts.order)
        import services.service_provider as sp
        with sp.ServiceProviderContainer(self.profile).rebuilding() as container:
            self.acct_del(delete)
            self.acct_add(add)
            self.acct_update(update)
            container.rebuild(self)
        self.order_set(accounts.order)
        if do_save: self.save_all_info()

    def update_server(self, accounts):
        '''
        This function should do the minmal amount of work required to
        synchronize our local list to the server and other remote clients.
        '''
        accounts_debug('!!!!! update server list')
        server_list = dict((a.id, a) for a in accounts)
        accounts_debug('server_list: %r', server_list)
        local_list = dict((a.id, a) for a in self)
        accounts_debug('local_list: %r', local_list)
        common = set(server_list.keys()).intersection(set(local_list.keys()))
        accounts_debug('common: %r', common)

        #update
        update = []
        for k in common:
            if local_list[k].total_hash() != server_list[k].total_hash():
                accounts_debug("update append: %r != %r", local_list[k].total_hash(), server_list[k].total_hash())
                update.append(local_list[k])
        accounts_debug("update: %r", update)
        #delete remainder of local list
        remote_del = set(server_list.keys()) - common
        delete = []
        for k in remote_del:
            delete.append(server_list[k])
        accounts_debug("delete: %r", delete)
        #add remainder of new list
        remote_add = set(local_list.keys()) - common
        add = []
        for k in remote_add:
            add.append(local_list[k])
        accounts_debug("add: %r", add)

        conn = self.profile.connection
        from digsby.accounts import ADD, DELETE, UPDATE
        order = self.order[:]
        def set_order(*args, **kwargs):
            self.last_server_order = order
        def new_done(func=None):
            done = Delegate()
            if func is not None:
                done += func
            done += set_order
            done += self.save_server_info
            return done

        for a in delete:
            accounts_debug('deleting: %r, a.id: %r', a, a.id)
            def del_id(_, a=a):
                self._all_acct_hash.pop(a.id, None)
            done = new_done(del_id)
            if SAVE_ACCOUNTS:
                conn.set_account(a, action=DELETE, order=order, success=done)

        for _accounts, action in ((update, UPDATE), (add, ADD)):
            for a in _accounts:
                accounts_debug('%s: %r, a.id: %r, a.total_hash(): %r', action, a, a.id, a.total_hash())
                h = a.total_hash()
                def on_update(_, a=a, h=h):
                    a.store_hash(h)
                    self._all_acct_hash[a.id] = h
                done = new_done(on_update)
                if SAVE_ACCOUNTS:
                    conn.set_account(a, action=action, order=order, success=done)

        if self.profile.order != accounts.order:
            done = new_done()
            if SAVE_ACCOUNTS:
                conn.set_accounts(order=order, success=done)

    def update_account(self, account, force=False):
        '''
        Called when an account changes.
        If the account is flagged that we are on the network thread (or at least
        as a result of network changes), then this does nothing.
        Otherwise, the account + new account order are pushed to the server
        in an update.
        '''
        self.save_local_info()
        return
        if not SAVE_ACCOUNTS:
            return
        if account.isflagged(DELETING):
            return
        if force or not account.isflagged(NETWORK_FLAG):
            h = account.total_hash()
            order = self.order[:]
            try:
                def done(*a, **k):
                    import services.service_provider as sp
                    opts = account.get_options()
                    sp.get_provider_for_account(account).update_info(opts)
                    self.last_server_order = order
                    account.store_hash(h)
                    self._all_acct_hash[account.id] = account._total_hash
                    self.save_server_info()
                if not force and h == account._total_hash and order == self.last_server_order \
                    and self._all_acct_hash[account.id] == account._total_hash:
                    return
                self.profile.connection.set_account(account, action = 'update',
                                                    order=order, success=done)
            except Exception:
                traceback.print_exc()

    def acct_del(self, accts):
        '''
        Network account delete.
        '''
        for acct in accts:
            for account in self:
                if acct.id == account.id:
                    acct2 = account
                    break
            else:
                acct2 = None
            if acct2 is not None:
                with self.accounts_flagged(NETWORK_FLAG):
                    if get(acct2, 'enabled', False):
                        acct2.enabled = False

                    self.remove(acct2)
                    self._all_acct_hash.pop(acct2.id, None)

                    from gui import toast
                    for id in getattr(acct2, 'popupids',()):
                        toast.cancel_id(id)

    def _get_order(self):
        self_order = oset([a.id for a in self])
        import services.service_provider as sp
        container = sp.ServiceProviderContainer(self.profile)
        sp_order = oset(container.get_order())
        return list(sp_order | self_order)

    def _set_order(self, new):
        import services.service_provider as sp
        lookup = dict((v,k) for (k,v) in enumerate(new))
        newlen = len(new)
        for k in ('im','em','so'):
            self._all_accounts[k].accounts.sort(key=lambda a: lookup.get(a.id, newlen))
        container = sp.ServiceProviderContainer(self.profile)
        container.set_order(new)

    order = property(_get_order, _set_order)

    def order_set(self, order):
        '''
        An order update, coming from the network.
        '''
        with self.accounts_flagged(NETWORK_FLAG):
            self.order = order[:]
            self.last_server_order = order[:]

    def accounts_set(self, stanza=None, accounts=None):
        '''
        Handle incoming network changes to the accounts list.
        '''
        if stanza is None:
            assert accounts
        else:
            accounts = digsby.accounts.Accounts(stanza.get_query())

        if self.maybe_delay_accounts(lambda: self.accounts_set(accounts=accounts)):
            return

        from digsby.accounts import ADD, UPDATE, DELETE, Accounts

        del_accts = [acct for acct in accounts if acct.action == DELETE]
        add_accts = [acct for acct in accounts if acct.action == ADD or acct.action == None]
        mod_accts = [acct for acct in accounts if acct.action == UPDATE]
        del_accts = Accounts(del_accts, accounts.order)
        add_accts = Accounts(add_accts, accounts.order)
        mod_accts = Accounts(mod_accts, accounts.order)

        import services.service_provider as sp
        with sp.ServiceProviderContainer(self.profile).rebuilding() as container:
            self.acct_del(del_accts)
            self.acct_add(add_accts)
            self.acct_update(mod_accts)
            self.save_all_info()
            container.rebuild(self)
        self.order_set(accounts.order)

    def acct_add(self, accts):
        '''
        Network account add.
        '''
        with self.accounts_flagged(NETWORK_FLAG):
            self.add_all(accts)

    def acct_update(self, accts):
        '''
        Network account update.
        '''
        for acct in accts:
            real_acct = [a for a in self if a.id == acct.id][0]
            info = dict(name = acct.username,
                   password = acct.password,
                   protocol = acct.protocol,
                   id=acct.id,
                   **cPickle.loads(acct.data))
            with real_acct.flagged(NETWORK_FLAG):
                real_acct.update_info(**info)
                real_acct.store_hash()
                self._all_acct_hash[real_acct.id] = real_acct._total_hash

    @contextmanager
    def accounts_flagged(self, flags):
        '''
        convenience function, nests the context managers of all
        account types and flags each with these flags.
        '''
        with nested(*[accts.accounts.flagged(flags)
                  for accts in self._all_accounts.values()]):
            yield

    def release_accounts(self, autologin=False):
        '''
        function to be called to apply all network account changes received from
        the network.
        '''
        with self.acct_delay_lock:
            self.delay_accounts = False
            self.acct_calls.call_and_clear()

        import plugin_manager.plugin_hub as plugin_hub
        plugin_hub.act('digsby.accounts.released.async')
        if autologin and sys.opts.autologin_accounts:
            log.debug('doing autologin')
            self.autologin()

    def autologin(self):
        'Auto login all accounts with autologin enabled.'
        for account in self:
            if is_im_account(account) and getattr(account, 'autologin', False):
                with_traceback(account.connect)

    def __iter__(self):
        #needs update to interleave based on overall order
        return itertools.chain(self.accounts, self.emailaccounts, self.socialaccounts)

    @property
    def reconnect(self):
        return pref('login.reconnect.attempt', False)

    @property
    def reconnect_times(self):
        return pref('login.reconnect.attempt_times', 5)

    def watch_account(self, acct):
        acct.add_observer(self.on_enabled_change, 'enabled')
        acct.add_observer(self.on_state_change, 'state')
        acct.add_observer(self.on_offline_change, 'offline_reason')

    def unwatch_account(self, acct):
        acct.remove_observer(self.on_enabled_change, 'enabled')
        acct.remove_observer(self.on_state_change, 'state')
        acct.remove_observer(self.on_offline_change, 'offline_reason')

    @util.callsback
    def disconnect_all(self, callback = None):
        '''
        Call (D|d)isconnect on all accounts and set_enabled(False) if appropriate.
        After they all go to OFFLINE state, call callback.success.
        '''
        self.disconnect_cb = callback

        for a in self.connected_accounts[:]:
            if a is not self.profile:
                log.debug('      im: Calling "disconnect" on %r', a)
                with traceguard:
                    a.disconnect()

        for a in self.emailaccounts:
            with traceguard:
                if a.state != a.Statuses.OFFLINE:
                    log.debug('   email: Calling "disconnect", "set_enabled(False)" on %r', a)
                    a.set_enabled(False)
                    a.disconnect()

        for a in self.socialaccounts:
            with traceguard:
                if a.state != a.Statuses.OFFLINE:
                    log.debug('  social: Calling "Disconnect", "set_enabled(False)" on %r', a)
                    a.set_enabled(False)
                    a.Disconnect()

        self._check_all_offline()

    def all_active_accounts(self):
        '''
        Like self.connected_accounts but also for email and social accounts
        '''
        accts = self.connected_accounts[:]
        try:
            accts.remove(self.profile)
        except ValueError:
            pass
        return accts

    def _check_all_offline(self):
        if getattr(self, 'disconnect_cb', None) is not None:
            active = self.all_active_accounts()
            if not active:
                # This attribute is ONLY set when disconnect_all is called.
                dccb, self.disconnect_cb = self.disconnect_cb, None
                log.debug('All accounts disconnected, calling disconnect callback: %r', dccb.success)
                import wx
                wx.CallAfter(dccb.success)
            else:
                log.debug('All accounts not disconnected yet, remaining = %r', active)


    def on_state_change(self, src, attr, old, new):
        assert attr in ('state', None)

        hooks.notify('account.state', src, new)

        # Update "connected_accounts" list
        conn = [a for a in self.accounts + [self.profile] if a.connected]
        if self.connected_accounts != conn:
            self.connected_accounts[:] = conn

        if new != StateMixin.Statuses.OFFLINE:
            if src in self.cancellers:
                self.cancellers.pop(src).cancel()
            x = self.reconnect_timers.pop(src,None)

            if x is not None:
                x.stop()

        if new == StateMixin.Statuses.OFFLINE:
            self._on_account_offline(src)
            self._check_all_offline()

        if new == StateMixin.Statuses.ONLINE:
            src.error_count = 0

            # for IM accounts signing on, set their profile.
            if src in self.accounts:
                self.profile.set_formatted_profile(src.connection)

    def _on_account_offline(self, src):
        '''
        Notifies the buddylist sorter than an account is now offline.
        '''
        sorter = getattr(self.profile.blist, 'new_sorter', None)
        if sorter is None:
            return

        if is_im_account(src) or src is self.profile:
            log.info('informing the sorter that (%r, %r) went offline', src.username, src.protocol)
            on_thread('sorter').call(sorter.removeAccount, src.username, src.protocol)

    def on_offline_change(self, src, attr, old, new):
        accounts_debug('%s\'s %s changed from %s to %s', src, attr, old, new)
        assert attr in ('offline_reason', None)
        attr = 'offline_reason'
        if new is None:
            new = getattr(src, attr)
        Reasons = StateMixin.Reasons

        conditions = (old       == new,                          # no change...this function shouldn't have been called in the first place
                      new       == StateMixin.Reasons.NONE,      # normal offline state, doesn't matter
                      )

        if any(conditions):
            return

        log.debug('%s offline reason: %r->%r', src, old, new)


        if getattr(Reasons, 'WILL_RECONNECT', None) in (new, old):
            # something we set - ignore for now
            # new means we set it lower down in this function, old means we're moving out of this state, which should
            # not be an error.
            log.debug('Skipping the rest because reason is WILL_RECONNECT')
            return

        if new == getattr(Reasons, 'BAD_PASSWORD', None) and src is self.profile:
            if not self.profile.has_authorized:
                log.debug('Wrong password for digsbyprofile - not going to reconnect')
                return
            else:
                new = None

        if src is self.profile and not self.profile.loaded:
            log.debug('DigsbyProfile has never connected, not reconnecting after %s state.', new)
            return


        if (is_im_account(src) or src is self.profile) and new not in (Reasons.BAD_PASSWORD, Reasons.NO_MAILBOX,
                       Reasons.OTHER_USER, Reasons.RATE_LIMIT, Reasons.SERVER_ERROR):

            maxerror = (pref('%s.max_error_tolerance' % src.protocol, False) or
                        getattr(src, 'max_error_tolerance', False) or
                        pref('login.max_error_tolerance', False) or

                        4

                        )

            count = src.error_count
            src.error_count += 1
            log.info('%s\'s error_count is now %d.', src, src.error_count,)


            if (self.reconnect or src is self.profile): #and count < maxerror:
                if src in self.reconnect_timers:
                    src.error_count -= 1
                    # account is already scheduled for a reconnect
                    return

                src.setnotifyif('offline_reason', Reasons.WILL_RECONNECT)
                # schedule/attempt reconnect
                reconnect_time = get((1,10,30,300), count, 300)

                if src in self.accounts or src is self.profile:
                    profile_on_return = False
                    if src is self.profile:
                        log.critical('Going to try to reconnect the digsbyprofile. This could get interesting...')
                        reconnect_time, profile_on_return = self.get_profile_reconnect_time()

                    def rct():
                        log.info('Reconnecting %s...', src)
                        try:
                            log.warning('src=%r...setting on_connect to change_state', src)
                            if src is self.profile:
                                def set_online(*a, **k):
                                    src.connection.setnotify('state', StateMixin.Statuses.ONLINE)
                                src.on_connect = set_online
                            if getattr(src, 'connection', None) is None:
                                src._reconnect()
                            else:
                                log.error('There was already a connection for this account that was supposed to reconnect: %r', src)
                        except Exception, e:
                            log.critical('Error while trying to reconnect %s (error was: %r)', src, e)
                            traceback.print_exc()
                        x = self.reconnect_timers.pop(src,None)
                        if x is not None:
                            x.stop()

                    log.info('Starting reconnect timer for %s. Will reconnect in %d seconds %r', src, reconnect_time, self.state_desc(src))
                    self.reconnect_timers[src] = rct_timer = call_later(reconnect_time, threaded(rct))

                    if profile_on_return:
                        def reconnect_profile_now(*a, **k):
                            rct_timer.done_at = 0
                            wakeup_timeout_thread()
                        self.profile.OnReturnFromIdle += reconnect_profile_now

                    return
                else:
                    assert isinstance(src, UpdateMixin)
                    # this is a social or email account -- it has its own timers and things
                    # and will attempt the next update when appropriate
                    return

            log.info('Error count too high, or reconnect disabled.')
        elif not is_im_account(src):
            log.info('%r is not an IM account. skipped a bunch of error_count/reconnect stuff.', src)