2412 lines
67 KiB
C
2412 lines
67 KiB
C
//---------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation 1993-1994
|
|
//
|
|
// File: twin.c
|
|
//
|
|
// This file contains special twin handling functions.
|
|
//
|
|
// (Even though we've moved to a briefcase metaphor,
|
|
// we still refer to twins internally...)
|
|
//
|
|
// History:
|
|
// 08-06-93 ScottH Transferred from twin code
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
#include "brfprv.h" // common headers
|
|
|
|
#include "res.h"
|
|
#include "recact.h"
|
|
|
|
|
|
// APPCOMPAT: due to a compiler bug, we need to declare this structure
|
|
// as a 1-element array because it has pointers to functions in it
|
|
// and it is in another datasegment.
|
|
VTBLENGINE g_vtblEngine[1] = { { 0 } }; // per-instance v-table
|
|
|
|
#define GetFunction(rgtable, name, type) \
|
|
((rgtable).##name = (type)GetProcAddress((rgtable).hinst, #name)); \
|
|
ASSERT((rgtable).##name)
|
|
|
|
#ifdef DEBUG
|
|
#define SzTR(tr) #tr,
|
|
#endif
|
|
|
|
#define MAX_RANGE 0x7fff
|
|
|
|
// Recitem dwUser values
|
|
#define RIU_CHANGED 1
|
|
#define RIU_SKIP 2
|
|
#define RIU_SHOWSTATUS 3
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Compare two structures by folder name
|
|
Returns: -1 if <, 0 if ==, 1 if >
|
|
Cond: --
|
|
*/
|
|
int CALLBACK _export NCompareFolders(
|
|
LPVOID lpv1,
|
|
LPVOID lpv2,
|
|
LPARAM lParam) // One of: CMP_RECNODES, CMP_FOLDERTWINS
|
|
{
|
|
switch (lParam)
|
|
{
|
|
case CMP_RECNODES:
|
|
return lstrcmpi(((PRECNODE)lpv1)->pcszFolder, ((PRECNODE)lpv2)->pcszFolder);
|
|
|
|
case CMP_FOLDERTWINS:
|
|
return lstrcmpi(((PCFOLDERTWIN)lpv1)->pcszOtherFolder, ((PCFOLDERTWIN)lpv2)->pcszOtherFolder);
|
|
|
|
default:
|
|
ASSERT(0); // should never get here
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Choose side functions
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Dump a CHOOSESIDE structure
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PRIVATE ChooseSide_Dump(
|
|
PCHOOSESIDE pchside)
|
|
{
|
|
BOOL bDump;
|
|
TCHAR szBuf[MAXMSGLEN];
|
|
|
|
ASSERT(pchside);
|
|
|
|
#define szDumpLabel TEXT(" *** ")
|
|
#define szDumpMargin TEXT(" ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_CHOOSESIDE);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (bDump)
|
|
{
|
|
wsprintf(szBuf, TEXT("%s.pszFolder = {%s}\r\n"), (LPTSTR)szDumpLabel, pchside->pszFolder);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.dwFlags = 0x%lx\r\n"), (LPTSTR)szDumpMargin, pchside->dwFlags);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.nRank = %ld\r\n"), (LPTSTR)szDumpMargin, pchside->nRank);
|
|
OutputDebugString(szBuf);
|
|
}
|
|
|
|
#undef szDumpLabel
|
|
#undef szDumpMargin
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Dump a CHOOSESIDE list
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC ChooseSide_DumpList(
|
|
HDSA hdsa)
|
|
{
|
|
BOOL bDump;
|
|
TCHAR szBuf[MAXMSGLEN];
|
|
|
|
ASSERT(hdsa);
|
|
|
|
#define szDumpLabel TEXT("Dump CHOOSESIDE list: ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_CHOOSESIDE);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (bDump)
|
|
{
|
|
int i;
|
|
int cel = DSA_GetItemCount(hdsa);
|
|
PCHOOSESIDE pchside;
|
|
|
|
wsprintf(szBuf, TEXT("%s.count = %lu\r\n"), (LPTSTR)szDumpLabel, cel);
|
|
OutputDebugString(szBuf);
|
|
|
|
if (NULL != (pchside = DSA_GetItemPtr(hdsa, 0)))
|
|
{
|
|
if (IsFlagSet(pchside->dwFlags, CSF_INSIDE))
|
|
OutputDebugString(TEXT("Rank for inside\r\n"));
|
|
else
|
|
OutputDebugString(TEXT("Rank for outside\r\n"));
|
|
}
|
|
|
|
for (i = 0; i < cel; i++)
|
|
{
|
|
pchside = DSA_GetItemPtr(hdsa, i);
|
|
|
|
ChooseSide_Dump(pchside);
|
|
}
|
|
}
|
|
|
|
#undef szDumpLabel
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Initialize an array of CHOOSESIDE elements from a
|
|
recitem list. Array is unsorted.
|
|
|
|
Returns: --
|
|
|
|
Cond: The contents of the array are safe as long as the
|
|
recitem list lives.
|
|
|
|
*/
|
|
void PUBLIC ChooseSide_InitAsFile(
|
|
HDSA hdsa,
|
|
PRECITEM pri)
|
|
{
|
|
CHOOSESIDE chside;
|
|
PRECNODE prn;
|
|
|
|
ASSERT(hdsa);
|
|
ASSERT(pri);
|
|
|
|
DSA_DeleteAllItems(hdsa);
|
|
|
|
// All entries start with these values
|
|
chside.dwFlags = 0;
|
|
chside.nRank = 0;
|
|
|
|
// Add each recnode
|
|
for (prn = pri->prnFirst; prn; prn = prn->prnNext)
|
|
{
|
|
chside.htwin = (HTWIN)prn->hObjectTwin;
|
|
chside.hvid = prn->hvid;
|
|
chside.pszFolder = prn->pcszFolder;
|
|
chside.prn = prn;
|
|
|
|
DSA_InsertItem(hdsa, 0x7fff, &chside);
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Create an array of CHOOSESIDE elements from a recitem
|
|
list. Array is unsorted.
|
|
|
|
Returns: standard result
|
|
|
|
Cond: The contents of the array are safe as long as the
|
|
recitem list lives.
|
|
|
|
*/
|
|
HRESULT PUBLIC ChooseSide_CreateAsFile(
|
|
HDSA * phdsa,
|
|
PRECITEM pri)
|
|
{
|
|
HRESULT hres;
|
|
HDSA hdsa;
|
|
|
|
ASSERT(phdsa);
|
|
ASSERT(pri);
|
|
|
|
hdsa = DSA_Create(sizeof(CHOOSESIDE), (int)pri->ulcNodes);
|
|
if (hdsa)
|
|
{
|
|
ChooseSide_InitAsFile(hdsa, pri);
|
|
hres = NOERROR;
|
|
}
|
|
else
|
|
hres = E_OUTOFMEMORY;
|
|
|
|
*phdsa = hdsa;
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Create an empty array of CHOOSESIDE elements.
|
|
|
|
Returns: standard result
|
|
Cond: --
|
|
*/
|
|
HRESULT PUBLIC ChooseSide_CreateEmpty(
|
|
HDSA * phdsa)
|
|
{
|
|
HRESULT hres;
|
|
HDSA hdsa;
|
|
|
|
ASSERT(phdsa);
|
|
|
|
hdsa = DSA_Create(sizeof(CHOOSESIDE), 4);
|
|
if (hdsa)
|
|
{
|
|
hres = NOERROR;
|
|
}
|
|
else
|
|
hres = E_OUTOFMEMORY;
|
|
|
|
*phdsa = hdsa;
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Create an array of CHOOSESIDE elements from a foldertwin
|
|
list. Array is unsorted.
|
|
|
|
Returns: standard result
|
|
|
|
Cond: The contents of the array are safe as long as the
|
|
foldertwin list lives.
|
|
|
|
*/
|
|
HRESULT PUBLIC ChooseSide_CreateAsFolder(
|
|
HDSA * phdsa,
|
|
PFOLDERTWINLIST pftl)
|
|
{
|
|
HRESULT hres;
|
|
HDSA hdsa;
|
|
CHOOSESIDE chside;
|
|
|
|
ASSERT(pftl);
|
|
|
|
hdsa = DSA_Create(sizeof(chside), (int)pftl->ulcItems);
|
|
if (hdsa)
|
|
{
|
|
PCFOLDERTWIN pft;
|
|
LPCTSTR pszFolderLast = NULL;
|
|
|
|
// All entries start with these values
|
|
chside.dwFlags = CSF_FOLDER;
|
|
chside.nRank = 0;
|
|
chside.prn = NULL;
|
|
|
|
// Special case the source folder
|
|
chside.htwin = (HTWIN)pftl->pcftFirst->hftSrc;
|
|
chside.hvid = pftl->pcftFirst->hvidSrc;
|
|
chside.pszFolder = pftl->pcftFirst->pcszSrcFolder;
|
|
|
|
// (Don't care if this fails)
|
|
DSA_InsertItem(hdsa, 0x7fff, &chside);
|
|
|
|
// Add the other folders (duplicates skipped)
|
|
for (pft = pftl->pcftFirst; pft; pft = pft->pcftNext)
|
|
{
|
|
// Duplicate?
|
|
if (pszFolderLast && IsSzEqual(pszFolderLast, pft->pcszOtherFolder))
|
|
continue; // Yes (hack: the engine gives us a sorted list)
|
|
|
|
chside.htwin = (HTWIN)pft->hftOther;
|
|
chside.hvid = pft->hvidOther;
|
|
chside.pszFolder = pft->pcszOtherFolder;
|
|
|
|
DSA_InsertItem(hdsa, 0x7fff, &chside);
|
|
|
|
pszFolderLast = pft->pcszOtherFolder;
|
|
}
|
|
*phdsa = hdsa;
|
|
hres = NOERROR;
|
|
}
|
|
else
|
|
hres = E_OUTOFMEMORY;
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Reset the ranks
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PRIVATE ChooseSide_ResetRanks(
|
|
HDSA hdsa)
|
|
{
|
|
int i;
|
|
int cel;
|
|
|
|
ASSERT(hdsa);
|
|
|
|
cel = DSA_GetItemCount(hdsa);
|
|
for (i = 0; i < cel; i++)
|
|
{
|
|
PCHOOSESIDE pchside = DSA_GetItemPtr(hdsa, i);
|
|
|
|
pchside->nRank = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Determine ranks based on whether each element in the
|
|
array is inside the briefcase.
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PRIVATE ChooseSide_RankForInside(
|
|
HDSA hdsa,
|
|
LPCTSTR pszBrfPath, // Root path of briefcase
|
|
LPCTSTR pszFolder) // If NULL, choose best outside element
|
|
{
|
|
int i;
|
|
int cel;
|
|
int cchLast = 0;
|
|
PCHOOSESIDE pchsideLast;
|
|
|
|
ASSERT(hdsa);
|
|
ASSERT(pszBrfPath);
|
|
ASSERT(pszFolder);
|
|
ASSERT(PathIsPrefix(pszBrfPath, pszFolder));
|
|
|
|
cel = DSA_GetItemCount(hdsa);
|
|
for (i = 0; i < cel; i++)
|
|
{
|
|
PCHOOSESIDE pchside = DSA_GetItemPtr(hdsa, i);
|
|
|
|
DEBUG_CODE( SetFlag(pchside->dwFlags, CSF_INSIDE); )
|
|
|
|
// Is this item inside this briefcase?
|
|
if (PathIsPrefix(pszBrfPath, pchside->pszFolder))
|
|
pchside->nRank++; // Yes
|
|
|
|
// Is this item inside this folder?
|
|
if (PathIsPrefix(pszFolder, pchside->pszFolder))
|
|
{
|
|
int cch = lstrlen(pchside->pszFolder);
|
|
|
|
pchside->nRank++; // Yes; even better
|
|
|
|
if (0 == cchLast)
|
|
{
|
|
cchLast = cch;
|
|
pchsideLast = pchside;
|
|
}
|
|
else
|
|
{
|
|
// Is this path deeper than the last prefix-matching path?
|
|
// (the path closer to the top is better)
|
|
if (cch > cchLast)
|
|
{
|
|
// Yes; demote this one
|
|
pchside->nRank--;
|
|
}
|
|
else
|
|
{
|
|
// No; demote previous one
|
|
ASSERT(pchsideLast);
|
|
pchsideLast->nRank--;
|
|
|
|
cchLast = cch;
|
|
pchsideLast = pchside;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Determine ranks based on whether each element in the
|
|
array is outside the briefcase.
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PRIVATE ChooseSide_RankForOutside(
|
|
HDSA hdsa,
|
|
LPCTSTR pszBrfPath) // Root path of briefcase
|
|
{
|
|
int i;
|
|
int cel;
|
|
|
|
ASSERT(hdsa);
|
|
ASSERT(pszBrfPath);
|
|
|
|
cel = DSA_GetItemCount(hdsa);
|
|
for (i = 0; i < cel; i++)
|
|
{
|
|
PCHOOSESIDE pchside = DSA_GetItemPtr(hdsa, i);
|
|
|
|
DEBUG_CODE( ClearFlag(pchside->dwFlags, CSF_INSIDE); )
|
|
|
|
// Is this item NOT in this briefcase?
|
|
if ( !PathIsPrefix(pszBrfPath, pchside->pszFolder) )
|
|
{
|
|
// Yes
|
|
int nDriveType = DRIVE_UNKNOWN;
|
|
int ndrive = PathGetDriveNumber(pchside->pszFolder);
|
|
|
|
if (-1 != ndrive)
|
|
{
|
|
nDriveType = DriveType(ndrive);
|
|
}
|
|
|
|
pchside->nRank += 2;
|
|
|
|
if (IsFlagClear(pchside->dwFlags, CSF_FOLDER))
|
|
{
|
|
// Is the file unavailable?
|
|
if (RNS_UNAVAILABLE == pchside->prn->rnstate ||
|
|
FS_COND_UNAVAILABLE == pchside->prn->fsCurrent.fscond)
|
|
{
|
|
// Yes; demote
|
|
pchside->nRank--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Is the folder unavailable?
|
|
FOLDERTWINSTATUS uStatus;
|
|
|
|
Sync_GetFolderTwinStatus((HFOLDERTWIN)pchside->htwin, NULL, 0,
|
|
&uStatus);
|
|
if (FTS_UNAVAILABLE == uStatus)
|
|
{
|
|
// Yes; demote
|
|
pchside->nRank--;
|
|
}
|
|
}
|
|
|
|
// Rank on locality of disk (the closer the better)
|
|
if (DRIVE_REMOVABLE == nDriveType || DRIVE_CDROM == nDriveType)
|
|
; // Floppy/removable (do nothing)
|
|
else if (PathIsUNC(pchside->pszFolder) || IsNetDrive(ndrive))
|
|
pchside->nRank++; // Net
|
|
else
|
|
pchside->nRank += 2; // Fixed disk
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Choose the element with the highest rank.
|
|
|
|
Returns: TRUE if any element distinguished itself
|
|
|
|
Cond: --
|
|
*/
|
|
BOOL PRIVATE ChooseSide_GetBestRank(
|
|
HDSA hdsa,
|
|
PCHOOSESIDE * ppchside)
|
|
{
|
|
BOOL bRet;
|
|
int i;
|
|
int cel;
|
|
int nRankCur = 0; // (start at 0 since 0 is not good enough to pass muster)
|
|
DEBUG_CODE( BOOL bDbgDup = FALSE; )
|
|
|
|
ASSERT(hdsa);
|
|
ASSERT(ppchside);
|
|
|
|
*ppchside = NULL;
|
|
|
|
cel = DSA_GetItemCount(hdsa);
|
|
for (i = 0; i < cel; i++)
|
|
{
|
|
PCHOOSESIDE pchside = DSA_GetItemPtr(hdsa, i);
|
|
|
|
#ifdef DEBUG
|
|
if (0 < nRankCur && nRankCur == pchside->nRank)
|
|
bDbgDup = TRUE;
|
|
#endif
|
|
|
|
if (nRankCur < pchside->nRank)
|
|
{
|
|
*ppchside = pchside;
|
|
nRankCur = pchside->nRank;
|
|
|
|
DEBUG_CODE( bDbgDup = FALSE; ) // Reset
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// We shouldn't get duplicate highest ranks
|
|
if (bDbgDup)
|
|
{
|
|
// Dump the chooseside list if there are duplicate highest ranks
|
|
ChooseSide_DumpList(hdsa);
|
|
}
|
|
ASSERT(FALSE == bDbgDup);
|
|
#endif
|
|
|
|
bRet = 0 < nRankCur;
|
|
ASSERT(bRet && *ppchside || !bRet && !*ppchside);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Get the best candidate element (inside or outside).
|
|
If the pszFolder is NULL, this function gets the best
|
|
outside path.
|
|
|
|
Returns: TRUE if an element was found
|
|
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC ChooseSide_GetBest(
|
|
HDSA hdsa,
|
|
LPCTSTR pszBrfPath, // Root path of briefcase
|
|
LPCTSTR pszFolder, // If NULL, choose best outside element
|
|
PCHOOSESIDE * ppchside)
|
|
{
|
|
ASSERT(hdsa);
|
|
ASSERT(0 < DSA_GetItemCount(hdsa));
|
|
ASSERT(pszBrfPath);
|
|
ASSERT(ppchside);
|
|
|
|
ChooseSide_ResetRanks(hdsa);
|
|
|
|
// Are we ranking for inside paths?
|
|
if (pszFolder)
|
|
{
|
|
// Yes; inside wins
|
|
ChooseSide_RankForInside(hdsa, pszBrfPath, pszFolder);
|
|
}
|
|
else
|
|
{
|
|
// No; outside wins
|
|
ChooseSide_RankForOutside(hdsa, pszBrfPath);
|
|
}
|
|
|
|
return ChooseSide_GetBestRank(hdsa, ppchside);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Get the next best candidate element (inside or outside).
|
|
ChooseSide_GetBest must have been previously called.
|
|
|
|
Returns: TRUE if an element was found
|
|
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC ChooseSide_GetNextBest(
|
|
HDSA hdsa,
|
|
PCHOOSESIDE * ppchside)
|
|
{
|
|
PCHOOSESIDE pchside;
|
|
|
|
ASSERT(hdsa);
|
|
ASSERT(0 < DSA_GetItemCount(hdsa));
|
|
ASSERT(ppchside);
|
|
|
|
// Get the best rank and reset it
|
|
ChooseSide_GetBestRank(hdsa, &pchside);
|
|
pchside->nRank = 0;
|
|
|
|
// Now get the next best rank
|
|
return ChooseSide_GetBestRank(hdsa, ppchside);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Frees an array of CHOOSESIDE elements.
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC ChooseSide_Free(
|
|
HDSA hdsa)
|
|
{
|
|
if (hdsa)
|
|
{
|
|
DSA_Destroy(hdsa);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Determine which node is inside and outside a briefcase.
|
|
|
|
This function takes a list of recnodes and determines
|
|
which node is "inside" a briefcase and which one is
|
|
"outside" a briefcase. "Inside" means the file exists
|
|
somewhere underneath the briefcase path indicated by
|
|
atomBrf. "Outside" is anywhere else (but may be in
|
|
a different briefcase as well).
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
HRESULT PUBLIC Sync_GetNodePair(
|
|
PRECITEM pri,
|
|
LPCTSTR pszBrfPath,
|
|
LPCTSTR pszInsideDir, // Which folder inside the briefcase to consider
|
|
PRECNODE * pprnInside,
|
|
PRECNODE * pprnOutside)
|
|
{
|
|
HRESULT hres;
|
|
HDSA hdsa;
|
|
|
|
ASSERT(pri);
|
|
ASSERT(pszBrfPath);
|
|
ASSERT(pszInsideDir);
|
|
ASSERT(pprnInside);
|
|
ASSERT(pprnOutside);
|
|
ASSERT(PathIsPrefix(pszBrfPath, pszInsideDir));
|
|
|
|
hres = ChooseSide_CreateAsFile(&hdsa, pri);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
PCHOOSESIDE pchside;
|
|
|
|
// Get inside folder
|
|
if (ChooseSide_GetBest(hdsa, pszBrfPath, pszInsideDir, &pchside))
|
|
{
|
|
*pprnInside = pchside->prn;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0);
|
|
*pprnInside = NULL;
|
|
hres = E_FAIL;
|
|
}
|
|
|
|
// Get outside folder
|
|
if (ChooseSide_GetBest(hdsa, pszBrfPath, NULL, &pchside))
|
|
{
|
|
*pprnOutside = pchside->prn;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0);
|
|
*pprnOutside = NULL;
|
|
hres = E_FAIL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
if (SUCCEEDED(hres) && IsFlagSet(g_uDumpFlags, DF_PATHS))
|
|
{
|
|
TRACE_MSG(TF_ALWAYS, TEXT("Choosing pairs: %s and %s"), (*pprnInside)->pcszFolder,
|
|
(*pprnOutside)->pcszFolder);
|
|
}
|
|
|
|
#endif
|
|
|
|
ChooseSide_Free(hdsa);
|
|
}
|
|
else
|
|
{
|
|
*pprnInside = NULL;
|
|
*pprnOutside = NULL;
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Checks if we've loaded the sync engine
|
|
Returns: TRUE if loaded
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC Sync_IsEngineLoaded()
|
|
{
|
|
BOOL bRet;
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bRet = g_vtblEngine[0].hinst != NULL;
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Load the SYNCENG.DLL and initialize the v-table.
|
|
Returns: TRUE on success
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC Sync_QueryVTable(void)
|
|
{
|
|
BOOL bRet = TRUE;
|
|
HINSTANCE hinst;
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
hinst = g_vtblEngine[0].hinst;
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
// We want to assure that the engine is loaded the same
|
|
// number of times that SYNCUI is (by a process). This prevents
|
|
// Kernel from nuking the engine prematurely (if a
|
|
// process is terminated).
|
|
//
|
|
// We go thru these hoops simply because SYNCUI does not
|
|
// load SYNCENG immediately upon PROCESS_ATTACH. We wait
|
|
// until we *really* need to load it the first time.
|
|
// Once we finally do load it, we need to keep the load
|
|
// count current.
|
|
//
|
|
// Kernel frees SYNCUI and SYNCENG for us.
|
|
//
|
|
if (NULL == hinst)
|
|
{
|
|
VTBLENGINE vtbl;
|
|
|
|
DEBUG_CODE( TRACE_MSG(TF_GENERAL, TEXT("Loading %s (cProcess = %d)"),
|
|
(LPCTSTR)c_szEngineDLL, g_cProcesses); )
|
|
|
|
ZeroInit(&vtbl, sizeof(VTBLENGINE));
|
|
|
|
// Don't be in the critical section when we load the DLL
|
|
// or call GetProcAddress, since our LibMain can block on
|
|
// this critical section.
|
|
ASSERT_NOT_EXCLUSIVE();
|
|
|
|
hinst = LoadLibrary(c_szEngineDLL);
|
|
|
|
if ( ISVALIDHINSTANCE(hinst) )
|
|
{
|
|
// We are loading for the first time. Fill the vtable.
|
|
//
|
|
vtbl.hinst = hinst;
|
|
|
|
// Get all the function addresses
|
|
//
|
|
GetFunction(vtbl, OpenBriefcase, OPENBRIEFCASEINDIRECT);
|
|
GetFunction(vtbl, SaveBriefcase, SAVEBRIEFCASEINDIRECT);
|
|
GetFunction(vtbl, CloseBriefcase, CLOSEBRIEFCASEINDIRECT);
|
|
GetFunction(vtbl, ClearBriefcaseCache, CLEARBRIEFCASECACHEINDIRECT);
|
|
GetFunction(vtbl, DeleteBriefcase, DELETEBRIEFCASEINDIRECT);
|
|
GetFunction(vtbl, GetOpenBriefcaseInfo, GETOPENBRIEFCASEINFOINDIRECT);
|
|
GetFunction(vtbl, FindFirstBriefcase, FINDFIRSTBRIEFCASEINDIRECT);
|
|
GetFunction(vtbl, FindNextBriefcase, FINDNEXTBRIEFCASEINDIRECT);
|
|
GetFunction(vtbl, FindBriefcaseClose, FINDBRIEFCASECLOSEINDIRECT);
|
|
|
|
GetFunction(vtbl, AddObjectTwin, ADDOBJECTTWININDIRECT);
|
|
GetFunction(vtbl, AddFolderTwin, ADDFOLDERTWININDIRECT);
|
|
GetFunction(vtbl, ReleaseTwinHandle, RELEASETWINHANDLEINDIRECT);
|
|
GetFunction(vtbl, DeleteTwin, DELETETWININDIRECT);
|
|
GetFunction(vtbl, GetObjectTwinHandle, GETOBJECTTWINHANDLEINDIRECT);
|
|
GetFunction(vtbl, IsFolderTwin, ISFOLDERTWININDIRECT);
|
|
GetFunction(vtbl, CreateFolderTwinList, CREATEFOLDERTWINLISTINDIRECT);
|
|
GetFunction(vtbl, DestroyFolderTwinList, DESTROYFOLDERTWINLISTINDIRECT);
|
|
GetFunction(vtbl, GetFolderTwinStatus, GETFOLDERTWINSTATUSINDIRECT);
|
|
GetFunction(vtbl, IsOrphanObjectTwin, ISORPHANOBJECTTWININDIRECT);
|
|
GetFunction(vtbl, CountSourceFolderTwins, COUNTSOURCEFOLDERTWINSINDIRECT);
|
|
GetFunction(vtbl, AnyTwins, ANYTWINSINDIRECT);
|
|
|
|
GetFunction(vtbl, CreateTwinList, CREATETWINLISTINDIRECT);
|
|
GetFunction(vtbl, DestroyTwinList, DESTROYTWINLISTINDIRECT);
|
|
GetFunction(vtbl, AddTwinToTwinList, ADDTWINTOTWINLISTINDIRECT);
|
|
GetFunction(vtbl, AddAllTwinsToTwinList, ADDALLTWINSTOTWINLISTINDIRECT);
|
|
GetFunction(vtbl, RemoveTwinFromTwinList, REMOVETWINFROMTWINLISTINDIRECT);
|
|
GetFunction(vtbl, RemoveAllTwinsFromTwinList, REMOVEALLTWINSFROMTWINLISTINDIRECT);
|
|
|
|
GetFunction(vtbl, CreateRecList, CREATERECLISTINDIRECT);
|
|
GetFunction(vtbl, DestroyRecList, DESTROYRECLISTINDIRECT);
|
|
GetFunction(vtbl, ReconcileItem, RECONCILEITEMINDIRECT);
|
|
GetFunction(vtbl, BeginReconciliation, BEGINRECONCILIATIONINDIRECT);
|
|
GetFunction(vtbl, EndReconciliation, ENDRECONCILIATIONINDIRECT);
|
|
|
|
GetFunction(vtbl, IsPathOnVolume, ISPATHONVOLUMEINDIRECT);
|
|
GetFunction(vtbl, GetVolumeDescription, GETVOLUMEDESCRIPTIONINDIRECT);
|
|
}
|
|
else
|
|
{
|
|
bRet = FALSE;
|
|
}
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
g_vtblEngine[0] = vtbl;
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Free the engine DLL if it is loaded
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_ReleaseVTable()
|
|
{
|
|
HINSTANCE hinst;
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
hinst = g_vtblEngine[0].hinst;
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (NULL != hinst)
|
|
{
|
|
DEBUG_CODE( TRACE_MSG(TF_GENERAL, TEXT("Freeing %s (cProcess = %d)"),
|
|
(LPCTSTR)c_szEngineDLL, g_cProcesses); )
|
|
|
|
// We must call FreeLibrary() on the sync engine even during a
|
|
// PROCESS_DETACH. We may be getting detached from a process even
|
|
// though the process isn't being terminated. If we don't unload
|
|
// the sync engine now, it won't be unloaded until the process is
|
|
// terminated.
|
|
//
|
|
FreeLibrary(hinst);
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
ZeroInit(&g_vtblEngine[0], sizeof(VTBLENGINE));
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
ASSERT(g_vtblEngine[0].hinst == NULL);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Set the last sync error.
|
|
Returns: same twinresult
|
|
Cond: --
|
|
*/
|
|
TWINRESULT PUBLIC Sync_SetLastError(
|
|
TWINRESULT tr)
|
|
{
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
ASSERTEXCLUSIVE();
|
|
|
|
MySetTwinResult(tr);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
return tr;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Get the last sync error.
|
|
Returns: twinresult
|
|
Cond: --
|
|
*/
|
|
TWINRESULT PUBLIC Sync_GetLastError()
|
|
{
|
|
TWINRESULT tr;
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
ASSERTEXCLUSIVE();
|
|
|
|
tr = MyGetTwinResult();
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
return tr;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Returns the number of recitems that would require
|
|
some reconciliation.
|
|
|
|
Returns: see above
|
|
Cond: --
|
|
*/
|
|
ULONG PUBLIC CountActionItems(
|
|
PRECLIST prl)
|
|
{
|
|
PRECITEM pri;
|
|
ULONG ulc;
|
|
|
|
for (pri = prl->priFirst, ulc = 0; pri; pri = pri->priNext)
|
|
{
|
|
if (IsFileRecItem(pri) &&
|
|
RIU_SKIP != pri->dwUser &&
|
|
RIA_NOTHING != pri->riaction &&
|
|
RIA_BROKEN_MERGE != pri->riaction)
|
|
{
|
|
ulc++;
|
|
pri->dwUser = RIU_SHOWSTATUS;
|
|
}
|
|
}
|
|
|
|
|
|
return ulc;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Displays appropriate error message on update errors
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PRIVATE HandleUpdateErrors(
|
|
HWND hwndOwner,
|
|
HRESULT hres,
|
|
UINT uFlags) // RF_*
|
|
{
|
|
// Is this an update while adding files?
|
|
if (IsFlagSet(uFlags, RF_ONADD))
|
|
{
|
|
// Yes
|
|
static SETbl const c_rgseUpdateOnAdd[] = {
|
|
// The out of memory message should be handled by caller
|
|
{ E_TR_DEST_OPEN_FAILED, IDS_ERR_ADD_READONLY, MB_WARNING },
|
|
{ E_TR_DEST_WRITE_FAILED, IDS_ERR_ADD_FULLDISK, MB_WARNING },
|
|
{ E_TR_UNAVAILABLE_VOLUME, IDS_ERR_ADD_UNAVAIL_VOL, MB_WARNING },
|
|
{ E_TR_SRC_OPEN_FAILED, IDS_ERR_ADD_SOURCE_FILE, MB_WARNING },
|
|
};
|
|
|
|
SEMsgBox(hwndOwner, IDS_CAP_ADD, hres, c_rgseUpdateOnAdd, ARRAYSIZE(c_rgseUpdateOnAdd));
|
|
}
|
|
else
|
|
{
|
|
// No
|
|
static SETbl const c_rgseUpdate[] = {
|
|
{ E_TR_OUT_OF_MEMORY, IDS_OOM_UPDATE, MB_ERROR },
|
|
{ E_TR_DEST_OPEN_FAILED, IDS_ERR_READONLY, MB_INFO },
|
|
{ E_TR_DEST_WRITE_FAILED, IDS_ERR_FULLDISK, MB_WARNING },
|
|
{ E_TR_UNAVAILABLE_VOLUME, IDS_ERR_UPD_UNAVAIL_VOL,MB_WARNING },
|
|
{ E_TR_FILE_CHANGED, IDS_ERR_FILE_CHANGED, MB_INFO },
|
|
{ E_TR_SRC_OPEN_FAILED, IDS_ERR_SOURCE_FILE, MB_WARNING },
|
|
};
|
|
|
|
SEMsgBox(hwndOwner, IDS_CAP_UPDATE, hres, c_rgseUpdate, ARRAYSIZE(c_rgseUpdate));
|
|
}
|
|
}
|
|
|
|
|
|
typedef struct tagPROGPARAM
|
|
{
|
|
HWND hwndProgress;
|
|
WORD wPosMax;
|
|
WORD wPosBase;
|
|
WORD wPosPrev;
|
|
BOOL bSkip;
|
|
} PROGPARAM, * PPROGPARAM;
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Status procedure that is called during a single
|
|
ReconcileItem call.
|
|
|
|
Returns: varies
|
|
Cond: --
|
|
*/
|
|
BOOL CALLBACK RecStatusProc(
|
|
RECSTATUSPROCMSG msg,
|
|
LPARAM lParam,
|
|
LPARAM lParamUser)
|
|
{
|
|
BOOL bRet;
|
|
PRECSTATUSUPDATE prsu = (PRECSTATUSUPDATE)lParam;
|
|
PPROGPARAM pprogparam = (PPROGPARAM)lParamUser;
|
|
HWND hwndProgress = pprogparam->hwndProgress;
|
|
WORD wPos;
|
|
|
|
bRet = !UpdBar_QueryAbort(hwndProgress);
|
|
|
|
switch (msg)
|
|
{
|
|
case RS_BEGIN_COPY:
|
|
case RS_DELTA_COPY:
|
|
case RS_END_COPY:
|
|
case RS_BEGIN_MERGE:
|
|
case RS_DELTA_MERGE:
|
|
case RS_END_MERGE:
|
|
#ifdef NEW_REC
|
|
case RS_BEGIN_DELETE:
|
|
case RS_DELTA_DELETE:
|
|
case RS_END_DELETE:
|
|
#endif
|
|
TRACE_MSG(TF_PROGRESS, TEXT("Reconcile progress = %lu of %lu"), prsu->ulProgress, prsu->ulScale);
|
|
ASSERT(prsu->ulProgress <= prsu->ulScale);
|
|
|
|
if (0 < prsu->ulScale && !pprogparam->bSkip)
|
|
{
|
|
wPos = LOWORD(LODWORD( (((__int64)pprogparam->wPosMax * prsu->ulProgress) / prsu->ulScale) ));
|
|
|
|
TRACE_MSG(TF_PROGRESS, TEXT("Max wPos = %u, new wPos = %u, old wPos = %u"),
|
|
pprogparam->wPosMax, wPos, pprogparam->wPosPrev);
|
|
|
|
if (wPos > pprogparam->wPosPrev && wPos < pprogparam->wPosMax)
|
|
{
|
|
WORD wPosReal = pprogparam->wPosBase + wPos;
|
|
|
|
TRACE_MSG(TF_PROGRESS, TEXT("Setting real position = %u"), wPosReal);
|
|
|
|
UpdBar_SetPos(hwndProgress, wPosReal);
|
|
pprogparam->wPosPrev = wPos;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Decides the description string while updating. The
|
|
string is something like "Copying from 'Foo' to 'Bar'"
|
|
or "Merging files in 'Foo' and 'Bar'"
|
|
|
|
Returns: string in pszBuf
|
|
|
|
Cond: --
|
|
*/
|
|
void PRIVATE DecideDescString(
|
|
LPCTSTR pszBrfPath,
|
|
PRECITEM pri,
|
|
LPTSTR pszBuf,
|
|
int cbBuf,
|
|
LPTSTR pszPathBuf)
|
|
{
|
|
HRESULT hres;
|
|
RA_ITEM * pitem;
|
|
|
|
ASSERT(pszBrfPath);
|
|
ASSERT(pri);
|
|
ASSERT(pszBuf);
|
|
|
|
hres = RAI_CreateFromRecItem(&pitem, pszBrfPath, pri);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
UINT ids;
|
|
LPTSTR pszMsg;
|
|
LPCTSTR pszFrom;
|
|
LPCTSTR pszTo;
|
|
|
|
lstrcpy(pszPathBuf, pitem->siInside.pszDir);
|
|
PathAppend(pszPathBuf, pitem->pszName);
|
|
|
|
switch (pitem->uAction)
|
|
{
|
|
case RAIA_TOOUT:
|
|
ids = IDS_UPDATE_Copy;
|
|
pszFrom = PathFindFileName(pitem->siInside.pszDir);
|
|
pszTo = PathFindFileName(pitem->siOutside.pszDir);
|
|
break;
|
|
|
|
case RAIA_TOIN:
|
|
ids = IDS_UPDATE_Copy;
|
|
pszFrom = PathFindFileName(pitem->siOutside.pszDir);
|
|
pszTo = PathFindFileName(pitem->siInside.pszDir);
|
|
break;
|
|
|
|
case RAIA_MERGE:
|
|
ids = IDS_UPDATE_Merge;
|
|
// (Arbitrary)
|
|
pszFrom = PathFindFileName(pitem->siInside.pszDir);
|
|
pszTo = PathFindFileName(pitem->siOutside.pszDir);
|
|
break;
|
|
|
|
case RAIA_DELETEOUT:
|
|
ids = IDS_UPDATE_Delete;
|
|
pszFrom = PathFindFileName(pitem->siOutside.pszDir);
|
|
pszTo = NULL;
|
|
break;
|
|
|
|
case RAIA_DELETEIN:
|
|
ids = IDS_UPDATE_Delete;
|
|
pszFrom = PathFindFileName(pitem->siInside.pszDir);
|
|
pszTo = NULL;
|
|
break;
|
|
|
|
default:
|
|
ASSERT(0);
|
|
ids = 0;
|
|
break;
|
|
}
|
|
|
|
if (ConstructMessage(&pszMsg, g_hinst, MAKEINTRESOURCE(ids),
|
|
pszFrom, pszTo))
|
|
{
|
|
lstrcpyn(pszBuf, pszMsg, cbBuf);
|
|
GFree(pszMsg);
|
|
}
|
|
else
|
|
*pszBuf = 0;
|
|
}
|
|
else
|
|
*pszBuf = 0;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Reconcile a given reclist
|
|
|
|
Returns: standard result
|
|
Cond: --
|
|
*/
|
|
HRESULT PUBLIC Sync_ReconcileRecList(
|
|
PRECLIST prl, // ptr to reclist
|
|
LPCTSTR pszBrfPath,
|
|
HWND hwndProgress,
|
|
UINT uFlags) // RF_*
|
|
{
|
|
HRESULT hres;
|
|
|
|
if (prl)
|
|
{
|
|
HWND hwndOwner = GetParent(hwndProgress);
|
|
HWND hwndStatusText = UpdBar_GetStatusWindow(hwndProgress);
|
|
TCHAR szPath[MAX_PATH];
|
|
TCHAR sz[MAXBUFLEN];
|
|
TWINRESULT tr;
|
|
PRECITEM pri;
|
|
PROGPARAM progparam;
|
|
ULONG ulcItems;
|
|
WORD wDelta;
|
|
|
|
DEBUG_CODE( Sync_DumpRecList(TR_SUCCESS, prl, TEXT("Updating")); )
|
|
|
|
hres = NOERROR; // assume success
|
|
|
|
// Grab the mutex to delay any further calculation in any
|
|
// Briefcase views' secondary threads until we're done
|
|
// processing here.
|
|
Delay_Own();
|
|
|
|
// Determine the range of the progress bar
|
|
UpdBar_SetRange(hwndProgress, MAX_RANGE);
|
|
|
|
ulcItems = CountActionItems(prl);
|
|
if (0 < ulcItems)
|
|
wDelta = (WORD)(MAX_RANGE / ulcItems);
|
|
else
|
|
wDelta = 0;
|
|
|
|
progparam.hwndProgress = hwndProgress;
|
|
|
|
// Start updating
|
|
Sync_BeginRec(prl->hbr);
|
|
|
|
ulcItems = 0;
|
|
for (pri = prl->priFirst; pri; pri = pri->priNext)
|
|
{
|
|
// Did the user explicitly skip this recitem or
|
|
// is this a broken merge?
|
|
if (RIU_SKIP == pri->dwUser ||
|
|
RIA_BROKEN_MERGE == pri->riaction)
|
|
{
|
|
// Yes; don't call ReconcileItem
|
|
continue;
|
|
}
|
|
|
|
// Is something going to be done to this recitem?
|
|
if (RIU_SHOWSTATUS == pri->dwUser)
|
|
{
|
|
// Yes; update the name of the file we're updating
|
|
UpdBar_SetName(hwndProgress, pri->pcszName);
|
|
DecideDescString(pszBrfPath, pri, sz, ARRAYSIZE(sz), szPath);
|
|
UpdBar_SetDescription(hwndProgress, sz);
|
|
|
|
ASSERT(0 < wDelta);
|
|
progparam.wPosBase = (WORD)(wDelta * ulcItems);
|
|
progparam.wPosMax = wDelta;
|
|
progparam.wPosPrev = 0;
|
|
progparam.bSkip = FALSE;
|
|
}
|
|
else
|
|
{
|
|
progparam.bSkip = TRUE;
|
|
}
|
|
|
|
// Call ReconcileItem even for nops, so the recnode states
|
|
// will be updated by the engine.
|
|
tr = Sync_ReconcileItem(pri, RecStatusProc, (LPARAM)&progparam,
|
|
RI_FL_FEEDBACK_WINDOW_VALID, hwndProgress, hwndStatusText);
|
|
if (TR_SUCCESS != tr &&
|
|
IsFileRecItem(pri)) // ignore folder recitem errors
|
|
{
|
|
// On some conditions, stop updating completely
|
|
hres = HRESULT_FROM_TR(tr);
|
|
|
|
switch (hres)
|
|
{
|
|
case E_TR_OUT_OF_MEMORY:
|
|
case E_TR_RH_LOAD_FAILED: {
|
|
// Couldn't load the merge handler. Tell the user but
|
|
// continue on...
|
|
int id = MsgBox(hwndOwner,
|
|
MAKEINTRESOURCE(IDS_ERR_NO_MERGE_HANDLER),
|
|
MAKEINTRESOURCE(IDS_CAP_UPDATE),
|
|
NULL,
|
|
MB_WARNING | MB_OKCANCEL,
|
|
PathGetDisplayName(szPath, sz));
|
|
|
|
if (IDOK == id)
|
|
break; // continue updating other files
|
|
}
|
|
|
|
goto StopUpdating;
|
|
|
|
case E_TR_DELETED_TWIN:
|
|
// Allow the updating to continue.
|
|
break;
|
|
|
|
case E_TR_DEST_OPEN_FAILED:
|
|
case E_TR_FILE_CHANGED:
|
|
if (IsFlagClear(uFlags, RF_ONADD))
|
|
{
|
|
// Allow the updating to continue. Remember the
|
|
// latest error for the end.
|
|
break;
|
|
}
|
|
// Fall thru
|
|
// | |
|
|
// v v
|
|
|
|
default:
|
|
goto StopUpdating;
|
|
}
|
|
}
|
|
|
|
// Was something done to this recitem?
|
|
if (RIU_SHOWSTATUS == pri->dwUser)
|
|
{
|
|
// Yes; update the progress bar
|
|
UpdBar_SetPos(hwndProgress, (WORD)(wDelta * ++ulcItems));
|
|
}
|
|
|
|
// Check if the Cancel button was pressed
|
|
if (UpdBar_QueryAbort(hwndProgress))
|
|
{
|
|
hres = E_ABORT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
StopUpdating:
|
|
if (FAILED(hres))
|
|
{
|
|
Sync_DumpRecItem(tr, pri, NULL);
|
|
HandleUpdateErrors(hwndOwner, hres, uFlags);
|
|
|
|
if (IsFlagSet(uFlags, RF_ONADD))
|
|
{
|
|
// Hack: since the caller also handles some error messages,
|
|
// return a generic failure code to prevent repeated
|
|
// error messages.
|
|
hres = E_FAIL;
|
|
}
|
|
}
|
|
// Were there any items at all?
|
|
else if (0 == prl->ulcItems)
|
|
{
|
|
// No
|
|
MsgBox(hwndOwner, MAKEINTRESOURCE(IDS_MSG_NoMatchingFiles),
|
|
MAKEINTRESOURCE(IDS_CAP_UPDATE), NULL, MB_INFO);
|
|
}
|
|
|
|
Sync_EndRec(prl->hbr);
|
|
|
|
Delay_Release();
|
|
}
|
|
else
|
|
hres = E_INVALIDARG;
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Status procedure that is called during a single
|
|
ReconcileItem call.
|
|
|
|
Returns: varies
|
|
Cond: --
|
|
*/
|
|
BOOL CALLBACK CreateRecListProc(
|
|
CREATERECLISTPROCMSG msg,
|
|
LPARAM lParam,
|
|
LPARAM lParamUser)
|
|
{
|
|
return !AbortEvt_Query((PABORTEVT)lParamUser);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Creates a reclist and optionally shows a progress
|
|
bar during the creation.
|
|
|
|
Returns: standard result
|
|
Cond: --
|
|
*/
|
|
HRESULT PUBLIC Sync_CreateRecListEx(
|
|
HTWINLIST htl,
|
|
PABORTEVT pabortevt,
|
|
PRECLIST * pprl)
|
|
{
|
|
TWINRESULT tr;
|
|
|
|
ASSERT(pprl);
|
|
|
|
tr = Sync_CreateRecList(htl, CreateRecListProc, (LPARAM)pabortevt, pprl);
|
|
return HRESULT_FROM_TR(tr);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Return true if the file or folder is a twin.
|
|
|
|
There are some cases when this function cannot successfully
|
|
determine this unless the caller first tells it explicitly
|
|
whether the object is a file or folder. Otherwise this
|
|
function will attempt to determine this on its own.
|
|
|
|
Returns: S_OK if it is a twin
|
|
S_FALSE if it is not
|
|
any other is an error
|
|
|
|
Cond: --
|
|
*/
|
|
HRESULT PUBLIC Sync_IsTwin(
|
|
HBRFCASE hbrfcase,
|
|
LPCTSTR pszPath,
|
|
UINT uFlags) // SF_* flags
|
|
{
|
|
HRESULT hres;
|
|
TWINRESULT tr;
|
|
|
|
ASSERT(pszPath);
|
|
|
|
// The caller may already know whether this is a twin or not.
|
|
// Remind him.
|
|
if (IsFlagSet(uFlags, SF_ISTWIN))
|
|
return S_OK;
|
|
else if (IsFlagSet(uFlags, SF_ISNOTTWIN))
|
|
return S_FALSE;
|
|
|
|
// Is this a folder?
|
|
if (IsFlagSet(uFlags, SF_ISFOLDER) ||
|
|
PathIsDirectory(pszPath))
|
|
{
|
|
// Yes; is it a twin?
|
|
BOOL bRet;
|
|
|
|
tr = Sync_IsFolder(hbrfcase, pszPath, &bRet);
|
|
if (TR_SUCCESS == tr)
|
|
{
|
|
// Yes/no
|
|
hres = bRet ? S_OK : S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
// Error
|
|
hres = HRESULT_FROM_TR(tr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No
|
|
HOBJECTTWIN hot;
|
|
TCHAR szDir[MAX_PATH];
|
|
|
|
lstrcpy(szDir, pszPath);
|
|
PathRemoveFileSpec(szDir);
|
|
tr = Sync_GetObject(hbrfcase, szDir, PathFindFileName(pszPath), &hot);
|
|
if (TR_SUCCESS == tr)
|
|
{
|
|
// Is it a twin?
|
|
if (NULL != hot)
|
|
{
|
|
// Yes
|
|
Sync_ReleaseTwin(hot);
|
|
hres = S_OK;
|
|
}
|
|
else
|
|
{
|
|
// No; (no need to release a null handle)
|
|
hres = S_FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Error
|
|
hres = HRESULT_FROM_TR(tr);
|
|
}
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Create a reclist with everything in it.
|
|
|
|
Returns: standard result
|
|
|
|
Cond: Caller must destroy the reclist
|
|
*/
|
|
HRESULT PUBLIC Sync_CreateCompleteRecList(
|
|
HBRFCASE hbrf,
|
|
PABORTEVT pabortevt,
|
|
PRECLIST * pprl)
|
|
{
|
|
HRESULT hres = E_OUTOFMEMORY;
|
|
HTWINLIST htl;
|
|
|
|
ASSERT(pprl);
|
|
|
|
*pprl = NULL;
|
|
|
|
if (TR_SUCCESS == Sync_CreateTwinList(hbrf, &htl))
|
|
{
|
|
Sync_AddAllToTwinList(htl);
|
|
|
|
hres = Sync_CreateRecListEx(htl, pabortevt, pprl);
|
|
Sync_DestroyTwinList(htl);
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Add a twin to the twinlist given the pathname. If the
|
|
pathname is not a twin, we don't add it.
|
|
|
|
Returns: TRUE on success, even when this isn't a twin
|
|
|
|
Cond: Caller must destroy the folder list if lplpftl is not NULL
|
|
*/
|
|
BOOL PUBLIC Sync_AddPathToTwinList(
|
|
HBRFCASE hbrf,
|
|
HTWINLIST htl,
|
|
LPCTSTR lpcszPath, // Path
|
|
PFOLDERTWINLIST * lplpftl) // May be NULL
|
|
{
|
|
BOOL bRet = FALSE;
|
|
|
|
ASSERT(lpcszPath);
|
|
ASSERT(htl);
|
|
|
|
if (lplpftl)
|
|
*lplpftl = NULL;
|
|
|
|
if (lpcszPath)
|
|
{
|
|
if (PathIsDirectory(lpcszPath))
|
|
{
|
|
BOOL fIsTwin = FALSE;
|
|
PFOLDERTWINLIST lpftl;
|
|
|
|
// We only want to return false if we couldn't mark something
|
|
// that should have been marked. If this isn't a twin,
|
|
// we still succeed.
|
|
|
|
bRet = TRUE;
|
|
|
|
Sync_IsFolder(hbrf, lpcszPath, &fIsTwin);
|
|
if (fIsTwin) // Is this actually twinned?
|
|
{
|
|
// This is a folder twin. Add to reclist "the folder way".
|
|
//
|
|
if (Sync_CreateFolderList(hbrf, lpcszPath, &lpftl) != TR_SUCCESS)
|
|
bRet = FALSE;
|
|
else
|
|
{
|
|
PCFOLDERTWIN lpcfolder;
|
|
|
|
ASSERT(lpftl->pcftFirst);
|
|
|
|
// only mark the ones that aren't in other briefcases
|
|
//
|
|
lpcfolder = lpftl->pcftFirst;
|
|
while (lpcfolder)
|
|
{
|
|
Sync_AddToTwinList(htl, lpcfolder->hftOther);
|
|
|
|
lpcfolder = lpcfolder->pcftNext;
|
|
}
|
|
|
|
if (lplpftl)
|
|
*lplpftl = lpftl;
|
|
else
|
|
Sync_DestroyFolderList(lpftl);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HOBJECTTWIN hot = NULL;
|
|
TCHAR szDir[MAX_PATH];
|
|
|
|
// Add the twins to the reclist "the object way"
|
|
//
|
|
lstrcpy(szDir, lpcszPath);
|
|
PathRemoveFileSpec(szDir);
|
|
Sync_GetObject(hbrf, szDir, PathFindFileName(lpcszPath), &hot);
|
|
|
|
if (hot) // Is this actually a twin?
|
|
{
|
|
// yep
|
|
Sync_AddToTwinList(htl, hot);
|
|
Sync_ReleaseTwin(hot);
|
|
}
|
|
if (lplpftl)
|
|
*lplpftl = NULL;
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Asks the user to confirm splitting one or more files.
|
|
|
|
Returns: IDYES or IDNO
|
|
Cond: --
|
|
*/
|
|
int PRIVATE ConfirmSplit(
|
|
HWND hwndOwner,
|
|
LPCTSTR pszPath,
|
|
UINT cFiles)
|
|
{
|
|
int idRet;
|
|
|
|
ASSERT(pszPath);
|
|
ASSERT(1 <= cFiles);
|
|
|
|
// Multiple files?
|
|
if (1 < cFiles)
|
|
{
|
|
// Yes
|
|
idRet = MsgBox(hwndOwner,
|
|
MAKEINTRESOURCE(IDS_MSG_ConfirmMultiSplit),
|
|
MAKEINTRESOURCE(IDS_CAP_ConfirmMultiSplit),
|
|
LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_SPLIT_MULT)),
|
|
MB_QUESTION,
|
|
cFiles);
|
|
}
|
|
else
|
|
{
|
|
// No
|
|
UINT ids;
|
|
UINT idi;
|
|
TCHAR szName[MAX_PATH];
|
|
|
|
if (PathIsDirectory(pszPath))
|
|
{
|
|
ids = IDS_MSG_ConfirmFolderSplit;
|
|
idi = IDI_SPLIT_FOLDER;
|
|
}
|
|
else
|
|
{
|
|
ids = IDS_MSG_ConfirmFileSplit;
|
|
idi = IDI_SPLIT_FILE;
|
|
}
|
|
|
|
idRet = MsgBox(hwndOwner,
|
|
MAKEINTRESOURCE(ids),
|
|
MAKEINTRESOURCE(IDS_CAP_ConfirmSplit),
|
|
LoadIcon(g_hinst, MAKEINTRESOURCE(idi)),
|
|
MB_QUESTION,
|
|
PathGetDisplayName(pszPath, szName));
|
|
}
|
|
return idRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Splits a path from its sync copy. Private function
|
|
called by Sync_Split.
|
|
|
|
Returns: standard result
|
|
S_OK if it is split
|
|
|
|
Cond: --
|
|
*/
|
|
HRESULT PRIVATE SplitPath(
|
|
HBRFCASE hbrf,
|
|
LPCTSTR pszPath,
|
|
HWND hwndOwner,
|
|
UINT uFlags) // SF_* flags
|
|
{
|
|
HRESULT hres;
|
|
TWINRESULT tr;
|
|
TCHAR sz[MAX_PATH];
|
|
|
|
if (pszPath)
|
|
{
|
|
// Is the object a folder?
|
|
if (IsFlagSet(uFlags, SF_ISFOLDER) ||
|
|
PathIsDirectory(pszPath))
|
|
{
|
|
// Yup
|
|
BOOL bIsTwin;
|
|
|
|
if (IsFlagSet(uFlags, SF_ISTWIN)) // Optimization
|
|
{
|
|
tr = TR_SUCCESS;
|
|
bIsTwin = TRUE;
|
|
}
|
|
else if (IsFlagSet(uFlags, SF_ISNOTTWIN)) // Optimization
|
|
{
|
|
tr = TR_SUCCESS;
|
|
bIsTwin = FALSE;
|
|
}
|
|
else
|
|
{
|
|
tr = Sync_IsFolder(hbrf, pszPath, &bIsTwin);
|
|
}
|
|
|
|
// Is this folder a twin?
|
|
if (TR_SUCCESS == tr)
|
|
{
|
|
if (bIsTwin)
|
|
{
|
|
// Yes; delete all the twin handles associated with it
|
|
PFOLDERTWINLIST lpftl;
|
|
|
|
tr = Sync_CreateFolderList(hbrf, pszPath, &lpftl);
|
|
if (TR_SUCCESS == tr)
|
|
{
|
|
PCFOLDERTWIN lpcfolder;
|
|
|
|
ASSERT(lpftl);
|
|
|
|
for (lpcfolder = lpftl->pcftFirst; lpcfolder;
|
|
lpcfolder = lpcfolder->pcftNext)
|
|
{
|
|
Sync_DeleteTwin(lpcfolder->hftOther);
|
|
}
|
|
|
|
Sync_DestroyFolderList(lpftl);
|
|
|
|
if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
// Send a notification so it is redrawn.
|
|
PathNotifyShell(pszPath, NSE_UPDATEITEM, FALSE);
|
|
}
|
|
hres = NOERROR;
|
|
}
|
|
}
|
|
else if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
// No
|
|
MsgBox(hwndOwner,
|
|
MAKEINTRESOURCE(IDS_MSG_FolderAlreadyOrphan),
|
|
MAKEINTRESOURCE(IDS_CAP_Split),
|
|
LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_SPLIT_FOLDER)),
|
|
MB_INFO,
|
|
PathGetDisplayName(pszPath, sz));
|
|
|
|
hres = S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
hres = S_FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No; it is a file
|
|
HOBJECTTWIN hot;
|
|
ULONG ulc;
|
|
|
|
lstrcpy(sz, pszPath);
|
|
PathRemoveFileSpec(sz);
|
|
|
|
// Is this file a twin?
|
|
// (We need the twin handle below, so we cannot take
|
|
// advantage of SF_ISTWIN or SF_ISNOTTWIN.)
|
|
tr = Sync_GetObject(hbrf, sz, PathFindFileName(pszPath), &hot);
|
|
|
|
if (TR_SUCCESS == tr)
|
|
{
|
|
if (hot)
|
|
{
|
|
// Yes; is this inside a folder twin?
|
|
// (If we remove this check, the engine needs to be able
|
|
// to exclude specific files from a folder twin.)
|
|
tr = Sync_CountSourceFolders(hot, &ulc);
|
|
if (TR_SUCCESS == tr)
|
|
{
|
|
if (0 < ulc)
|
|
{
|
|
// Yes; can't do it
|
|
if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
UINT rgids[2] = { IDS_ERR_1_CantSplit, IDS_ERR_2_CantSplit };
|
|
LPTSTR psz;
|
|
|
|
if (FmtString(&psz, IDS_ERR_F_CantSplit, rgids, ARRAYSIZE(rgids)))
|
|
{
|
|
// This object twin belongs to a folder twin. We can't
|
|
// allow this action.
|
|
MsgBox(hwndOwner, psz, MAKEINTRESOURCE(IDS_CAP_STATUS),
|
|
LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_SPLIT_FILE)), MB_ERROR);
|
|
GFree(psz);
|
|
}
|
|
}
|
|
hres = S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
// No; delete the twin
|
|
Sync_DeleteTwin(hot);
|
|
|
|
if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
// Send a notification so it's redrawn immediately.
|
|
PathNotifyShell(pszPath, NSE_UPDATEITEM, FALSE);
|
|
}
|
|
hres = NOERROR;
|
|
}
|
|
}
|
|
|
|
Sync_ReleaseTwin(hot);
|
|
}
|
|
else if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
// No
|
|
MsgBox(hwndOwner,
|
|
MAKEINTRESOURCE(IDS_MSG_FileAlreadyOrphan),
|
|
MAKEINTRESOURCE(IDS_CAP_Split),
|
|
LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_SPLIT_FILE)),
|
|
MB_INFO,
|
|
PathGetDisplayName(pszPath, sz));
|
|
|
|
hres = S_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TR_SUCCESS != tr)
|
|
hres = HRESULT_FROM_TR(tr);
|
|
}
|
|
else
|
|
hres = S_FALSE;
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Deletes a series of twins from the engine database.
|
|
The user is optionally asked to confirm the action.
|
|
|
|
If a file is an orphan, the user is optionally
|
|
notified. The user is also optionally notified
|
|
of any errors.
|
|
|
|
Returns: standard result
|
|
S_OK if anything was deleted
|
|
|
|
Cond: --
|
|
*/
|
|
HRESULT PUBLIC Sync_Split(
|
|
HBRFCASE hbrf,
|
|
LPCTSTR pszList,
|
|
UINT cFiles,
|
|
HWND hwndOwner,
|
|
UINT uFlags)
|
|
{
|
|
HRESULT hres;
|
|
UINT id;
|
|
TCHAR szCanon[MAX_PATH];
|
|
TCHAR sz[MAX_PATH];
|
|
|
|
ASSERT(pszList);
|
|
ASSERT(0 < cFiles);
|
|
|
|
// Special precondition: is it a single file?
|
|
if (1 == cFiles)
|
|
{
|
|
// Yes; is it a twin?
|
|
BrfPathCanonicalize(pszList, szCanon);
|
|
hres = Sync_IsTwin(hbrf, szCanon, uFlags);
|
|
if (S_FALSE == hres)
|
|
{
|
|
// No; tell the user. Don't bother confirming the action first.
|
|
if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
UINT ids;
|
|
UINT idi;
|
|
|
|
if (IsFlagSet(uFlags, SF_ISFOLDER) ||
|
|
PathIsDirectory(szCanon))
|
|
{
|
|
ids = IDS_MSG_FolderAlreadyOrphan;
|
|
idi = IDI_SPLIT_FOLDER;
|
|
}
|
|
else
|
|
{
|
|
ids = IDS_MSG_FileAlreadyOrphan;
|
|
idi = IDI_SPLIT_FILE;
|
|
}
|
|
|
|
MsgBox(hwndOwner,
|
|
MAKEINTRESOURCE(ids),
|
|
MAKEINTRESOURCE(IDS_CAP_Split),
|
|
LoadIcon(g_hinst, MAKEINTRESOURCE(idi)),
|
|
MB_INFO,
|
|
PathGetDisplayName(szCanon, sz));
|
|
}
|
|
}
|
|
else if (S_OK == hres)
|
|
{
|
|
// Yes
|
|
if (IsFlagClear(uFlags, SF_NOCONFIRM))
|
|
id = ConfirmSplit(hwndOwner, szCanon, 1);
|
|
else
|
|
id = IDYES;
|
|
|
|
if (IDYES == id)
|
|
{
|
|
hres = SplitPath(hbrf, szCanon, hwndOwner, uFlags);
|
|
if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
SHChangeNotifyHandleEvents();
|
|
}
|
|
}
|
|
else
|
|
hres = S_FALSE;
|
|
}
|
|
}
|
|
|
|
// Multiselection: ask the user first
|
|
else
|
|
{
|
|
if (IsFlagClear(uFlags, SF_NOCONFIRM))
|
|
id = ConfirmSplit(hwndOwner, pszList, cFiles);
|
|
else
|
|
id = IDYES;
|
|
|
|
if (IDYES == id)
|
|
{
|
|
// Remove all the files from the engine database
|
|
LPCTSTR psz;
|
|
UINT i;
|
|
HRESULT hresT;
|
|
|
|
hres = S_FALSE; // assume success but nothing done
|
|
|
|
for (i = 0, psz = pszList; i < cFiles; i++)
|
|
{
|
|
// Get dragged file/folder name
|
|
//
|
|
BrfPathCanonicalize(psz, szCanon);
|
|
|
|
hresT = SplitPath(hbrf, szCanon, hwndOwner, uFlags);
|
|
if (S_OK == hresT)
|
|
hres = S_OK; // (Don't set back to FALSE once it is TRUE)
|
|
else if (FAILED(hresT))
|
|
{
|
|
hres = hresT;
|
|
break;
|
|
}
|
|
|
|
DataObj_NextFile(psz); // Set psz to next file in list
|
|
}
|
|
|
|
if (IsFlagClear(uFlags, SF_QUIET))
|
|
{
|
|
SHChangeNotifyHandleEvents(); // (Do this after the loop)
|
|
}
|
|
}
|
|
else
|
|
hres = S_FALSE;
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Change the recitem action and the two recnodes of
|
|
importance to the specified action.
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_ChangeRecItemAction(
|
|
PRECITEM pri,
|
|
LPCTSTR pszBrfPath,
|
|
LPCTSTR pszInsideDir, // Folder inside the briefcase
|
|
UINT riaction) // One of RAIA_* values to change to
|
|
{
|
|
HRESULT hres;
|
|
PRECNODE prnInside;
|
|
PRECNODE prnOutside;
|
|
|
|
// Determine which node is inside the briefcase and which one is
|
|
// outside.
|
|
//
|
|
hres = Sync_GetNodePair(pri, pszBrfPath, pszInsideDir, &prnInside, &prnOutside);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
ASSERT(prnInside);
|
|
ASSERT(prnOutside);
|
|
|
|
switch(riaction)
|
|
{
|
|
case RAIA_TOIN:
|
|
pri->dwUser = RIU_CHANGED;
|
|
pri->riaction = RIA_COPY;
|
|
prnInside->rnaction = RNA_COPY_TO_ME;
|
|
prnOutside->rnaction = RNA_COPY_FROM_ME;
|
|
break;
|
|
|
|
case RAIA_TOOUT:
|
|
pri->dwUser = RIU_CHANGED;
|
|
pri->riaction = RIA_COPY;
|
|
prnInside->rnaction = RNA_COPY_FROM_ME;
|
|
prnOutside->rnaction = RNA_COPY_TO_ME;
|
|
break;
|
|
|
|
case RAIA_SKIP:
|
|
pri->dwUser = RIU_SKIP;
|
|
break;
|
|
|
|
case RAIA_MERGE:
|
|
pri->dwUser = RIU_CHANGED;
|
|
pri->riaction = RIA_MERGE;
|
|
prnInside->rnaction = RNA_MERGE_ME;
|
|
prnOutside->rnaction = RNA_MERGE_ME;
|
|
break;
|
|
|
|
#ifdef NEW_REC
|
|
case RAIA_DONTDELETE:
|
|
if (RNA_DELETE_ME == prnInside->rnaction)
|
|
{
|
|
pri->dwUser = RIU_CHANGED;
|
|
pri->riaction = RIA_NOTHING;
|
|
prnInside->rnaction = RNA_NOTHING;
|
|
}
|
|
else if (RNA_DELETE_ME == prnOutside->rnaction)
|
|
{
|
|
pri->dwUser = RIU_CHANGED;
|
|
pri->riaction = RIA_NOTHING;
|
|
prnOutside->rnaction = RNA_NOTHING;
|
|
}
|
|
break;
|
|
|
|
case RAIA_DELETEIN:
|
|
pri->dwUser = RIU_CHANGED;
|
|
pri->riaction = RIA_DELETE;
|
|
prnInside->rnaction = RNA_DELETE_ME;
|
|
prnOutside->rnaction = RNA_NOTHING;
|
|
break;
|
|
|
|
case RAIA_DELETEOUT:
|
|
pri->dwUser = RIU_CHANGED;
|
|
pri->riaction = RIA_DELETE;
|
|
prnInside->rnaction = RNA_NOTHING;
|
|
prnOutside->rnaction = RNA_DELETE_ME;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
// (The other values don't make sense here)
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////// PRIVATE FUNCTIONS
|
|
|
|
|
|
#ifdef DEBUG
|
|
/*----------------------------------------------------------
|
|
Purpose: Dumps the contents of the given twin structure to
|
|
to debug out
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_FnDump(
|
|
LPVOID lpvBuf,
|
|
UINT cbBuf)
|
|
{
|
|
int bDump;
|
|
|
|
#define szDumpTwin TEXT("Dump TWIN: ")
|
|
#define szDumpSp TEXT(" ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_CREATETWIN);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (!bDump)
|
|
return ;
|
|
|
|
if (cbBuf == sizeof(NEWOBJECTTWIN))
|
|
{
|
|
PNEWOBJECTTWIN lpnot = (PNEWOBJECTTWIN)lpvBuf;
|
|
|
|
TRACE_MSG(TF_ALWAYS, TEXT("%s.Folder1 = {%s}\r\n%s.Folder2 = {%s}\r\n%s.Name = {%s}\r\n"),
|
|
(LPTSTR)szDumpTwin, lpnot->pcszFolder1,
|
|
(LPTSTR)szDumpSp, lpnot->pcszFolder2,
|
|
(LPTSTR)szDumpSp, lpnot->pcszName);
|
|
}
|
|
else if (cbBuf == sizeof(NEWFOLDERTWIN))
|
|
{
|
|
PNEWFOLDERTWIN lpnft = (PNEWFOLDERTWIN)lpvBuf;
|
|
|
|
TRACE_MSG(TF_ALWAYS, TEXT("%s.Folder1 = {%s}\r\n%s.Folder2 = {%s}\r\n%s.Name = {%s}\r\n%s.dwFlags = 0x%04lx\r\n"),
|
|
(LPTSTR)szDumpTwin, lpnft->pcszFolder1,
|
|
(LPTSTR)szDumpSp, lpnft->pcszFolder2,
|
|
(LPTSTR)szDumpSp, lpnft->pcszName,
|
|
(LPTSTR)szDumpSp, (DWORD)lpnft->dwFlags);
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Return English form of RIA_ flags
|
|
Returns:
|
|
Cond: --
|
|
*/
|
|
LPTSTR PRIVATE LpszFromItemAction(
|
|
ULONG riaction)
|
|
{
|
|
switch (riaction)
|
|
{
|
|
DEBUG_CASE_STRING( RIA_NOTHING );
|
|
DEBUG_CASE_STRING( RIA_COPY );
|
|
DEBUG_CASE_STRING( RIA_MERGE );
|
|
DEBUG_CASE_STRING( RIA_BROKEN_MERGE );
|
|
|
|
#ifdef NEW_REC
|
|
DEBUG_CASE_STRING( RIA_DELETE );
|
|
#endif
|
|
|
|
default: return TEXT("RIA unknown");
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Return English form of RNA_ flags
|
|
Returns:
|
|
Cond: --
|
|
*/
|
|
LPTSTR PRIVATE LpszFromNodeAction(
|
|
ULONG rnaction)
|
|
{
|
|
switch (rnaction)
|
|
{
|
|
DEBUG_CASE_STRING( RNA_NOTHING );
|
|
DEBUG_CASE_STRING( RNA_COPY_TO_ME );
|
|
DEBUG_CASE_STRING( RNA_COPY_FROM_ME );
|
|
DEBUG_CASE_STRING( RNA_MERGE_ME );
|
|
|
|
#ifdef NEW_REC
|
|
DEBUG_CASE_STRING( RNA_DELETE_ME );
|
|
#endif
|
|
|
|
default: return TEXT("RNA unknown");
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Return English form of RNS_ flags
|
|
Returns:
|
|
Cond: --
|
|
*/
|
|
LPTSTR PRIVATE LpszFromNodeState(
|
|
ULONG rnstate)
|
|
{
|
|
switch (rnstate)
|
|
{
|
|
#ifdef NEW_REC
|
|
DEBUG_CASE_STRING( RNS_NEVER_RECONCILED );
|
|
#endif
|
|
|
|
DEBUG_CASE_STRING( RNS_UNAVAILABLE );
|
|
DEBUG_CASE_STRING( RNS_DOES_NOT_EXIST );
|
|
DEBUG_CASE_STRING( RNS_DELETED );
|
|
DEBUG_CASE_STRING( RNS_NOT_RECONCILED );
|
|
DEBUG_CASE_STRING( RNS_UP_TO_DATE );
|
|
DEBUG_CASE_STRING( RNS_CHANGED );
|
|
|
|
default: return TEXT("RNS unknown");
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Dump the RECNODE
|
|
Returns:
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_DumpRecNode(
|
|
TWINRESULT tr,
|
|
PRECNODE lprn)
|
|
{
|
|
BOOL bDump;
|
|
TCHAR szBuf[MAXMSGLEN];
|
|
|
|
#define szDumpLabel TEXT("\tDump RECNODE: ")
|
|
#define szDumpMargin TEXT("\t ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_RECNODE);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (!bDump || lprn == NULL || tr == TR_OUT_OF_MEMORY || tr == TR_INVALID_PARAMETER)
|
|
return ;
|
|
|
|
wsprintf(szBuf, TEXT("%s.Folder = {%s}\r\n"), (LPTSTR)szDumpLabel, lprn->pcszFolder);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.hObjectTwin = %lx\r\n"), (LPTSTR)szDumpMargin, lprn->hObjectTwin);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.rnstate = %s\r\n"), (LPTSTR)szDumpMargin, LpszFromNodeState(lprn->rnstate));
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.rnaction = %s\r\n"), (LPTSTR)szDumpMargin, LpszFromNodeAction(lprn->rnaction));
|
|
OutputDebugString(szBuf);
|
|
OutputDebugString(TEXT("\r\n"));
|
|
|
|
#undef szDumpLabel
|
|
#undef szDumpMargin
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Dump the RECITEM
|
|
Returns:
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_DumpRecItem(
|
|
TWINRESULT tr,
|
|
PRECITEM lpri,
|
|
LPCTSTR pszMsg)
|
|
{
|
|
BOOL bDump;
|
|
PRECNODE lprn;
|
|
TCHAR szBuf[MAXMSGLEN];
|
|
|
|
#define szDumpLabel TEXT("Dump RECITEM: ")
|
|
#define szDumpMargin TEXT(" ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_RECITEM);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (!bDump || lpri == NULL || tr == TR_OUT_OF_MEMORY || tr == TR_INVALID_PARAMETER)
|
|
return ;
|
|
|
|
if (pszMsg)
|
|
TRACE_MSG(TF_ALWAYS, pszMsg);
|
|
|
|
wsprintf(szBuf, TEXT("tr = %s\r\n"), (LPTSTR)SzFromTR(tr));
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.Name = {%s}\r\n"), (LPTSTR)szDumpLabel, lpri->pcszName);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.hTwinFamily = %lx\r\n"), (LPTSTR)szDumpMargin, lpri->hTwinFamily);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.ulcNodes = %lu\r\n"), (LPTSTR)szDumpMargin, lpri->ulcNodes);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.riaction = %s\r\n"), (LPTSTR)szDumpMargin, LpszFromItemAction(lpri->riaction));
|
|
OutputDebugString(szBuf);
|
|
|
|
lprn = lpri->prnFirst;
|
|
while (lprn)
|
|
{
|
|
Sync_DumpRecNode(tr, lprn);
|
|
lprn = lprn->prnNext;
|
|
}
|
|
|
|
#undef szDumpLabel
|
|
#undef szDumpMargin
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Dump the RECLIST
|
|
Returns:
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_DumpRecList(
|
|
TWINRESULT tr,
|
|
PRECLIST lprl,
|
|
LPCTSTR pszMsg)
|
|
{
|
|
BOOL bDump;
|
|
PRECITEM lpri;
|
|
TCHAR szBuf[MAXMSGLEN];
|
|
|
|
#define szDumpLabel TEXT("Dump RECLIST: ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_RECLIST);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (!bDump)
|
|
return ;
|
|
|
|
if (pszMsg)
|
|
TRACE_MSG(TF_ALWAYS, pszMsg);
|
|
|
|
// Note we only dump on TR_SUCCESS
|
|
//
|
|
wsprintf(szBuf, TEXT("tr = %s\r\n"), (LPTSTR)SzFromTR(tr));
|
|
OutputDebugString(szBuf);
|
|
|
|
if (lprl == NULL || tr == TR_OUT_OF_MEMORY || tr == TR_INVALID_PARAMETER)
|
|
return ;
|
|
|
|
wsprintf(szBuf, TEXT("%s.ulcItems = %lu\r\n"), (LPTSTR)szDumpLabel, lprl->ulcItems);
|
|
OutputDebugString(szBuf);
|
|
|
|
lpri = lprl->priFirst;
|
|
while (lpri)
|
|
{
|
|
Sync_DumpRecItem(TR_SUCCESS, lpri, NULL);
|
|
lpri = lpri->priNext;
|
|
}
|
|
|
|
#undef szDumpLabel
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Dump the FOLDERTWIN
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_DumpFolderTwin(
|
|
PCFOLDERTWIN pft)
|
|
{
|
|
BOOL bDump;
|
|
TCHAR szBuf[MAXMSGLEN];
|
|
|
|
#define szDumpLabel TEXT("Dump FOLDERTWIN: ")
|
|
#define szDumpMargin TEXT(" ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_FOLDERTWIN);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (!bDump || pft == NULL)
|
|
return ;
|
|
|
|
wsprintf(szBuf, TEXT("%s.Name = {%s}\r\n"), (LPTSTR)szDumpLabel, pft->pcszName);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.pszSrcFolder = {%s}\r\n"), (LPTSTR)szDumpMargin, pft->pcszSrcFolder);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.pszOtherFolder = {%s}\r\n"), (LPTSTR)szDumpMargin, pft->pcszOtherFolder);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.dwFlags = %lx\r\n"), (LPTSTR)szDumpMargin, pft->dwFlags);
|
|
OutputDebugString(szBuf);
|
|
|
|
wsprintf(szBuf, TEXT("%s.dwUser = %lx\r\n"), (LPTSTR)szDumpMargin, pft->dwUser);
|
|
OutputDebugString(szBuf);
|
|
|
|
#undef szDumpLabel
|
|
#undef szDumpMargin
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Dump the FOLDERTWINLIST
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC Sync_DumpFolderTwinList(
|
|
PFOLDERTWINLIST pftl,
|
|
LPCTSTR pszMsg)
|
|
{
|
|
BOOL bDump;
|
|
PCFOLDERTWIN pft;
|
|
TCHAR szBuf[MAXMSGLEN];
|
|
|
|
#define szDumpLabel TEXT("Dump FOLDERTWINLIST: ")
|
|
|
|
ENTEREXCLUSIVE();
|
|
{
|
|
bDump = IsFlagSet(g_uDumpFlags, DF_FOLDERTWIN);
|
|
}
|
|
LEAVEEXCLUSIVE();
|
|
|
|
if (!bDump)
|
|
return ;
|
|
|
|
if (pszMsg)
|
|
TRACE_MSG(TF_ALWAYS, pszMsg);
|
|
|
|
if (pftl == NULL)
|
|
return ;
|
|
|
|
wsprintf(szBuf, TEXT("%s.ulcItems = %lu\r\n"), (LPTSTR)szDumpLabel, pftl->ulcItems);
|
|
OutputDebugString(szBuf);
|
|
|
|
for (pft = pftl->pcftFirst; pft; pft = pft->pcftNext)
|
|
{
|
|
Sync_DumpFolderTwin(pft);
|
|
}
|
|
|
|
#undef szDumpLabel
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|