CLASSUserActivity crashes client / server at W32_OSLockObject

Hi all,

I’m trying to use the CLASSUserActivity script library to track user detail. Got this from the Sandbox. Haven’t seen anyone else have this error.

Got it from here: http://www-10.lotus.com/ldd/sandbox.nsf/ecc552f1ab6e46e4852568a90055c4cd/c12a2fd2142758b68525688d00708397?OpenDocument

When I run the code, and when the admins try to run it from the server console, pretty much whoever tries to run it, it brings down whatever you’re running it on, be it your client or (eek!) the server.

Thankfully when it’s taken a server down, it bounces back quickly enough, but still…

I’ve walked through it step by painful step, and it bombs out on line 181 of the declarations, here:

puActivity = W32_OSLockObject(Me.rethUserInfo)

Has anyone else come across this?

More importantly (for me…) has anyone got a fix for this? It’s really really really important that we can capture the user activity.

It rather bugs me - no pun intended - that this isn’t an inbuilt class in Notes, to be honest, and really something that IBM needs to include on the next release.

Cheers from Down Under,

Marion.

Subject: CLASSUserActivity crashes client / server at W32_OSLockObject

  1. what exactly kind of boom happens. Some error message/redbox?2) what error handling do you use? I hope not “on error resume next”? and you’d rather use “Option declare” - just in case, if you are not yet using it

  2. don’t ever use “As Any” in the declarations. For me it worked when I changed the declaration:

Declare Sub CopyMemory Lib “KERNEL32” Alias “RtlMoveMemory” ( hpvDest As DBACTIVITY_ENTRY,…

  1. the code will not work for filenames containing accented characters; possibly, I suspect, also for usernames containing them

In any case I did not see your kind of problem. I assume it occurs when providing a db the code signer has no access to or something like this. Make sure you can open the db before providing it to the class.

The error handling in this class is very moderate, but it looks you should not be able to get to the indicated line if you have not manage to retrieve the object.

Just in case - what does your calling routine look like? I take you have not changed the class itself, but you also do not run the sample agent code (it would just read activity from your mail file) - you use the class from your own code.

Subject: CLASSUserActivity crashes client / server at W32_OSLockObject

Hi Normunds and Rob,

Thanks for your input. I guess it would help if I posted the whole code huh? :wink:

Here’s my agent. It’s to log all databases in a directory, and pick up last user activity, so we can weed out unused DBs.

So yes I capture errors, but I do use Resume Next. The actual error itself isn’t being logged as I’m not logging from the class itself, which is bloomin’ annoying. Not a fan of custom classes myself, would much rather just use regular subs and functions in a script library.

OK enough waffling, here’s the code:

=======================================

Option Public

Option Declare

Use “Agent Log”

Use “CLASSUserActivity”

Dim session As NotesSession

Dim dbThis As NotesDatabase

Dim i As Integer

Sub Initialize

Dim lookupView As NotesView

Dim lookupDoc As NotesDocument

Dim lookupItem As NotesItem

Dim mflookupView As NotesView

Dim db As NotesDatabase

Dim dbDir As NotesDbDirectory

Dim doc As NotesDocument

Dim eListDoc As NotesDocument

Dim eListItem As NotesItem

Dim strFolderPortion As String

Dim intPos As Integer



Call StartAgentLog



On Error Goto ErrorHandler



Set session = New NotesSession

Set dbThis = session.CurrentDatabase



'Lookups for mail server list and directory exceptions list

Set lookupView = dbThis.GetView("Lookup")

Set lookupDoc = lookupView.GetDocumentByKey("MailServers", True)

Set lookupItem = lookupDoc.GetFirstItem("LookupValues")

Set mflookupView = dbThis.GetView("MailFileLookup")

Set eListDoc = lookupView.GetDocumentByKey("ExceptionList", True)

Set eListItem = eListDoc.GetFirstItem("LookupValues")



'Get rid of this server's old DB info capture documents

Call DeleteOldDocs



Set dbDir = New NotesDbDirectory(dbThis.Server)

Call WriteAgentLog("dbThis.Server: " & dbThis.Server)

Call WriteAgentLog("dbDir.Name: " & dbDir.Name)

Set db = dbDir.GetFirstDatabase( DATABASE )



'Loop through the databases

'on each server

i = 1

While Not (db Is Nothing)

	

	Print i

	

	If Not(isOnRoot(db)) Then

		'No - extract the left most folder

		strFolderPortion = Left$(db.Filepath, Instr(1, db.Filepath, "\", 5))

		

		'Search for exceptions

		If Isnull(Arraygetindex(eListItem.Values, Cvar(strFolderPortion), 5)) Then		

			'Not in Exceptions List, so try to open the database

			If Not(db.IsOpen) Then Call db.Open(db.Server,db.FilePath)

			

			'Proceed if managed to open database otherwise write to log and continue

			If db.IsOpen Then

				Call CreateNewDoc(db, doc)

				Call LogUserActivity(db, doc)

				Call IsOrphanMailFile(db, doc, mflookupView, lookupItem.Values)

				

				'Save the doc and get the next

				'database in the directory

				Call doc.ComputeWithForm(False, False)

				Call doc.Save(True, False)

’ Else

'RestartHere:

’ Call WriteAgentLog( "*** Unable to Open " & db.FilePath & ", " & db.Title)

			End If

			'^^ End of checking that database can be opened				

		End If

	End If

	

	'Get the next database to have a gander at

	Set db = dbDir.GetNextDatabase

	i = i+1

Wend



'All done, stop the Agent Log

Call StopAgentLog	

Exit Sub

Errorhandler:

Call ErrorAgentLog("Initialize: " & Err() & ", " & Error() & " " & "at line " & Erl())

Resume Next

End Sub

Sub DeleteOldDocs

Dim coll As NotesDocumentCollection

Dim colldoc As NotesDocument

Dim tempdoc As NotesDocument

Dim strSearch As String



On Error Goto ErrorHandler



'Get rid of the old documents for this server

strSearch = |Form="DatabaseCapture" & Deleted=("" : "No") & db_server="| & dbThis.Server & |"|

Set coll = dbThis.Search(strSearch, Nothing, 0)



If coll.Count > 0 Then

	'Loop through the documents to

	'ensure they all get deleted properly

	Set colldoc = coll.GetFirstDocument

	Do Until colldoc Is Nothing

		Set tempdoc = coll.GetNextDocument(colldoc)

		Call colldoc.Remove(True)

		Set colldoc = tempdoc

	Loop

End If



Exit Sub

Errorhandler:

Call ErrorAgentLog("DeleteOldDocs: " & Err() & ", " & Error() & " " & "at line " & Erl())

Resume Next

End Sub

Sub CreateNewDoc(db As NotesDatabase, doc As NotesDocument)

Dim linkitem As NotesRichTextItem



On Error Goto ErrorHandler



'Create a new Database Capture document

Set doc = New NotesDocument(dbThis)

With doc

	Call .ReplaceItemValue("Form", "DatabaseCapture")

	Call .ReplaceItemValue("db_server", dbThis.Server)

	Call .ReplaceItemValue("db_path", db.FilePath)

	Call .ReplaceItemValue("db_title", db.Title)

	Call .ReplaceItemValue("db_size", db.Size)

	Call .ReplaceItemValue("db_notesurl", db.NotesURL)

	Call .ReplaceItemValue("db_replicaid", db.ReplicaID)

End With



'Add a DB Link

Set linkitem = New NotesRichTextItem(doc, "db_link")

Call linkitem.AppendDocLink( db, "Click to open " & db.Title )



Exit Sub

Errorhandler:

Call ErrorAgentLog("CreateNewDoc: " & Err() & ", " & Error() & " " & "at line " & Erl())

Resume Next

End Sub

Sub LogUserActivity(db As NotesDatabase, doc As NotesDocument)

If Not (i = 65) Then

	

'User Activity

	Dim ua As NotesUserActivity

	Dim uae As NotesUserActivityEntry

	

	On Error Goto ErrorHandler

'On Error Resume Next

	

	Set ua = New NotesUserActivity(db)

	

	If ua.HasUserActivity Then

		Set uae = ua.GetNthUserActivityEntry(1)

'	Call .ReplaceItemValue("rtime", Cdat(uae.Time))

		doc.rtime = uae.Time

		doc.rname = uae.UserName

	End If

	

End If



Exit Sub

Errorhandler:

Call ErrorAgentLog("LogUserActivity: " & Err() & ", " & Error() & " " & "at line " & Erl())

Resume Next

End Sub

Sub LogUserActivity(db As NotesDatabase, doc As NotesDocument)

If Not (i = 65) Then

	

'User Activity

	Dim ua As NotesUserActivity

	Dim uae As NotesUserActivityEntry

	

	On Error Goto ErrorHandler

'On Error Resume Next

	

	Set ua = New NotesUserActivity(db)

	

	If ua.HasUserActivity Then

		Set uae = ua.GetNthUserActivityEntry(1)

'	Call .ReplaceItemValue("rtime", Cdat(uae.Time))

		doc.rtime = uae.Time

		doc.rname = uae.UserName

	End If

	

End If



Exit Sub

Errorhandler:

Call ErrorAgentLog("LogUserActivity: " & Err() & ", " & Error() & " " & "at line " & Erl())

Resume Next

End Sub

Function isOnRoot(db As NotesDatabase) As Boolean

On Error Goto ErrorHandler



Dim intPos As Integer

isOnRoot = False

intPos = Instr(1, db.Filepath, "\", 5)

If Not(intPos <> 0) Then isOnRoot = True



Exit Function

Errorhandler:

Call ErrorAgentLog("isOnRoot: " & Err() & ", " & Error() & " " & "at line " & Erl())

Resume Next

End Function

Function isMailServer(db As NotesDatabase, varLookupValues As Variant) As Boolean

On Error Goto ErrorHandler



isMailServer = False

If Not(Isnull(Arraygetindex(varLookupValues, Cvar(db.Server), 5))) Then isMailServer = True

Exit Function



Exit Function

Errorhandler:

Call ErrorAgentLog("isMailServer: " & Err() & ", " & Error() & " " & "at line " & Erl())

Resume Next

End Function

=======================================

And here’s the third-party UserActivity class:

Option Public

Option Explicit

Const MAXALPHATIMEDATE = 80

Type TIMEDATE

Innard1 As Long

Innard2 As Long

End Type

Type DBACTIVITY

First As TIMEDATE

Last As TIMEDATE

Uses As Long

Reads As Long

Writes As Long

PrevDayUses As Long

PrevDayReads As Long

PrevDayWrites As Long

PrevWeekUses As Long

PrevWeekReads As Long

PrevWeekWrites As Long

PrevMonthUses As Long

PrevMonthReads As Long

PrevMonthWrites As Long

End Type

Type DBACTIVITY_ENTRY

Time As TIMEDATE

Reads As Integer

Writes As Integer

UserNameOffset As Long

End Type

Declare Function W32_NSFDbOpen Lib “nnotes.dll” Alias “NSFDbOpen” ( Byval dbName As String, hDb As Long) As Integer

Declare Function W32_NSFDbClose Lib “nnotes.dll” Alias “NSFDbClose” ( Byval hDb As Long) As Integer

Declare Function W32_NSFDbGetUserActivity Lib “nnotes.dll” Alias “NSFDbGetUserActivity” ( Byval hDB As Long, Byval flags As Long, retDbActivity As DBActivity, rethUserInfo As Long, retUserCount As Long) As Integer

Declare Function W32_OSLockObject Lib “nnotes.dll” Alias “OSLockObject” ( Byval handle) As Long

Declare Sub OSUnlockObject Lib “NNOTES.DLL” Alias “OSUnlockObject” (Byval handle)

Declare Sub W32_OSMemFree Lib “NNOTES.DLL” Alias “OSMemFree” (Byval handle)

Declare Sub CopyMemory Lib “KERNEL32” Alias “RtlMoveMemory” ( hpvDest As Any, Byval hpvSource As Long, Byval cbCopy As Long)

Declare Sub CopyMemoryString Lib “KERNEL32” Alias “RtlMoveMemory” ( Byval hpvDest As String, Byval hpvSource As Long, Byval cbCopy As Long)

Declare Sub ConvertTIMEDATEToText Lib “NNOTES.DLL” Alias “ConvertTIMEDATEToText” (Byval IntlFormat As Long,Byval TextFormat As Long, actTIMEDATE As TIMEDATE, Byval retTextBuffer As String,Byval TextBufferLength As Integer,retTextLength As Integer)

Class NotesUserActivityEntry

Public UserName As String

Public Reads As Long

Public Writes As Long

Public Time As String

End Class

Class NotesUserActivity

Private hDb As Long

Private pDbActivity As DBACTIVITY

Private rethUserInfo As Long

Private retUserCount As Long

Private prvdb As NotesDatabase

Private flgHasActivity As Integer



Sub Delete

	If Me.flgHasActivity Then Call W32_OSMemFree(rethUserInfo)

	If hDb <> 0 Then Call W32_NSFDbClose(hDb)   

End Sub



Sub New (inpNotesDatabase As NotesDatabase)

	

	Dim sDatabase As String

	Dim rc As Integer     

	

	Me.flgHasActivity = False

	

      'Get a valid NotesDatabase to the specified database         

	If inpNotesDatabase Is Nothing Then 

		Error 14101, "NotesUserActivity: Database Object is invalid"

		Exit Sub

	End If

	

	Set prvdb = New NotesDatabase(inpNotesDatabase.Server, inpNotesDatabase.FilePath)

	

	If prvdb.Server = "" Then

		sdatabase = prvdb.filepath

	Else

		sdatabase = prvdb.server + "!!" + prvdb.filepath

	End If

	

      'Open the target database

	rc = W32_NSFDbOpen(sDatabase,Me.hDb)

	If rc <> 0 Then

		Me.flgHasActivity = False

	End If

	

      'Get the Summary User information

	rc = W32_NSFDbGetUserActivity(Me.hDb, &h0, Me.pDbActivity, Me.rethUserInfo, Me.retUserCount)

	If rc <> 0 Then

		Me.flgHasActivity = False

	End If      

	Me.flgHasActivity = True

End Sub 



 'Global Times

Public Function First As String

	First = ConvertTIMEtoText(pDbActivity.First)     

End Function

Public Function Last As String

	Last = ConvertTIMEtoText(pDbActivity.Last)     

End Function



 'Total summary

Public Function Uses As Long

	Uses = pDbActivity.Uses  

End Function

Public Function Reads As Long

	Reads = pDbActivity.Reads          

End Function

Public Function Writes As Long

	Writes = pDbActivity.Writes

End Function



 'Day summary

Public Function PrevDayUses As Long

	PrevDayUses = pDbActivity.PrevDayUses     

End Function

Public Function PrevDayReads As Long

	PrevDayReads = pDbActivity.PrevDayReads     

End Function

Public Function PrevDayWrites As Long

	PrevDayWrites = pDbActivity.PrevDayReads

End Function



 'Week summary

Public Function PrevWeekUses As Long

	PrevWeekUses = pDbActivity.PrevWeekUses          

End Function

Public Function PrevWeekReads As Long

	PrevWeekReads = pDbActivity.PrevWeekReads

End Function

Public Function PrevWeekWrites As Long

	PrevWeekWrites= pDbActivity.PrevWeekWrites

End Function



 'Month summary

Public Function PrevMonthUses As Long

	PrevMonthUses = pDbActivity.PrevMonthUses               

End Function

Public Function PrevMonthReads As Long

	PrevMonthReads = pDbActivity.PrevMonthReads     

End Function

Public Function PrevMonthWrites As Long

	PrevMonthWrites = pDbActivity.PrevMonthWrites

End Function



Public Function UserActivityCount As Long

	UserActivityCount = retUserCount

End Function



Public Function HasUserActivity As Integer

	HasUserActivity = Me.flgHasActivity

End Function



Public Function Parent As NotesDatabase

	Set Parent = prvdb

End Function



Public Function GetNthUserActivityEntry(inpEntry As Long) As NotesUserActivityEntry

	Dim puActivity As Long

	Dim lEntry As Long

	Dim puActivityEntry As DBACTIVITY_ENTRY

	Dim StructureOffset As Long

	Dim UsernameOffset As Long

	Dim spUsername As String * 256

	Dim sUsername As String

	Dim nuae As New NotesUserActivityEntry

	

	lEntry = inpEntry - 1

	

	If Not Me.flgHasActivity Then Error 14104, "NotesUserActivity: No activity available"

	

	If lEntry > Me.retUserCount Or lEntry < 0 Then

		Error 14103, "NotesUserActivity: Subscript out of range."

	End If

	

      'Lock the structure get the required entry

	puActivity = W32_OSLockObject(Me.rethUserInfo)

	StructureOffset = puActivity + (Lenb(puActivityEntry) * lEntry)

	Call CopyMemory (puActivityEntry, StructureOffset, Len(puActivityEntry))

	

      'Load the User name for the Activity Structure

	UsernameOffset = puActivity + puActivityEntry.UserNameOffset

	spUsername = Space(256)

	Call CopyMemoryString(spUsername, UsernameOffset,Lenb(spUsername))

	sUserName = Left(spUsername, Instr(spUsername, Chr(0)) - 1)

	

	With nuae

		.UserName = sUserName

		.Reads = puActivityEntry.Reads

		.Writes = puActivityEntry.Writes

		.Time = ConvertTIMEtoText(puActivityEntry.Time)

	End With

	

	Call OSUnlockObject(rethUserInfo)

	

	Set GetNthUserActivityEntry = nuae

	

End Function

End Class

Function ConvertTIMEtoText(TIMESTRUCT As TIMEDATE) As String

Dim spTime As String * MAXALPHATIMEDATE

Dim retLength As Integer



spTime = Space(MAXALPHATIMEDATE)

Call ConvertTIMEDATEToText (&h0,&h0, TIMESTRUCT, spTime,MAXALPHATIMEDATE,retLength)

ConvertTIMEtoText = Left(spTime,retLength)

End Function

Subject: RE: CLASSUserActivity crashes client / server at W32_OSLockObject

ok, you do not answer the questions, and I could ask more - do you get any errors recorded before you crash… but but. whatever.

  1. you use more advanced version of “on error resume next” - you write down the error and continue… I would not.

  2. class is done IMO a bit crummy, because when it does not find any activity info it still has “flgHasActivity”= True; and further if you try get the first info entry it’s checking will fail and it will let you continue with a request for the 1st record even if none are returned

  3. returning to my previous post - do not use “As Any” in declarations

  4. have fun

Subject: RE: CLASSUserActivity crashes client / server at W32_OSLockObject

Hi Normunds,

Sorry I was trying to respond to you and Rob at the same time, d’oh!

  1. what exactly kind of boom happens. Some error message/redbox?

Got a screen capture this time. I’m not using Native Notes, so here’s a link to it: http://img.photobucket.com/albums/v99/PhilAndMarion/error.jpg

  1. what error handling do you use? I hope not “on error resume next”? and you’d rather use “Option declare” - just in case, if you are not yet using it

I do write to an agent log, but even when I tried putting in error capturing, even just in a messagebox, in the class, it didn’t bring the messagebox up, it just went belly-up.

  1. don’t ever use “As Any” in the declarations. For me it worked when I changed the declaration:

Declare Sub CopyMemory Lib “KERNEL32” Alias “RtlMoveMemory” ( hpvDest As DBACTIVITY_ENTRY,…

I will try that right now! :slight_smile:

  1. the code will not work for filenames containing accented characters; possibly, I suspect, also for usernames containing them

I don’t believe I’ve ever seen any such characters in our filenames.

For your latest questions:

  1. you use more advanced version of “on error resume next” - you write down the error and continue… I would not.

Would you just exit the whole agent? I’m just trying to get it to log errors and go to the next database to log info on.

  1. class is done IMO a bit crummy, because when it does not find any activity info it still has “flgHasActivity”= True; and further if you try get the first info entry it’s checking will fail and it will let you continue with a request for the 1st record even if none are returned

Hmm, hadn’t spotted that, will look into that after I’ve changed the “As Any” thing. Thank you!

  1. returning to my previous post - do not use “As Any” in declarations

Doing that next, will report back.

  1. have fun

Heh… always the goal! :wink:

Cheers, and thank you ever so much! Will post again soon…

Marion.

Subject: RE: CLASSUserActivity crashes client / server at W32_OSLockObject

  1. you use more advanced version of “on error resume next” - you write down the error and continue… I would not.

Would you just exit the whole agent? I’m just trying to get it to log errors and go to the next database to log info on.

** I’d write down the error and start from the next db, not just have “resume next” at the end of error handling that means you are going to do something with parameters/objects that have just thrown an error

  1. class is done IMO a bit crummy, because when it does not find any activity info it still has “flgHasActivity”= True; and further if you try get the first info entry it’s checking will fail and it will let you continue with a request for the 1st record even if none are returned

Hmm, hadn’t spotted that, will look into that after I’ve changed the “As Any” thing. Thank you!

**This one is more important than the last one. This one gives you the belly-up.

  1. returning to my previous post - do not use “As Any” in declarations

Doing that next, will report back.

**BTW there seems to be one more error in original class. IMO it should say:

Call CopyMemory (puActivityEntry, StructureOffset, Lenb(puActivityEntry))

i.e. “Lenb” instead of “Len”

Subject: RE: CLASSUserActivity crashes client / server at W32_OSLockObject

Hi Normunds,

Thank you for your continued help and input on this.

OK… I changed the “Len” to “Lenb” and set a “RestartHere” point in the code, so once it throws an error, it just picks up the next database and continues. Well, in theory. :wink:

This is a c+p of the properties box for the field picked up when user activity is not logged:

Field Name: rname

Data Type: Text List

Data Length: 16 bytes

Seq Num: 1

Dup Item ID: 0

Field Flags: SUMMARY

“ØéìØéìØéìØéì”

Lovely huh?

So it’s still picking something up even when there’s no user activity. That’s not the bad thing though… am still getting the RBOD. :frowning:

Here’s where the “New” is called. I’ve added some exit subs

====================================

Sub New (inpNotesDatabase As NotesDatabase)

	

	Dim sDatabase As String

	Dim rc As Integer     

	

	Me.flgHasActivity = False

	

      'Get a valid NotesDatabase to the specified database         

	If inpNotesDatabase Is Nothing Then 

		Error 14101, "NotesUserActivity: Database Object is invalid"

		Me.flgHasActivity = False

		Exit Sub   <=== added

	End If

	

	Set prvdb = New NotesDatabase(inpNotesDatabase.Server, inpNotesDatabase.FilePath)

	

	If prvdb.Server = "" Then

		sdatabase = prvdb.filepath

	Else

		sdatabase = prvdb.server + "!!" + prvdb.filepath

	End If

	

      'Open the target database

	rc = W32_NSFDbOpen(sDatabase,Me.hDb)

	If rc <> 0 Then

		Me.flgHasActivity = False

		Exit Sub   <=== added

	End If

	

      'Get the Summary User information

	rc = W32_NSFDbGetUserActivity(Me.hDb, &h0, Me.pDbActivity, Me.rethUserInfo, Me.retUserCount)

	If rc <> 0 Then

		Me.flgHasActivity = False

		Exit Sub   <=== added

	End If

	

	Me.flgHasActivity = True

	

End Sub

====================================

Still getting the same LookupHandle RBOD. Am at a complete loss now. :frowning:

Cheers,

Marion.

Subject: RE: CLASSUserActivity crashes client / server at W32_OSLockObject

I already mentioned that the class will not protect you against looking for the 1st entry if none was returned and will turn belly up if you tried that. I assume that the call to get user activity can be successful even if there are 0 records returned. So why not check Me.retUserCount (as the sample script is doing) before getting in with the request for #1 entry?

Subject: RE: CLASSUserActivity crashes client / server at W32_OSLockObject

Hmmm… actually I thought that was already covered by this piece of the class code:

Public Function GetNthUserActivityEntry(inpEntry As Long) As NotesUserActivityEntry

	

	If Me.flgHasActivity = True Then

		

		Dim puActivity As Long

		Dim lEntry As Long

		Dim puActivityEntry As DBACTIVITY_ENTRY

		Dim StructureOffset As Long

		Dim UsernameOffset As Long

		Dim spUsername As String * 256

		Dim sUsername As String

		Dim nuae As New NotesUserActivityEntry

		

		lEntry = inpEntry - 1

		

		If Not Me.flgHasActivity Then

			Error 14104, "NotesUserActivity: No activity available"

			Exit Function

		End If

		

		If lEntry > Me.retUserCount Or lEntry < 0 Then

			Error 14103, "NotesUserActivity: Subscript out of range."

			Exit Function

		End If

Am stuck. :frowning:

Thanks,

Marion.

Subject: RE: CLASSUserActivity crashes client / server at W32_OSLockObject

in my understanding, if you call the routine with inpEntry=1 then the line lEntry = inpEntry - 1 will set lEntry==0

and the line:

If lEntry > Me.retUserCount Or lEntry < 0 Then

will let the routine continue. And you get your crash. I thinlk I mentioned this already twice in this thread.

Why can’t you check for the number of users returned or even better set Me.flgHasActivity = False if Me.retUserCount=0 ?

Subject: SOLVED: CLASSUserActivity crashes client / server at W32_OSLockObject

First of all, many many grateful thanks to Normunds Kalnberzins for his wonderful help over the past what-seems-like-forever time!

I made one crucial tweak in the code, to come up with this:

===========================

Public Function GetNthUserActivityEntry(inpEntry As Long) As NotesUserActivityEntry

	

	If Me.flgHasActivity = True Then

		

		Dim puActivity As Long

		Dim lEntry As Long

		Dim puActivityEntry As DBACTIVITY_ENTRY

		Dim StructureOffset As Long

		Dim UsernameOffset As Long

		Dim spUsername As String * 256

		Dim sUsername As String

		Dim nuae As New NotesUserActivityEntry

		

		lEntry = inpEntry - 1

		

		If Not Me.flgHasActivity Then

			Error 14104, "NotesUserActivity: No activity available"

			Exit Function

		End If

		

		If lEntry > Me.retUserCount Or lEntry < 0 Then

			Error 14103, "NotesUserActivity: Subscript out of range."

			Exit Function

		End If

		

		If Me.retUserCount=0 Then   '<== This "if" statement has been added

			Me.flgHasActivity = False

			Exit Function

		End If

		

		'Lock the structure get the required entry

		puActivity = W32_OSLockObject(Me.rethUserInfo)

		StructureOffset = puActivity + (Lenb(puActivityEntry) * lEntry)

		Call CopyMemory (puActivityEntry, StructureOffset, Lenb(puActivityEntry))

		

		'Load the User name for the Activity Structure

		UsernameOffset = puActivity + puActivityEntry.UserNameOffset

		spUsername = Space(256)

		Call CopyMemoryString(spUsername, UsernameOffset,Lenb(spUsername))

		sUserName = Left(spUsername, Instr(spUsername, Chr(0)) - 1)

		

		With nuae

			.UserName = sUserName

			.Reads = puActivityEntry.Reads

			.Writes = puActivityEntry.Writes

			.Time = ConvertTIMEtoText(puActivityEntry.Time)

		End With

		

		Call OSUnlockObject(rethUserInfo)

		

		Set GetNthUserActivityEntry = nuae

		

	End If

	

End Function

===========================

And I still hate custom classes. Sure they’re a very time-saving (cough in theory) and sophisticated way of doing code, but when you have to say so many times in so many places “hey if there’s no user activity, don’t go on”, it gets to be a bit much.

Anyway, many thanks again to Normunds, you were a supreme help!

Cheers from the Land of the Long White Cloud,

Marion.

Subject: RE: SOLVED: CLASSUserActivity crashes client / server at W32_OSLockObject

I’m glad you managed. What I really meant was to add the code

If Me.retUserCount=0 then Me.flgHasActivity = False

in the constructor of the class (Sub New). So in one place we find out if we have any info or not.

And I’m 100% supporter of custom classes. The problem probably is in your expectancy - taking a sandbox code and putting it in a production application without changes and/or understanding how it is coded is a bit utopian not to say dangerous. It will save your time by giving you the idea and some draft code, but you still need to “take the ownership” of it.

Subject: Updated version of ClassUserActivity

We’ve completely reviewed the code in the NotesUser Activity class (CLASSUserActivity) application and released an update of it.

Improvements in ‘release 2’ include:

  • Numerous fixes and enhancements to the original code.

  • Correction to data types and memory alignments when making calls to Lotus C-API code from LotusScript.

  • Implementation of error handling throughout the code.

  • Enhancement to sample agent for collection and displaying retrieved user activity information.

You can download the update from our website at:

Hope it helps!

Regards,

Alex

Subject: CLASSUserActivity crashes client / server at W32_OSLockObject

Hello

I’m also using this class for quite some time and indeed had my crashes in the beginning. The Sandbox entry has a several comments explaining issues in the code. In my current class these comments have been implemented and so far this has removed all crashes.

Below you find the current declaration section of the class library, with the changes, this might also help for you.

Const MAXALPHATIMEDATE = 80

Const MAXENTRIES=500

Type TIMEDATE

Innard1 As Long

Innard2 As Long

End Type

Type DBACTIVITY

First As TIMEDATE

Last As TIMEDATE

Uses As Long

Reads As Long

Writes As Long

PrevDayUses As Long

PrevDayReads As Long

PrevDayWrites As Long

PrevWeekUses As Long

PrevWeekReads As Long

PrevWeekWrites As Long

PrevMonthUses As Long

PrevMonthReads As Long

PrevMonthWrites As Long

End Type

Type DBACTIVITY_ENTRY

Time As TIMEDATE

Reads As Integer

Writes As Integer

UserNameOffset As Long

End Type

Declare Function W32_NSFDbOpen Lib “nnotes.dll” Alias “NSFDbOpen” ( Byval dbName As String, hDb As Long) As Integer

Declare Function W32_NSFDbClose Lib “nnotes.dll” Alias “NSFDbClose” ( Byval hDb As Long) As Integer

Declare Function W32_NSFDbGetUserActivity Lib “nnotes.dll” Alias “NSFDbGetUserActivity” ( Byval hDB As Long, Byval flags As Long, retDbActivity As DBActivity, rethUserInfo As Long, retUserCount As Long) As Integer

Declare Function W32_OSLockObject Lib “nnotes.dll” Alias “OSLockObject” ( Byval handle) As Long

Declare Sub OSUnlockObject Lib “NNOTES.DLL” Alias “OSUnlockObject” (Byval handle)

Declare Sub W32_OSMemFree Lib “NNOTES.DLL” Alias “OSMemFree” (Byval handle)

’ Declare Sub CopyMemory Lib “KERNEL32” Alias “RtlMoveMemory” ( hpvDest As Any, Byval hpvSource As Long, Byval cbCopy As Long)

Declare Sub CopyMemory Lib “KERNEL32” Alias “RtlMoveMemory” ( hpvDest As Any, hpvSource As Any, Byval cbCopy As Long) ’ Sandbox comment modification

’ Declare Sub CopyMemoryString Lib “KERNEL32” Alias “RtlMoveMemory” ( Byval hpvDest As String, Byval hpvSource As Long, Byval cbCopy As Long)

Declare Sub CopyMemoryString Lib “KERNEL32” Alias “RtlMoveMemory” ( Byval hpvDest As Lmbcs String, Byval hpvSource As Long, Byval cbCopy As Long) ’ Modification from sandbox comment for dom 6

Declare Sub ConvertTIMEDATEToText Lib “NNOTES.DLL” Alias “ConvertTIMEDATEToText” (Byval IntlFormat As Long,Byval TextFormat As Long, actTIMEDATE As TIMEDATE, Byval retTextBuffer As String,Byval TextBufferLength As Integer,retTextLength As Integer)

Class NotesUserActivityEntry

Public UserName As String

Public Reads As Long

Public Writes As Long

Public Time As String

End Class

Class NotesUserActivity

Private hDb As Long

Private pDbActivity As DBACTIVITY

Private rethUserInfo As Long

Private retUserCount As Long

Private prvdb As NotesDatabase

Private flgHasActivity As Integer



Sub Delete

	If Me.flgHasActivity Then Call W32_OSMemFree(rethUserInfo)

	If hDb <> 0 Then Call W32_NSFDbClose(hDb)   

End Sub



Sub New (inpNotesDatabase As NotesDatabase)

	

	Dim sDatabase As String

	Dim rc As Integer     

	

	Me.flgHasActivity = False

	

      'Get a valid NotesDatabase to the specified database         

	If inpNotesDatabase Is Nothing Then 

		Error 14101, "NotesUserActivity: Database Object is invalid"

		Exit Sub

	End If

	

	Set prvdb = New NotesDatabase(inpNotesDatabase.Server, inpNotesDatabase.FilePath)

	

	If prvdb.Server = ""   Then 

		sdatabase = prvdb.filepath

	Else

		sdatabase = prvdb.server + "!!" + prvdb.filepath

	End If

	

	Me.flgHasActivity = False

      'Open the target database

	rc = W32_NSFDbOpen(sDatabase,Me.hDb)

	If rc = 0 Then

		rc = W32_NSFDbGetUserActivity(Me.hDb, &h0, Me.pDbActivity, Me.rethUserInfo, Me.retUserCount)

		If rc = 0 Then Me.flgHasActivity = True	

	End If

End Sub 



 'Global Times

Public Function First As String

	First = ConvertTIMEtoText(pDbActivity.First)     

End Function

Public Function Last As String

	Last = ConvertTIMEtoText(pDbActivity.Last)     

End Function



 'Total summary

Public Function Uses As Long

	Uses = pDbActivity.Uses  

End Function

Public Function Reads As Long

	Reads = pDbActivity.Reads          

End Function

Public Function Writes As Long

	Writes = pDbActivity.Writes

End Function



 'Day summary

Public Function PrevDayUses As Long

	PrevDayUses = pDbActivity.PrevDayUses     

End Function

Public Function PrevDayReads As Long

	PrevDayReads = pDbActivity.PrevDayReads     

End Function

Public Function PrevDayWrites As Long

	PrevDayWrites = pDbActivity.PrevDayReads

End Function



 'Week summary

Public Function PrevWeekUses As Long

	PrevWeekUses = pDbActivity.PrevWeekUses          

End Function

Public Function PrevWeekReads As Long

	PrevWeekReads = pDbActivity.PrevWeekReads

End Function

Public Function PrevWeekWrites As Long

	PrevWeekWrites= pDbActivity.PrevWeekWrites

End Function



 'Month summary

Public Function PrevMonthUses As Long

	PrevMonthUses = pDbActivity.PrevMonthUses               

End Function

Public Function PrevMonthReads As Long

	PrevMonthReads = pDbActivity.PrevMonthReads     

End Function

Public Function PrevMonthWrites As Long

	PrevMonthWrites = pDbActivity.PrevMonthWrites

End Function



Public Function UserActivityCount As Long

	UserActivityCount = retUserCount

End Function



Public Function HasUserActivity As Integer

	HasUserActivity = Me.flgHasActivity

End Function



Public Function Parent As NotesDatabase

	Set Parent = prvdb

End Function



Public Function GetNthUserActivityEntry(inpEntry As Long) As NotesUserActivityEntry

	Dim puActivity As Long

	Dim lEntry As Long

	Dim puActivityEntry As DBACTIVITY_ENTRY

	Dim StructureOffset As Long

	Dim UsernameOffset As Long

	Dim spUsername As String * 256

	Dim sUsername As String

	Dim nuae As New NotesUserActivityEntry

	

	lEntry = inpEntry - 1

	

	If Not Me.flgHasActivity Then Error 14104, "NotesUserActivity: No activity available"

	

	If lEntry > Me.retUserCount Or lEntry < 0 Then

		Error 14103, "NotesUserActivity: Subscript out of range."

	End If

	

      'Lock the structure get the required entry

	puActivity = W32_OSLockObject(Me.rethUserInfo)

	StructureOffset = puActivity + (Lenb(puActivityEntry) * lEntry)

’ Call CopyMemory (puActivityEntry, StructureOffset, Len(puActivityEntry))

	Call CopyMemory (puActivityEntry, Byval StructureOffset, Len(puActivityEntry)) ' Modification based on Sandbox comment

	

      'Load the User name for the Activity Structure

	UsernameOffset = puActivity + puActivityEntry.UserNameOffset

	spUsername = Space(256)

	Call CopyMemoryString(spUsername, UsernameOffset,Lenb(spUsername))

’ Call CopyMemoryString(spUsername, usernameOffset, Lenb(spUsername))

	sUserName = Left(spUsername, Instr(spUsername, Chr(0)) - 1)

	

	With nuae

		.UserName = sUserName

		.Reads = puActivityEntry.Reads

		.Writes = puActivityEntry.Writes

		.Time = ConvertTIMEtoText(puActivityEntry.Time)

	End With

	

	Call OSUnlockObject(rethUserInfo)

	

	Set GetNthUserActivityEntry = nuae

	

End Function

End Class

Subject: CLASSUserActivity crashes client / server at W32_OSLockObject

I guess that the actual failure is “Access Violation” which normally means you are addressing a field that points to an invalid address (like 000000000).

You have to make sure that the Me.retUserInfo is pointing to something that actually exists. Maybe you can add a test round this code.

I have noticed that there are a few applications in the Sandbox, that fail when you run them. Another example was: ScriptSorter. So I ended up writing my own ScriptSorter; that’s why I did not applaude when Andre Guirard showed that function at the Lotusphere :wink: (Sorry Andre).

Subject: *double post