diff --git a/organize/tracking/access.py b/organize/tracking/access.py index 6ab816e..fad16fc 100644 --- a/organize/tracking/access.py +++ b/organize/tracking/access.py @@ -22,10 +22,13 @@ Recording changes to loops objects. $Id$ """ +import logging import os +import time +import transaction from zope.app.publication.interfaces import IEndRequestEvent -from zope.interface import Interface +from zope.interface import Interface, implements from zope.cachedescriptors.property import Lazy from zope.component import adapter from zope.security.proxy import removeSecurityProxy @@ -35,13 +38,14 @@ from cybertools.tracking.btree import Track, getTimeStamp from cybertools.tracking.interfaces import ITrack from cybertools.tracking.logfile import Logger, loggers from loops.interfaces import ILoopsObject -from loops.organize.party import getPersonForUser -from loops.security.common import getCurrentPrincipal +from loops.organize.tracking.base import BaseRecordManager from loops import util +# logging + +logfile_option = 'organize.tracking.logfile' request_key = 'loops.organize.tracking.access' -loggers_key = 'loops.access' fields = { '001': ('principal', 'node', 'target', 'view', 'params'), @@ -71,11 +75,12 @@ def logAccess(event): logger = loggers.get(loggers_key) if not logger: options = IOptions(context.getLoopsRoot()) - logfile = options('organize.tracking.logfile') + logfile = options(logfile_option) if not logfile: return - path = os.path.join(util.getVarDirectory(), logfile[0]) - logger = loggers[loggers_key] = Logger(loggers_key, path) + fn = logfile[0] + path = os.path.join(util.getVarDirectory(), fn) + logger = loggers[fn] = Logger(fn, path) logger.log(marshall(data)) @@ -85,3 +90,88 @@ def marshall(data): for key in fields[version]: values.append(data.get(key) or '') return ';'.join(values) + + +# record manager + +class AccessRecordManager(BaseRecordManager): + + storageName = 'access' + + def __init__(self, context, request): + self.context = context + self.request = request + + @Lazy + def logfile(self): + value = self.options(logfile_option) + return value and value[0] or None + + @Lazy + def valid(self): + return self.storage is not None and self.logfile + + def loadRecordsFromLog(self): + if not self.valid: + return + fn = self.logfile + path = os.path.join(util.getVarDirectory(), fn) + logger = loggers.get(fn) + if not logger: + logger = loggers[fn] = Logger(fn, path) + if not os.path.exists(path): + return + lf = open(path, 'r') + for idx, line in enumerate(lf): + self.processLogRecord(idx, line) + lf.close() + transaction.commit() + logger.doRollover() + + def processLogRecord(self, idx, line): + if not line: + return + values = line.split(';') + timeString = values.pop(0) + version = values.pop(0) + if version not in fields: + logging.getLogger('AccessRecordManager').warn( + 'Undefined logging record version %r on record %i.' + % (version, idx)) + return + if len(values) != len(fields[version]): + logging.getLogger('AccessRecordManager').warn( + 'Length of record %i does not match version %r.' + % (idx, version)) + return + data = {} + for idx, field in enumerate(fields[version]): + data[field] = values[idx] + timeStamp = timeStringToTimeStamp(timeString) + personId = self.getPersonId(data['principal']) + taskId = data['target'] or data['node'] + existing = self.storage.query(taskId=taskId, userName=self.personId, + timeStamp=timeStamp) + for track in existing: + if track.data == data: # has been recorded already + return + self.storage.saveUserTrack(taskId, 0, personId, data, + timeStamp=timeStamp) + + +class IAccessRecord(ITrack): + + pass + + +class AccessRecord(Track): + + implements(IAccessRecord) + + typeName = 'AccessRecord' + + +def timeStringToTimeStamp(timeString): + s, decimal = timeString.split(',') + t = time.strptime(s, '%Y-%m-%d %H:%M:%S') + return int(time.mktime(t)) diff --git a/organize/tracking/base.py b/organize/tracking/base.py new file mode 100644 index 0000000..ab3c2c6 --- /dev/null +++ b/organize/tracking/base.py @@ -0,0 +1,71 @@ +# +# Copyright (c) 2008 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 +# + +""" +Base class(es) for track/record managers. + +$Id$ +""" + +from zope.cachedescriptors.property import Lazy + +from cybertools.meta.interfaces import IOptions +from loops.organize.party import getPersonForUser +from loops.organize.util import getPrincipalForUserId +from loops.security.common import getCurrentPrincipal +from loops import util + + +class BaseRecordManager(object): + + context = None + valid = True + storageName = None + + @Lazy + def options(self): + #return IOptions(self.context) + return IOptions(self.loopsRoot) + + @Lazy + def loopsRoot(self): + return self.context.getLoopsRoot() + + @Lazy + def storage(self): + records = self.loopsRoot.getRecordManager() + if records is not None: + return records.get(self.storageName) + return None + + @Lazy + def personId(self): + return self.getPersonId() + + def getPersonId(self, userId=None): + if userId is None: + principal = getCurrentPrincipal() + else: + principal = getPrincipalForUserId(userId, context=self.context) + if principal is not None: + person = getPersonForUser(self.context, principal=principal) + if person is None: + return principal.id + return util.getUidForObject(person) + return None + diff --git a/organize/tracking/change.py b/organize/tracking/change.py index fb9a195..b3e3057 100644 --- a/organize/tracking/change.py +++ b/organize/tracking/change.py @@ -37,24 +37,20 @@ from loops.resource import ResourceManager from loops.interfaces import IAssignmentEvent, IDeassignmentEvent from loops.interfaces import ILoopsObject from loops.organize.party import getPersonForUser +from loops.organize.tracking.base import BaseRecordManager from loops.security.common import getCurrentPrincipal from loops import util -class ChangeManager(object): +class ChangeManager(BaseRecordManager): - context = None + storageName = 'changes' def __init__(self, context): if isinstance(context, (ConceptManager, ResourceManager)): return self.context = context - @Lazy - def options(self): - #return IOptions(self.context) - return IOptions(self.loopsRoot) - @Lazy def valid(self): return (not (self.context is None or @@ -62,27 +58,6 @@ class ChangeManager(object): self.personId is None) and self.options('organize.tracking.changes')) - @Lazy - def loopsRoot(self): - return self.context.getLoopsRoot() - - @Lazy - def storage(self): - records = self.loopsRoot.getRecordManager() - if records is not None: - return records.get('changes') - return None - - @Lazy - def personId(self): - principal = getCurrentPrincipal() - if principal is not None: - person = getPersonForUser(self.context, principal=principal) - if person is None: - return principal.id - return util.getUidForObject(person) - return None - def recordModification(self, action='modify', **kw): if not self.valid: return @@ -117,10 +92,6 @@ class ChangeRecord(Track): def recordModification(obj, event): ChangeManager(obj).recordModification() -#@adapter(ILoopsObject, IObjectCreatedEvent) -#def recordCreation(obj, event): -# ChangeManager(obj).recordModification('create') - @adapter(ILoopsObject, IObjectAddedEvent) def recordAdding(obj, event): ChangeManager(obj).recordModification('add') diff --git a/organize/tracking/configure.zcml b/organize/tracking/configure.zcml index d1b4fea..567501c 100644 --- a/organize/tracking/configure.zcml +++ b/organize/tracking/configure.zcml @@ -26,6 +26,13 @@ + + diff --git a/organize/tracking/setup.py b/organize/tracking/setup.py index 6f4fbdf..7a847e7 100644 --- a/organize/tracking/setup.py +++ b/organize/tracking/setup.py @@ -27,6 +27,7 @@ from zope.interface import implements, Interface from cybertools.tracking.btree import TrackingStorage from loops.organize.tracking.change import ChangeRecord +from loops.organize.tracking.access import AccessRecord from loops.setup import SetupManager as BaseSetupManager @@ -36,3 +37,5 @@ class SetupManager(BaseSetupManager): records = self.context.getRecordManager() changes = self.addObject(records, TrackingStorage, 'changes', trackFactory=ChangeRecord) + access = self.addObject(records, TrackingStorage, 'access', + trackFactory=AccessRecord) diff --git a/organize/util.py b/organize/util.py index d304342..ddcc10d 100644 --- a/organize/util.py +++ b/organize/util.py @@ -84,6 +84,14 @@ def getInternalPrincipal(id, context=None): raise PrincipalLookupError(id) +def getPrincipalForUserId(id, context=None): + auth = component.getUtility(IAuthentication, context=context) + try: + return auth.getPrincipal(id) + except PrincipalLookupError: + return None + + def getTrackingStorage(obj, name): records = obj.getLoopsRoot().getRecordManager() if records is not None: