diff --git a/browser/node.py b/browser/node.py index f04e12a..b32c1ee 100644 --- a/browser/node.py +++ b/browser/node.py @@ -59,6 +59,7 @@ from loops import util from loops.util import _ from loops.browser.common import BaseView from loops.browser.concept import ConceptView +from loops.organize.interfaces import IPresence from loops.organize.tracking import access from loops.versioning.util import getVersion @@ -135,9 +136,21 @@ class NodeView(BaseView): icon='cybertools.icons/user.png', url=url, priority=10) + if self.globalOptions('organize.showPresence'): + cm.register('portlet_right', 'presence', title=_(u'Presence'), + subMacro=node_macros.macros['presence'], + icon='cybertools.icons/group.png', + priority=11) # force early portlet registrations by target by setting up target view self.virtualTarget + @Lazy + def usersPresent(self): + presence = component.getUtility(IPresence) + presence.update(self.request.principal.id) + data = presence.getPresentUsers() + return data + @Lazy def view(self): name = self.request.get('loops.viewName', '') or self.context.viewName diff --git a/browser/node_macros.pt b/browser/node_macros.pt index 8944c32..0fc3916 100644 --- a/browser/node_macros.pt +++ b/browser/node_macros.pt @@ -236,6 +236,13 @@ + + +
+ + + +
\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -158,6 +158,9 @@ msgstr "Aktuelles Objekt zu Lesezeichen hinzufügen" msgid "Remove from favorites" msgstr "Aus Lesezeichen entfernen" +msgid "Presence" +msgstr "Anwesenheit" + msgid "Actions" msgstr "Aktionen" diff --git a/organize/README.txt b/organize/README.txt index 99b15fc..af84133 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -415,6 +415,13 @@ Send Email to Members u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.97\n\n' +Show Presence of Other Users +============================ + + >>> from loops.organize.presence import Presence + >>> component.provideUtility(Presence()) + + Fin de partie ============= diff --git a/organize/auth.py b/organize/auth.py index b1819df..24690a1 100644 --- a/organize/auth.py +++ b/organize/auth.py @@ -34,6 +34,9 @@ from zope.app.security.interfaces import IAuthentication from zope import schema from zope.traversing.api import getParent +from cybertools.browser.loops.auth import LoopsSessionCredentialsPlugin \ + as BaseSessionCredentialsPlugin +from loops.organize.interfaces import IPresence from loops.util import _ @@ -98,6 +101,14 @@ class PersonBasedAuthenticator(Persistent, Contained): return InternalPrincipal(self, login) +class LoopsSessionCredentialsPlugin(BaseSessionCredentialsPlugin): + + def logout(self, request): + presence = component.getUtility(IPresence) + presence.removePresentUser(request.principal.id) + super(LoopsSessionCredentialsPlugin, self).logout(request) + + class InternalPrincipal(object): def __init__(self, auth, login): diff --git a/organize/configure.zcml b/organize/configure.zcml index 2a6c454..7e075ab 100644 --- a/organize/configure.zcml +++ b/organize/configure.zcml @@ -50,6 +50,11 @@ + + + + diff --git a/organize/interfaces.py b/organize/interfaces.py index 7b59a0b..6e35ea0 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -177,3 +177,34 @@ class IAllocated(IConceptSchema): )), default='standard', required=True) + + +# presence + +class IPresence(Interface): + """ Utility for getting information about active principals, + mapping principal.id to timestamp of last activity. + """ + + def update(self, principalId): + """ Update Dictionary of active users, by calling addPresentUser(); + automaticly check for inactive users by calling + removeInactiveUsers(). + """ + + def addPresentUser(self, principalId): + """Add a user to dictionary of active users. + """ + + def removeInactiveUsers(self): + """ Remove a user from dictionary of active users if user + didn't interact for last min_until_logout minutes. + """ + + def getPresentUsers(self): + """ Return list of titles of active users. + """ + + def removePresentUser(self, principalId): + """ Remove user from dictionary of active users, e.g. when user logs out. + """ diff --git a/organize/presence.py b/organize/presence.py new file mode 100644 index 0000000..b1b951f --- /dev/null +++ b/organize/presence.py @@ -0,0 +1,68 @@ +# +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Utility for collecting information about logged-in/active users. + +Author: Hannes Plattner. + +$Id$ +""" + +from zope.interface import implements +from zope.cachedescriptors.property import Lazy + +from cybertools.meta.interfaces import IOptions +from cybertools.util.date import getTimeStamp +from loops.organize.interfaces import IPresence +from loops.organize import util + + +class Presence(object): + + implements(IPresence) + + def __init__(self, min_until_logout=10, presentUsers=None): + self.min_until_logout = min_until_logout + self.presentUsers = presentUsers or {} + + def update(self, principalId): + self.addPresentUser(principalId) + self.removeInactiveUsers() + + def addPresentUser(self, principalId): + self.presentUsers[principalId] = getTimeStamp() + + def removeInactiveUsers(self): + toDelete = [] + for id, timeStamp in self.presentUsers.iteritems(): + if (getTimeStamp() - timeStamp) > (self.min_until_logout*60): + toDelete.append(id) + for id in toDelete: + if id in self.presentUsers: + del self.presentUsers[id] + + def getPresentUsers(self): + ret = [] + for id, timeStamp in self.presentUsers.iteritems(): + ret.append(util.getPrincipalForUserId(id).title) + return ret + + def removePresentUser(self, principalId): + if principalId in self.presentUsers: + del self.presentUsers[principalId]