Lotus Notes Attachment Reporter

Windows specific forum
Amundo
Enthusiast
Enthusiast
Posts: 191
Joined: Thu Feb 16, 2006 1:41 am
Location: New Zealand

Lotus Notes Attachment Reporter

Post by Amundo »

Greetings all,

Wasn't sure where to post this, so I ended up here. Needed a way to see whether users at work were stashing stuff inside their mailboxes, so went on a search to find a report that would tell me. Started trying to use the COM interface, but fell short of the mark as far as being able to tell what size the email attachment actually used up on the server (Notes/Domino kindly compresses attachments, and will readily tell you how big the ORIGINAL filesize is, but if you want to know more, well...). After much hunting for information, found snippets of C/C++ and LotusScript code that would return the compressed filesize.

Of course, everyone I've told, and I'm sure most anyone reading this, will ask "Why the hell didn't you just use LotusScript or the C/C++ API supplied by Lotus/IBM?". I'm gonna give the same, tired cliched(?) answer - because it is there! Well, because I wanted to see if it could be done..in PB, of course! And I wouldn't be posting here if it couldn't!!

Have only tested on WinXP Pro w/SP2 and Notes 6.5.3 client and Version 6.5 of the C-API, but I'm sure the functions I use are applicable to at least Release 5.x and above. Please don't laugh at my code, this is my first PB program. Program will prompt firstly for a textfile with NSF names in, followed by an output filename. Then it will open a console window and spit its report out to it, as well as the output file specified.

Please be brutal with your criticisms!

(Question about using STATIC as opposed to PROTECTED: Does STATIC allocate storage in the heap and PROTECTED on the stack, hence I wouldn't see any difference in compiled filesizes?)

Oops! Forgot to specify, you'll need the Lotus C-API LIB file to compile, and the Lotus Notes client installed to run. So, I'm guessing 99.9% of you won't be abel to do much with this :(


Code: Select all

; TO DO:
;======
;
; Enable browsing of servers and multiple selection of NSFs instead of reading them from a file
;
; Reporting on person other than owner of NSF, in the From column (better reporting of TO/FROM info)
;
; Tidier, more structured (more OOP-like?) code, break down into more procedures
;
; Be able to choose which USER to login with, and/or specify which NOTES.INI to use

EnableExplicit

;-Constants
;{
#MAX_PATH_LEN						= 1024
#NSF_INFO_SIZE					= 128
#INFOPARSE_TITLE				= 0
#INFOPARSE_CATEGORIES		= 1
#INFOPARSE_CLASS				= 2
#INFOPARSE_DESIGN_CLASS	= 3
#NOTE_CLASS_DOCUMENT		= $0001
#MAXPATH								= 256
#NOTEID_LENGTH					= 6
#DIRFILE								= 0
#LOGFILE								= 1
#MAX_STRING_LEN					= 255
#MIN_STRING_LEN					= 1
#MAXALPHATIMEDATE				=	80+1
#ERR_ITEM_NOT_FOUND			=	$200+34

; 	/*	Data Type Definitions from NSFDATA.H follow */
;
; /*	Class definitions.  Classes are defined To be the
; 	"generic" classes of Data type that the internal formula computation
; 	mechanism recognizes when doing recalcs. */

#CLASS_NOCOMPUTE					=	(0 << 8)
#CLASS_ERROR							=	(1 << 8)
#CLASS_UNAVAILABLE				=	(2 << 8)
#CLASS_NUMBER							=	(3 << 8)
#CLASS_TIME								=	(4 << 8)
#CLASS_TEXT								=	(5 << 8)
#CLASS_FORMULA						=	(6 << 8)
#CLASS_USERID							=	(7 << 8)

#CLASS_MASK								=	$ff00

; /*  All datatypes below are passed To NSF in either host (machine-specific
; 	byte ordering And padding) Or canonical form (Intel 86 packed form).
; 	The format of each datatype, As it is passed To And from NSF functions,
; 	is listed below in the comment field Next To each of the Data types.
; 	(This host/canonical issue is Not applicable To Intel86 machines,
; 	because on that machine, they are the same And no conversion is required).
; 	On all other machines, use the ODS subroutine package To perform
; 	conversions of those datatypes in canonical format before they can
; 	be interpreted. */
;
; /*	"Computable" Data Types */

#TYPE_ERROR								=	0 + #CLASS_ERROR			;/* Host form */
#TYPE_UNAVAILABLE					=	0 + #CLASS_UNAVAILABLE	;/* Host form */
#TYPE_TEXT								=	0 + #CLASS_TEXT			;/* Host form */
#TYPE_TEXT_LIST						=	1 + #CLASS_TEXT			;/* Host form */
#TYPE_NUMBER							=	0 + #CLASS_NUMBER		;/* Host form */
#TYPE_NUMBER_RANGE				=	1 + #CLASS_NUMBER		;/* Host form */
#TYPE_TIME								=	0 + #CLASS_TIME			;/* Host form */
#TYPE_TIME_RANGE					=	1 + #CLASS_TIME			;/* Host form */
#TYPE_FORMULA							=	0 + #CLASS_FORMULA		;/* Canonical form */
#TYPE_USERID							=	0 + #CLASS_USERID		;/* Host form */

; /*	"Non-Computable" Data Types */

#TYPE_INVALID_OR_UNKNOWN	=	0 + #CLASS_NOCOMPUTE		;/* Host form */
#TYPE_COMPOSITE						=	1 + #CLASS_NOCOMPUTE		;/* Canonical form, >64K handled by more than one item of same name concatenated */
#TYPE_COLLATION						=	2 + #CLASS_NOCOMPUTE		;/* Canonical form */
#TYPE_OBJECT							=	3 + #CLASS_NOCOMPUTE		;/* Canonical form */
#TYPE_NOTEREF_LIST				=	4 + #CLASS_NOCOMPUTE		;/* Host form */
#TYPE_VIEW_FORMAT					=	5 + #CLASS_NOCOMPUTE		;/* Canonical form */
#TYPE_ICON								=	6 + #CLASS_NOCOMPUTE		;/* Canonical form */
#TYPE_NOTELINK_LIST				=	7 + #CLASS_NOCOMPUTE		;/* Host form */
#TYPE_SIGNATURE						=	8 + #CLASS_NOCOMPUTE		;/* Canonical form */
#TYPE_SEAL								=	9 + #CLASS_NOCOMPUTE		;/* Canonical form */
#TYPE_SEALDATA						=	10 + #CLASS_NOCOMPUTE	;/* Canonical form */
#TYPE_SEAL_LIST						=	11 + #CLASS_NOCOMPUTE	;/* Canonical form */
#TYPE_HIGHLIGHTS					=	12 + #CLASS_NOCOMPUTE	;/* Host form */
#TYPE_WORKSHEET_DATA			=	13 + #CLASS_NOCOMPUTE	;/* Used ONLY by Chronicle product */
; 															/* Canonical form */
#TYPE_USERDATA						=	14 + #CLASS_NOCOMPUTE	;/* Arbitrary format Data - see format below */
; 															/* Canonical form */
#TYPE_QUERY								=	15 + #CLASS_NOCOMPUTE	;/* Saved query CD records; Canonical form */
#TYPE_ACTION							=	16 + #CLASS_NOCOMPUTE	;/* Saved action CD records; Canonical form */
#TYPE_ASSISTANT_INFO			=	17 + #CLASS_NOCOMPUTE	;/* Saved assistant info */
#TYPE_VIEWMAP_DATASET			=	18 + #CLASS_NOCOMPUTE	;/* Saved ViewMap dataset; Canonical form */
#TYPE_VIEWMAP_LAYOUT			=	19 + #CLASS_NOCOMPUTE	;/* Saved ViewMap layout; Canonical form */
#TYPE_LSOBJECT						=	20 + #CLASS_NOCOMPUTE	;/* Saved LS Object code For an agent.	*/
#TYPE_HTML								=	21 + #CLASS_NOCOMPUTE	;/* LMBCS-encoded HTML, >64K handled by more than one item of same name concatenated */
#TYPE_SCHED_LIST					=	22 + #CLASS_NOCOMPUTE	;/* Busy time schedule entries list; Host form */
#TYPE_CALENDAR_FORMAT			=	24 + #CLASS_NOCOMPUTE	;/* Canonical form */
#TYPE_MIME_PART						=	25 + #CLASS_NOCOMPUTE	;/* MIME body part; Canonical form */
#TYPE_RFC822_TEXT			 		=	2 + #CLASS_TEXT			;/* RFC822( RFC2047) message header; Canonical form */
;}

;-Enumerations
;{
Enumeration
	#NOCOMP
	#HUFF
	#LZ1
EndEnumeration
;}

;-Structures
;{
Structure BlockID
	hPool.l
	Block.l
EndStructure

Structure TIMEDATE
	Innards.LONG[2]
EndStructure

Structure DBID
	x.TIMEDATE
EndStructure

Structure NOTEID
	x.LONG
EndStructure

Structure GLOBALINSTANCEID
	File.DBID
	Note.TIMEDATE
	NoteID.NOTEID
EndStructure

Structure ORIGINATORID
	File.DBID
	Note.TIMEDATE
	Sequence.LONG
	SequenceTime.TIMEDATE
EndStructure

Structure SEARCH_MATCH
	ID.GLOBALINSTANCEID
	OriginatorID.ORIGINATORID
	NoteClass.WORD
	SERetFlags.BYTE
	Privileges.BYTE
	SummaryLength.WORD
EndStructure

Structure TFMT
	Date.b					;						/* Date Display Format */
	Time.b					;						/* Time Display Format */
	Zone.b				;						/* Time Zone Display Format */
	Struc.b			; 					/* Overall Date/Time Structure */
EndStructure

Structure RANGE
	ListEntries.w						;/* list entries following */
	RangeEntries.w					;/* range entries following */
EndStructure
;}

;-Globals
;{
;*************************************************************
;*************************************************************
;****                                                     ****
;**** Change following lines depending on Local or Server ****
;****                                                     ****
;*************************************************************
;*************************************************************
;Global Server$ = "DOMINO1"				; Domino server (common) name
Global Server$ = ""							; Local server

Global SearchMatch.SEARCH_MATCH, ParameterCount
Global hNote.l, NoteID.l, pDB, hr
Global cDoc ; count documents in database

Global DFhandle
Global LFhandle

Global FullPath${#MAXPATH} = Space(#MAXPATH)
Global FileName${#MAXPATH} = Space(#MAXPATH)
Global ItemName${#MAXPATH} = Space(#MAXPATH)
Global ItemNameLen = Len(ItemName$)
Global NSFName${#MAX_PATH_LEN} = Space(#MAX_PATH_LEN)
Global Path${#MAX_PATH_LEN} = Space(#MAX_PATH_LEN)
;Global ViewID.l		= 0
;Global ViewName${256}

Global StandardFile$ = ""   ; set initial file+path to display
Global Pattern$ = ""
Global Pattern = 0    ; use the first of the three possible patterns as standard
Global File$ = ""
Global SubjectLine${#MAX_STRING_LEN} = Space(#MAX_STRING_LEN)
Global DeliveredDate${#MAXALPHATIMEDATE} = Space(#MAXALPHATIMEDATE)
Global FromLine${#MAX_STRING_LEN} = Space(#MAX_STRING_LEN)
;}

;-Import "C:\_Lotus_C_API_65\notesapi\lib\mswin32\notes.lib"
;{
Import "C:\_Lotus_C_API_65\notesapi\lib\mswin32\notes.lib"
	NSFItemInfo(a.l, b.s, c.l, d.l, e.l, f.l, g.l) As "_NSFItemInfo@28"
	NSFItemInfoNext(a.l, b.l, c.l, d.s, e.l, f.l, g.l, h.l, i.l) As "_NSFItemInfoNext@36"
	NotesInit() As "_NotesInit@0"
	NotesTerm() As "_NotesTerm@0"
	;	OSGetDataDirectory(a.l) As "_OSGetDataDirectory@4"
	NSFDbClose(a.l) As "_NSFDbClose@4"
	;	NSFDbDirGet(a.l, b.s) As "_NSFDbDirGet@8"
	;	NSFDbInfoGet(a.l, b.l) As "_NSFDbInfoGet@8"
	NSFDbOpen(a.l, *b) As "_NSFDbOpen@8"
	;	NSFDbPathGet(a.l, b.s, c.s) As "_NSFDbPathGet@12"
	;	NSFDbInfoParse(a.l, b.l, c.l, d.l) As "_NSFDbInfoParse@16"
	NSFSearch(hDB.l, hFormula.l, ViewTitle.l, SearchFlags.l, NoteClassMask.l, Since.l, action_routine.l, EnumRoutineParameter.l, retUntil.l) As "_NSFSearch@36"
	NSFNoteOpen(a.l, b.l, c.l, d.l) As "_NSFNoteOpen@16"
	;	NSFItemScan(a.l, b.l, c.l) As "_NSFItemScan@12"
	NSFNoteClose(a.l) As "_NSFNoteClose@4"
	; ***NOTE! NSFItemQuery in documentation only lists 9 parameters, use 10 and pass
	; Block structure members as two separate parameters
	NSFItemQuery(a.l, b.l, c.l, d.l, e.l, f.l, g.l, h.l, i.l, j.l) As "_NSFItemQuery@40"
	OSLockObject(a.l) As "_OSLockObject@4"
	OSUnlockObject(a.l) As "_OSUnlockObject@4"
	NSFDbGetObjectSize(a.l, b.l, c.l, d.l, e.l, f.l) As "_NSFDbGetObjectSize@24"
	OSPathNetConstruct(a.l, b.l, c.l, d.l) As "_OSPathNetConstruct@16"
	NSGetServerList(a.l, b.l) As "_NSGetServerList@8"
	ConvertTIMEDATEToText(a.l, b.l, c.l, d.l, e.l, f.l) As "_ConvertTIMEDATEToText@24"
	NSFItemGetTextListEntry(a.l, b.s, c.l, d.l, e.l) As "_NSFItemGetTextListEntry@20"
	NSFItemGetTextListEntries(a.l, b.s) As "_NSFItemGetTextListEntries@8"
	NSFItemGetText(a.l, b.s, c.l, d.l) As "_NSFItemGetText@16"
EndImport
;}

Procedure ShowFileInfo(NoteID.l, *vB.BlockID)
	Shared SubjectLine$
	Protected AttachmentName$, *p.l
	Protected dT, oID, cT, sl.l
	Protected FileSize, FileClass, FilePriv

	Print(NSFName$+",h"+RSet(Hex(NoteID),#NOTEID_LENGTH,"0")+",")
	WriteString(#LOGFILE, NSFName$+",h"+RSet(Hex(NoteID),#NOTEID_LENGTH,"0")+",")

	If *vB\hPool = 0 : ProcedureReturn : EndIf
	*p = OSLockObject(*vB\hPool)
	*p = *p + *vB\Block
	If Not (*p)
		MessageRequester("EXTREME DANGER, WILL ROBINSON!", "Warning, *p pointer is a big fat zero!")
		OSUnlockObject(*vB\hPool)
		ProcedureReturn
	EndIf
	sl = PeekW(*p+8) & $0000FFFF
	Debug "sl: "+Hex(sl)
	If (sl >= #MIN_STRING_LEN And  sl <= #MAX_STRING_LEN)
		AttachmentName$ = PeekS(*p+38, sl)
		AttachmentName$ = RSet(AttachmentName$, Len(AttachmentName$)+1, Chr(34))
		AttachmentName$ = LSet(AttachmentName$, Len(AttachmentName$)+1, Chr(34))
		Print(AttachmentName$)
		WriteString(#LOGFILE, AttachmentName$)
	Else
		Print("{INVALID LENGTH STRING}")
		WriteString(#LOGFILE, "{INVALID LENGTH STRING}")
	EndIf
	Print(", "+Str(PeekL(*p+18)))
	WriteString(#LOGFILE, ", "+Str(PeekL(*p+18)))
	dT.l = 0
	dT = PeekW(*p+2)
	Debug "dT: "+Hex(dT)
	oID = PeekL(*p+4)
	Debug "oID: "+Hex(oID)
	cT = PeekL(*p+12)
	Debug "cT: "+Hex(cT)
	NSFDbGetObjectSize(pDB, oID, dT, @FileSize, @FileClass, @FilePriv)
	Print(", "+Str(FileSize))
	WriteString(#LOGFILE, ", "+Str(FileSize))
	Select cT
	Case #NOCOMP
		Print( ",None")
		WriteString(#LOGFILE, ",None")
	Case #HUFF
		Print( ",Huffman")
		WriteString(#LOGFILE, ",Huffman")
	Case #LZ1
		Print( ",LZ1")
		WriteString(#LOGFILE, ",LZ1")
	Default
		Print( ",Unknown")
		WriteString(#LOGFILE, ",Unknown")
	EndSelect

	OSUnlockObject(*vB\hPool)

	Print(","+FromLine$)
	WriteString(#LOGFILE, ","+FromLine$)

	Print(","+DeliveredDate$)
	WriteString(#LOGFILE, ","+DeliveredDate$)

	PrintN(","+SubjectLine$)
	WriteStringN(#LOGFILE, ","+SubjectLine$)

EndProcedure

Procedure GetSubject(hNote)
	;********************************************************************
	;* If we find at least one "$FILE" item, get the "Subject" field... *
	;********************************************************************

	Static  NoteID.l = 0, 	ItemBlock.BlockID, 	ValueBlock.BlockID, 	DataType.l = 0, 	ValueBlockLen.l = 0
	Static NameLengthPtr.l = 0, 	ItemFlagsPtr.l = 0, 	ValueDatatypePtr.l = 0
	Protected *pPool
	Protected hr
	Protected ItemCount
	Shared SubjectLine$

	hr = NSFItemInfo(	hNote, "Subject", 7, @ItemBlock, @DataType, @ValueBlock, @ValueBlockLen)
	Debug "GetSubject> Datatype: "+Hex(DataType)
	Debug "GetSubject> ValueBlockLen: "+Hex(ValueBlockLen)
	Select Datatype
	Case #TYPE_TEXT
		Debug "GetSubject> #TYPE_TEXT"
		hr = NSFItemGetText(hNote, "Subject", @SubjectLine$, SizeOf(SubjectLine$))
		PokeB(@SubjectLine$+hr, 0)
	Case #TYPE_TEXT_LIST
		Debug "GetSubject> #TYPE_TEXT_LIST"
		hr = NSFItemGetTextListEntry(hNote, "Subject", 0, @SubjectLine$, SizeOf(SubjectLine$)-1)
		Debug "GetSubject> SizeOf(SubjectLine$):"+StrU(SizeOf(SubjectLine$),2)
		PokeB(@SubjectLine$+hr, 0)
		Debug "GetSubject> hr=NSFItemGetTextListEntry: "+Hex(hr)
	Default
		Debug "GetSubject> UNKNOWN_TYPE"
		SubjectLine$ = "UNKNOWN_TYPE"
	EndSelect
	; strip out, then bound with double-quotes
	SubjectLine$ = RemoveString(SubjectLine$, Chr(34))
	SubjectLine$ = RSet(SubjectLine$, Len(SubjectLine$)+1, Chr(34))
	SubjectLine$ = LSet(SubjectLine$, Len(SubjectLine$)+1, Chr(34))
	Debug "GetSubject> SubjectLine$: "+SubjectLine$

EndProcedure

Procedure GetDeliveredDate(hNote)
	;**************************************************************************
	;* If we find at least one "$FILE" item, get the "DeliveredDate" field... *
	;**************************************************************************

	Static  NoteID.l, 	ItemBlock.BlockID, 	ValueBlock.BlockID, 	DataType.l, 	ValueBlockLen.l
	Static NameLengthPtr.l, 	ItemFlagsPtr.l, 	ValueDatatypePtr.l, TextBufferLen.l
	Protected *pPool.l
	Protected td.TFMT
	Protected hr

	With td
	\Date = 0
	\Time = 0
	\Zone = 0
	\Struc = 0
	EndWith

	hr = NSFItemInfo(	hNote, "DeliveredDate", 13, @ItemBlock, @DataType, @ValueBlock, @ValueBlockLen)
	Debug "GetDeliveredDate> NSFItemInfo('DeliveredDate') returned: "+Hex(hr)
	; if error returned, use "PostedDate"...
	If hr <> #NOERROR
		hr = NSFItemInfo(	hNote, "PostedDate", 10, @ItemBlock, @DataType, @ValueBlock, @ValueBlockLen)
		Debug "GetDeliveredDate> NSFItemInfo('PostedDate') returned: "+Hex(hr)
	EndIf
	If ValueBlock\hPool
		*pPool = OSLockObject(ValueBlock\hPool)
		*pPool = *pPool + ValueBlock\Block
		If Not (*pPool)
			MessageRequester("EXTREME DANGER, WILL ROBINSON!", "Warning, *pPool pointer is a big fat zero!")
			OSUnlockObject(ValueBlock\hPool)
			ProcedureReturn
		EndIf
		If DataType = #TYPE_TIME_RANGE
			*pPool + SizeOf(RANGE)	; skip over RANGE structure
		EndIf
		*pPool + SizeOf(WORD)	; skip over data type WORD
		hr = ConvertTIMEDATEToText(#NUL, td, *pPool, @DeliveredDate$, #MAXALPHATIMEDATE, @TextBufferLen)
		OSUnlockObject(ValueBlock\hPool)
		PokeB(@DeliveredDate$+TextBufferLen, 0)
		If hr = #NOERROR
			DeliveredDate$ = RSet(DeliveredDate$, Len(DeliveredDate$)+1, Chr(34))
			DeliveredDate$ = LSet(DeliveredDate$, Len(DeliveredDate$)+1, Chr(34))
		Else
			DeliveredDate$ = "INVALID OR BLANK DATE"
		EndIf
		Debug "GetDeliveredDate> DeliveredDate$: "+DeliveredDate$
	EndIf

EndProcedure

Procedure GetFrom(hNote)
	;*****************************************************************
	;* If we find at least one "$FILE" item, get the "From" field... *
	;*****************************************************************

	Static  NoteID.l = 0, 	ItemBlock.BlockID, 	ValueBlock.BlockID, 	DataType.l = 0, 	ValueBlockLen.l = 0
	Static NameLengthPtr.l = 0, 	ItemFlagsPtr.l = 0, 	ValueDatatypePtr.l = 0
	Protected *pPool
	Protected hr, fCanonical, CommonNamePos, CommonNameLen, s$

	hr = NSFItemInfo(	hNote, "From", 4, @ItemBlock, @DataType, @ValueBlock, @ValueBlockLen)
	Select Datatype
	Case #TYPE_TEXT
		Debug "GetFrom> #TYPE_TEXT"
		hr = NSFItemGetText(hNote, "From", @FromLine$, SizeOf(FromLine$))
		PokeB(@FromLine$+hr, 0)
	Case #TYPE_TEXT_LIST
		Debug "GetFrom> #TYPE_TEXT_LIST"
		hr = NSFItemGetTextListEntry(hNote, "From", 0, @FromLine$, SizeOf(FromLine$)-1)
		Debug "GetFrom> SizeOf(FromLine$):"+StrU(SizeOf(FromLine$),2)
		PokeB(@FromLine$+hr, 0)
		Debug "GetFrom> hr=NSFItemGetTextListEntry: "+Hex(hr)
	Default
		Debug "GetFrom> UNKNOWN_RECIPIENT"
		FromLine$ = "UNKNOWN_RECIPIENT"
	EndSelect
	; Primitive Canonical Name stripper follows, which assumes "cn=" And "o=" both exist
	; in the target string. Given "CN=My Name/O=My Organisation", will truncate to "My Name"
	s$ = LCase(FromLine$)	; wish there was a case flag for the string functions - Fred?
	fCanonical = 0				; reset our "flag"
	CommonNamePos = FindString(s$, "cn=", 1)
	If (CommonNamePos) : fCanonical + 1 : EndIf
	If FindString(s$, "/o=", 1) : fCanonical + 1 : EndIf
	If fCanonical = 2		; looks like we got a live one!
		CommonNamePos = CommonNamePos + 3		; skip over "cn="
		CommonNameLen = FindString(s$, "/", CommonNamePos) - CommonNamePos
		FromLine$ = Mid(FromLine$, CommonNamePos, CommonNameLen)
	EndIf
	; strip out, then bound with double-quotes
	FromLine$ = RemoveString(FromLine$, Chr(34))
	FromLine$ = RSet(FromLine$, Len(FromLine$)+1, Chr(34))
	FromLine$ = LSet(FromLine$, Len(FromLine$)+1, Chr(34))
	Debug "GetFrom> FromLine$: "+FromLine$

EndProcedure

Procedure NoteFound(*db_handle.l, *sm.SEARCH_MATCH, *summary_info)

	Protected NoteID.l = 0, 	ItemBlock.BlockID, 	ValueBlock.BlockID, 	DataType.l = 0, 	ValueBlockLen.l = 0
	Protected PreviousBlock.BlockID
	Protected NameLengthPtr.l = 0, 	ItemFlagsPtr.l = 0, 	ValueDatatypePtr.l = 0
	Protected hr

	Shared hNote.l
	Shared ItemName$
	Shared ItemNameLen

	NoteID.l = PeekL(*sm\ID\NoteID)
	Debug ("Hex(NoteID)) = " + Hex(NoteID))

	cDoc = cDoc + 1
	hr = NSFNoteOpen(*db_handle.l, NoteID, 0, @hNote)
	If hr <> #NOERROR
		PrintN("Error opening note: 'h"+RSet(Hex(NoteID),#NOTEID_LENGTH,"0")+"'")
		WriteStringN(#LOGFILE, "Error opening note: 'h"+RSet(Hex(NoteID),#NOTEID_LENGTH,"0")+"'")
	Else

		;====================================================================================
		;= Note was opened okay, now look up any info we need before closing the note again =
		;====================================================================================

		; Just realised what may be a bug where it was not reporting multiple found items, could be due
		; to the fact that I have "interrupted" the find chain for FILE$ items, by putting in calls to find
		; the other item types ("Subject", DeliveredDate" and "From") in-between the initial NSFItemInfo()
		; and the subsequent NSFItemInfoNext()! The whole thing can be done more elegantly by using arrays, etc
		; but I can't be bothered - it works! Therefore, the following NSFItemInfo() is just to trigger
		; the rest of the processing -  it will be called again after the other 3 items are found...
		hr = NSFItemInfo(	hNote, "$FILE", 5, @ItemBlock, @DataType, @ValueBlock, @ValueBlockLen)

		;***********************************************************************************************
		;* If we find at least one "$FILE" item, also get the Subject, DeliveredDate and From items... *
		;***********************************************************************************************

		;	Okay, there is at least one FILE$ item in this note. Find the other information we need before starting
		; the loop to find all FILE$ items:
		If hr = #NOERROR
			GetSubject(hNote)
			GetDeliveredDate(hNote)
			GetFrom(hNote)

			; Now prime the search for FILE$ items again...
			hr = NSFItemInfo(	hNote, "$FILE", 5, @ItemBlock, @DataType, @ValueBlock, @ValueBlockLen)

			While hr = #NOERROR
				ShowFileInfo(NoteID, @ValueBlock)
				hr = NSFItemInfoNext(	hNote, ItemBlock\hPool, ItemBlock\Block, "$FILE", 5, @ItemBlock, @DataType, @ValueBlock, @ValueBlockLen)
			Wend
		EndIf

	EndIf
	NSFNoteClose(hNote)

	BlowThisJoint:

EndProcedure

Procedure FindServers()
	;******************************************************
	;* I'll make a decent version of this utility one day *
	;* that will allow browsing of servers...one day...   *
	;******************************************************

	; Structure WORD_ARRAY
	; 	ww.w[0]
	; EndStructure

	Protected hServerList, i
	Protected pServerList
	Protected wServerCount.w
	Dim ServerNameSizeArray.w(0)
	Protected pServerNameSizeArray
	Protected pServerNameStrings
	Protected hr

	; /* Get the list of available servers.  Setting the first parameter
	;    To NULL gets a list of known servers on all ports.
	; */
	;
	; sError = NSGetServerList( (char far *) NULL, &hServerList);

	hr = NSGetServerList(#NUL, @hServerList)
	;
	; If (sError != NOERROR)
	; {
	;   Return;
	; }
	If hr <> #NOERROR
		PrintN("Error getting list of servers, aborting")
		WriteStringN(#LOGFILE, "Error getting list of servers")
		ProcedureReturn
	EndIf
	;
	pServerList = OSLockObject(hServerList);
	wServerCount = PeekW(pServerList)

	Dim ServerNameStrings$(wServerCount)
	;
	pServerNameSizeArray = pServerList + SizeOf(WORD);
	;
	pServerNameStrings = pServerList + SizeOf(wServerCount) + ((wServerCount) * SizeOf(WORD));
	;
	; For (i=0; i<wServerCount; pServerNameStrings+=pServerNameSizeArray[i], i++)
	For i=0 To wServerCount-1
		;	Debug PeekW(*pServerNameSizeArray\ww[i])
		Debug PeekW(pServerNameSizeArray+(SizeOf(WORD)*i))
		Debug PeekS(pServerNameStrings, PeekW(pServerNameSizeArray+(SizeOf(WORD)*i)))
		; {
		;   memmove (szServerString, pServerNameStrings, pServerNameSizeArray[i]);
		ServerNameStrings$(i) = PeekS(pServerNameStrings, PeekW(pServerNameSizeArray+(SizeOf(WORD)*i)))
		;   szServerString[pServerNameSizeArray[i]] = '\0';
		;   SendDlgItemMessage(hDlg, SERVLIST_LISTBOX, LB_ADDSTRING,
		;                     (WORD) NULL,
		;                     (LONG)(LPSTR) szServerString);
		; }
		pServerNameStrings = pServerNameStrings + PeekW(pServerNameSizeArray+(SizeOf(WORD)*i))
	Next i

	OSUnlockObject (hServerList);
	; OSMemFree (hServerList);
EndProcedure

;-MAIN
;{

OpenConsole()

StandardFile$ = "C:\lotus\notes\data\ServerDir.txt"   ; set initial file+path to display
Pattern$ = "Server NSF List (*.txt)|*.txt|All files (*.*)|*.*"
Pattern = 0    ; use the first of the three possible patterns as standard
File$ = OpenFileRequester("Select Directory Listing Input File", StandardFile$, Pattern$, Pattern)

DFhandle = ReadFile(#DIRFILE, File$)
If Not DFhandle
	MessageRequester("ERROR", "Unable to open "+File$+" for input", 0)
	Goto BlowTheJoint
EndIf

StandardFile$ = "C:\lotus\notes\data\AttachmentLog.csv"   ; set initial file+path to display
Pattern$ = "Comma Separated (*.csv)|*.csv|Log (*.log)|*.log|All files (*.*)|*.*"
Pattern = 0    ; use the first of the three possible patterns as standard
File$ = SaveFileRequester("Select CSV Output File", StandardFile$, Pattern$, Pattern)

LFhandle = CreateFile(#LOGFILE, File$)
If Not LFhandle
	MessageRequester("ERROR", "Unable to open "+File$+" for output", 0)
	Goto BlowTheJoint
EndIf

WriteStringN(#LOGFILE, "FILENAME,NOTEID,ATTACHMENT,ORIGINAL-SIZE,COMPRESSED-SIZE,COMPRESSION,FROM,DATE,SUBJECT")

;-NotesInit()
Global fNotesInit = NotesInit()
Debug ("NotesInit() rc: " + Hex(fNotesInit))
If Not fNotesInit
	MessageRequester("CRAP!", "Extreme error initialising NOTES, can't bear to go on")
	Goto BlowTheJoint
EndIf

;FindServers()

While Not Eof(#DIRFILE)

	cdoc = 0

	FileName$ = ReadString(#DIRFILE)

	Debug "FileName$: "+FileName$
	Debug "FullPath$: "+FullPath$

	hr = OSPathNetConstruct(#Null, @Server$, @FileName$, @FullPath$)
	Debug "FullPath$: "+FullPath$

	pDB = 0 : hr.l = NSFDbOpen(@FullPath$, @pDB.l)
	Debug ( "NSFDbOpen rc: " + Hex(hr))
	Debug ( "NSFDbOpen pointer: " + Hex(pDB))

	; only display NSF filename, nothing else
	NSFName$ = RemoveString(FullPath$, Server$+"!!", 1)
	NSFName$ = GetFilePart(FullPath$)

	If hr <> #NOERROR
		PrintN("Error opening database: '"+NSFName$+"'")
		WriteStringN(#LOGFILE, "Error opening database: '"+NSFName$+"'")
		Goto ErrExit
	EndIf

	hr = NSFSearch(pDB, #Null, #Null, 0, #NOTE_CLASS_DOCUMENT, #Null, @NoteFound(), pDB, #Null)

	Goto ErrExit

	;-ErrExit:
	ErrExit:
	If pDB
		hr = NSFDbClose(pDB) : Debug ( "NSFDbClose rc: " + Hex(hr))
		If LFhandle : WriteStringN(#LOGFILE, NSFName$ + ",Document count," + Str(cDoc))
		Else : PrintN(NSFName$ + ",Document Count," + Str(cDoc))
		EndIf
	EndIf

Wend

;-NotesTerm()
If Not fNotesInit : NotesTerm() : EndIf

;-BlowTheJoint:
BlowTheJoint:
CloseConsole()

End
;} END MAIN

Win8.1, PB5.x, okayish CPU, onboard video card, fuzzy monitor (or is that my eyesight?)
"When the facts change, I change my mind" - John Maynard Keynes
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Post by Kwai chang caine »

First, thanks (A little bit laster :wink: ) for sharing this great code 8)
Oops! Forgot to specify, you'll need the Lotus C-API LIB file to compile, and the Lotus Notes client installed to run. So, I'm guessing 99.9% of you won't be abel to do much with this
But where can i found this files ??? :roll:
ImageThe happiness is a road...
Not a destination
Amundo
Enthusiast
Enthusiast
Posts: 191
Joined: Thu Feb 16, 2006 1:41 am
Location: New Zealand

Post by Amundo »

Better late than never for a reply, Kwai Chang!

Lotus Notes is a commercial (groupware) product from IBM, used (mainly) in large corporations.
Win8.1, PB5.x, okayish CPU, onboard video card, fuzzy monitor (or is that my eyesight?)
"When the facts change, I change my mind" - John Maynard Keynes
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Post by Kwai chang caine »

No LOTUS i know, i have it in my job.
I want to say this file "C-API LIB"
Where i can found it ???
Or then i have again understand nothing :oops:
ImageThe happiness is a road...
Not a destination
Amundo
Enthusiast
Enthusiast
Posts: 191
Joined: Thu Feb 16, 2006 1:41 am
Location: New Zealand

Post by Amundo »

Sorry Kwai Chang, been away a long time.

You can download the "C API Toolkit" from the IBM website. In there is the "notes.lib" file needed to link with your program.
Win8.1, PB5.x, okayish CPU, onboard video card, fuzzy monitor (or is that my eyesight?)
"When the facts change, I change my mind" - John Maynard Keynes
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Post by Kwai chang caine »

Never mind, an answer after a long time is better that no answer 8)
It's not esay to find this lib in the great site of IBM :cry:

If you have a day the time to put the link....

Again thanks for your great works 8)
ImageThe happiness is a road...
Not a destination
Post Reply