|
|
//---------------------------------------------------------------------------
//
// 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) { wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.pszFolder = {%s}\r\n"), (LPTSTR)szDumpLabel, pchside->pszFolder); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.dwFlags = 0x%lx\r\n"), (LPTSTR)szDumpMargin, pchside->dwFlags); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(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;
wnsprintf(szBuf, ARRAYSIZE(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); ASSERT(pchside); 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); ASSERT(pchside); 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); ASSERT(pchside); 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); ASSERT(pchside); #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 cchBuf, LPTSTR pszPathBuf, int cchPathBuf) // Must be MAX_PATH
{ 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;
lstrcpyn(pszPathBuf, pitem->siInside.pszDir, cchPathBuf); 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, cchBuf); 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, ARRAYSIZE(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, ARRAYSIZE(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];
lstrcpyn(szDir, pszPath, ARRAYSIZE(szDir)); 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"
//
lstrcpyn(szDir, lpcszPath, ARRAYSIZE(szDir)); 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, ARRAYSIZE(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, ARRAYSIZE(sz)));
hres = S_FALSE; } else { hres = S_FALSE; } } } else { // No; it is a file
HOBJECTTWIN hot; ULONG ulc;
lstrcpyn(sz, pszPath, ARRAYSIZE(sz)); 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, ARRAYSIZE(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, ARRAYSIZE(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, ARRAYSIZE(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, ARRAYSIZE(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 ;
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.Folder = {%s}\r\n"), (LPTSTR)szDumpLabel, lprn->pcszFolder); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.hObjectTwin = %lx\r\n"), (LPTSTR)szDumpMargin, lprn->hObjectTwin); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.rnstate = %s\r\n"), (LPTSTR)szDumpMargin, LpszFromNodeState(lprn->rnstate)); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(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);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("tr = %s\r\n"), (LPTSTR)SzFromTR(tr)); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.Name = {%s}\r\n"), (LPTSTR)szDumpLabel, lpri->pcszName); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.hTwinFamily = %lx\r\n"), (LPTSTR)szDumpMargin, lpri->hTwinFamily); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.ulcNodes = %lu\r\n"), (LPTSTR)szDumpMargin, lpri->ulcNodes); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(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
//
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("tr = %s\r\n"), (LPTSTR)SzFromTR(tr)); OutputDebugString(szBuf);
if (lprl == NULL || tr == TR_OUT_OF_MEMORY || tr == TR_INVALID_PARAMETER) return ;
wnsprintf(szBuf, ARRAYSIZE(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 ;
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.Name = {%s}\r\n"), (LPTSTR)szDumpLabel, pft->pcszName); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.pszSrcFolder = {%s}\r\n"), (LPTSTR)szDumpMargin, pft->pcszSrcFolder); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.pszOtherFolder = {%s}\r\n"), (LPTSTR)szDumpMargin, pft->pcszOtherFolder); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.dwFlags = %lx\r\n"), (LPTSTR)szDumpMargin, pft->dwFlags); OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(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 ;
wnsprintf(szBuf, ARRAYSIZE(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
|