How to export the contents of a Domino.Doc Library on the local file system? (part II)

This is the scond part of a two parts article.

The Cabinet documents created by the PopolaCabinets agent stores also the total binders numbers and the total documents number.
There are also two datetime fields used to store the start and the end time of the export.

The Cabinet form to store cabinet and binders info.

The EsportaCabinet agent

Here follows the code for the EsportaCabinet agent: is the agent that create the file system structure and populates it with the Domino.Doc attachments.

Option Public
Use "OpenLogFunctions"

Dim FromArray(7) As String
Dim ToArray(7) As String
Sub Initialize
'Declare all API objects as Variants

	Dim theApi As Variant
	Dim theLibrary As Variant
	Dim theCabinets As Variant
	Dim theCabinet As Variant
	Dim theRooms As Variant
	Dim theRoom As Variant

	Dim theBinder As Variant
	Dim theField As Variant

	Dim theDocuments As Variant
	Dim theNewDocument As Variant
	Dim theProfile As Variant
	Dim isalink As Boolean

	Dim Db As NotesDatabase
	Dim doc As NotesDocument
	Dim ses As New NotesSession

	Dim fulldirpath As String

	On Error Goto errorHandler

	FromArray(0) = "/"
	FromArray(1) = ":"
	FromArray(2) = "*"
	FromArray(3) = "?"
	FromArray(4) = "<"
 	FromArray(5) = ">"
	FromArray(6) = "|"
	FromArray(7) = "\\"

	ToArray(0) = "]"
	ToArray(1) = "-"
	ToArray(2) = "-"
	ToArray(3) = "-"
	ToArray(4) = "-"
	ToArray(5) = "-"
	ToArray(6) = "-"
	ToArray(7) = "-"

'Create the Api object
	Set theApi = CreateObject("DominoDoc.API")

'Connect to a domdoc server using Notes
	Call theApi.SetNotesLogin("-Lotus Notes ID password-")
	Set theLibrary = theApi.GetLibrary("notes://-server name-/-Domino.Doc library.nsf-")

	Call LogEvent("Started the export run", SEVERITY_LOW, Nothing)
	'Get the Cabinets object from the Library
	Set theCabinets = theLibrary.Cabinets

	Dim c As Long
	Call LogEvent(Cstr(c) & " cabinets presenti", SEVERITY_LOW, Nothing)

	Set db = ses.CurrentDatabase
	Set collection = db.UnprocessedDocuments
	If collection.Count = 0 Then
		Msgbox "Selezionare almeno un cabinet da esportare!!", 16, "None Selected"
		Exit Sub
	End If
	Set doc = collection.GetFirstDocument
	While Not doc Is Nothing
		Call SetDate(doc, "LastEsportedStart")
		Call doc.Save(True, False)
		cabName$ = doc.cName(0)
		Set theCabinet = theLibrary.Cabinets(cabName$)

		Set theRooms = theCabinet.Rooms
		If theRooms.Count > 0 Then
			Set theRoom = theRooms.ItemByIndex(0)
			roomname$ = PulisciDirName(theRoom.Title)
			roompath$ = roomname$ & "\"
			If CreateDir("C:\domdoc\" & roomname$  ) Then
				Call LogEvent("Creata directory " & roomname$ & " per la room " & theRoom.Title, SEVERITY_LOW, Nothing)
			End If
			roompath$ = ""
		End If

		cabinetname$ =PulisciDirName(theCabinet.Title)
		cabinetpath$ = cabinetname$ & "\"
		thedir = CreateDir("C:\domdoc\" & roompath$ & cabinetname$  )
		If CreateDir("C:\domdoc\" & roompath$ & cabinetname$  ) Then
			Call LogEvent("Creata directory " & cabinetname$ & " per il cabinet " & theCabinet.Title, SEVERITY_LOW, Nothing)
		End If

'Get binders
		Set theBinders = theCabinet.Binders

'Loop to get all Binders
		Dim x As Long
		Call LogEvent(Cstr(x) & " binders presenti nel cabinet " & theCabinet.Title, SEVERITY_LOW, Nothing)
		For k = 0 To (x-1)
			Set theBinder=theBinders.ItemByIndex(k)
			Set theDocuments = theBinder.Documents
			y = theDocuments.Count

			Call LogEvent(Cstr(y) & " documenti presenti nel binder " & directory$ & " del cabinet " & theCabinet.Title, SEVERITY_LOW, Nothing)

			directory$ =PulisciDirName(directory$)

			category$ = ""
			Set theProfile = theBinder.Profile
			On Error Goto accessProfileHandler
			If Not(theProfile Is Nothing) Then
				If (theProfile.IsValid ) Then
					Dim h As Long
					For m = 0 To (h-1)
						Set theField = theProfile.Fields.ItemByIndex(m)
						If theField.Name = "BinderCategory" Then
							category$ = theField.value
						End If
				End If
			End If
			On Error Goto errorHandler
			If (category$ <> "") Then
				thedir = CreaDirFromCat( "C:\domdoc\" & roompath$ & cabinetpath$, category$)
				Call LogEvent("Create le directory " & thedir & " per le categorie del binder " & theBinder.Title & " del cabinet " & theCabinet.Title, SEVERITY_LOW, Nothing)
				fulldirpath = "C:\domdoc\" & roompath$ & cabinetpath$ & thedir & "\" & directory$
				fulldirpath = "C:\domdoc\" & roompath$ & cabinetpath$ & directory$
			End If

			If CreateDir(fulldirpath) Then
				Call LogEvent("Create la directory " & fulldirpath & " per il binder " & theBinder.Title & " del cabinet " & theCabinet.Title, SEVERITY_LOW, Nothing)
			End If

' loop to get all documents in the binder
			For i = 0 To ( y-1)

				Set theNewDocument = theDocuments.ItemByIndex(i)
				isalink = theNewDocument.IsLink
				If Not(isalink) Then

					On Error Goto saveContentHandler
					If Not(theNewDocument.IsNew) Then
						Call ExportDocAndVersions(db, theNewDocument,  fulldirpath, docTitle$, theBinder.Title, theCabinet.Title)
					End If
					On Error Goto errorHandler
					Print fulldirpath & "-" & Cstr(i)
					Call LogEvent("Trovato il link - " & theNewDocument.Title & " - nel binder " & theBinder.Title & " del cabinet " & theCabinet.Title, SEVERITY_LOW, Nothing)
				End If

		Call SetDate(doc, "LastEsported")
		Call doc.Save(True, False)
		Set doc = collection.GetNextDocument(doc)
	Call LogEvent("Finished the Export run", SEVERITY_LOW, Nothing)
	Exit Sub
	Call LogErrorEx("Errore grave nell'esportazione.", SEVERITY_HIGH, Nothing)
	Exit Sub
	On Error Goto 0
	Call LogErrorEx("Errore nel salvataggio del file " & filename$, SEVERITY_MEDIUM, Nothing)
	Resume resumeSavingContent
	On Error Goto 0
	Call LogErrorEx("Errore nell'accesso al profilo del binder " & theBinder.Profile & " del cabinet " & theCabinet.Title, SEVERITY_MEDIUM, Nothing)
	Resume resumeProfile
End Sub

The function PulisciDirName

This function replace the non permitted chars in filename with the ones specifed in the global ToArray string array. The function checks also for foler ending with one or more “.” (dot) and replace the trailing dot(s) with “_” (underscore).

Function PulisciDirName (dirname As String) As String
	On Error Goto processError
	Dim i As Integer
	Dim dot As String

	directory$ = dirname
	suffDir$ = directory$
	i = 1
	dot = ""
	While Right(suffDir$, 1) = "."
		dot = dot + "."
		suffDir$ = Left(directory$, Len(directory$) - i)
		i = i + 1
	directory$ = suffDir$ & dot

	directory$ =Replace(directory$, FromArray,	ToArray)

	PulisciDirName = Trim(directory$)
	Exit Function
	Call LogErrorEx("PulisciDirName failed", SEVERITY_LOW, Nothing)
	PulisciDirName = Trim(directory$)
	Exit Function
End Function

The function NameShortner

If the full pathname is greater than 255 chars the Dir$ function crashes the notes client.
So before saving the attachment to disk we check the length and we shorten the filename if needed.
In order to obtain unique file name we use the passwordGenerator function to append a 2 chars random suffix.

Function NameShortner(fulldirpath As String, docTitle As String, allegato As String, v As Long, d As Long) As String
	Dim fullwithtitle As String
	Dim toShort As Integer

	fullwithtitle = fulldirpath & "\" & docTitle
	If Len(fullwithTitle) < 239 Then  		NameShortner = docTitle  	Else  		toShort = 250 - Len(fulldirpath)  - 2  		If toShort > 0 Then
			NameShortner = Left(docTitle, toShort) & passwordGenerator(2)
			While Not(Dir( fulldirpath & "\" & NameShortner)="")
				NameShortner = Left(docTitle, toShort) & passwordGenerator(2)
			NameShortner = ""
		End If
	End If
End Function

The function CreaDirFromCat

This function takes the binder categories and generates a file system path for these categories. It appends the _c suffix at each created folder.

Function CreaDirFromCat(sCurrDir As String, sCatName As String) As String
	On Error Goto processError
	Dim folders As Variant

	CreaDirFromCat  = ""
	folders = Split(sCatName, "\")

	For x=0 To Ubound(folders)
		fold$ = folders(x)
		fold$ = PulisciDirName(fold$)
		sCurrDir = sCurrDir & "\" & fold$ & "_c"
		CreaDirFromCat = CreaDirFromCat & "\" & fold$ & "_c"
	CreaDirFromCat = Right(CreaDirFromCat, Len(CreaDirFromCat)-1)
	Exit Function
	Call LogErrorEx("CreaDirFromCat failed", SEVERITY_LOW, Nothing)
	CreaDirFromCat = ""
	Exit Function
End Function

The function GetExt

It returns the filename extensions.

Function GetExt(path As String) As String
	On Error Goto errorHandler
	Dim table As Variant
	Dim fileName As String

	fileName = path
	If Instr(fileName, "\") > 0 Then
		table = Split(Trim(fileName), "\")
		fileName = table(Ubound(table))
	End If
	If Instr(fileName, ".") > 0 Then
		table = Split(fileName, ".")
		GetExt =  "." & table(Ubound(table))
		GetExt = ".xxx"
	End If
	Exit Function
	Call LogErrorEx("GetExt failed " & path , SEVERITY_MEDIUM, Nothing)
	GetExt = ".xxx"
	Exit Function
End Function

The function SetDate

Set the specified field of the specified document with the current date/time.

Function SetDate(doc As NotesDocument, strFieldName As String)

	Dim tdate As New NotesDateTime(Now())
	Call doc.ReplaceItemValue(strFieldName, tdate)

End Function

The sub ExportDocAndVersions

To be descripted.

Sub ExportDocAndVersions(db As NotesDatabase, theDocument As Variant, fulldirpath As String, docTitle As String, Binder As String, Cabinet As String)
	On Error Goto processError
	Dim revHistDocs As Variant
	Dim revHistDoc As Variant
	Dim k As Long
	Dim v As Long
	Dim d As Long
	Dim IsVersion As Boolean
	Dim docTitle_ori As String

	docTitle = PulisciDirName(docTitle)

	Set revHistDocs = theDocument.RevisionHistoryDocuments
	k = revHistDocs.Count
	If (k>0) Then
		For i = 0 To (k-1)
			Set revHistDoc = theDocument.RevisionHistoryDocuments.ItemByIndex(i)
			IsVersion = theDocument.IsVersion
			'If IsVersion Then

			v = revHistDoc.VersionNumber
			d = revHistDoc.DraftNumber

			filename$=fulldirpath &"\" & docTitle &"_-_" & AppendVersion(attach$, v, d)
			If (Len(filename$) < 250) Then
				If attach$ <>""  And Dir$(filename$) = "" Then
					Call revHistDoc.GetContents(filename$)
					Call LogDoc(db, revHistDoc, filename$, docTitle, Binder, Cabinet, "")
				End If
				Call LogEvent("Esportazione versione - " & attach$ & " - nel path " & filename$, SEVERITY_LOW, Nothing)
				docTitle_ori = docTitle
				docTitle = NameShortner(fulldirpath, docTitle, attach$, v, d)
				filename$=fulldirpath &"\" & docTitle & "_ver" & Cstr(v) &  "-" & Cstr(d)  & "." &GetExt(attach$)
				If (docTitle = "") Then
					Call LogDoc(db, revHistDoc, filename$, docTitle, Binder, Cabinet, "path troppo lungo")
					Call LogEvent("NESSUNA esportazione versione - " & attach$ & " - nel path " & filename$, SEVERITY_LOW, Nothing)
					docTitle = docTitle_ori
					Call revHistDoc.GetContents(filename$)
					Call LogDoc(db, revHistDoc, filename$, docTitle_ori, Binder, Cabinet, "path accorciato: " & docTitle)

				End If
			End If
	End If
	Exit Sub
	Call LogErrorEx("ExportDocAndVersions failed", SEVERITY_LOW, Nothing)

	Exit Sub
End Sub

The function CreateDir

Nothing to say.

Function CreateDir(sDirName As String) As Boolean
	On Error Goto processError
	sDirName = Trim(sDirName)
	If Trim(Dir(sDirName,ATTR_DIRECTORY ))="" Then
		Mkdir(sDirName )
		CreateDir = True
		CreateDir = False
	End If
	Exit Function
	Call LogErrorEx("PulisciDirName failed", SEVERITY_LOW, Nothing)
	CreateDir = False
	Exit Function
End Function

The Document form to store document info and the exported attachment path

The function LogDoc

This function creates a notes document with the Document form for each exported attachment.

Function LogDoc(db As NotesDatabase, theNewDocument As Variant, filename As String, masterDocTitle As String, binder As String, cabinet As String, reason As String)
	On Error Goto processError
	Dim HasDraft As String
	Dim HasReviewCopy As String
	Dim HasVersion As String
	Dim IsCurrentRevision As String
	Dim IsLocked As String
	Dim IsReviewCopy As String
	Dim IsVersion As String
	Dim IsWorkingCopy As String

	Dim Title As String
	Dim Id As String
	Dim UniversalId As String

	Dim doc As NotesDocument

	If theNewDocument.HasDraft Then
		HasDraft =  "1"
		HasDraft =  "0"
	End If
	If theNewDocument.HasReviewCopy Then
		HasReviewCopy = "1"
		HasReviewCopy = "0"
	End If
	If theNewDocument.HasVersion Then
		HasVersion = "1"
		HasVersion = "0"
	End If
	If theNewDocument.IsCurrentRevision Then
		IsCurrentRevision = "1"
		IsCurrentRevision = "0"
	End If
	If theNewDocument.IsLocked Then
		IsLocked ="1"
		IsLocked ="0"
	End If
	If theNewDocument.IsReviewCopy Then
		IsReviewCopy = "1"
		IsReviewCopy = "0"
	End If
	If theNewDocument.IsVersion Then
		IsVersion = "1"
		IsVersion = "0"
	End If
	If theNewDocument.IsWorkingCopy Then
		IsWorkingCopy = "1"
		IsWorkingCopy = "0"
	End If
	Title = theNewDocument.Title
	Id = theNewDocument.Id
	UniversalId = theNewDocument.UniversalId
	Set doc = db.CreateDocument
	doc.Form ="Document"
	Call SetDate(doc, "LastExported")
	doc.dHasDraft = HasDraft
	doc.dHasReviewCopy = HasReviewCopy
	doc.dHasVersion = HasVersion
	doc.dIsCurrentRevision = IsCurrentRevision
	doc.dIsLocked = IsLocked
	doc.dIsReviewCopy = IsReviewCopy
	doc.dIsVersion = IsVersion
	doc.dIsWorkingCopy = IsWorkingCopy
	doc.dTitle = Title
	doc.dId = Id
	doc.dUniversalId = UniversalId
	doc.dBinder = binder
	doc.dCabinet = Cabinet
	doc.dFilename = filename
	doc.dDraft = theNewDocument.DraftNumber
	doc.dVersion = theNewDocument.VersionNumber
	doc.dReason = reason
	doc.dMasterDocTitle = masterDocTitle
	Call doc.Save(True, False)
	Exit Function
	Call LogErrorEx("PulisciDirName failed", SEVERITY_MEDIUM, Nothing)
	LogDoc = ""
	Exit Function
End Function

The function passwordGenerator

Used to generate a random suffix

Function passwordGenerator(Byval lngLength As Long) As String
' Description: Generate a random password of 'user input' length
' Parameters : lngLength - the length of the password to be generated
' Returns    : String    - Randomly generated password
' Created    : 08/21/1999 Andrew Ells-O'Brien (
' Updated    : Rocky Oliver, Sapphire Oak Technologies (
    ' made it work in LotusScript
	On Error Goto errHandler
	Dim iChr As Integer
	Dim c As Long
	Dim strResult As String
	Dim iAsc As String

	Randomize Timer

	For c = 1 To lngLength
          ' Randomly decide what set of ASCII chars we will use
		iAsc = Int(3 * Rnd + 1)

          'Randomly pick a char from the random set
		Select Case iAsc
		Case 1
			iChr = Int((Asc("Z") - Asc("A") + 1) * Rnd + Asc("A"))
		Case 2
			iChr = Int((Asc("z") - Asc("a") + 1) * Rnd + Asc("a"))
		Case 3
			iChr = Int((Asc("9") - Asc("0") + 1) * Rnd + Asc("0"))
		Case Else
			Error 1000, "PasswordGenerator has a problem."
		End Select

		strResult = strResult & Chr(iChr)
	Next c

	PasswordGenerator = strResult

	Exit Function

	Call LogErrorEx("PasswordGenerator failed " & path , SEVERITY_MEDIUM, Nothing)
	PasswordGenerator = ""
	Resume getOut
End Function

The function AppendVersion

This function append the version and draft number at the exported filename suing this pattern: _ver-.

Function AppendVersion(path As String, Version As Long, Draft As Long) As String
	On Error Goto errorHandler
	Dim table As Variant
	Dim fileName As String
	Dim RemoveFileExtension As String

	fileName = path
	If Instr(fileName, "\") > 0 Then
		table = Split(Trim(fileName), "\")
		fileName = table(Ubound(table))
	End If
	If Instr(fileName, ".") > 0 Then
		table = Split(fileName, ".")
		AppendVersion = Left(path, Len(path) - Len(table(Ubound(table))) - 1) & "_ver" & Cstr(Version) &  "-" & Cstr(Draft) & "." & table(Ubound(table))
		AppendVersion = path & "_ver" & Cstr(Version)
	End If
	Exit Function
	Call LogErrorEx("AppendVersion failed " & path & "-" & Cstr(Version) & "-" & Cstr(Draft), SEVERITY_MEDIUM, Nothing)
	AppendVersion = path
	Exit Function
End Function

How does it works?

On the C drive of the PC folder create the domdoc folder.

Here you’ll store the exported attachments.
From this folder an agent will create a directory tree based on the hierarchy of Domino.Doc

  • Rooms (folder is always present – no documents)
  • Cabinet (folder is always present – no documents)
  • Folders categories (folder(s) is(are) optional(s) – no documents)
  • Binder Folder (folder is always present – any document can be found here)
  • Documents

For exporting documents and their versions the following naming scheme will be used:
<document-title> _-_ _ <filename_without_extension> _ver<version>-<draft>.<extension>

  • <document-title> is the title that was given to the document in Domino.Doc. Most of the time coincides with the filename attachment without the extension.
  • <filename> is the name of the attachment
  • <version> is the version number
  • <draft> is the number of draft

To support the export procedure you create two Lotus Notes database.

The first, APPL (edomdocs-v1.nsf), “supervises” the export process.

The second LOG (OpenLog1_5Apache.nsf) is the log of the various exports.

APPL contains an agent (PopolaCabinets) that examines the structure of Domino.Doc and creates so many documents for those cabinets to be exported.

For each cabinet is gathered a wealth of information, including the number of binders and documents it contains.

Then selecting one or more cabinets from the Cabintes view in Lotus Notes client, you can launch its export. The choice is due to the fact that the procedure is time intensive and also it is believed that many cabinet does not need to be exported.

After starting the export a document will be created in the same database (one document for each of the exported files).

If the document can not be exported because its fullpathname exceeds the limit of 250 characters, the document created in APP report the event and it trys to “shorten” the exported filename.

NOTES: The titles of ROOMS, CABINETS and the categories were “filtered” by replacing illegal characters with the character _The / character (often used in these names) has been replaced with the character].

The procedure always checks the presence of fullpathname: if the directory or file already exist, they are overwritten. This is not true for “shortned” filename because we generate a random file name.

Here you can download the Lotus Notes app: [download id=”3″]

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.