added explicit 'run' management, will basis for SCORM-compliant API, e.g. for yeepa

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1705 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2007-05-01 09:04:42 +00:00
parent b79c15740d
commit 747e65e265
3 changed files with 117 additions and 20 deletions

View file

@ -6,6 +6,11 @@ User tracking in the loops framework
>>> from cybertools.tracking.btree import TrackingStorage
Let's create a tracking storage and store a few tracks in it. A track
is basically an arbitrary mapping. (In the following examples we
ignore the ``run`` argument and use a 0 value for it; thus we are just
working with the current run of a task.)
>>> tracks = TrackingStorage()
>>> tracks.saveUserTrack('a001', 0, 'u1', {'somekey': 'somevalue'})
'0000001'
@ -21,6 +26,10 @@ User tracking in the loops framework
>>> [str(id) for id in tracks.getTaskIds()]
['a001']
We can query the tracking storage using the tracks' metadata. These
are mapped to btree indexes, so we get fast access to the resulting
track data.
>>> tracks.query(taskId='a001')
[<Track ['a001', 1, 'u1', '...-...-... ...:...']: {'somekey': 'somevalue'}>]
@ -38,14 +47,14 @@ What happens if we store more than on record for one set of keys?
>>> [t.data for t in t2]
[{'somekey': 'somevalue'}, {'somekey': 'newvalue'}]
It is also possible to retrieve the last entry for a set of keys directliy:
It is also possible to retrieve the last entry for a set of keys directly.
>>> tracks.getLastUserTrack('a001', 0, 'u1')
<Track ['a001', 1, 'u1', ...]: {'somekey': 'newvalue'}>
Instead of creating a new track object for each call one can also replace
an existing one (if present). The replaced entry is always the last one
for a given set of keys:
for a given set of keys.
>>> tracks.saveUserTrack('a001', 0, 'u1', {'somekey': 'newvalue2'}, replace=True)
'0000003'
@ -63,6 +72,30 @@ The tracks of a tracking store may be reindexed:
>>> tracks.reindexTracks()
Runs
----
We may explicitly start a new run for a given task. This will also replace
the task's current run.
>>> tracks.startRun('a001')
3
>>> tracks.saveUserTrack('a001', 0, 'u1', {'k1': 'value1'})
'0000005'
>>> tracks.getLastUserTrack('a001', 0, 'u1')
<Track ['a001', 3, 'u1', ...]: {'k1': 'value1'}>
We still have access to older runs.
>>> tracks.getLastUserTrack('a001', 1, 'u1')
<Track ['a001', 1, 'u1', ...]: {'somekey': 'newvalue2'}>
We can also retrieve a run object with the run's data.
>>> run = tracks.getRun(3)
>>> run
<Run 3, ..., ..., False>
Fin de partie
=============

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
# Copyright (c) 2007 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
@ -17,7 +17,7 @@
#
"""
BTree-based implementation of user tracking.
BTree-based implementation of user interaction tracking.
$Id$
"""
@ -29,10 +29,27 @@ from zope.app.container.btree import BTreeContainer
from zope.index.field import FieldIndex
from persistent import Persistent
from BTrees import OOBTree
from BTrees import OOBTree, IOBTree
from BTrees.IFBTree import intersection
from interfaces import ITrackingStorage, ITrack
from interfaces import IRun, ITrackingStorage, ITrack
class Run(object):
implements(IRun)
id = start = end = 0
finished = False
def __init__(self, id):
self.id = id
def __repr__(self):
return '<Run %s>' % ', '.join((str(self.id),
timeStamp2ISO(self.start),
timeStamp2ISO(self.end),
str(self.finished)))
class TrackingStorage(BTreeContainer):
@ -48,6 +65,7 @@ class TrackingStorage(BTreeContainer):
self.indexes = OOBTree.OOBTree()
for idx in self.indexAttributes:
self.indexes[idx] = FieldIndex()
self.runs = IOBTree.IOBTree()
self.currentRuns = OOBTree.OOBTree()
self.taskUsers = OOBTree.OOBTree()
@ -56,18 +74,36 @@ class TrackingStorage(BTreeContainer):
def startRun(self, taskId):
self.runId += 1
self.currentRuns[taskId] = self.runId
return self.runId
def stopRun(self, taskId):
runId = self.currentRuns.get(taskId)
if runId:
del self.currentRuns[taskId]
runId = self.runId
self.currentRuns[taskId] = runId
run = self.runs[runId] = Run(runId)
run.start = run.end = getTimeStamp()
return runId
def stopRun(self, taskId, runId=0, finish=True):
currentRun = self.currentRuns.get(taskId)
runId = runId or currentRun
if runId and runId == currentRun:
del self.currentRuns[taskId]
run = self.getRun(runId)
if run is not None:
run.end = getTimeStamp()
run.finished = finish
return runId
def getRun(self, runId=0, taskId=None):
if taskId and not runId:
runId = self.currentRuns.get(taskId)
if runId:
return self.runs.get(runId)
def saveUserTrack(self, taskId, runId, userName, data, replace=False):
if not runId:
runId = self.currentRuns.get(taskId) or self.startRun(taskId)
run = self.getRun(runId)
if run is None:
raise ValueError('Invalid run: %i.' % runId)
run.end = getTimeStamp()
trackNum = 0
if replace:
track = self.getLastUserTrack(taskId, runId, userName)
@ -79,8 +115,7 @@ class TrackingStorage(BTreeContainer):
self.trackNum += 1
trackNum = self.trackNum
trackId = self.idFromNum(trackNum)
timeStamp = int(time.time())
track = Track(taskId, runId, userName, timeStamp, data)
track = Track(taskId, runId, userName, getTimeStamp(), data)
self[trackId] = track
self.indexTrack(trackNum, track)
return trackId
@ -161,3 +196,5 @@ class Track(Persistent):
def timeStamp2ISO(ts):
return time.strftime('%Y-%m-%d %H:%M', time.gmtime(ts))
def getTimeStamp():
return int(time.time())

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
# Copyright (c) 2007 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
@ -28,6 +28,16 @@ from zope import schema
# user interaction tracking
class IRun(Interface):
""" A set of interactions, sort of a session.
"""
id = Attribute('A unique integer that identifies a run within a tracking storage')
start = Attribute('Timestamp of run creation')
end = Attribute('Timestamp of last interaction or of stopping the run')
finished = Attribute('Boolean that is set to True if run was finished explicitly')
class ITrack(Interface):
""" Result data from the interactions of a user with an task.
"""
@ -41,16 +51,33 @@ class ITrackingStorage(Interface):
"""
def startRun(taskId):
""" Creates a new run for the task given and return its id.
""" Create a new run for the task given and return its id.
"""
def stopRun(taskId):
""" Remove the current run entry for the task given.
def stopRun(taskId, runId=0, finish=True):
""" Stop/finish a run.
If the runId is 0 use the task's current run.
If the run is the task's current one remove it from the set
of current runs.
Set the run's ``finished`` flag to the value of the ``finish``
argument.
Return the real runId.
"""
def saveUserTrack(taskId, runId, userName, data):
def getRun(runId, taskId=None):
""" Return the run object identified by ``runId``. Return None
if there is no corresponding run.
If ``runId`` is 0 and a ``taskId`` is given return the
current run of the task.
"""
def saveUserTrack(taskId, runId, userName, data, replace=False):
""" Save the data given (typically a mapping object) to the user track
corresponding to the user name, task id, and run id given.
If the runId is 0 use the task's current run.
If the ``replace`` flag is set, the new track replaces the last
one for the given set of keys.
Return the new track item's id.
"""
def query(**criteria):