API
@-Formulas
JavaScript
LotusScript
Reg Exp
Web Design
Notes Client
XPages
 
Change Replica ID
In a recent project, we were copying databases from one server to another. There were many databases to do, so we decided to write an agent to do the copy. But we wanted copies, not replicas. We found out that the NotesDatabase.CreateCopy method does not copy documents (it only copies the design). We could have used that method, then got a handle to all the documents in the source database and copied them over to the destination database. But instead we used another approach.

We decided to create a new replica of the database, then change its replica ID to something unique. This would effectively create a copy of the database. Here in part 1 of this tip, we talk about the Change Replica ID function that is needed. In part 2 of this tip, we will talk about the main structure of the copy database code.

Before I get into the code, let me point out some things:

First, note that if this script is running on a server, it will need to run unrestricted. That's because the Change Replica ID function uses Notes API calls.
Second, note that if you (or the agent signer) will need to have access to all the documents in the source database to perform a true copy. This code does not bypass any reader field security. The only way to get around that is if you are an administrator on your server and are running under full administrator rights.

OK, here is the code for the Change Replica ID function. We placed this in its own script library so it could be isolated. The API information needs to be placed in the (Declarations) section of the script:

(Declarations)
Type TIMEDATE
   Innards(1) As Long
End Type

Type DBREPLICAINFO
   ID As TIMEDATE
   Flags As Integer
   CutoffInterval As Integer
   Cutoff As TIMEDATE
End Type

Declare Function W32_OSPathNetConstruct Lib "nnotes.dll" Alias "OSPathNetConstruct" _
(Byval portName As Integer, Byval serverName As String, Byval fileName As String, _
Byval pathName As String) As Integer
Declare Function W32_NSFDbOpen Lib "nnotes.dll" Alias "NSFDbOpen" _
(Byval pathName 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_NSFDbReplicaInfoGet Lib "nnotes.dll" Alias "NSFDbReplicaInfoGet" _
(Byval hDb As Long, retReplicationInfo As DBREPLICAINFO) As Integer
Declare Function W32_NSFDbReplicaInfoSet Lib "nnotes.dll" Alias "NSFDbReplicaInfoSet" _
(Byval hDb As Long, inpReplicationInfo As DBREPLICAINFO) As Integer

We define a couple custom types of our own for the time/date information in the API, and the replica information in the API. Then we define the four API calls that will be needed. I won't go into great detail on the API calls - that's what the API documentation is for.

Function ChangeReplicaID(source As NotesDatabase, dest As NotesDatabase) As Integer
   Dim fullPath As String*256
   Dim hDb As Long
   Dim status As Integer
   Dim replicaInfo As DBREPLICAINFO
   Dim compare As String
   Dim destCompare As String
   
   ' Open up the source database
   Call W32_OSPathNetConstruct(0, source.Server, source.FilePath, fullPath)
   status = W32_NSFDbOpen(fullPath, hDb)
   If status <> 0 Then
      ChangeReplicaID = False
      Exit Function ' ==========
   End If
   ' Get the replica ID of the source database
   status = W32_NSFDbReplicaInfoGet(hDb, replicaInfo)
   If status <> 0 Then
      ChangeReplicaID = False
      Exit Function ' ==========
   End If

The first part of the function opens up the source database. Take note of how the fullPath variable is defined. When dealing with the Notes API, it can't dynamically size a string. So if one of the parameters to an API function is a string that will be populated by the function, then you have to make sure the string has enough room for the return value. In this code, the string is sized to 256 characters. You could also define a string and then populate the string with a series of characters (ASCII value 0).

The first API function builds the "API version" of the path to the database. For a local database, it is the path to the database. For a server database, it is the server name, then two exclamation points, then the path to the database. But instead of relying on the code to figure out the "API version", we just call a function to tells what the value should be.

If the source database could not be opened, or the replica information could not be read, the function returns the value False indicating that the replica information was not changed.

   ' Put the replica ID in a string format so we can compare later on if the change took
   compare = Right("0000000" + Hex(replicaInfo.ID.Innards(1)), 8)
   compare = compare + Right("0000000" + Hex(replicaInfo.ID.Innards(0)), 8)
   ' Close the source database - the replica info has been captured
   status = W32_NSFDbClose(hDb)
   ' Make sure what we captures is legit - it should match the database property
   If compare <> source.ReplicaID Then
      ChangeReplicaID = False
      Exit Function ' ==========
   End If

The next part of the function makes sure that the replica information that was read is the correct value. There's a NotesDatabase property called ReplicaID that gives the replica id. So we take what was read in by the API and format it to be the same as the database property. Then we can compare them to see if what the API read to see if we were really reading the value through the API, or if the API was just saying it was read.

   ' Open up the destination database
   Call W32_OSPathNetConstruct(0, dest.Server, dest.FilePath, fullPath)
   status = W32_NSFDbOpen(fullPath, hDb)
   If status <> 0 Then
      ChangeReplicaID = False
      Exit Function ' ==========
   End If
   ' Attempt to set the replica ID on the destination database
   status = W32_NSFDbReplicaInfoSet(hDb, replicaInfo)
   If status <> 0 Then
      ChangeReplicaID = False
      Exit Function ' ==========
   End If

The next part of the function sets the replica ID on the destination database through the API. We have to create a path and open the database in the same way we did for the source database.

   ' See if the change was successful
   status = W32_NSFDbReplicaInfoGet(hDb, replicaInfo)
   If status <> 0 Then
      ChangeReplicaID = False
      Exit Function ' ==========
   End If
   ' Compare what the replica ID is now to the source database
   destCompare = Right("0000000" + Hex(replicaInfo.ID.Innards(1)), 8)
   destCompare = destCompare + Right("0000000" + Hex(replicaInfo.ID.Innards(0)), 8)
   ' Close the destination database
   status = W32_NSFDbClose(hDb)
   ' If the destination matches the source, then the change was a success
   If destCompare = compare Then
      ChangeReplicaID = True
   Else
      ChangeReplicaID = False
   End If
End Function

Our code doesn't trust a return value of zero from the NSFDbReplicaInfoSet function. We check it out for ourselves. If, after doing the change, the replica ID matches what the source replica ID was, then we know the change was successful and our function returns the value True.