//--------------------------------------------------------------------------- // // 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