/*
 * twin.c - Twin ADT module.
 */

/*



*/


/* Headers
 **********/

#include "project.h"
#pragma hdrstop

#include "stub.h"
#include "oleutil.h"


/* Constants
 ************/

/* twin family pointer array allocation constants */

#define NUM_START_TWIN_FAMILY_PTRS        (16)
#define NUM_TWIN_FAMILY_PTRS_TO_ADD       (16)


/* Types
 ********/

/* twin families database structure header */

typedef struct _twinfamiliesdbheader
{
   /* number of twin families */

   LONG lcTwinFamilies;
}
TWINFAMILIESDBHEADER;
DECLARE_STANDARD_TYPES(TWINFAMILIESDBHEADER);

/* individual twin family database structure header */

typedef struct _twinfamilydbheader
{
   /* stub flags */

   DWORD dwStubFlags;

   /* old string handle of name */

   HSTRING hsName;

   /* number of object twins in family */

   LONG lcObjectTwins;
}
TWINFAMILYDBHEADER;
DECLARE_STANDARD_TYPES(TWINFAMILYDBHEADER);

/* object twin database structure */

typedef struct _dbobjecttwin
{
   /* stub flags */

   DWORD dwStubFlags;

   /* old handle to folder string */

   HPATH hpath;

   /* time stamp at last reconciliation */

   FILESTAMP fsLastRec;
}
DBOBJECTTWIN;
DECLARE_STANDARD_TYPES(DBOBJECTTWIN);

/* GenerateSpinOffObjectTwin() callback structure */

typedef struct _spinoffobjecttwininfo
{
   PCFOLDERPAIR pcfp;

   HLIST hlistNewObjectTwins;
}
SPINOFFOBJECTTWININFO;
DECLARE_STANDARD_TYPES(SPINOFFOBJECTTWININFO);

typedef void (CALLBACK *COPYOBJECTTWINPROC)(POBJECTTWIN, PCDBOBJECTTWIN);


/***************************** Private Functions *****************************/

/* Module Prototypes
 ********************/

PRIVATE_CODE TWINRESULT TwinJustTheseTwoObjects(HBRFCASE, HPATH, HPATH, LPCTSTR, POBJECTTWIN *, POBJECTTWIN *, HLIST);
PRIVATE_CODE BOOL CreateTwinFamily(HBRFCASE, LPCTSTR, PTWINFAMILY *);
PRIVATE_CODE void CollapseTwinFamilies(PTWINFAMILY, PTWINFAMILY);
PRIVATE_CODE BOOL GenerateSpinOffObjectTwin(PVOID, PVOID);
PRIVATE_CODE BOOL BuildBradyBunch(PVOID, PVOID);
PRIVATE_CODE BOOL CreateObjectTwinAndAddToList(PTWINFAMILY, HPATH, HLIST, POBJECTTWIN *, PHNODE);
PRIVATE_CODE BOOL CreateListOfGeneratedObjectTwins(PCFOLDERPAIR, PHLIST);
PRIVATE_CODE void NotifyNewObjectTwins(HLIST, HCLSIFACECACHE);
PRIVATE_CODE HRESULT NotifyOneNewObjectTwin(PINotifyReplica, PCOBJECTTWIN, LPCTSTR);
PRIVATE_CODE HRESULT CreateOtherReplicaMonikers(PCOBJECTTWIN, PULONG, PIMoniker **);
PRIVATE_CODE COMPARISONRESULT TwinFamilySortCmp(PCVOID, PCVOID);
PRIVATE_CODE COMPARISONRESULT TwinFamilySearchCmp(PCVOID, PCVOID);
PRIVATE_CODE BOOL ObjectTwinSearchCmp(PCVOID, PCVOID);
PRIVATE_CODE TWINRESULT WriteTwinFamily(HCACHEDFILE, PCTWINFAMILY);
PRIVATE_CODE TWINRESULT WriteObjectTwin(HCACHEDFILE, PCOBJECTTWIN);
PRIVATE_CODE TWINRESULT ReadTwinFamily(HCACHEDFILE, HBRFCASE, PCDBVERSION, HHANDLETRANS, HHANDLETRANS);
PRIVATE_CODE TWINRESULT ReadObjectTwin(HCACHEDFILE, PCDBVERSION, PTWINFAMILY, HHANDLETRANS);
PRIVATE_CODE void CopyTwinFamilyInfo(PTWINFAMILY, PCTWINFAMILYDBHEADER);
PRIVATE_CODE void CopyObjectTwinInfo(POBJECTTWIN, PCDBOBJECTTWIN);
PRIVATE_CODE void CopyM8ObjectTwinInfo(POBJECTTWIN, PCDBOBJECTTWIN);
PRIVATE_CODE BOOL DestroyObjectTwinStubWalker(PVOID, PVOID);
PRIVATE_CODE BOOL MarkObjectTwinNeverReconciledWalker(PVOID, PVOID);
PRIVATE_CODE BOOL LookForSrcFolderTwinsWalker(PVOID, PVOID);
PRIVATE_CODE BOOL IncrementSrcFolderTwinsWalker(PVOID, PVOID);
PRIVATE_CODE BOOL ClearSrcFolderTwinsWalker(PVOID, PVOID);
PRIVATE_CODE BOOL SetTwinFamilyWalker(PVOID, PVOID);
PRIVATE_CODE BOOL InsertNodeAtFrontWalker(POBJECTTWIN, PVOID);

#ifdef VSTF

PRIVATE_CODE BOOL IsValidObjectTwinWalker(PVOID, PVOID);
PRIVATE_CODE BOOL IsValidPCNEWOBJECTTWIN(PCNEWOBJECTTWIN);
PRIVATE_CODE BOOL IsValidPCSPINOFFOBJECTTWININFO(PCSPINOFFOBJECTTWININFO);

#endif

#ifdef DEBUG

PRIVATE_CODE BOOL AreTwinFamiliesValid(HPTRARRAY);

#endif


/*
** TwinJustTheseTwoObjects()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE TWINRESULT TwinJustTheseTwoObjects(HBRFCASE hbr, HPATH hpathFolder1,
                                           HPATH hpathFolder2, LPCTSTR pcszName,
                                           POBJECTTWIN *ppot1,
                                           POBJECTTWIN *ppot2,
                                           HLIST hlistNewObjectTwins)
{
   TWINRESULT tr = TR_OUT_OF_MEMORY;
   HNODE hnodeSearch;
   BOOL bFound1;
   BOOL bFound2;

   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_HANDLE(hpathFolder1, PATH));
   ASSERT(IS_VALID_HANDLE(hpathFolder2, PATH));
   ASSERT(IS_VALID_STRING_PTR(pcszName, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(ppot1, POBJECTTWIN));
   ASSERT(IS_VALID_WRITE_PTR(ppot2, POBJECTTWIN));
   ASSERT(IS_VALID_HANDLE(hlistNewObjectTwins, LIST));

   /* Determine twin families of existing object twins. */

   bFound1 = FindObjectTwin(hbr, hpathFolder1, pcszName, &hnodeSearch);

   if (bFound1)
      *ppot1 = (POBJECTTWIN)GetNodeData(hnodeSearch);

   bFound2 = FindObjectTwin(hbr, hpathFolder2, pcszName, &hnodeSearch);

   if (bFound2)
      *ppot2 = (POBJECTTWIN)GetNodeData(hnodeSearch);

   /* Take action based upon existence of two object twins. */

   if (! bFound1 && ! bFound2)
   {
      PTWINFAMILY ptfNew;

      /* Neither object is already present.  Create a new twin family. */

      if (CreateTwinFamily(hbr, pcszName, &ptfNew))
      {
         HNODE hnodeNew1;

         if (CreateObjectTwinAndAddToList(ptfNew, hpathFolder1,
                                          hlistNewObjectTwins, ppot1,
                                          &hnodeNew1))
         {
            HNODE hnodeNew2;

            if (CreateObjectTwinAndAddToList(ptfNew, hpathFolder2,
                                             hlistNewObjectTwins, ppot2,
                                             &hnodeNew2))
            {
               TRACE_OUT((TEXT("TwinJustTheseTwoObjects(): Created a twin family for object %s in folders %s and %s."),
                          pcszName,
                          DebugGetPathString(hpathFolder1),
                          DebugGetPathString(hpathFolder2)));

               ASSERT(IsStubFlagClear(&(ptfNew->stub), STUB_FL_DELETION_PENDING));

               tr = TR_SUCCESS;
            }
            else
            {
               DeleteNode(hnodeNew1);
               DestroyStub(&((*ppot1)->stub));
TWINJUSTTHESETWOOBJECTS_BAIL:
               DestroyStub(&(ptfNew->stub));
            }
         }
         else
            goto TWINJUSTTHESETWOOBJECTS_BAIL;
      }
   }
   else if (bFound1 && bFound2)
   {
      /*
       * Both objects are already present.  Are they members of the same twin
       * family?
       */

      if ((*ppot1)->ptfParent == (*ppot2)->ptfParent)
      {
         /* Yes, same twin family.  Complain that these twins already exist. */

         TRACE_OUT((TEXT("TwinJustTheseTwoObjects(): Object %s is already twinned in folders %s and %s."),
                    pcszName,
                    DebugGetPathString(hpathFolder1),
                    DebugGetPathString(hpathFolder2)));

         tr = TR_DUPLICATE_TWIN;
      }
      else
      {
         /*
          * No, different twin families.  Collapse the two families.
          *
          * "That's the way they became the Brady bunch..."
          *
          * *ppot1 and *ppot2 remain valid across this call.
          */

         TRACE_OUT((TEXT("TwinJustTheseTwoObjects(): Collapsing separate twin families for object %s in folders %s and %s."),
                    pcszName,
                    DebugGetPathString(hpathFolder1),
                    DebugGetPathString(hpathFolder2)));

         CollapseTwinFamilies((*ppot1)->ptfParent, (*ppot2)->ptfParent);

         tr = TR_SUCCESS;
      }
   }
   else
   {
      PTWINFAMILY ptfParent;
      HNODE hnodeUnused;

      /*
       * Only one of the two objects is present.  Add the new object twin
       * to the existing object twin's family.
       */

      if (bFound1)
      {
         /* First object is already a twin. */

         ptfParent = (*ppot1)->ptfParent;

         if (CreateObjectTwinAndAddToList(ptfParent, hpathFolder2,
                                          hlistNewObjectTwins, ppot2,
                                          &hnodeUnused))
         {
            TRACE_OUT((TEXT("TwinJustTheseTwoObjects(): Adding twin of object %s\\%s to existing twin family including %s\\%s."),
                       DebugGetPathString(hpathFolder2),
                       pcszName,
                       DebugGetPathString(hpathFolder1),
                       pcszName));

            tr = TR_SUCCESS;
         }
      }
      else
      {
         /* Second object is already a twin. */

         ptfParent = (*ppot2)->ptfParent;

         if (CreateObjectTwinAndAddToList(ptfParent, hpathFolder1,
                                          hlistNewObjectTwins, ppot1,
                                          &hnodeUnused))
         {
            TRACE_OUT((TEXT("TwinJustTheseTwoObjects(): Adding twin of object %s\\%s to existing twin family including %s\\%s."),
                       DebugGetPathString(hpathFolder1),
                       pcszName,
                       DebugGetPathString(hpathFolder2),
                       pcszName));

            tr = TR_SUCCESS;
         }
      }
   }

   ASSERT((tr != TR_SUCCESS && tr != TR_DUPLICATE_TWIN) ||
          IS_VALID_STRUCT_PTR(*ppot1, COBJECTTWIN) && IS_VALID_STRUCT_PTR(*ppot2, COBJECTTWIN));

   return(tr);
}


/*
** CreateTwinFamily()
**
** Creates a new empty twin family, and adds it to a briefcase.
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL CreateTwinFamily(HBRFCASE hbr, LPCTSTR pcszName, PTWINFAMILY *pptf)
{
   BOOL bResult = FALSE;
   PTWINFAMILY ptfNew;

   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_STRING_PTR(pcszName, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(pptf, PTWINFAMILY));

   /* Try to create a new TWINFAMILY structure. */

   if (AllocateMemory(sizeof(*ptfNew), &ptfNew))
   {
      NEWLIST nl;
      HLIST hlistObjectTwins;

      /* Create a list of object twins for the new twin family. */

      nl.dwFlags = 0;

      if (CreateList(&nl, &hlistObjectTwins))
      {
         HSTRING hsName;

         /* Add the object name to the name string table. */

         if (AddString(pcszName, GetBriefcaseNameStringTable(hbr), 
            GetHashBucketIndex, &hsName))
         {
            ARRAYINDEX aiUnused;

            /* Fill in TWINFAMILY fields. */

            InitStub(&(ptfNew->stub), ST_TWINFAMILY);

            ptfNew->hsName = hsName;
            ptfNew->hlistObjectTwins = hlistObjectTwins;
            ptfNew->hbr = hbr;

            MarkTwinFamilyNeverReconciled(ptfNew);

            /* Add the twin family to the briefcase's list of twin families. */

            if (AddPtr(GetBriefcaseTwinFamilyPtrArray(hbr), TwinFamilySortCmp, ptfNew, &aiUnused))
            {
               *pptf = ptfNew;
               bResult = TRUE;

               ASSERT(IS_VALID_STRUCT_PTR(*pptf, CTWINFAMILY));
            }
            else
            {
               DeleteString(hsName);
CREATETWINFAMILY_BAIL1:
               DestroyList(hlistObjectTwins);
CREATETWINFAMILY_BAIL2:
               FreeMemory(ptfNew);
            }
         }
         else
            goto CREATETWINFAMILY_BAIL1;
      }
      else
         goto CREATETWINFAMILY_BAIL2;
   }

   return(bResult);
}


/*
** CollapseTwinFamilies()
**
** Collapses two twin families into one.  N.b., this function should only be
** called on two twin families with the same object name!
**
** Arguments:     ptf1 - pointer to destination twin family
**                ptf2 - pointer to source twin family
**
** Returns:       void
**
** Side Effects:  Twin family *ptf2 is destroyed.
*/
PRIVATE_CODE void CollapseTwinFamilies(PTWINFAMILY ptf1, PTWINFAMILY ptf2)
{
   ASSERT(IS_VALID_STRUCT_PTR(ptf1, CTWINFAMILY));
   ASSERT(IS_VALID_STRUCT_PTR(ptf2, CTWINFAMILY));

   ASSERT(CompareNameStringsByHandle(ptf1->hsName, ptf2->hsName) == CR_EQUAL);

   /* Use the first twin family as the collapsed twin family. */

   /*
    * Change the parent twin family of the object twins in the second twin
    * family to the first twin family.
    */

   EVAL(WalkList(ptf2->hlistObjectTwins, &SetTwinFamilyWalker, ptf1));

   /* Append object list from second twin family on to first. */

   AppendList(ptf1->hlistObjectTwins, ptf2->hlistObjectTwins);

   MarkTwinFamilyNeverReconciled(ptf1);

   /* Wipe out the old twin family. */

   DestroyStub(&(ptf2->stub));

   ASSERT(IS_VALID_STRUCT_PTR(ptf1, CTWINFAMILY));

   return;
}


/*
** GenerateSpinOffObjectTwin()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL GenerateSpinOffObjectTwin(PVOID pot, PVOID pcsooti)
{
   BOOL bResult;
   HPATH hpathMatchingFolder;
   HNODE hnodeUnused;

   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(IS_VALID_STRUCT_PTR(pcsooti, CSPINOFFOBJECTTWININFO));

   /*
    * Append the generated object twin's subpath to the matching folder twin's
    * base path for subtree twins.
    */

   if (BuildPathForMatchingObjectTwin(
                     ((PCSPINOFFOBJECTTWININFO)pcsooti)->pcfp, pot,
                     GetBriefcasePathList(((POBJECTTWIN)pot)->ptfParent->hbr),
                     &hpathMatchingFolder))
   {
      /*
       * Does this generated object twin's twin family already contain an
       * object twin generated by the other half of the pair of folder twins?
       */

      if (! SearchUnsortedList(((POBJECTTWIN)pot)->ptfParent->hlistObjectTwins,
                               &ObjectTwinSearchCmp, hpathMatchingFolder,
                               &hnodeUnused))
      {
         /*
          * No.  Does the other object twin already exist in a different twin
          * family?
          */

         if (FindObjectTwin(((POBJECTTWIN)pot)->ptfParent->hbr,
                            hpathMatchingFolder,
                            GetString(((POBJECTTWIN)pot)->ptfParent->hsName),
                            &hnodeUnused))
         {
            /* Yes. */

            ASSERT(((PCOBJECTTWIN)GetNodeData(hnodeUnused))->ptfParent != ((POBJECTTWIN)pot)->ptfParent);

            bResult = TRUE;
         }
         else
         {
            POBJECTTWIN potNew;

            /*
             * No.  Create a new object twin, and add it to the bookkeeping
             * list of new object twins.
             */

            bResult = CreateObjectTwinAndAddToList(
                     ((POBJECTTWIN)pot)->ptfParent, hpathMatchingFolder,
                     ((PCSPINOFFOBJECTTWININFO)pcsooti)->hlistNewObjectTwins,
                     &potNew, &hnodeUnused);

#ifdef DEBUG

            if (bResult)
            {
               TRACE_OUT((TEXT("GenerateSpinOffObjectTwin(): Generated spin-off object twin for object %s\\%s."),
                          DebugGetPathString(potNew->hpath),
                          GetString(potNew->ptfParent->hsName)));
            }

#endif

         }
      }
      else
         bResult = TRUE;

      DeletePath(hpathMatchingFolder);
   }
   else
      bResult = FALSE;

   return(bResult);
}


/*
** BuildBradyBunch()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL BuildBradyBunch(PVOID pot, PVOID pcfp)
{
   BOOL bResult;
   HPATH hpathMatchingFolder;
   HNODE hnodeOther;

   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(IS_VALID_STRUCT_PTR(pcfp, CFOLDERPAIR));

   /*
    * Append the generated object twin's subpath to the matching folder twin's
    * base path for subtree twins.
    */

   bResult = BuildPathForMatchingObjectTwin(
                     pcfp, pot,
                     GetBriefcasePathList(((POBJECTTWIN)pot)->ptfParent->hbr),
                     &hpathMatchingFolder);

   if (bResult)
   {
      /*
       * Does this generated object twin's twin family already contain an object
       * twin generated by the other half of the pair of folder twins?
       */

      if (! SearchUnsortedList(((POBJECTTWIN)pot)->ptfParent->hlistObjectTwins,
                               &ObjectTwinSearchCmp, hpathMatchingFolder,
                               &hnodeOther))
      {
         /*
          * The other object twin should already exist in a different twin family.
          */

         if (EVAL(FindObjectTwin(((POBJECTTWIN)pot)->ptfParent->hbr,
                                 hpathMatchingFolder,
                                 GetString(((POBJECTTWIN)pot)->ptfParent->hsName),
                                 &hnodeOther)))
         {
            PCOBJECTTWIN pcotOther;

            pcotOther = (PCOBJECTTWIN)GetNodeData(hnodeOther);

            if (EVAL(pcotOther->ptfParent != ((POBJECTTWIN)pot)->ptfParent))
            {
               /* It does.  Crush them. */

               CollapseTwinFamilies(((POBJECTTWIN)pot)->ptfParent,
                                    pcotOther->ptfParent);

               TRACE_OUT((TEXT("BuildBradyBunch(): Collapsed separate twin families for object %s\\%s and %s\\%s."),
                          DebugGetPathString(((POBJECTTWIN)pot)->hpath),
                          GetString(((POBJECTTWIN)pot)->ptfParent->hsName),
                          DebugGetPathString(pcotOther->hpath),
                          GetString(pcotOther->ptfParent->hsName)));
            }
         }
      }

      DeletePath(hpathMatchingFolder);
   }

   return(bResult);
}


/*
** CreateObjectTwinAndAddToList()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL CreateObjectTwinAndAddToList(PTWINFAMILY ptf, HPATH hpathFolder,
                                          HLIST hlistObjectTwins,
                                          POBJECTTWIN *ppot, PHNODE phnode)
{
   BOOL bResult = FALSE;

   ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));
   ASSERT(IS_VALID_HANDLE(hpathFolder, PATH));
   ASSERT(IS_VALID_HANDLE(hlistObjectTwins, LIST));
   ASSERT(IS_VALID_WRITE_PTR(ppot, POBJECTTWIN));
   ASSERT(IS_VALID_WRITE_PTR(phnode, HNODE));

   if (CreateObjectTwin(ptf, hpathFolder, ppot))
   {
      if (InsertNodeAtFront(hlistObjectTwins, NULL, *ppot, phnode))
         bResult = TRUE;
      else
         DestroyStub(&((*ppot)->stub));
   }

   return(bResult);
}


/*
** CreateListOfGeneratedObjectTwins()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL CreateListOfGeneratedObjectTwins(PCFOLDERPAIR pcfp,
                                             PHLIST phlistGeneratedObjectTwins)
{
   NEWLIST nl;
   BOOL bResult = FALSE;

   ASSERT(IS_VALID_STRUCT_PTR(pcfp, CFOLDERPAIR));
   ASSERT(IS_VALID_WRITE_PTR(phlistGeneratedObjectTwins, HLIST));

   nl.dwFlags = 0;

   if (CreateList(&nl, phlistGeneratedObjectTwins))
   {
      if (EnumGeneratedObjectTwins(pcfp, &InsertNodeAtFrontWalker, *phlistGeneratedObjectTwins))
         bResult = TRUE;
      else
         DestroyList(*phlistGeneratedObjectTwins);
   }

   return(bResult);
}


/*
** NotifyNewObjectTwins()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE void NotifyNewObjectTwins(HLIST hlistNewObjectTwins,
                                       HCLSIFACECACHE hcic)
{
   BOOL bContinue;
   HNODE hnode;

   ASSERT(IS_VALID_HANDLE(hlistNewObjectTwins, LIST));
   ASSERT(IS_VALID_HANDLE(hcic, CLSIFACECACHE));

   for (bContinue = GetFirstNode(hlistNewObjectTwins, &hnode);
        bContinue;
        bContinue = GetNextNode(hnode, &hnode))
   {
      PCOBJECTTWIN pcot;
      TCHAR rgchPath[MAX_PATH_LEN];
      CLSID clsidReplicaNotification;

      pcot = (PCOBJECTTWIN)GetNodeData(hnode);

      rgchPath[0] = TEXT('\0');
      GetPathString(pcot->hpath, rgchPath);
      CatPath(rgchPath, GetString(pcot->ptfParent->hsName));
      ASSERT(lstrlen(rgchPath) < ARRAYSIZE(rgchPath));

      if (SUCCEEDED(GetReplicaNotificationClassID(rgchPath,
                                                  &clsidReplicaNotification)))
      {
         PINotifyReplica pinr;

         if (SUCCEEDED(GetClassInterface(hcic, &clsidReplicaNotification,
                                         &IID_INotifyReplica, &pinr)))
            /* Ignore return value. */
            NotifyOneNewObjectTwin(pinr, pcot, rgchPath);
         else
            TRACE_OUT((TEXT("NotifyNewObjectTwins(): Failed to get INotifyReplica for replica %s."),
                       rgchPath));
      }
      else
         TRACE_OUT((TEXT("NotifyNewObjectTwins(): Failed to get replica notification class ID for replica %s."),
                    rgchPath));
   }

   return;
}


/*
** NotifyOneNewObjectTwin()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE HRESULT NotifyOneNewObjectTwin(PINotifyReplica pinr, PCOBJECTTWIN pcot,
                                       LPCTSTR pcszPath)
{
   HRESULT hr;
   HSTGIFACE hstgi;

   ASSERT(IS_VALID_STRUCT_PTR(pinr, CINotifyReplica));
   ASSERT(IS_VALID_STRUCT_PTR(pcot, COBJECTTWIN));
   ASSERT(IS_VALID_STRING_PTR(pcszPath, CSTR));

   hr = GetStorageInterface((PIUnknown)pinr, &hstgi);

   if (SUCCEEDED(hr))
   {
      hr = LoadFromStorage(hstgi, pcszPath);

      if (SUCCEEDED(hr))
      {
         ULONG ulcOtherReplicas;
         PIMoniker *ppimkOtherReplicas;

         /*
          * RAIDRAID: (16270) (Performance) We may create a file moniker for
          * the same object twin multiple times here.
          */

         hr = CreateOtherReplicaMonikers(pcot, &ulcOtherReplicas,
                                         &ppimkOtherReplicas);

         if (SUCCEEDED(hr))
         {
            hr = pinr->lpVtbl->YouAreAReplica(pinr, ulcOtherReplicas,
                                              ppimkOtherReplicas);

            if (SUCCEEDED(hr))
            {
               hr = SaveToStorage(hstgi);

               if (SUCCEEDED(hr))
                  TRACE_OUT((TEXT("NotifyOneNewObjectTwin(): Replica %s successfully notified."),
                             pcszPath));
               else
                  WARNING_OUT((TEXT("NotifyOneNewObjectTwin(): Failed to save replica %s to storage."),
                               pcszPath));
            }
            else
               WARNING_OUT((TEXT("NotifyOneNewObjectTwin(): Failed to notify replica %s."),
                            pcszPath));

            ReleaseIUnknowns(ulcOtherReplicas,
                             (PIUnknown *)ppimkOtherReplicas);
         }
         else
            WARNING_OUT((TEXT("NotifyOneNewObjectTwin(): Failed to create monikers for other replicas of replica %s."),
                         pcszPath));
      }
      else
         WARNING_OUT((TEXT("NotifyOneNewObjectTwin(): Failed to load replica %s from storage."),
                      pcszPath));

      ReleaseStorageInterface(hstgi);
   }
   else
      WARNING_OUT((TEXT("NotifyOneNewObjectTwin(): Failed to get storage interface for replica %s."),
                   pcszPath));

   return(hr);
}


/*
** CreateOtherReplicaMonikers()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE HRESULT CreateOtherReplicaMonikers(PCOBJECTTWIN pcotMaster,
                                           PULONG pulcOtherReplicas,
                                           PIMoniker **pppimk)
{
   HRESULT hr;
   HLIST hlist;
   ULONG ulcOtherReplicas;

   ASSERT(IS_VALID_STRUCT_PTR(pcotMaster, COBJECTTWIN));
   ASSERT(IS_VALID_WRITE_PTR(pulcOtherReplicas, ULONG));
   ASSERT(IS_VALID_WRITE_PTR(pppimk, PIMoniker *));

   hlist = pcotMaster->ptfParent->hlistObjectTwins;

   ulcOtherReplicas = GetNodeCount(hlist);
   ASSERT(ulcOtherReplicas > 0);
   ulcOtherReplicas--;

   if (AllocateMemory(ulcOtherReplicas * sizeof(**pppimk), (PVOID *)pppimk))
   {
      BOOL bContinue;
      HNODE hnode;

      hr = S_OK;
      *pulcOtherReplicas = 0;

      for (bContinue = GetFirstNode(hlist, &hnode);
           bContinue;
           bContinue = GetNextNode(hnode, &hnode))
      {
         PCOBJECTTWIN pcot;

         pcot = (PCOBJECTTWIN)GetNodeData(hnode);

         if (pcot != pcotMaster)
         {
            TCHAR rgchPath[MAX_PATH_LEN];

            rgchPath[0] = TEXT('\0');
            GetPathString(pcot->hpath, rgchPath);

            hr = MyCreateFileMoniker(rgchPath,
                                     GetString(pcot->ptfParent->hsName),
                                     &((*pppimk)[*pulcOtherReplicas]));

            if (SUCCEEDED(hr))
            {
               ASSERT(*pulcOtherReplicas < ulcOtherReplicas);
               (*pulcOtherReplicas)++;
            }
            else
               break;
         }
      }

      if (FAILED(hr))
         ReleaseIUnknowns(*pulcOtherReplicas, *(PIUnknown **)pppimk);
   }
   else
      hr = E_OUTOFMEMORY;

   return(hr);
}


/*
** TwinFamilySortCmp()
**
** Pointer comparison function used to sort the global array of twin families.
**
** Arguments:     pctf1 - pointer to TWINFAMILY describing first twin family
**                pctf2 - pointer to TWINFAMILY describing second twin family
**
** Returns:
**
** Side Effects:  none
**
** Twin families are sorted by:
**    1) name string
**    2) pointer value
*/
PRIVATE_CODE COMPARISONRESULT TwinFamilySortCmp(PCVOID pctf1, PCVOID pctf2)
{
   COMPARISONRESULT cr;

   ASSERT(IS_VALID_STRUCT_PTR(pctf1, CTWINFAMILY));
   ASSERT(IS_VALID_STRUCT_PTR(pctf2, CTWINFAMILY));

   cr = CompareNameStringsByHandle(((PCTWINFAMILY)pctf1)->hsName, ((PCTWINFAMILY)pctf2)->hsName);

   if (cr == CR_EQUAL)
      /* Same name strings.  Now sort by pointer value. */
      cr = ComparePointers(pctf1, pctf2);

   return(cr);
}


/*
** TwinFamilySearchCmp()
**
** Pointer comparison function used to search the global array of twin families
** for the first twin family for a given name.
**
** Arguments:     pcszName - name string to search for
**                pctf - pointer to TWINFAMILY to examine
**
** Returns:
**
** Side Effects:  none
**
** Twin families are searched by:
**    1) name string
*/
PRIVATE_CODE COMPARISONRESULT TwinFamilySearchCmp(PCVOID pcszName, PCVOID pctf)
{
   ASSERT(IS_VALID_STRING_PTR(pcszName, CSTR));
   ASSERT(IS_VALID_STRUCT_PTR(pctf, CTWINFAMILY));

   return(CompareNameStrings(pcszName, GetString(((PCTWINFAMILY)pctf)->hsName)));
}


/*
** ObjectTwinSearchCmp()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL ObjectTwinSearchCmp(PCVOID hpath, PCVOID pcot)
{
   ASSERT(IS_VALID_HANDLE((HPATH)hpath, PATH));
   ASSERT(IS_VALID_STRUCT_PTR(pcot, COBJECTTWIN));

   return(ComparePaths((HPATH)hpath, ((PCOBJECTTWIN)pcot)->hpath));
}


/*
** WriteTwinFamily()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PRIVATE_CODE TWINRESULT WriteTwinFamily(HCACHEDFILE hcf, PCTWINFAMILY pctf)
{
   TWINRESULT tr = TR_BRIEFCASE_WRITE_FAILED;
   DWORD dwcbTwinFamilyDBHeaderOffset;

   ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
   ASSERT(IS_VALID_STRUCT_PTR(pctf, CTWINFAMILY));

   /* Save initial file poisition. */

   dwcbTwinFamilyDBHeaderOffset = GetCachedFilePointerPosition(hcf);

   if (dwcbTwinFamilyDBHeaderOffset != INVALID_SEEK_POSITION)
   {
      TWINFAMILYDBHEADER tfdbh;

      /* Leave space for the twin family's header. */

      ZeroMemory(&tfdbh, sizeof(tfdbh));

      if (WriteToCachedFile(hcf, (PCVOID)&tfdbh, sizeof(tfdbh), NULL))
      {
         BOOL bContinue;
         HNODE hnode;
         LONG lcObjectTwins = 0;

         /* Save twin family's object twins. */

         ASSERT(GetNodeCount(pctf->hlistObjectTwins) >= 2);

         tr = TR_SUCCESS;

         for (bContinue = GetFirstNode(pctf->hlistObjectTwins, &hnode);
              bContinue;
              bContinue = GetNextNode(hnode, &hnode))
         {
            POBJECTTWIN pot;

            pot = (POBJECTTWIN)GetNodeData(hnode);

            tr = WriteObjectTwin(hcf, pot);

            if (tr == TR_SUCCESS)
            {
               ASSERT(lcObjectTwins < LONG_MAX);
               lcObjectTwins++;
            }
            else
               break;
         }

         /* Save twin family's database header. */

         if (tr == TR_SUCCESS)
         {
            ASSERT(lcObjectTwins >= 2);

            tfdbh.dwStubFlags = (pctf->stub.dwFlags & DB_STUB_FLAGS_MASK);
            tfdbh.hsName = pctf->hsName;
            tfdbh.lcObjectTwins = lcObjectTwins;

            tr = WriteDBSegmentHeader(hcf, dwcbTwinFamilyDBHeaderOffset, &tfdbh, sizeof(tfdbh));
         }
      }
   }

   return(tr);
}


/*
** WriteObjectTwin()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PRIVATE_CODE TWINRESULT WriteObjectTwin(HCACHEDFILE hcf, PCOBJECTTWIN pcot)
{
   TWINRESULT tr;
   DBOBJECTTWIN dbot;

   ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
   ASSERT(IS_VALID_STRUCT_PTR(pcot, COBJECTTWIN));

   /* Set up object twin database structure. */

   dbot.dwStubFlags = (pcot->stub.dwFlags & DB_STUB_FLAGS_MASK);
   dbot.hpath = pcot->hpath;
   dbot.hpath = pcot->hpath;
   dbot.fsLastRec = pcot->fsLastRec;

   /* Save object twin database structure. */

   if (WriteToCachedFile(hcf, (PCVOID)&dbot, sizeof(dbot), NULL))
      tr = TR_SUCCESS;
   else
      tr = TR_BRIEFCASE_WRITE_FAILED;

   return(tr);
}


/*
** ReadTwinFamily()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PRIVATE_CODE TWINRESULT ReadTwinFamily(HCACHEDFILE hcf, HBRFCASE hbr,
                                  PCDBVERSION pcdbver,
                                  HHANDLETRANS hhtFolderTrans,
                                  HHANDLETRANS hhtNameTrans)
{
   TWINRESULT tr = TR_CORRUPT_BRIEFCASE;
   TWINFAMILYDBHEADER tfdbh;
   DWORD dwcbRead;

   ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_READ_PTR(pcdbver, DBVERSION));
   ASSERT(IS_VALID_HANDLE(hhtFolderTrans, HANDLETRANS));
   ASSERT(IS_VALID_HANDLE(hhtNameTrans, HANDLETRANS));

   if (ReadFromCachedFile(hcf, &tfdbh, sizeof(tfdbh), &dwcbRead) &&
       dwcbRead == sizeof(tfdbh))
   {
      if (tfdbh.lcObjectTwins >= 2)
      {
         HSTRING hsName;

         if (TranslateHandle(hhtNameTrans, (HGENERIC)(tfdbh.hsName), (PHGENERIC)&hsName))
         {
            PTWINFAMILY ptfParent;

            if (CreateTwinFamily(hbr, GetString(hsName), &ptfParent))
            {
               LONG l;

               CopyTwinFamilyInfo(ptfParent, &tfdbh);

               tr = TR_SUCCESS;

               for (l = tfdbh.lcObjectTwins;
                    l > 0 && tr == TR_SUCCESS;
                    l--)
                  tr = ReadObjectTwin(hcf, pcdbver, ptfParent, hhtFolderTrans);

               if (tr != TR_SUCCESS)
                  DestroyStub(&(ptfParent->stub));
            }
            else
               tr = TR_OUT_OF_MEMORY;
         }
      }
   }

   return(tr);
}


/*
** ReadObjectTwin()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE TWINRESULT ReadObjectTwin(HCACHEDFILE hcf, 
                                  PCDBVERSION pcdbver,
                                  PTWINFAMILY ptfParent,
                                  HHANDLETRANS hhtFolderTrans)
{
   TWINRESULT tr;
   DBOBJECTTWIN dbot;
   DWORD dwcbRead;
   HPATH hpath;
   DWORD dwcbSize;
   COPYOBJECTTWINPROC pfnCopy;

   ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
   ASSERT(IS_VALID_READ_PTR(pcdbver, DBVERSION));
   ASSERT(IS_VALID_STRUCT_PTR(ptfParent, CTWINFAMILY));
   ASSERT(IS_VALID_HANDLE(hhtFolderTrans, HANDLETRANS));

   if (HEADER_M8_MINOR_VER == pcdbver->dwMinorVer)
   {
      /* The M8 database does not have the ftModLocal in the FILESTAMP
      ** structure.
      */

      dwcbSize = sizeof(dbot) - sizeof(FILETIME);
      pfnCopy = CopyM8ObjectTwinInfo;
   }
   else
   {
      ASSERT(HEADER_MINOR_VER == pcdbver->dwMinorVer);
      dwcbSize = sizeof(dbot);
      pfnCopy = CopyObjectTwinInfo;
   }

   if ((ReadFromCachedFile(hcf, &dbot, dwcbSize, &dwcbRead) &&
        dwcbRead == dwcbSize) &&
       TranslateHandle(hhtFolderTrans, (HGENERIC)(dbot.hpath), (PHGENERIC)&hpath))
   {
      POBJECTTWIN pot;

      /* Create the new object twin and add it to the twin family. */

      if (CreateObjectTwin(ptfParent, hpath, &pot))
      {
          pfnCopy(pot, &dbot);

          tr = TR_SUCCESS;
      }
      else
         tr = TR_OUT_OF_MEMORY;
   }
   else
      tr = TR_CORRUPT_BRIEFCASE;

   return(tr);
}


/*
** CopyTwinFamilyInfo()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PRIVATE_CODE void CopyTwinFamilyInfo(PTWINFAMILY ptf,
                                PCTWINFAMILYDBHEADER pctfdbh)
{
   ASSERT(IS_VALID_WRITE_PTR(ptf, TWINFAMILY));
   ASSERT(IS_VALID_READ_PTR(pctfdbh, CTWINFAMILYDBHEADER));

   ptf->stub.dwFlags = pctfdbh->dwStubFlags;

   return;
}


/*
** CopyObjectTwinInfo()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PRIVATE_CODE void CopyObjectTwinInfo(POBJECTTWIN pot, PCDBOBJECTTWIN pcdbot)
{
   ASSERT(IS_VALID_WRITE_PTR(pot, OBJECTTWIN));
   ASSERT(IS_VALID_READ_PTR(pcdbot, CDBOBJECTTWIN));

   pot->stub.dwFlags = pcdbot->dwStubFlags;
   pot->fsLastRec = pcdbot->fsLastRec;

   return;
}


/*
** CopyM8ObjectTwinInfo()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PRIVATE_CODE void CopyM8ObjectTwinInfo(POBJECTTWIN pot, PCDBOBJECTTWIN pcdbot)
{
   ASSERT(IS_VALID_WRITE_PTR(pot, OBJECTTWIN));
   ASSERT(IS_VALID_READ_PTR(pcdbot, CDBOBJECTTWIN));

   pot->stub.dwFlags = pcdbot->dwStubFlags;
   pot->fsLastRec = pcdbot->fsLastRec;

   /* The pot->fsLastRec.ftModLocal field is invalid, so fill it in */

   if ( !FileTimeToLocalFileTime(&pot->fsLastRec.ftMod, &pot->fsLastRec.ftModLocal) )
   {
      /* Just copy the time if FileTimeToLocalFileTime failed */

      pot->fsLastRec.ftModLocal = pot->fsLastRec.ftMod;
   }

   return;
}


#pragma warning(disable:4100) /* "unreferenced formal parameter" warning */

/*
** DestroyObjectTwinStubWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL DestroyObjectTwinStubWalker(PVOID pot, PVOID pvUnused)
{
   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(! pvUnused);

   /*
    * Set ulcSrcFolderTwins to 0 so UnlinkObjectTwin() succeeds.
    * DestroyStub() will unlink and destroy any new twin family created.
    */

   ((POBJECTTWIN)pot)->ulcSrcFolderTwins = 0;
   DestroyStub(&(((POBJECTTWIN)pot)->stub));

   return(TRUE);
}


/*
** MarkObjectTwinNeverReconciledWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL MarkObjectTwinNeverReconciledWalker(PVOID pot, PVOID pvUnused)
{
   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(! pvUnused);

   MarkObjectTwinNeverReconciled(pot);

   return(TRUE);
}


/*
** LookForSrcFolderTwinsWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL LookForSrcFolderTwinsWalker(PVOID pot, PVOID pvUnused)
{
   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(! pvUnused);

   return(! ((POBJECTTWIN)pot)->ulcSrcFolderTwins);
}


/*
** IncrementSrcFolderTwinsWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL IncrementSrcFolderTwinsWalker(PVOID pot, PVOID pvUnused)
{
   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(! pvUnused);

   ASSERT(((POBJECTTWIN)pot)->ulcSrcFolderTwins < ULONG_MAX);
   ((POBJECTTWIN)pot)->ulcSrcFolderTwins++;

   return(TRUE);
}


/*
** ClearSrcFolderTwinsWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL ClearSrcFolderTwinsWalker(PVOID pot, PVOID pvUnused)
{
   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(! pvUnused);

   ((POBJECTTWIN)pot)->ulcSrcFolderTwins = 0;

   return(TRUE);
}

#pragma warning(default:4100) /* "unreferenced formal parameter" warning */


/*
** SetTwinFamilyWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL SetTwinFamilyWalker(PVOID pot, PVOID ptfParent)
{
   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(IS_VALID_STRUCT_PTR(ptfParent, CTWINFAMILY));

   ((POBJECTTWIN)pot)->ptfParent = ptfParent;

   return(TRUE);
}


/*
** InsertNodeAtFrontWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL InsertNodeAtFrontWalker(POBJECTTWIN pot, PVOID hlist)
{
   HNODE hnodeUnused;

   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(IS_VALID_HANDLE(hlist, LIST));

   return(InsertNodeAtFront(hlist, NULL, pot, &hnodeUnused));
}


#ifdef VSTF

/*
** IsValidObjectTwinWalker()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL IsValidObjectTwinWalker(PVOID pcot, PVOID pctfParent)
{
   return(IS_VALID_STRUCT_PTR(pcot, COBJECTTWIN) &&
          EVAL(((PCOBJECTTWIN)pcot)->ptfParent == pctfParent) &&
          EVAL(IsStubFlagClear(&(((PCOBJECTTWIN)pcot)->stub), STUB_FL_KEEP) ||
               IsStubFlagSet(&(((PCTWINFAMILY)pctfParent)->stub),
                             STUB_FL_DELETION_PENDING)));
}


/*
** IsValidPCNEWOBJECTTWIN()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL IsValidPCNEWOBJECTTWIN(PCNEWOBJECTTWIN pcnot)
{
   return(IS_VALID_READ_PTR(pcnot, CNEWOBJECTTWIN) &&
          EVAL(pcnot->ulSize == sizeof(*pcnot)) &&
          IS_VALID_STRING_PTR(pcnot->pcszFolder1, CSTR) &&
          IS_VALID_STRING_PTR(pcnot->pcszFolder2, CSTR) &&
          IS_VALID_STRING_PTR(pcnot->pcszName, CSTR));
}


/*
** IsValidPCSPINOFFOBJECTTWININFO()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL IsValidPCSPINOFFOBJECTTWININFO(PCSPINOFFOBJECTTWININFO pcsooti)
{
   return(IS_VALID_READ_PTR(pcsooti, CSPINOFFOBJECTTWININFO) &&
          IS_VALID_STRUCT_PTR(pcsooti->pcfp, CFOLDERPAIR) &&
          IS_VALID_HANDLE(pcsooti->hlistNewObjectTwins, LIST));
}

#endif


#ifdef DEBUG

/*
** AreTwinFamiliesValid()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PRIVATE_CODE BOOL AreTwinFamiliesValid(HPTRARRAY hpaTwinFamilies)
{
   ARRAYINDEX aicPtrs;
   ARRAYINDEX ai;

   ASSERT(IS_VALID_HANDLE(hpaTwinFamilies, PTRARRAY));

   aicPtrs = GetPtrCount(hpaTwinFamilies);

   for (ai = 0; ai < aicPtrs; ai++)
   {
      PCTWINFAMILY pctf;

      pctf = GetPtr(hpaTwinFamilies, ai);

      if (! IS_VALID_STRUCT_PTR(pctf, CTWINFAMILY) ||
          ! EVAL(GetNodeCount(pctf->hlistObjectTwins) >= 2))
         break;
   }

   return(ai == aicPtrs);
}

#endif


/****************************** Public Functions *****************************/


/*
** CompareNameStrings()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE COMPARISONRESULT CompareNameStrings(LPCTSTR pcszFirst, LPCTSTR pcszSecond)
{
   ASSERT(IS_VALID_STRING_PTR(pcszFirst, CSTR));
   ASSERT(IS_VALID_STRING_PTR(pcszSecond, CSTR));

   return(MapIntToComparisonResult(lstrcmpi(pcszFirst, pcszSecond)));
}


/*
** CompareNameStringsByHandle()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE COMPARISONRESULT CompareNameStringsByHandle(HSTRING hsFirst,
                                                        HSTRING hsSecond)
{
   ASSERT(IS_VALID_HANDLE(hsFirst, STRING));
   ASSERT(IS_VALID_HANDLE(hsSecond, STRING));

   return(CompareStringsI(hsFirst, hsSecond));
}


/*
** TranslatePATHRESULTToTWINRESULT()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE TWINRESULT TranslatePATHRESULTToTWINRESULT(PATHRESULT pr)
{
   TWINRESULT tr;

   switch (pr)
   {
      case PR_SUCCESS:
         tr = TR_SUCCESS;
         break;

      case PR_UNAVAILABLE_VOLUME:
         tr = TR_UNAVAILABLE_VOLUME;
         break;

      case PR_OUT_OF_MEMORY:
         tr = TR_OUT_OF_MEMORY;
         break;

      default:
         ASSERT(pr == PR_INVALID_PATH);
         tr = TR_INVALID_PARAMETER;
         break;
   }

   return(tr);
}


/*
** CreateTwinFamilyPtrArray()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL CreateTwinFamilyPtrArray(PHPTRARRAY phpa)
{
   NEWPTRARRAY npa;

   ASSERT(IS_VALID_WRITE_PTR(phpa, HPTRARRAY));

   /* Try to create a twin family pointer array. */

   npa.aicInitialPtrs = NUM_START_TWIN_FAMILY_PTRS;
   npa.aicAllocGranularity = NUM_TWIN_FAMILY_PTRS_TO_ADD;
   npa.dwFlags = NPA_FL_SORTED_ADD;

   return(CreatePtrArray(&npa, phpa));
}


/*
** DestroyTwinFamilyPtrArray()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE void DestroyTwinFamilyPtrArray(HPTRARRAY hpa)
{
   ARRAYINDEX aicPtrs;
   ARRAYINDEX ai;

   ASSERT(IS_VALID_HANDLE(hpa, PTRARRAY));

   /* First free all twin families in pointer array. */

   aicPtrs = GetPtrCount(hpa);

   for (ai = 0; ai < aicPtrs; ai++)
      DestroyTwinFamily(GetPtr(hpa, ai));

   /* Now wipe out the pointer array. */

   DestroyPtrArray(hpa);

   return;
}


/*
** GetTwinBriefcase()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE HBRFCASE GetTwinBriefcase(HTWIN htwin)
{
   HBRFCASE hbr;

   ASSERT(IS_VALID_HANDLE(htwin, TWIN));

   switch (((PSTUB)htwin)->st)
   {
      case ST_OBJECTTWIN:
         hbr = ((PCOBJECTTWIN)htwin)->ptfParent->hbr;
         break;

      case ST_TWINFAMILY:
         hbr = ((PCTWINFAMILY)htwin)->hbr;
         break;

      case ST_FOLDERPAIR:
         hbr = ((PCFOLDERPAIR)htwin)->pfpd->hbr;
         break;

      default:
         ERROR_OUT((TEXT("GetTwinBriefcase() called on unrecognized stub type %d."),
                    ((PSTUB)htwin)->st));
         hbr = NULL;
         break;
   }

   return(hbr);
}


/*
** FindObjectTwinInList()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL FindObjectTwinInList(HLIST hlist, HPATH hpath, PHNODE phnode)
{
   ASSERT(IS_VALID_HANDLE(hlist, LIST));
   ASSERT(IS_VALID_HANDLE(hpath, PATH));
   ASSERT(IS_VALID_WRITE_PTR(phnode, HNODE));

   return(SearchUnsortedList(hlist, &ObjectTwinSearchCmp, hpath, phnode));
}


/*
** EnumTwins()
**
** Enumerates folder twins and twin families in a briefcase.
**
** Arguments:
**
** Returns:       TRUE if halted.  FALSE if not.
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL EnumTwins(HBRFCASE hbr, ENUMTWINSPROC etp, LPARAM lpData,
                           PHTWIN phtwinStop)
{
   HPTRARRAY hpa;
   ARRAYINDEX aicPtrs;
   ARRAYINDEX ai;

   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_CODE_PTR(etp, ENUMTWINSPROC));
   ASSERT(IS_VALID_WRITE_PTR(phtwinStop, HTWIN));

   /* Enumerate folder pairs. */

   *phtwinStop = NULL;

   hpa = GetBriefcaseFolderPairPtrArray(hbr);

   aicPtrs = GetPtrCount(hpa);

   for (ai = 0; ai < aicPtrs; ai++)
   {
      PCFOLDERPAIR pcfp;

      pcfp = GetPtr(hpa, ai);

      ASSERT(IS_VALID_STRUCT_PTR(pcfp, CFOLDERPAIR));

      if (! (*etp)((HTWIN)pcfp, lpData))
      {
         *phtwinStop = (HTWIN)pcfp;
         break;
      }
   }

   if (! *phtwinStop)
   {
      /* Enumerate twin families. */

      hpa = GetBriefcaseTwinFamilyPtrArray(hbr);

      aicPtrs = GetPtrCount(hpa);

      for (ai = 0; ai < aicPtrs; ai++)
      {
         PCTWINFAMILY pctf;

         pctf = GetPtr(hpa, ai);

         ASSERT(IS_VALID_STRUCT_PTR(pctf, CTWINFAMILY));

         if (! (*etp)((HTWIN)pctf, lpData))
         {
            *phtwinStop = (HTWIN)pctf;
            break;
         }
      }
   }

   return(*phtwinStop != NULL);
}


/*
** FindObjectTwin()
**
** Looks for a twin family containing a specified object twin.
**
** Arguments:     hpathFolder - folder containing object
**                pcszName - name of object
**
** Returns:       Handle to list node containing pointer to object twin if
**                found, or NULL if not found.
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL FindObjectTwin(HBRFCASE hbr, HPATH hpathFolder,
                                LPCTSTR pcszName, PHNODE phnode)
{
   BOOL bFound = FALSE;
   HPTRARRAY hpaTwinFamilies;
   ARRAYINDEX aiFirst;

   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_HANDLE(hpathFolder, PATH));
   ASSERT(IS_VALID_STRING_PTR(pcszName, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(phnode, HNODE));

   /* Search for a matching twin family. */

   *phnode = NULL;

   hpaTwinFamilies = GetBriefcaseTwinFamilyPtrArray(hbr);

   if (SearchSortedArray(hpaTwinFamilies, &TwinFamilySearchCmp, pcszName,
                         &aiFirst))
   {
      ARRAYINDEX aicPtrs;
      ARRAYINDEX ai;
      PTWINFAMILY ptf;

      /*
       * aiFirst holds the index of the first twin family with a common object
       * name matching pcszName.
       */

      /*
       * Now search each of these twin families for a folder matching
       * pcszFolder.
       */

      aicPtrs = GetPtrCount(hpaTwinFamilies);

      ASSERT(aicPtrs > 0);
      ASSERT(aiFirst >= 0);
      ASSERT(aiFirst < aicPtrs);

      for (ai = aiFirst; ai < aicPtrs; ai++)
      {
         ptf = GetPtr(hpaTwinFamilies, ai);

         ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));

         /* Is this a twin family of objects of the given name? */

         if (CompareNameStrings(GetString(ptf->hsName), pcszName) == CR_EQUAL)
         {
            bFound = SearchUnsortedList(ptf->hlistObjectTwins,
                                        &ObjectTwinSearchCmp, hpathFolder,
                                        phnode);

            if (bFound)
               break;
         }
         else
            /* No.  Stop searching. */
            break;
      }
   }

   return(bFound);
}


/*
** TwinObjects()
**
** Twins two objects.
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
**
** N.b., *ppot1 and *ppot2 are valid if TR_SUCCESS or TR_DUPLICATE_TWIN is
** returned.
*/
PUBLIC_CODE TWINRESULT TwinObjects(HBRFCASE hbr, HCLSIFACECACHE hcic,
                                   HPATH hpathFolder1, HPATH hpathFolder2,
                                   LPCTSTR pcszName, POBJECTTWIN *ppot1,
                                   POBJECTTWIN *ppot2)
{
   TWINRESULT tr;

   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_HANDLE(hcic, CLSIFACECACHE));
   ASSERT(IS_VALID_HANDLE(hpathFolder1, PATH));
   ASSERT(IS_VALID_HANDLE(hpathFolder2, PATH));
   ASSERT(IS_VALID_STRING_PTR(pcszName, CSTR));
   ASSERT(IS_VALID_WRITE_PTR(ppot1, POBJECTTWIN));
   ASSERT(IS_VALID_WRITE_PTR(ppot2, POBJECTTWIN));

   /* Fail twinning a file to itself. */

   if (ComparePaths(hpathFolder1, hpathFolder2) != CR_EQUAL)
   {
      NEWLIST nl;
      HLIST hlistNewObjectTwins;

      nl.dwFlags = 0;

      if (CreateList(&nl, &hlistNewObjectTwins))
      {
         /* Twin 'em. */

         tr = TwinJustTheseTwoObjects(hbr, hpathFolder1, hpathFolder2,
                                      pcszName, ppot1, ppot2,
                                      hlistNewObjectTwins);

         /*
          * Add any new object twins to the lists of generated object twins for
          * all intersecting folder twins.  Create new spin-off object twins
          * from the other folder twin connected to each intersecting folder
          * twin.  Spin-off object twins are added to the twin family as they
          * are created.
          */

         if (tr == TR_SUCCESS)
         {
            if (ApplyNewObjectTwinsToFolderTwins(hlistNewObjectTwins))
            {
               /*
                * Notify new object twins that they are object twins.  Don't
                * notify folder object twins.
                */

               if (*pcszName)
                  NotifyNewObjectTwins(hlistNewObjectTwins, hcic);
            }
            else
               tr = TR_OUT_OF_MEMORY;
         }

         if (tr != TR_SUCCESS)
            /*
             * We must maintain a consistent internal state by deleting any new
             * twin family and object twins on failure, independent of source
             * folder twin count.
             */
            EVAL(WalkList(hlistNewObjectTwins, &DestroyObjectTwinStubWalker,
                          NULL));

         DestroyList(hlistNewObjectTwins);
      }
      else
         tr = TR_OUT_OF_MEMORY;
   }
   else
      tr = TR_SAME_FOLDER;

   ASSERT((tr != TR_SUCCESS && tr != TR_DUPLICATE_TWIN) ||
          IS_VALID_STRUCT_PTR(*ppot1, COBJECTTWIN) && IS_VALID_STRUCT_PTR(*ppot2, COBJECTTWIN));

   return(tr);
}


/*
** CreateObjectTwin()
**
** Creates a new object twin, and adds it to a twin family.
**
** Arguments:     ptf - pointer to parent twin family
**                hpathFolder - folder of new object twin
**
** Returns:       Pointer to new object twin if successful, or NULL if
**                unsuccessful.
**
** Side Effects:  none
**
** N.b., this function does not first check to see if the object twin already
** exists in the family.
*/
PUBLIC_CODE BOOL CreateObjectTwin(PTWINFAMILY ptf, HPATH hpathFolder,
                             POBJECTTWIN *ppot)
{
   BOOL bResult = FALSE;
   POBJECTTWIN potNew;

   ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));
   ASSERT(IS_VALID_HANDLE(hpathFolder, PATH));
   ASSERT(IS_VALID_WRITE_PTR(ppot, POBJECTTWIN));

#ifdef DEBUG

   {
      HNODE hnodeUnused;

      /* Is this object twin already in a twin family? */

      if (FindObjectTwin(ptf->hbr, hpathFolder, GetString(ptf->hsName), &hnodeUnused))
         ERROR_OUT((TEXT("CreateObjectTwin(): An object twin for %s\\%s already exists."),
                    DebugGetPathString(hpathFolder),
                    GetString(ptf->hsName)));
   }

#endif

   /* Create a new OBJECTTWIN structure. */

   if (AllocateMemory(sizeof(*potNew), &potNew))
   {
      if (CopyPath(hpathFolder, GetBriefcasePathList(ptf->hbr), &(potNew->hpath)))
      {
         HNODE hnodeUnused;

         /* Fill in new OBJECTTWIN fields. */

         InitStub(&(potNew->stub), ST_OBJECTTWIN);

         potNew->ptfParent = ptf;
         potNew->ulcSrcFolderTwins = 0;

         MarkObjectTwinNeverReconciled(potNew);

         /* Add the object twin to the twin family's list of object twins. */

         if (InsertNodeAtFront(ptf->hlistObjectTwins, NULL, potNew, &hnodeUnused))
         {
            *ppot = potNew;
            bResult = TRUE;

            ASSERT(IS_VALID_STRUCT_PTR(*ppot, COBJECTTWIN));
         }
         else
         {
            DeletePath(potNew->hpath);
CREATEOBJECTTWIN_BAIL:
            FreeMemory(potNew);
         }
      }
      else
         goto CREATEOBJECTTWIN_BAIL;
   }

   return(bResult);
}


/*
** UnlinkObjectTwin()
**
** Unlinks an object twin.
**
** Arguments:     pot - pointer to object twin to unlink
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PUBLIC_CODE TWINRESULT UnlinkObjectTwin(POBJECTTWIN pot)
{
   TWINRESULT tr;

   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));

   ASSERT(IsStubFlagClear(&(pot->stub), STUB_FL_UNLINKED));

   TRACE_OUT((TEXT("UnlinkObjectTwin(): Unlinking object twin for folder %s."),
              DebugGetPathString(pot->hpath)));

   /* Is the object twin's twin family being deleted? */

   if (IsStubFlagSet(&(pot->ptfParent->stub), STUB_FL_BEING_DELETED))
      /* Yes.  No need to unlink the object twin. */
      tr = TR_SUCCESS;
   else
   {
      /* Are there any folder twin sources left for this object twin? */

      if (! pot->ulcSrcFolderTwins)
      {
         HNODE hnode;

         /*
          * Search the object twin's parent's list of object twins for the
          * object twin to be unlinked.
          */

         if (EVAL(FindObjectTwinInList(pot->ptfParent->hlistObjectTwins, pot->hpath, &hnode)) &&
             EVAL(GetNodeData(hnode) == pot))
         {
            ULONG ulcRemainingObjectTwins;

            /* Unlink the object twin. */

            DeleteNode(hnode);

            SetStubFlag(&(pot->stub), STUB_FL_UNLINKED);

            /*
             * If we have just unlinked the second last object twin in a twin
             * family, destroy the twin family.
             */

            ulcRemainingObjectTwins = GetNodeCount(pot->ptfParent->hlistObjectTwins);

            if (ulcRemainingObjectTwins < 2)
            {

#ifdef DEBUG

               TCHAR rgchName[MAX_NAME_LEN];

               lstrcpy(rgchName, GetString(pot->ptfParent->hsName));

#endif

               /* It's the end of the family line. */

               tr = DestroyStub(&(pot->ptfParent->stub));

#ifdef DEBUG

               if (tr == TR_SUCCESS)
                  TRACE_OUT((TEXT("UnlinkObjectTwin(): Implicitly destroyed twin family for object %s."),
                             rgchName));

#endif

               if (ulcRemainingObjectTwins == 1 &&
                   tr == TR_HAS_FOLDER_TWIN_SRC)
                  tr = TR_SUCCESS;
            }
            else
               tr = TR_SUCCESS;
         }
         else
            tr = TR_INVALID_PARAMETER;

         ASSERT(tr == TR_SUCCESS);
      }
      else
         tr = TR_HAS_FOLDER_TWIN_SRC;
   }

   return(tr);
}


/*
** DestroyObjectTwin()
**
** Destroys an object twin.
**
** Arguments:     pot - pointer to object twin to destroy
**
** Returns:       void
**
** Side Effects:  none
*/
PUBLIC_CODE void DestroyObjectTwin(POBJECTTWIN pot)
{
   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));

   TRACE_OUT((TEXT("DestroyObjectTwin(): Destroying object twin for folder %s."),
              DebugGetPathString(pot->hpath)));

   DeletePath(pot->hpath);
   FreeMemory(pot);

   return;
}


/*
** UnlinkTwinFamily()
**
** Unlinks a twin family.
**
** Arguments:     ptf - pointer to twin family to unlink
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PUBLIC_CODE TWINRESULT UnlinkTwinFamily(PTWINFAMILY ptf)
{
   TWINRESULT tr;

   ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));

   ASSERT(IsStubFlagClear(&(ptf->stub), STUB_FL_UNLINKED));
   ASSERT(IsStubFlagClear(&(ptf->stub), STUB_FL_BEING_DELETED));

   /*
    * A twin family containing object twins generated by folder twins may not
    * be deleted, since those object twins may not be directly deleted.
    */

   if (WalkList(ptf->hlistObjectTwins, &LookForSrcFolderTwinsWalker, NULL))
   {
      HPTRARRAY hpaTwinFamilies;
      ARRAYINDEX aiUnlink;

      TRACE_OUT((TEXT("UnlinkTwinFamily(): Unlinking twin family for object %s."),
                 GetString(ptf->hsName)));

      /* Search for the twin family to be unlinked. */

      hpaTwinFamilies = GetBriefcaseTwinFamilyPtrArray(ptf->hbr);

      if (EVAL(SearchSortedArray(hpaTwinFamilies, &TwinFamilySortCmp, ptf,
                                 &aiUnlink)))
      {
         /* Unlink the twin family. */

         ASSERT(GetPtr(hpaTwinFamilies, aiUnlink) == ptf);

         DeletePtr(hpaTwinFamilies, aiUnlink);

         SetStubFlag(&(ptf->stub), STUB_FL_UNLINKED);
      }

      tr = TR_SUCCESS;
   }
   else
      tr = TR_HAS_FOLDER_TWIN_SRC;

   return(tr);
}


/*
** DestroyTwinFamily()
**
** Destroys a twin family.
**
** Arguments:     ptf - pointer to twin family to destroy
**
** Returns:       void
**
** Side Effects:  none
*/
PUBLIC_CODE void DestroyTwinFamily(PTWINFAMILY ptf)
{
   ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));

   ASSERT(IsStubFlagClear(&(ptf->stub), STUB_FL_BEING_DELETED));

   TRACE_OUT((TEXT("DestroyTwinFamily(): Destroying twin family for object %s."),
              GetString(ptf->hsName)));

   SetStubFlag(&(ptf->stub), STUB_FL_BEING_DELETED);

   /*
    * Destroy the object twins in the family one by one.  Be careful not to use
    * an object twin after it has been destroyed.
    */

   EVAL(WalkList(ptf->hlistObjectTwins, &DestroyObjectTwinStubWalker, NULL));

   /* Destroy TWINFAMILY fields. */

   DestroyList(ptf->hlistObjectTwins);
   DeleteString(ptf->hsName);
   FreeMemory(ptf);

   return;
}


/*
** MarkTwinFamilyNeverReconciled()
**
** Marks a twin family as never reconciled.
**
** Arguments:     ptf - pointer to twin family to be marked never reconciled
**
** Returns:       void
**
** Side Effects:  Clears the twin family's last reconciliation time stamp.
**                Marks all the object twins in the family never reconciled.
*/
PUBLIC_CODE void MarkTwinFamilyNeverReconciled(PTWINFAMILY ptf)
{
   /*
    * If we're being called from CreateTwinFamily(), the fields we're about to
    * set may currently be invalid.  Don't fully verify the TWINFAMILY
    * structure.
    */

   ASSERT(IS_VALID_WRITE_PTR(ptf, TWINFAMILY));

   /* Mark all object twins in twin family as never reconciled. */

   EVAL(WalkList(ptf->hlistObjectTwins, MarkObjectTwinNeverReconciledWalker, NULL));

   return;
}


/*
** MarkObjectTwinNeverReconciled()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PUBLIC_CODE void MarkObjectTwinNeverReconciled(PVOID pot)
{
   /*
    * If we're being called from CreateObjectTwin(), the fields we're about to
    * set may currently be invalid.  Don't fully verify the OBJECTTWIN
    * structure.
    */

   ASSERT(IS_VALID_WRITE_PTR((PCOBJECTTWIN)pot, COBJECTTWIN));

   ASSERT(IsStubFlagClear(&(((PCOBJECTTWIN)pot)->stub), STUB_FL_NOT_RECONCILED));

   ZeroMemory(&(((POBJECTTWIN)pot)->fsLastRec),
              sizeof(((POBJECTTWIN)pot)->fsLastRec));

   ((POBJECTTWIN)pot)->fsLastRec.fscond = FS_COND_UNAVAILABLE;

   return;
}


/*
** MarkTwinFamilyDeletionPending()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE void MarkTwinFamilyDeletionPending(PTWINFAMILY ptf)
{
   ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));

   if (IsStubFlagClear(&(ptf->stub), STUB_FL_DELETION_PENDING))
      TRACE_OUT((TEXT("MarkTwinFamilyDeletionPending(): Deletion now pending for twin family for %s."),
                 GetString(ptf->hsName)));

   SetStubFlag(&(ptf->stub), STUB_FL_DELETION_PENDING);

   return;
}


/*
** UnmarkTwinFamilyDeletionPending()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE void UnmarkTwinFamilyDeletionPending(PTWINFAMILY ptf)
{
   BOOL bContinue;
   HNODE hnode;

   ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));

   if (IsStubFlagSet(&(ptf->stub), STUB_FL_DELETION_PENDING))
   {
      for (bContinue = GetFirstNode(ptf->hlistObjectTwins, &hnode);
           bContinue;
           bContinue = GetNextNode(hnode, &hnode))
      {
         POBJECTTWIN pot;

         pot = GetNodeData(hnode);

         ClearStubFlag(&(pot->stub), STUB_FL_KEEP);
      }

      ClearStubFlag(&(ptf->stub), STUB_FL_DELETION_PENDING);

      TRACE_OUT((TEXT("UnmarkTwinFamilyDeletionPending(): Deletion no longer pending for twin family for %s."),
                 GetString(ptf->hsName)));
   }

   return;
}


/*
** IsTwinFamilyDeletionPending()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL IsTwinFamilyDeletionPending(PCTWINFAMILY pctf)
{
   ASSERT(IS_VALID_STRUCT_PTR(pctf, CTWINFAMILY));

   return(IsStubFlagSet(&(pctf->stub), STUB_FL_DELETION_PENDING));
}


/*
** ClearTwinFamilySrcFolderTwinCount()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE void ClearTwinFamilySrcFolderTwinCount(PTWINFAMILY ptf)
{
   ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));

   EVAL(WalkList(ptf->hlistObjectTwins, &ClearSrcFolderTwinsWalker, NULL));

   return;
}


/*
** EnumObjectTwins()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL EnumObjectTwins(HBRFCASE hbr,
                                 ENUMGENERATEDOBJECTTWINSPROC egotp,
                                 PVOID pvRefData)
{
   BOOL bResult = TRUE;
   HPTRARRAY hpaTwinFamilies;
   ARRAYINDEX aicPtrs;
   ARRAYINDEX ai;

   /* pvRefData may be any value. */

   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_CODE_PTR(egotp, ENUMGENERATEDOBJECTTWINPROC));

   /* Walk the array of twin families. */

   hpaTwinFamilies = GetBriefcaseTwinFamilyPtrArray(hbr);

   aicPtrs = GetPtrCount(hpaTwinFamilies);
   ai = 0;

   while (ai < aicPtrs)
   {
      PTWINFAMILY ptf;
      BOOL bContinue;
      HNODE hnodePrev;

      ptf = GetPtr(hpaTwinFamilies, ai);

      ASSERT(IS_VALID_STRUCT_PTR(ptf, CTWINFAMILY));
      ASSERT(IsStubFlagClear(&(ptf->stub), STUB_FL_UNLINKED));

      /* Lock the twin family so it isn't deleted out from under us. */

      LockStub(&(ptf->stub));

      /*
       * Walk each twin family's list of object twins, calling the callback
       * function with each object twin.
       */

      bContinue = GetFirstNode(ptf->hlistObjectTwins, &hnodePrev);

      while (bContinue)
      {
         HNODE hnodeNext;
         POBJECTTWIN pot;

         bContinue = GetNextNode(hnodePrev, &hnodeNext);

         pot = (POBJECTTWIN)GetNodeData(hnodePrev);

         ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));

         bResult = (*egotp)(pot, pvRefData);

         if (! bResult)
            break;

         hnodePrev = hnodeNext;
      }

      /* Was the twin family unlinked? */

      if (IsStubFlagClear(&(ptf->stub), STUB_FL_UNLINKED))
         /* No. */
         ai++;
      else
      {
         /* Yes. */
         aicPtrs--;
         ASSERT(aicPtrs == GetPtrCount(hpaTwinFamilies));
         TRACE_OUT((TEXT("EnumObjectTwins(): Twin family for object %s unlinked by callback."),
                    GetString(ptf->hsName)));
      }

      UnlockStub(&(ptf->stub));

      if (! bResult)
         break;
   }

   return(bResult);
}


/*
** ApplyNewFolderTwinsToTwinFamilies()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
**
** If FALSE is returned, the array of twin families is in the same state it was
** in before ApplyNewFolderTwinsToTwinFamilies() was called.  No clean-up is
** required by the caller in case of failure.
**
** This function collapses a pair of separate twin families when an object twin
** in one twin family intersects one of the folder twins in the pair of new
** folder twins and an object twin in the other twin family intersects the
** other folder twin in the pair of new folder twins.
**
** This function generates a spinoff object twin when an existing object twin
** intersects one of the folder twins in the pair of new folder twins, and no
** corresponding object twin for the other folder twin in the pair of new
** folder twins exists in the briefcase.  The spinoff object twin is added to
** the generating object twin's twin family.  A spinoff object twins cannot
** cause any existing pairs of twin families to be collapsed because the
** spinoff object twin did not previously exist in a twin family.
**
** A new folder twin may collapse pairs of existing twin families.  E.g.,
** consider the following scenario:
**
** 1) Twin families (c:\, d:\, foo), (e:\, f:\, foo), (c:\, d:\, bar), and
**    (e:\, f:\, bar) exist.
** 2) New folder twin (d:\, e:\, *.*) is added.
** 3) Twin families (c:\, d:\, foo) and (e:\, f:\, foo) must be collpased into
**    a single twin family because of the (d:\, e:\, *.*) folder twin.
** 4) Twin families (c:\, d:\, bar) and (e:\, f:\, bar) must be collpased into
**    a single twin family because of the (d:\, e:\, *.*) folder twin.
**
** So we see that new folder twin (d:\, e:\, *.*) must collapse two pairs of
** existing twin families a single twin family each.  Twin family
** (c:\, d:\, foo) plus twin family (e:\, f:\, foo) becomes twin family
** (c:\, d:\, e:\, f:\, foo).  Twin family (c:\, d:\, bar) plus twin family
** (e:\, f:\, bar) becomes twin family (c:\, d:\, e:\, f:\, bar).
*/
PUBLIC_CODE BOOL ApplyNewFolderTwinsToTwinFamilies(PCFOLDERPAIR pcfp)
{
   BOOL bResult = FALSE;
   HLIST hlistGeneratedObjectTwins;

   ASSERT(IS_VALID_STRUCT_PTR(pcfp, CFOLDERPAIR));

   /*
    * Create lists to contain existing object twins generated by both folder
    * twins.
    */

   if (CreateListOfGeneratedObjectTwins(pcfp, &hlistGeneratedObjectTwins))
   {
      HLIST hlistOtherGeneratedObjectTwins;

      if (CreateListOfGeneratedObjectTwins(pcfp->pfpOther,
                                           &hlistOtherGeneratedObjectTwins))
      {
         NEWLIST nl;
         HLIST hlistNewObjectTwins;

         /* Create list to contain spin-off object twins. */

         nl.dwFlags = 0;

         if (CreateList(&nl, &hlistNewObjectTwins))
         {
            SPINOFFOBJECTTWININFO sooti;

            /*
             * Generate list of new object twins generated by new folder twins
             * to seed ApplyNewObjectTwinToFolderTwins().
             */

            sooti.pcfp = pcfp;
            sooti.hlistNewObjectTwins = hlistNewObjectTwins;

            if (WalkList(hlistGeneratedObjectTwins, &GenerateSpinOffObjectTwin,
                         &sooti))
            {
               sooti.pcfp = pcfp->pfpOther;
               ASSERT(sooti.hlistNewObjectTwins == hlistNewObjectTwins);

               if (WalkList(hlistOtherGeneratedObjectTwins,
                            &GenerateSpinOffObjectTwin, &sooti))
               {
                  /*
                   * ApplyNewObjectTwinsToFolderTwins() sets ulcSrcFolderTwins
                   * for all object twins in hlistNewObjectTwins.
                   */

                  if (ApplyNewObjectTwinsToFolderTwins(hlistNewObjectTwins))
                  {
                     /*
                      * Collapse separate twin families joined by new folder
                      * twin.
                      */

                     EVAL(WalkList(hlistGeneratedObjectTwins, &BuildBradyBunch,
                                   (PVOID)pcfp));

                     /*
                      * We don't need to call BuildBradyBunch() for
                      * pcfp->pfpOther and hlistOtherGeneratedObjectTwins since
                      * one twin family from each collapsed pair of twin
                      * families must come from each list of generated object
                      * twins.
                      */

                     /*
                      * Increment source folder twin count for all pre-existing
                      * object twins generated by the new folder twins.
                      */

                     EVAL(WalkList(hlistGeneratedObjectTwins,
                                   &IncrementSrcFolderTwinsWalker, NULL));
                     EVAL(WalkList(hlistOtherGeneratedObjectTwins,
                                   &IncrementSrcFolderTwinsWalker, NULL));

                     bResult = TRUE;
                  }
               }
            }

            /* Wipe out any new object twins on failure. */

            if (! bResult)
               EVAL(WalkList(hlistNewObjectTwins, &DestroyObjectTwinStubWalker,
                             NULL));

            DestroyList(hlistNewObjectTwins);
         }

         DestroyList(hlistOtherGeneratedObjectTwins);
      }

      DestroyList(hlistGeneratedObjectTwins);
   }

   return(bResult);
}


/*
** TransplantObjectTwin()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PUBLIC_CODE TWINRESULT TransplantObjectTwin(POBJECTTWIN pot,
                                            HPATH hpathOldFolder,
                                            HPATH hpathNewFolder)
{
   TWINRESULT tr;

   ASSERT(IS_VALID_STRUCT_PTR(pot, COBJECTTWIN));
   ASSERT(IS_VALID_HANDLE(hpathOldFolder, PATH));
   ASSERT(IS_VALID_HANDLE(hpathNewFolder, PATH));

   /* Is this object twin rooted in the renamed folder's subtree? */

   if (IsPathPrefix(pot->hpath, hpathOldFolder))
   {
      TCHAR rgchPathSuffix[MAX_PATH_LEN];
      LPCTSTR pcszSubPath;
      HPATH hpathNew;

      /* Yes.  Change the object twin's root. */

      pcszSubPath = FindChildPathSuffix(hpathOldFolder, pot->hpath,
                                        rgchPathSuffix);

      if (AddChildPath(GetBriefcasePathList(pot->ptfParent->hbr),
                       hpathNewFolder, pcszSubPath, &hpathNew))
      {
         TRACE_OUT((TEXT("TransplantObjectTwin(): Transplanted object twin %s\\%s to %s\\%s."),
                    DebugGetPathString(pot->hpath),
                    GetString(pot->ptfParent->hsName),
                    DebugGetPathString(hpathNew),
                    GetString(pot->ptfParent->hsName)));

         DeletePath(pot->hpath);
         pot->hpath = hpathNew;

         tr = TR_SUCCESS;
      }
      else
         tr = TR_OUT_OF_MEMORY;
   }
   else
      tr = TR_SUCCESS;

   return(tr);
}


/*
** IsFolderObjectTwinName()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL IsFolderObjectTwinName(LPCTSTR pcszName)
{
   ASSERT(IS_VALID_STRING_PTR(pcszName, CSTR));

   return(! *pcszName);
}


/*
** IsValidHTWIN()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL IsValidHTWIN(HTWIN htwin)
{
   BOOL bValid = FALSE;

   if (IS_VALID_STRUCT_PTR((PCSTUB)htwin, CSTUB))
   {
      switch (((PSTUB)htwin)->st)
      {
         case ST_OBJECTTWIN:
            bValid = IS_VALID_HANDLE((HOBJECTTWIN)htwin, OBJECTTWIN);
            break;

         case ST_TWINFAMILY:
            bValid = IS_VALID_HANDLE((HTWINFAMILY)htwin, TWINFAMILY);
            break;

         case ST_FOLDERPAIR:
            bValid = IS_VALID_HANDLE((HFOLDERTWIN)htwin, FOLDERTWIN);
            break;

         default:
            ERROR_OUT((TEXT("IsValidHTWIN() called on unrecognized stub type %d."),
                       ((PSTUB)htwin)->st));
            break;
      }
   }
   else
      ERROR_OUT((TEXT("IsValidHTWIN() called on bad twin handle %#lx."),
                 htwin));

   return(bValid);
}


/*
** IsValidHTWINFAMILY()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL IsValidHTWINFAMILY(HTWINFAMILY htf)
{
   return(IS_VALID_STRUCT_PTR((PTWINFAMILY)htf, CTWINFAMILY));
}


/*
** IsValidHOBJECTTWIN()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL IsValidHOBJECTTWIN(HOBJECTTWIN hot)
{
   return(IS_VALID_STRUCT_PTR((POBJECTTWIN)hot, COBJECTTWIN));
}


#ifdef VSTF

/*
** IsValidPCTWINFAMILY()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL IsValidPCTWINFAMILY(PCTWINFAMILY pctf)
{
   BOOL bResult;

   /* All the fields of an unlinked twin family should be valid. */

   /* Don't validate hbr. */

   /*
    * In some cases there may be fewer than two object twins in a twin family,
    * e.g., when two twin families are being collapsed, when a twin family is
    * being deleted, and when a twin family is being read in from a database.
    */

   if (IS_VALID_READ_PTR(pctf, CTWINFAMILY) &&
       IS_VALID_STRUCT_PTR(&(pctf->stub), CSTUB) &&
       FLAGS_ARE_VALID(GetStubFlags(&(pctf->stub)), ALL_TWIN_FAMILY_FLAGS) &&
       IS_VALID_HANDLE(pctf->hsName, STRING) &&
       IS_VALID_HANDLE(pctf->hlistObjectTwins, LIST))
      bResult = WalkList(pctf->hlistObjectTwins, &IsValidObjectTwinWalker, (PVOID)pctf);
   else
      bResult = FALSE;

   return(bResult);
}


/*
** IsValidPCOBJECTTWIN()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL IsValidPCOBJECTTWIN(PCOBJECTTWIN pcot)
{
   /*
    * All the fields of an unlinked object twin should be valid, except
    * possibly ptfParent and fsCurrent.
    */

   /*
    *   Winner of the 1995 "I think the compiler generates better code
    *                       if its takes up less space on the screen" award.
    *
    *   Running up in the  "Make the debugger execute 2K of code as an
    *                       atomic operation while debugger" category.
    *
    */

   return(IS_VALID_READ_PTR(pcot, COBJECTTWIN) &&
          IS_VALID_STRUCT_PTR(&(pcot->stub), CSTUB) &&
          FLAGS_ARE_VALID(GetStubFlags(&(pcot->stub)), ALL_OBJECT_TWIN_FLAGS) &&
          IS_VALID_HANDLE(pcot->hpath, PATH) &&
          (IsStubFlagSet(&(pcot->stub), STUB_FL_UNLINKED) ||
           IS_VALID_READ_PTR(pcot->ptfParent, CTWINFAMILY)) &&
          IS_VALID_STRUCT_PTR(&(pcot->fsLastRec), CFILESTAMP) &&
          (IsStubFlagClear(&(pcot->stub), STUB_FL_FILE_STAMP_VALID) ||
           (IS_VALID_STRUCT_PTR(&(pcot->fsCurrent), CFILESTAMP))) &&
          EVAL(! (! IsReconciledFileStamp(&(pcot->fsLastRec)) &&
                  IsStubFlagSet(&(pcot->stub), STUB_FL_NOT_RECONCILED))));
}

#endif


/*
** WriteTwinFamilies()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PUBLIC_CODE TWINRESULT WriteTwinFamilies(HCACHEDFILE hcf, HPTRARRAY hpaTwinFamilies)
{
   TWINRESULT tr = TR_BRIEFCASE_WRITE_FAILED;
   DWORD dwcbTwinFamiliesDBHeaderOffset;

   ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
   ASSERT(IS_VALID_HANDLE(hpaTwinFamilies, PTRARRAY));

   /* Save initial file poisition. */

   dwcbTwinFamiliesDBHeaderOffset = GetCachedFilePointerPosition(hcf);

   if (dwcbTwinFamiliesDBHeaderOffset != INVALID_SEEK_POSITION)
   {
      TWINFAMILIESDBHEADER tfdbh;

      /* Leave space for the twin families' header. */

      ZeroMemory(&tfdbh, sizeof(tfdbh));

      if (WriteToCachedFile(hcf, (PCVOID)&tfdbh, sizeof(tfdbh), NULL))
      {
         ARRAYINDEX aicPtrs;
         ARRAYINDEX ai;

         tr = TR_SUCCESS;

         aicPtrs = GetPtrCount(hpaTwinFamilies);

         for (ai = 0;
              ai < aicPtrs && tr == TR_SUCCESS;
              ai++)
            tr = WriteTwinFamily(hcf, GetPtr(hpaTwinFamilies, ai));

         if (tr == TR_SUCCESS)
         {
            /* Save twin families' header. */

            tfdbh.lcTwinFamilies = aicPtrs;

            tr = WriteDBSegmentHeader(hcf, dwcbTwinFamiliesDBHeaderOffset,
                                      &tfdbh, sizeof(tfdbh));

            if (tr == TR_SUCCESS)
               TRACE_OUT((TEXT("WriteTwinFamilies(): Wrote %ld twin families."),
                          tfdbh.lcTwinFamilies));
         }
      }
   }

   return(tr);
}


/*
** ReadTwinFamilies()
**
**
**
** Arguments:
**
** Returns:       TWINRESULT
**
** Side Effects:  none
*/
PUBLIC_CODE TWINRESULT ReadTwinFamilies(HCACHEDFILE hcf, HBRFCASE hbr,
                                   PCDBVERSION pcdbver,
                                   HHANDLETRANS hhtFolderTrans,
                                   HHANDLETRANS hhtNameTrans)
{
   TWINRESULT tr;
   TWINFAMILIESDBHEADER tfdbh;
   DWORD dwcbRead;

   ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
   ASSERT(IS_VALID_HANDLE(hbr, BRFCASE));
   ASSERT(IS_VALID_READ_PTR(pcdbver, DBVERSION));
   ASSERT(IS_VALID_HANDLE(hhtFolderTrans, HANDLETRANS));
   ASSERT(IS_VALID_HANDLE(hhtNameTrans, HANDLETRANS));

   if (ReadFromCachedFile(hcf, &tfdbh, sizeof(tfdbh), &dwcbRead) &&
       dwcbRead == sizeof(tfdbh))
   {
      LONG l;

      tr = TR_SUCCESS;

      TRACE_OUT((TEXT("ReadTwinFamilies(): Reading %ld twin families."),
                 tfdbh.lcTwinFamilies));

      for (l = 0;
           l < tfdbh.lcTwinFamilies && tr == TR_SUCCESS;
           l++)
         tr = ReadTwinFamily(hcf, hbr, pcdbver, hhtFolderTrans, hhtNameTrans);

      ASSERT(AreTwinFamiliesValid(GetBriefcaseTwinFamilyPtrArray(hbr)));
   }
   else
      tr = TR_CORRUPT_BRIEFCASE;

   return(tr);
}


/***************************** Exported Functions ****************************/


/******************************************************************************

@doc SYNCENGAPI

@api TWINRESULT | AddObjectTwin | Twins two objects.

@parm HBRFCASE | hbr | A handle to the open briefcase that the new object twins
are to be added to.

@parm PCNEWOBJECTTWIN | pcnot | A pointer to a CNEWOBJECTTWIN describing the
objects to be twinned.

@parm PHTWINFAMILY | phtf | A pointer to an HTWINFAMILY to be filled in with
a handle to the twin family to which the object twins were added.  This handle
may refer to a new or existing twin family.  *phtf is only valid if TR_SUCCESS
is returned.

@rdesc If the objects were twinned successfully, TR_SUCCESS is returned, and
*phTwinFamily contains a handle to the associated twin family.  Otherwise, the
objects were not twinned successfully, the return value indicates the error
that occurred, and *phtf is undefined.  If one or both of the volumes
specified by the NEWOBJECTTWIN structure is not present, TR_UNAVAILABLE_VOLUME
will be returned, and the object twin will not be added.

@comm Once the caller is finshed with the twin handle returned by
AddObjectTwin(), ReleaseTwinHandle() should be called to release the twin
handle.  DeleteTwin() does not release a twin handle returned by
AddObjectTwin().

@xref ReleaseTwinHandle DeleteTwin

******************************************************************************/

SYNCENGAPI TWINRESULT WINAPI AddObjectTwin(HBRFCASE hbr, PCNEWOBJECTTWIN pcnot,
                                           PHTWINFAMILY phtf)
{
   TWINRESULT tr;

   if (BeginExclusiveBriefcaseAccess())
   {
      DebugEntry(AddObjectTwin);

#ifdef EXPV
      /* Verify parameters. */

      if (IS_VALID_HANDLE(hbr, BRFCASE) &&
          IS_VALID_STRUCT_PTR(pcnot, CNEWOBJECTTWIN) &&
          EVAL(pcnot->ulSize == sizeof(*pcnot)) &&
          IS_VALID_WRITE_PTR(phtf, HTWINFAMILY))
#endif
      {
         HCLSIFACECACHE hcic;

         if (CreateClassInterfaceCache(&hcic))
         {
            HPATHLIST hplBriefcase;
            HPATH hpathFolder1;

            InvalidatePathListInfo(GetBriefcasePathList(hbr));

            hplBriefcase = GetBriefcasePathList(hbr);

            tr = TranslatePATHRESULTToTWINRESULT(AddPath(hplBriefcase,
                                                         pcnot->pcszFolder1,
                                                         &hpathFolder1));

            if (tr == TR_SUCCESS)
            {
               HPATH hpathFolder2;

               tr = TranslatePATHRESULTToTWINRESULT(AddPath(hplBriefcase,
                                                            pcnot->pcszFolder2,
                                                            &hpathFolder2));

               if (tr == TR_SUCCESS)
               {
                  POBJECTTWIN pot1;
                  POBJECTTWIN pot2;

                  tr = TwinObjects(hbr, hcic, hpathFolder1, hpathFolder2,
                                   pcnot->pcszName, &pot1, &pot2);

                  /*
                   * These twins are not really duplicates unless they were already
                   * connected as object twins.
                   */

                  if (tr == TR_DUPLICATE_TWIN &&
                      (IsStubFlagClear(&(pot1->stub), STUB_FL_FROM_OBJECT_TWIN) ||
                       IsStubFlagClear(&(pot2->stub), STUB_FL_FROM_OBJECT_TWIN)))
                     tr = TR_SUCCESS;

                  if (tr == TR_SUCCESS)
                  {
                     /* Success! */

                     ASSERT(pot1->ptfParent == pot2->ptfParent);
                     ASSERT(IS_VALID_HANDLE((HTWINFAMILY)(pot1->ptfParent), TWINFAMILY));

                     LockStub(&(pot1->ptfParent->stub));

                     SetStubFlag(&(pot1->stub), STUB_FL_FROM_OBJECT_TWIN);
                     SetStubFlag(&(pot2->stub), STUB_FL_FROM_OBJECT_TWIN);

                     *phtf = (HTWINFAMILY)(pot1->ptfParent);
                  }

                  DeletePath(hpathFolder2);
               }

               DeletePath(hpathFolder1);
            }

            DestroyClassInterfaceCache(hcic);
         }
         else
            tr = TR_OUT_OF_MEMORY;
      }
#ifdef EXPV
      else
         tr = TR_INVALID_PARAMETER;
#endif

      DebugExitTWINRESULT(AddObjectTwin, tr);

      EndExclusiveBriefcaseAccess();
   }
   else
      tr = TR_REENTERED;

   return(tr);
}


/******************************************************************************

@doc SYNCENGAPI

@api TWINRESULT | ReleaseTwinHandle | Releases a twin handle returned by
AddObjectTwin(), AddFolderTwin(), or GetObjectTwinHandle().

@parm HTWIN | hTwin | The twin handle that is to be released.

@rdesc If the twin handle was released successfully, TR_SUCCESS is returned.
Otherwise, the twin handle was not released successfully, and the return value
indicates the error that occurred.  hTwin is no longer a valid twin handle
after ReleaseTwinHandle() is called.

@comm If the lock count of the twin drops to 0 and deletion is pending against
the twin, the twin is deleted.  If ReleaseTwinHandle() is called with a valid
handle to a twin that has been deleted, TR_SUCCESS will be returned.
DeleteTwin() does not release a twin handle returned by AddObjectTwin(),
AddFolderTwin(), or GetObjectTwinHandle().  ReleaseTwinHandle() should be
called to release a twin handle returned by AddObjectTwin(), AddFolderTwin(),
or GetObjectTwinHandle().  DeleteTwin() should be called before
ReleaseTwinHandle() if the twin is to be deleted.

@xref AddObjectTwin AddFolderTwin DeleteTwin

******************************************************************************/

SYNCENGAPI TWINRESULT WINAPI ReleaseTwinHandle(HTWIN hTwin)
{
   TWINRESULT tr;

   if (BeginExclusiveBriefcaseAccess())
   {
      DebugEntry(ReleaseTwinHandle);

#ifdef EXPV
      /* Verify parameters. */

      if (IS_VALID_HANDLE(hTwin, TWIN))
#endif
      {
         UnlockStub((PSTUB)hTwin);

         tr = TR_SUCCESS;
      }
#ifdef EXPV
      else
         tr = TR_INVALID_PARAMETER;
#endif

      DebugExitTWINRESULT(ReleaseTwinHandle, tr);

      EndExclusiveBriefcaseAccess();
   }
   else
      tr = TR_REENTERED;

   return(tr);
}


/******************************************************************************

@doc SYNCENGAPI

@api TWINRESULT | DeleteTwin | Deletes a twin from the synchronization
database.  A twin is added to the synchronization database by AddObjectTwin()
or AddFolderTwin().

@parm HTWIN | htwin | A handle to the twin being deleted.

@rdesc If the twin was deleted successfully, TR_SUCCESS is returned.
Otherwise, the twin was not deleted successfully, and the return value
indicates the error that occurred.

@comm If DeleteTwin() is called with a valid handle to a twin that has been
deleted, TR_SUCCESS will be returned.  DeleteTwin() does not release a twin
handle returned by AddObjectTwin(), AddFolderTwin(), or GetObjectTwinHandle().
ReleaseTwinHandle() should be called to release a twin handle returned by
AddObjectTwin(), AddFolderTwin(), or GetObjectTwinHandle().  DeleteTwin()
should be called before ReleaseTwinHandle() if the twin is to be deleted.
DeleteTwin() will always succeed on a valid HFOLDERTWIN.  DeleteTwin() will
fail on a valid HOBJECTTWIN for any object twin that has source folder twins,
returning TR_HAS_FOLDER_TWIN_SRC.  DeleteTwin() will also fail on a valid
HTWINFAMILY for any twin family that contains two or more object twins with
source folder twins, returning TR_HAS_FOLDER_TWIN_SRC.  A twin family cannot
contain only one object twin with source folder twins.  Twin families can only
contain 0, 2, or more object twins with source folder twins.

@xref AddObjectTwin AddFolderTwin ReleaseTwinHandle IsOrphanObjectTwin
CountSourceFolderTwins

******************************************************************************/

SYNCENGAPI TWINRESULT WINAPI DeleteTwin(HTWIN hTwin)
{
   TWINRESULT tr;

   if (BeginExclusiveBriefcaseAccess())
   {
      DebugEntry(DeleteTwin);

#ifdef EXPV
      /* Verify parameters. */

      if (IS_VALID_HANDLE(hTwin, TWIN))
#endif
      {
         tr = DestroyStub((PSTUB)hTwin);
      }
#ifdef EXPV
      else
         tr = TR_INVALID_PARAMETER;
#endif

      DebugExitTWINRESULT(DeleteTwin, tr);

      EndExclusiveBriefcaseAccess();
   }
   else
      tr = TR_REENTERED;

   return(tr);
}


/******************************************************************************

@doc SYNCENGAPI

@api TWINRESULT | GetObjectTwinHandle | Determines whether or not an object is
a twin.  If the object is a twin, a twin handle for the twinned object is
returned.

@parm HBRFCASE | hbr | A handle to the open briefcase to be checked for the
object twin.

@parm PCSTR | pcszFolder | A pointer to a string indicating the object's
folder.

@parm PCSTR | pcszName | A pointer to a string indicating the object's name.

@parm PHOBJECTTWIN | phot | A pointer to an HOBJECTTWIN to be filled in with
a handle to the object twin or NULL.  If the object is a twin, *phObjectTwin
is filled in with a handle to the object twin.  If the object is not a twin,
*phObjectTwin is filled in with NULL.  *phObjectTwin is only valid if
TR_SUCCESS is returned.

@rdesc If the lookup was successful, TR_SUCCESS is returned.  Otherwise, the
lookup was not successful, and the return value indicates the error that
occurred.

@comm Once the caller is finshed with the twin handle returned by
GetObjectTwinHandle(), ReleaseTwinHandle() should be called to release the twin
handle.  N.b., DeleteTwin() does not release a twin handle returned by
GetObjectTwinHandle().

@xref AddObjectTwin ReleaseTwinHandle DeleteTwin

******************************************************************************/

SYNCENGAPI TWINRESULT WINAPI GetObjectTwinHandle(HBRFCASE hbr,
                                                 LPCTSTR pcszFolder,
                                                 LPCTSTR pcszName,
                                                 PHOBJECTTWIN phot)
{
   TWINRESULT tr;

   if (BeginExclusiveBriefcaseAccess())
   {
      DebugEntry(GetObjectTwinHandle);

#ifdef EXPV
      /* Verify parameters. */

      if (IS_VALID_HANDLE(hbr, BRFCASE) &&
          IS_VALID_STRING_PTR(pcszFolder, CSTR) &&
          IS_VALID_STRING_PTR(pcszName, CSTR) &&
          IS_VALID_WRITE_PTR(phot, HOBJECTTWIN))
#endif
      {
         HPATH hpath;

         InvalidatePathListInfo(GetBriefcasePathList(hbr));

         tr = TranslatePATHRESULTToTWINRESULT(
               AddPath(GetBriefcasePathList(hbr), pcszFolder, &hpath));

         if (tr == TR_SUCCESS)
         {
            BOOL bFound;
            HNODE hnode;
            POBJECTTWIN pot;

            /* Is this object already an object twin? */

            bFound = FindObjectTwin(hbr, hpath, pcszName, &hnode);

            if (bFound)
               /* Yes. */
               pot = (POBJECTTWIN)GetNodeData(hnode);
            else
               /*
                * No.  Expand folder twins, and check for a generating folder
                * twin.
                */
               tr = TryToGenerateObjectTwin(hbr, hpath, pcszName, &bFound,
                                            &pot);

            if (tr == TR_SUCCESS)
            {
               if (bFound)
               {
                  LockStub(&(pot->stub));

                  TRACE_OUT((TEXT("GetObjectTwinHandle(): %s\\%s is an object twin."),
                             DebugGetPathString(hpath),
                             pcszName));

                  *phot = (HOBJECTTWIN)pot;

                  ASSERT(IS_VALID_HANDLE(*phot, OBJECTTWIN));
               }
               else
               {
                  TRACE_OUT((TEXT("GetObjectTwinHandle(): %s\\%s is not an object twin."),
                             DebugGetPathString(hpath),
                             pcszName));

                  *phot = NULL;
               }
            }

            DeletePath(hpath);
         }
      }
#ifdef EXPV
      else
         tr = TR_INVALID_PARAMETER;
#endif

      DebugExitTWINRESULT(GetObjectTwinHandle, tr);

      EndExclusiveBriefcaseAccess();
   }
   else
      tr = TR_REENTERED;

   return(tr);
}


/******************************************************************************

@doc SYNCENGAPI

@api TWINRESULT | IsOrphanObjectTwin | Determines whether or not an object twin
was added to the synchronization database through a call to AddObjectTwin().

@parm HOBJECTTWIN | hot | A handle to the object twin whose orphan status is to
be determined.

@parm PBOOL | pbIsOrphanObjectTwin | A pointer to a BOOL to be filled in with
TRUE if the object twin was added through AddObjectTwin().
*pbIsOrphanObjectTwin is only valid if TR_SUCCESS is returned.

@rdesc If the lookup was successful, TR_SUCCESS is returned.  Otherwise, the
lookup was not successful, and the return value indicates the error that
occurred.

@comm If IsOrphanObjectTwin() is called with a valid handle to an object twin
that has been deleted, TR_DELETED_TWIN will be returned.

@xref AddObjectTwin

******************************************************************************/

SYNCENGAPI TWINRESULT WINAPI IsOrphanObjectTwin(HOBJECTTWIN hot,
                                                PBOOL pbIsOrphanObjectTwin)
{
   TWINRESULT tr;

   if (BeginExclusiveBriefcaseAccess())
   {
      DebugEntry(IsOrphanObjectTwin);

#ifdef EXPV
      /* Verify parameters. */

      if (IS_VALID_HANDLE(hot, OBJECTTWIN) &&
          IS_VALID_WRITE_PTR(pbIsOrphanObjectTwin, BOOL))
#endif
      {
         /* Has this object twin been deleted? */

         if (IsStubFlagClear(&(((POBJECTTWIN)(hot))->stub), STUB_FL_UNLINKED))
         {
            /* No. */

            if (IsStubFlagSet(&(((POBJECTTWIN)hot)->stub), STUB_FL_FROM_OBJECT_TWIN))
            {
               *pbIsOrphanObjectTwin = TRUE;

               TRACE_OUT((TEXT("IsOrphanObjectTwin(): Object twin %s\\%s is an orphan object twin."),
                          DebugGetPathString(((POBJECTTWIN)hot)->hpath),
                          GetString(((POBJECTTWIN)hot)->ptfParent->hsName)));
            }
            else
            {
               *pbIsOrphanObjectTwin = FALSE;

               TRACE_OUT((TEXT("IsOrphanObjectTwin(): Object twin %s\\%s is not an orphan object twin."),
                          DebugGetPathString(((POBJECTTWIN)hot)->hpath),
                          GetString(((POBJECTTWIN)hot)->ptfParent->hsName)));
            }

            ASSERT(*pbIsOrphanObjectTwin ||
                   ((POBJECTTWIN)hot)->ulcSrcFolderTwins);

            tr = TR_SUCCESS;
         }
         else
            /* Yes. */
            tr = TR_DELETED_TWIN;
      }
#ifdef EXPV
      else
         tr = TR_INVALID_PARAMETER;
#endif

      DebugExitTWINRESULT(IsOrphanObjectTwin, tr);

      EndExclusiveBriefcaseAccess();
   }
   else
      tr = TR_REENTERED;

   return(tr);
}


/******************************************************************************

@doc SYNCENGAPI

@api TWINRESULT | CountSourceFolderTwins | Determines the number of folder
twins that generate an object twin.

@parm HOBJECTTWIN | hot | A handle to the object twin whose folder twin sources
are to be counted.

@parm PULONG | pulcSrcFolderTwins | A pointer to a ULONG to be filled in with
the number of folder twins that generate the object twin.  *pulcSrcFolderTwins
is only valid if TR_SUCCESS is returned.

@rdesc If the lookup was successful, TR_SUCCESS is returned.  Otherwise, the
lookup was not successful, and the return value indicates the error that
occurred.

@comm If CountSourceFolderTwins() is called with a valid handle to a folder
twin that has been deleted, TR_DELETED_TWIN will be returned.

@xref AddFolderTwin

******************************************************************************/

SYNCENGAPI TWINRESULT WINAPI CountSourceFolderTwins(HOBJECTTWIN hot,
                                                    PULONG pulcSrcFolderTwins)
{
   TWINRESULT tr;

   if (BeginExclusiveBriefcaseAccess())
   {
      DebugEntry(CountSourceFolderTwins);

#ifdef EXPV
      /* Verify parameters. */

      if (IS_VALID_HANDLE(hot, OBJECTTWIN) &&
          IS_VALID_WRITE_PTR(pulcSrcFolderTwins, ULONG))
#endif
      {
         /* Has this object twin been deleted? */

         if (IsStubFlagClear(&(((POBJECTTWIN)(hot))->stub), STUB_FL_UNLINKED))
         {
            /* No. */

            *pulcSrcFolderTwins = ((POBJECTTWIN)hot)->ulcSrcFolderTwins;

            ASSERT(*pulcSrcFolderTwins > 0 ||
                   IsStubFlagSet(&(((POBJECTTWIN)hot)->stub), STUB_FL_FROM_OBJECT_TWIN));

            TRACE_OUT((TEXT("CountSourceFolderTwins(): Object twin %s\\%s has %lu source folder twins."),
                       DebugGetPathString(((POBJECTTWIN)hot)->hpath),
                       GetString(((POBJECTTWIN)hot)->ptfParent->hsName),
                       *pulcSrcFolderTwins));

            tr = TR_SUCCESS;
         }
         else
            /* Yes. */
            tr = TR_DELETED_TWIN;
      }
#ifdef EXPV
      else
         tr = TR_INVALID_PARAMETER;
#endif

      DebugExitTWINRESULT(CountSourceFolderTwins, tr);

      EndExclusiveBriefcaseAccess();
   }
   else
      tr = TR_REENTERED;

   return(tr);
}


/******************************************************************************

@doc SYNCENGAPI

@api BOOL | AnyTwins | Determines whether or not any twins currently exist in a
briefcase.

@parm HBRFCASE | hbr | A handle to the open briefcase to be checked for twins.

@parm PBOOL | pbAnyTwins | A pointer to a BOOL to be filled in with TRUE if
the given briefcase contains any twins or FALSE if not.  *pbAnyTwins is only
valid if TR_SUCCESS is returned.

@rdesc If the lookup was successful, TR_SUCCESS is returned.  Otherwise, the
lookup was not successful, and the return value indicates the error that
occurred.

******************************************************************************/

SYNCENGAPI TWINRESULT WINAPI AnyTwins(HBRFCASE hbr, PBOOL pbAnyTwins)
{
   TWINRESULT tr;

   if (BeginExclusiveBriefcaseAccess())
   {
      DebugEntry(AnyTwins);

#ifdef EXPV
      /* Verify parameters. */

      if (IS_VALID_HANDLE(hbr, BRFCASE) &&
          IS_VALID_WRITE_PTR(pbAnyTwins, BOOL))
#endif
      {
         if (GetPtrCount(GetBriefcaseTwinFamilyPtrArray(hbr)) ||
             GetPtrCount(GetBriefcaseFolderPairPtrArray(hbr)))
         {
            *pbAnyTwins = TRUE;

            TRACE_OUT((TEXT("AnyTwins(): There are twins in briefcase %#lx."),
                       hbr));
         }
         else
         {
            *pbAnyTwins = FALSE;

            TRACE_OUT((TEXT("AnyTwins(): There are not any twins in briefcase %#lx."),
                       hbr));
         }

         tr = TR_SUCCESS;
      }
#ifdef EXPV
      else
         tr = TR_INVALID_PARAMETER;
#endif

      DebugExitTWINRESULT(AnyTwins, tr);

      EndExclusiveBriefcaseAccess();
   }
   else
      tr = TR_REENTERED;

   return(tr);
}