windows-nt/Source/XPSP1/NT/shell/ext/cscui/update.cpp
2020-09-26 16:20:57 +08:00

4405 lines
144 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1999
//
// File: update.cpp
//
// Authors;
// Jeff Saathoff (jeffreys)
//
// Notes;
// SyncMgr integration
//--------------------------------------------------------------------------
#include "pch.h"
#include "msgbox.h" // CscWin32Message
#include "folder.h"
#include <openfile.h> // OpenOfflineFile
#include "cscst.h" // PostToSystray
#include "uihooks.h" // Self-host notifications
#include "fopendlg.h" // OpenFilesWarningDialog
#include "nopin.h"
#include "statdlg.h" // ReconnectServers
#include "security.h"
#include "strings.h"
#define RAS_CONNECT_DELAY (10 * 1000)
// Maximum length of username
#define MAX_USERNAME_CHARS 64
// SYNCTHREADDATA.dwSyncStatus flags
#define SDS_SYNC_OUT 0x00000001 // CSCMergeShare
#define SDS_SYNC_IN_QUICK 0x00000002 // CSCFillSparseFiles(FALSE)
#define SDS_SYNC_IN_FULL 0x00000004 // CSCFillSparseFiles(TRUE)
#define SDS_SYNC_FORCE_INWARD 0x00000008
#define SDS_SYNC_RAS_CONNECTED 0x00000010
#define SDS_SYNC_RESTART_MERGE 0x00000020
#define SDS_SYNC_DELETE_DELETE 0x00000040
#define SDS_SYNC_DELETE_RESTORE 0x00000080
#define SDS_SYNC_AUTOCACHE 0x00000100
#define SDS_SYNC_CONFLICT_KEEPLOCAL 0x00000200
#define SDS_SYNC_CONFLICT_KEEPNET 0x00000400
#define SDS_SYNC_CONFLICT_KEEPBOTH 0x00000800
#define SDS_SYNC_STARTED 0x00010000
#define SDS_SYNC_ERROR 0x00020000
#define SDS_SYNC_CANCELLED 0x00040000
#define SDS_SYNC_FILE_SKIPPED 0x00080000
#define SDS_SYNC_DELETE_CONFLICT_MASK (SDS_SYNC_DELETE_DELETE | SDS_SYNC_DELETE_RESTORE)
#define SDS_SYNC_FILE_CONFLICT_MASK (SDS_SYNC_CONFLICT_KEEPLOCAL | SDS_SYNC_CONFLICT_KEEPNET | SDS_SYNC_CONFLICT_KEEPBOTH)
// Sync Flags used internally by CCscUpdate
#define CSC_SYNC_OUT 0x00000001L
#define CSC_SYNC_IN_QUICK 0x00000002L
#define CSC_SYNC_IN_FULL 0x00000004L
#define CSC_SYNC_SETTINGS 0x00000008L
#define CSC_SYNC_MAYBOTHERUSER 0x00000010L
#define CSC_SYNC_NOTIFY_SYSTRAY 0x00000020L
#define CSC_SYNC_LOGOFF 0x00000040L
#define CSC_SYNC_LOGON 0x00000080L
#define CSC_SYNC_IDLE 0x00000100L
#define CSC_SYNC_NONET 0x00000200L
#define CSC_SYNC_PINFILES 0x00000400L
#define CSC_SYNC_PIN_RECURSE 0x00000800L
#define CSC_SYNC_OFWARNINGDONE 0x00001000L
#define CSC_SYNC_CANCELLED 0x00002000L
#define CSC_SYNC_SHOWUI_ALWAYS 0x00004000L
#define CSC_SYNC_IGNORE_ACCESS 0x00008000L
#define CSC_SYNC_EFS_PIN_NONE 0x00010000L
#define CSC_SYNC_EFS_PIN_ALL 0x00020000L
#define CSC_SYNC_RECONNECT 0x00040000L
#define CSC_LOCALLY_MODIFIED (FLAG_CSC_COPY_STATUS_DATA_LOCALLY_MODIFIED \
| FLAG_CSC_COPY_STATUS_LOCALLY_DELETED \
| FLAG_CSC_COPY_STATUS_LOCALLY_CREATED)
HICON g_hCscIcon = NULL;
// Used for marshalling data into the SyncMgr process
typedef struct _CSC_UPDATE_DATA
{
DWORD dwUpdateFlags;
DWORD dwFileBufferOffset;
} CSC_UPDATE_DATA, *PCSC_UPDATE_DATA;
LPTSTR GetErrorText(DWORD dwErr)
{
UINT idString = (UINT)-1;
LPTSTR pszError = NULL;
switch (dwErr)
{
case ERROR_INVALID_NAME:
// "Files of this type cannot be made available offline."
idString = IDS_CACHING_DISALLOWED;
break;
}
if ((UINT)-1 != idString)
{
LoadStringAlloc(&pszError, g_hInstance, idString);
}
else if (NOERROR != dwErr)
{
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwErr,
0,
(LPTSTR)&pszError,
1,
NULL);
}
return pszError;
}
//*************************************************************
//
// CscUpdateCache
//
// Purpose: Invoke SyncMgr to update the CSC cache
//
// Parameters: pNamelist - list of files passed to the CSC SyncMgr handler
//
//
// Return: HRESULT
//
//*************************************************************
HRESULT
CscUpdateCache(DWORD dwUpdateFlags, CscFilenameList *pfnl)
{
HRESULT hr;
HRESULT hrComInit = E_FAIL;
ISyncMgrSynchronizeInvoke *pSyncInvoke = NULL;
DWORD dwSyncMgrFlags = 0;
ULONG cbDataLength = sizeof(CSC_UPDATE_DATA);
PCSC_UPDATE_DATA pUpdateData = NULL;
PCSC_NAMELIST_HDR pNamelist = NULL;
TraceEnter(TRACE_UPDATE, "CscUpdateCache");
hrComInit = CoInitialize(NULL);
hr = CoCreateInstance(CLSID_SyncMgr,
NULL,
CLSCTX_SERVER,
IID_ISyncMgrSynchronizeInvoke,
(LPVOID*)&pSyncInvoke);
FailGracefully(hr, "Unable to create SyncMgr object");
if (dwUpdateFlags & CSC_UPDATE_SELECTION)
{
if (NULL == pfnl || (0 == (CSC_UPDATE_SHOWUI_ALWAYS & dwUpdateFlags) && 0 == pfnl->GetShareCount()))
ExitGracefully(hr, E_INVALIDARG, "CSC_UPDATE_SELECTION with no selection");
pNamelist = pfnl->CreateListBuffer();
if (!pNamelist)
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create namelist buffer");
cbDataLength += pNamelist->cbSize;
}
//
// Alloc a buffer for the cookie data
//
pUpdateData = (PCSC_UPDATE_DATA)LocalAlloc(LPTR, cbDataLength);
if (!pUpdateData)
ExitGracefully(hr, E_OUTOFMEMORY, "LocalAlloc failed");
pUpdateData->dwUpdateFlags = dwUpdateFlags;
if (pNamelist)
{
pUpdateData->dwFileBufferOffset = sizeof(CSC_UPDATE_DATA);
CopyMemory(ByteOffset(pUpdateData, pUpdateData->dwFileBufferOffset),
pNamelist,
pNamelist->cbSize);
}
if (dwUpdateFlags & CSC_UPDATE_STARTNOW)
dwSyncMgrFlags |= SYNCMGRINVOKE_STARTSYNC;
//
// Start SyncMgr
//
hr = pSyncInvoke->UpdateItems(dwSyncMgrFlags,
CLSID_CscUpdateHandler,
cbDataLength,
(LPBYTE)pUpdateData);
exit_gracefully:
if (pNamelist)
CscFilenameList::FreeListBuffer(pNamelist);
if (pUpdateData)
LocalFree(pUpdateData);
DoRelease(pSyncInvoke);
if (SUCCEEDED(hrComInit))
CoUninitialize();
TraceLeaveResult(hr);
}
//*************************************************************
//
// GetNewVersionName
//
// Purpose: Create unique names for copies of a file
//
// Parameters: LPTSTR pszUNCPath - fully qualified UNC name of file
// LPTSTR pszShare - \\server\share that file lives on
// LPTSTR pszDrive - drive mapping to use for net operations
// LPTSTR *ppszNewName - filename for new version returned here (must free)
//
// Return: Win32 error code
//
//*************************************************************
DWORD
GetNewVersionName(LPCTSTR pszUNCPath,
LPCTSTR pszShare,
LPCTSTR pszDrive,
LPTSTR *ppszNewName)
{
DWORD dwErr = NOERROR;
LPTSTR pszDriveLetterPath = NULL;
LPTSTR pszPath = NULL;
LPTSTR pszFile = NULL;
LPTSTR pszExt = NULL;
LPTSTR pszWildCardName = NULL;
TCHAR szUserName[MAX_USERNAME_CHARS];
ULONG nLength;
ULONG nMaxVersion = 0;
ULONG cOlderVersions = 0;
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA fd;
LPTSTR pszT;
TraceEnter(TRACE_UPDATE, "GetNewVersionName");
TraceAssert(pszUNCPath != NULL);
TraceAssert(ppszNewName != NULL);
*ppszNewName = NULL;
// 1. Split the path into components.
// 2. Build wildcard name "X:\dir\foo (johndoe v*).txt"
// 3. Do a findfirst/findnext loop to get the min & max version #
// and count the number of old versions.
// 4. Increment the max version # and build the new filename as:
// "foo (johndoe v<max+1>).txt"
// Assume that the UNC name contains more than the share
TraceAssert(!StrCmpNI(pszUNCPath, pszShare, lstrlen(pszShare)));
TraceAssert(lstrlen(pszUNCPath) > lstrlen(pszShare));
// Copy the path (without \\server\share)
if (!LocalAllocString(&pszPath, pszUNCPath + lstrlen(pszShare)))
ExitGracefully(dwErr, ERROR_OUTOFMEMORY, "LocalAllocString failed");
// Find the file part of the name
pszT = PathFindFileName(pszPath);
if (!pszT)
ExitGracefully(dwErr, ERROR_INVALID_PARAMETER, "Incomplete path");
// Copy the filename
if (!LocalAllocString(&pszFile, pszT))
ExitGracefully(dwErr, ERROR_OUTOFMEMORY, "LocalAllocString failed");
// Look for the file extension
pszT = PathFindExtension(pszFile);
if (pszT)
{
// Copy the extension and truncate the file root at this point
LocalAllocString(&pszExt, pszT);
*pszT = TEXT('\0');
}
// Truncate the path
PathRemoveFileSpec(pszPath);
// Get the user name
nLength = ARRAYSIZE(szUserName);
if (!GetUserName(szUserName, &nLength))
LoadString(g_hInstance, IDS_UNKNOWN_USER, szUserName, ARRAYSIZE(szUserName));
// Build the wildcard path "X:\dir\foo (johndoe v*).txt"
nLength = FormatStringID(&pszWildCardName, g_hInstance, IDS_VERSION_FORMAT, pszFile, szUserName, c_szStar, pszExt);
if (!nLength)
ExitGracefully(dwErr, GetLastError(), "Unable to format string");
nLength += lstrlen(pszUNCPath) + lstrlen(szUserName);
pszDriveLetterPath = (LPTSTR)LocalAlloc(LPTR, MAX(nLength, ULONG(MAX_PATH)) * sizeof(TCHAR));
if (!pszDriveLetterPath)
ExitGracefully(dwErr, ERROR_OUTOFMEMORY, "LocalAlloc failed");
PathCombine(pszDriveLetterPath, pszDrive, pszPath);
PathAppend(pszDriveLetterPath, pszWildCardName);
nLength = (ULONG)(StrStr(pszWildCardName, c_szStar) - pszWildCardName); // remember where the '*' is
// Search for existing versions of the file with this username
hFind = FindFirstFile(pszDriveLetterPath, &fd);
if (hFind != INVALID_HANDLE_VALUE)
{
ULONG nVersion;
do
{
nVersion = StrToLong(&fd.cFileName[nLength]);
if (nVersion > nMaxVersion)
{
nMaxVersion = nVersion;
}
cOlderVersions++;
}
while (FindNextFile(hFind, &fd));
FindClose(hFind);
}
// Build the new file name to return to the caller.
// This one is version nMaxVersion+1.
ULongToString(nMaxVersion+1, pszDriveLetterPath, lstrlen(pszDriveLetterPath));
nLength = FormatStringID(ppszNewName, g_hInstance, IDS_VERSION_FORMAT, pszFile, szUserName, pszDriveLetterPath, pszExt);
if (!nLength)
ExitGracefully(dwErr, GetLastError(), "Unable to format string");
exit_gracefully:
LocalFreeString(&pszDriveLetterPath);
LocalFreeString(&pszPath);
LocalFreeString(&pszFile);
LocalFreeString(&pszExt);
LocalFreeString(&pszWildCardName);
if (NOERROR != dwErr)
{
LocalFreeString(ppszNewName);
}
TraceLeaveValue(dwErr);
}
//*************************************************************
//
// ConflictDlgCallback
//
// Purpose: Display local or remote file from conflict dialog
//
// Parameters: hWnd - conflict dialog handle (used as parent for UI)
// uMsg - one of RFCCM_*
// wParam - depends on uMsg (unused)
// lParam - pointer to context data (RFCDLGPARAM)
//
//
// Return: TRUE on success, FALSE otherwise
//
//*************************************************************
typedef struct _CONFLICT_DATA
{
LPCTSTR pszShare;
LPCTSTR pszDrive;
} CONFLICT_DATA;
BOOL
ConflictDlgCallback(HWND hWnd, UINT uMsg, WPARAM /*wParam*/, LPARAM lParam)
{
RFCDLGPARAM *pdlgParam = (RFCDLGPARAM*)lParam;
CONFLICT_DATA cd = {0};
LPTSTR pszTmpName = NULL;
ULONG cchShare = 0;
LPTSTR szFile;
DWORD dwErr = NOERROR;
TraceEnter(TRACE_UPDATE, "ConflictDlgCallback");
if (NULL == pdlgParam)
{
TraceAssert(FALSE);
TraceLeaveValue(FALSE);
}
szFile = (LPTSTR)LocalAlloc(LMEM_FIXED,
MAX(StringByteSize(pdlgParam->pszLocation)
+ StringByteSize(pdlgParam->pszFilename), MAX_PATH_BYTES));
if (!szFile)
TraceLeaveValue(FALSE);
if (pdlgParam->lCallerData)
cd = *(CONFLICT_DATA*)pdlgParam->lCallerData;
if (cd.pszShare)
cchShare = lstrlen(cd.pszShare);
switch (uMsg)
{
case RFCCM_VIEWLOCAL:
// Build UNC path and view what's in the cache
PathCombine(szFile, pdlgParam->pszLocation, pdlgParam->pszFilename);
dwErr = OpenOfflineFile(szFile);
break;
case RFCCM_VIEWNETWORK:
// Build drive letter (non-UNC) path and ShellExecute it
PathCombine(szFile, cd.pszDrive, pdlgParam->pszLocation + cchShare);
PathAppend(szFile, pdlgParam->pszFilename);
{
SHELLEXECUTEINFO si = {0};
si.cbSize = sizeof(si);
si.fMask = SEE_MASK_FLAG_NO_UI;
si.hwnd = hWnd;
si.lpFile = szFile;
si.nShow = SW_NORMAL;
Trace((TEXT("ShellExecuting \"%s\""), szFile));
if (!ShellExecuteEx(&si))
dwErr = GetLastError();
}
break;
}
if (NOERROR != dwErr)
CscWin32Message(hWnd, dwErr, CSCUI::SEV_ERROR);
LocalFree(szFile);
TraceLeaveValue(TRUE);
}
//*************************************************************
//
// ShowConflictDialog
//
// Purpose: Invoke the conflict resolution dialog
//
// Parameters: hWndParent - dialog parent window
// pszUNCPath - full UNC of file that conflicts
// pszNewName - filespec to use for new copy of file (e.g. "foo (johndoe v1).txt"
// pszShare - "\\server\share"
// pszDrive - "X:" drive mapping of remote connection
// pfdLocal - Information about local file
// pfdRemote - Information about remote file
//
//
// Return: HRESULT
//
//*************************************************************
typedef int (WINAPI *PFNSYNCMGRRESOLVECONFLICT)(HWND hWndParent, RFCDLGPARAM *pdlgParam);
TCHAR const c_szSyncMgrDll[] = TEXT("mobsync.dll");
#ifdef UNICODE
CHAR const c_szResolveConflict[] = "SyncMgrResolveConflictW";
#else
CHAR const c_szResolveConflict[] = "SyncMgrResolveConflictA";
#endif
BOOL FileHasAssociation(LPCTSTR pszFile)
{
HRESULT hr = HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
if (pszFile)
{
pszFile = PathFindExtension(pszFile);
if (pszFile && *pszFile)
{
IQueryAssociations *pAssoc = NULL;
hr = AssocCreate(CLSID_QueryAssociations,
IID_IQueryAssociations,
(LPVOID*)&pAssoc);
if (SUCCEEDED(hr))
{
hr = pAssoc->Init(ASSOCF_IGNOREBASECLASS, pszFile, NULL, NULL);
pAssoc->Release();
}
}
}
return SUCCEEDED(hr);
}
int
ShowConflictDialog(HWND hWndParent,
LPCTSTR pszUNCPath,
LPCTSTR pszNewName,
LPCTSTR pszShare,
LPCTSTR pszDrive,
LPWIN32_FIND_DATA pfdLocal,
LPWIN32_FIND_DATA pfdRemote)
{
int nResult = 0;
TCHAR szUser[MAX_USERNAME_CHARS];
LPTSTR pszPath = NULL;
LPTSTR pszFile = NULL;
TCHAR szRemoteDate[MAX_PATH];
TCHAR szLocalDate[MAX_PATH];
ULONG nLength;
SYSTEMTIME st;
RFCDLGPARAM dp = {0};
CONFLICT_DATA cd;
BOOL bLocalIsDir = FALSE;
BOOL bRemoteIsDir = FALSE;
static PFNSYNCMGRRESOLVECONFLICT pfnResolveConflict = NULL;
TraceEnter(TRACE_UPDATE, "ShowConflictDialog");
TraceAssert(pszUNCPath);
if (NULL == pfnResolveConflict)
{
// The CSC Update handler is loaded by SyncMgr, so assume the SyncMgr
// dll is already loaded. We don't want to link to the LIB to keep
// SyncMgr from loading every time our context menu or icon overlay
// handler is loaded (for example).
HMODULE hSyncMgrDll = GetModuleHandle(c_szSyncMgrDll);
if (NULL != hSyncMgrDll)
pfnResolveConflict = (PFNSYNCMGRRESOLVECONFLICT)GetProcAddress(hSyncMgrDll,
c_szResolveConflict);
if (NULL == pfnResolveConflict)
return 0;
}
TraceAssert(NULL != pfnResolveConflict);
szUser[0] = TEXT('\0');
nLength = ARRAYSIZE(szUser);
GetUserName(szUser, &nLength);
szRemoteDate[0] = TEXT('\0');
if (NULL != pfdRemote)
{
DWORD dwFlags = FDTF_DEFAULT;
SHFormatDateTime(&pfdRemote->ftLastWriteTime, &dwFlags, szRemoteDate, ARRAYSIZE(szRemoteDate));
if (pfdRemote->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
bRemoteIsDir = TRUE;
}
szLocalDate[0] = TEXT('\0');
if (NULL != pfdLocal)
{
DWORD dwFlags = FDTF_DEFAULT;
SHFormatDateTime(&pfdLocal->ftLastWriteTime, &dwFlags, szLocalDate, ARRAYSIZE(szLocalDate));
if (pfdLocal->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
bLocalIsDir = TRUE;
}
if (!LocalAllocString(&pszPath, pszUNCPath))
ExitGracefully(nResult, 0, "LocalAllocString failed");
pszFile = PathFindFileName(pszUNCPath);
PathRemoveFileSpec(pszPath);
dp.dwFlags = RFCF_APPLY_ALL;
dp.pszFilename = pszFile;
dp.pszLocation = pszPath;
dp.pszNewName = pszNewName;
dp.pszNetworkModifiedBy = NULL;
dp.pszLocalModifiedBy = szUser;
dp.pszNetworkModifiedOn = szRemoteDate;
dp.pszLocalModifiedOn = szLocalDate;
dp.pfnCallBack = NULL;
dp.lCallerData = 0;
// Only turn on the View buttons (set a callback) if we're
// dealing with files that have associations.
if (!(bLocalIsDir || bRemoteIsDir) && FileHasAssociation(pszFile))
{
// Save both the share name and drive letter for building paths to view files
cd.pszShare = pszShare;
cd.pszDrive = pszDrive;
dp.pfnCallBack = ConflictDlgCallback;
dp.lCallerData = (LPARAM)&cd;
}
nResult = (*pfnResolveConflict)(hWndParent, &dp);
exit_gracefully:
LocalFreeString(&pszPath);
// No need to free pszFile
TraceLeaveValue(nResult);
}
///////////////////////////////////////////////////////////////////////////////
// //
// SyncMgr integration implementation //
// //
///////////////////////////////////////////////////////////////////////////////
CCscUpdate::CCscUpdate() : m_cRef(1), m_ShareLog(HKEY_CURRENT_USER, c_szCSCShareKey),
m_pSyncMgrCB(NULL), m_hSyncThreads(NULL),
m_pFileList(NULL), m_hSyncItems(NULL), m_hwndDlgParent(NULL),
m_hSyncInProgMutex(NULL), m_pConflictPinList(NULL),
m_pSilentFolderList(NULL), m_pSpecialFolderList(NULL),
m_bCacheIsEncrypted(IsCacheEncrypted(NULL))
{
DllAddRef();
InitializeCriticalSection(&m_csThreadList);
if (!g_hCscIcon)
g_hCscIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_CSCUI_ICON));
m_hSyncMutex = CreateMutex(NULL, FALSE, c_szSyncMutex);
}
CCscUpdate::~CCscUpdate()
{
TraceEnter(TRACE_UPDATE, "CCscUpdate::~CCscUpdate");
SyncCompleted();
TraceAssert(NULL == m_hSyncInProgMutex);
// We should never get here while a sync thread is still running
TraceAssert(NULL == m_hSyncThreads || 0 == DPA_GetPtrCount(m_hSyncThreads));
if (NULL != m_hSyncThreads)
DPA_Destroy(m_hSyncThreads);
DeleteCriticalSection(&m_csThreadList);
if (NULL != m_hSyncItems)
DSA_Destroy(m_hSyncItems);
DoRelease(m_pSyncMgrCB);
delete m_pFileList;
delete m_pConflictPinList;
delete m_pSilentFolderList;
delete m_pSpecialFolderList;
if (NULL != m_hSyncMutex)
CloseHandle(m_hSyncMutex);
DllRelease();
TraceLeaveVoid();
}
HRESULT WINAPI
CCscUpdate::CreateInstance(REFIID riid, LPVOID *ppv)
{
HRESULT hr;
CCscUpdate *pThis;
TraceEnter(TRACE_UPDATE, "CCscUpdate::CreateInstance");
TraceAssert(IsCSCEnabled());
pThis = new CCscUpdate;
if (pThis)
{
hr = pThis->QueryInterface(riid, ppv);
pThis->Release();
}
else
hr = E_OUTOFMEMORY;
TraceLeaveResult(hr);
}
///////////////////////////////////////////////////////////////////////////////
// //
// SyncMgr integration implementation (IUnknown) //
// //
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CCscUpdate::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CCscUpdate, ISyncMgrSynchronize),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CCscUpdate::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CCscUpdate::Release()
{
if (InterlockedDecrement(&m_cRef))
return m_cRef;
delete this;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// //
// Sync Manager integration implementation (ISyncMgrSynchronize) //
// //
///////////////////////////////////////////////////////////////////////////////
STDMETHODIMP
CCscUpdate::Initialize(DWORD /*dwReserved*/,
DWORD dwSyncFlags,
DWORD cbCookie,
const BYTE *pCookie)
{
HRESULT hr = S_OK;
HKEY hkCSC;
BOOL bNoNet = TRUE;
TraceEnter(TRACE_UPDATE, "CCscUpdate::Initialize");
TraceAssert(IsCSCEnabled());
if (!(SYNCMGRFLAG_SETTINGS & dwSyncFlags) && ::IsSyncInProgress())
{
//
// We need to guard against running multiple syncs at the
// same time. User notification in the UI is handled where
// the UI code calls CscUpdate(). This is so that the UI
// message contains the proper context with respect to what
// the user is doing.
//
TraceLeaveResult(E_FAIL);
}
m_dwSyncFlags = 0;
delete m_pFileList;
m_pFileList = NULL;
delete m_pConflictPinList;
m_pConflictPinList = NULL;
// We used to get the tray status to check for NoNet, but
// there's a timing problem at logon (the tray window may not
// be created yet). So ask RDR instead. If this call fails,
// then RDR must be dead, so bNoNet defaults to TRUE.
CSCIsServerOffline(NULL, &bNoNet);
switch (dwSyncFlags & SYNCMGRFLAG_EVENTMASK)
{
case SYNCMGRFLAG_CONNECT: // Logon
if (bNoNet)
ExitGracefully(hr, E_FAIL, "No Logon sync when no net");
m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_LOGON | CSC_SYNC_NOTIFY_SYSTRAY; // | CSC_SYNC_RECONNECT;
if (CConfig::eSyncFull == CConfig::GetSingleton().SyncAtLogon())
{
m_dwSyncFlags |= CSC_SYNC_IN_FULL;
}
break;
case SYNCMGRFLAG_PENDINGDISCONNECT: // Logoff
if (bNoNet)
ExitGracefully(hr, E_FAIL, "No Logoff sync when no net");
m_dwSyncFlags = CSC_SYNC_LOGOFF;
if (CConfig::eSyncFull == CConfig::GetSingleton().SyncAtLogoff())
m_dwSyncFlags |= CSC_SYNC_OUT | CSC_SYNC_IN_FULL;
else
m_dwSyncFlags |= CSC_SYNC_IN_QUICK;
break;
case SYNCMGRFLAG_INVOKE: // CscUpdateCache
if (pCookie != NULL && cbCookie > 0)
{
PCSC_UPDATE_DATA pUpdateData = (PCSC_UPDATE_DATA)pCookie;
TraceAssert(cbCookie >= sizeof(CSC_UPDATE_DATA));
DWORD dwUpdateFlags = pUpdateData->dwUpdateFlags;
if (dwUpdateFlags & CSC_UPDATE_SELECTION)
{
TraceAssert(cbCookie > sizeof(CSC_UPDATE_DATA));
// Create the filelist from the selection provided
m_pFileList = new CscFilenameList((PCSC_NAMELIST_HDR)ByteOffset(pUpdateData, pUpdateData->dwFileBufferOffset),
true);
if (!m_pFileList)
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create CscFilenameList object");
if (!m_pFileList->IsValid())
ExitGracefully(hr, E_FAIL, "Unable to initialize CscFilenameList object");
if (CSC_UPDATE_SHOWUI_ALWAYS & dwUpdateFlags)
{
m_dwSyncFlags |= CSC_SYNC_SHOWUI_ALWAYS;
}
else if (0 == m_pFileList->GetShareCount())
ExitGracefully(hr, E_UNEXPECTED, "CSC_UPDATE_SELECTION with no selection");
}
if (dwUpdateFlags & CSC_UPDATE_RECONNECT)
{
m_dwSyncFlags |= CSC_SYNC_RECONNECT;
}
if (dwUpdateFlags & CSC_UPDATE_UNATTENDED)
{
dwSyncFlags &= ~SYNCMGRFLAG_MAYBOTHERUSER;
}
if (dwUpdateFlags & CSC_UPDATE_NOTIFY_DONE)
{
//
// Caller of CscUpdateCache want's systray notification
// when sync is complete.
//
m_dwSyncFlags |= CSC_SYNC_NOTIFY_SYSTRAY;
}
if (dwUpdateFlags & CSC_UPDATE_FILL_ALL)
m_dwSyncFlags |= CSC_SYNC_IN_FULL;
else if (dwUpdateFlags & CSC_UPDATE_FILL_QUICK)
m_dwSyncFlags |= CSC_SYNC_IN_QUICK;
if (dwUpdateFlags & CSC_UPDATE_REINT)
m_dwSyncFlags |= CSC_SYNC_OUT;
if (dwUpdateFlags & CSC_UPDATE_PIN_RECURSE)
m_dwSyncFlags |= CSC_SYNC_PINFILES | CSC_SYNC_PIN_RECURSE | CSC_SYNC_IN_QUICK;
else if (dwUpdateFlags & CSC_UPDATE_PINFILES)
m_dwSyncFlags |= CSC_SYNC_PINFILES | CSC_SYNC_IN_QUICK;
if (dwUpdateFlags & CSC_UPDATE_IGNORE_ACCESS)
m_dwSyncFlags |= CSC_SYNC_IGNORE_ACCESS;
}
break;
case SYNCMGRFLAG_IDLE: // Auto-sync at idle time
if (bNoNet)
ExitGracefully(hr, E_FAIL, "No idle sync when no net");
m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_IN_QUICK | CSC_SYNC_IDLE | CSC_SYNC_NOTIFY_SYSTRAY;
break;
case SYNCMGRFLAG_MANUAL: // Run "mobsync.exe"
m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_IN_FULL | CSC_SYNC_NOTIFY_SYSTRAY | CSC_SYNC_RECONNECT;
break;
case SYNCMGRFLAG_SCHEDULED: // User scheduled sync
m_dwSyncFlags = CSC_SYNC_OUT | CSC_SYNC_IN_FULL | CSC_SYNC_NOTIFY_SYSTRAY;
break;
}
if (!(m_dwSyncFlags & CSC_SYNC_PINFILES))
m_dwSyncFlags |= CSC_SYNC_EFS_PIN_NONE; // skip EFS if not pinning
if (dwSyncFlags & SYNCMGRFLAG_SETTINGS)
m_dwSyncFlags |= CSC_SYNC_SETTINGS;
if (!m_dwSyncFlags)
ExitGracefully(hr, E_UNEXPECTED, "Nothing to do");
if (dwSyncFlags & SYNCMGRFLAG_MAYBOTHERUSER)
m_dwSyncFlags |= CSC_SYNC_MAYBOTHERUSER;
if (bNoNet)
m_dwSyncFlags |= CSC_SYNC_NONET;
hr = GetSilentFolderList();
if (FAILED(hr))
{
m_dwSyncFlags = 0;
}
exit_gracefully:
TraceLeaveResult(hr);
}
STDMETHODIMP
CCscUpdate::GetHandlerInfo(LPSYNCMGRHANDLERINFO *ppSyncMgrHandlerInfo)
{
HRESULT hr = S_OK;
LPSYNCMGRHANDLERINFO pHandlerInfo;
TraceEnter(TRACE_UPDATE, "CCscUpdate::GetHandlerInfo");
if (NULL == ppSyncMgrHandlerInfo)
TraceLeaveResult(E_INVALIDARG);
*ppSyncMgrHandlerInfo = NULL;
pHandlerInfo = (LPSYNCMGRHANDLERINFO)CoTaskMemAlloc(sizeof(SYNCMGRHANDLERINFO));
if (NULL == pHandlerInfo)
ExitGracefully(hr, E_OUTOFMEMORY, "LocalAlloc failed");
pHandlerInfo->cbSize = sizeof(SYNCMGRHANDLERINFO);
pHandlerInfo->hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_CSCUI_ICON));
pHandlerInfo->SyncMgrHandlerFlags = (m_dwSyncFlags & CSC_SYNC_LOGOFF) ? 0 :
(SYNCMGRHANDLER_HASPROPERTIES | SYNCMGRHANDLER_MAYESTABLISHCONNECTION);
LoadStringW(g_hInstance,
IDS_APPLICATION,
pHandlerInfo->wszHandlerName,
ARRAYSIZE(pHandlerInfo->wszHandlerName));
*ppSyncMgrHandlerInfo = pHandlerInfo;
exit_gracefully:
TraceLeaveResult(hr);
}
STDMETHODIMP
CCscUpdate::EnumSyncMgrItems(LPSYNCMGRENUMITEMS *ppenum)
{
HRESULT hr;
PUPDATEENUM pNewEnum;
TraceEnter(TRACE_UPDATE, "CCscUpdate::EnumSyncMgrItems");
*ppenum = NULL;
pNewEnum = new CUpdateEnumerator(this);
if (pNewEnum)
{
hr = pNewEnum->QueryInterface(IID_ISyncMgrEnumItems, (LPVOID*)ppenum);
pNewEnum->Release();
}
else
hr = E_OUTOFMEMORY;
TraceLeaveResult(hr);
}
STDMETHODIMP
CCscUpdate::GetItemObject(REFSYNCMGRITEMID /*rItemID*/, REFIID /*riid*/, LPVOID * /*ppv*/)
{
return E_NOTIMPL;
}
STDMETHODIMP
CCscUpdate::ShowProperties(HWND hWndParent, REFSYNCMGRITEMID rItemID)
{
CSCEntry *pShareEntry;
LPCTSTR pszShareName = TEXT("");
pShareEntry = m_ShareLog.Get(rItemID);
// We don't enumerate shares to SyncMgr unless a share entry
// exists in the registry, so m_ShareLog.Get should never fail here.
if (pShareEntry)
pszShareName = pShareEntry->Name();
COfflineFilesFolder::Open();
// Notify SyncMgr that the ShowProperties is done.
if (NULL != m_pSyncMgrCB)
m_pSyncMgrCB->ShowPropertiesCompleted(S_OK);
return S_OK;
}
STDMETHODIMP
CCscUpdate::SetProgressCallback(LPSYNCMGRSYNCHRONIZECALLBACK pCallback)
{
TraceEnter(TRACE_UPDATE, "CCscUpdate::SetProgressCallback");
DoRelease(m_pSyncMgrCB);
m_pSyncMgrCB = pCallback;
if (m_pSyncMgrCB)
m_pSyncMgrCB->AddRef();
TraceLeaveResult(S_OK);
}
STDMETHODIMP
CCscUpdate::PrepareForSync(ULONG cNumItems,
SYNCMGRITEMID *pItemID,
HWND /*hWndParent*/,
DWORD /*dwReserved*/)
{
HRESULT hr = S_OK;
TraceEnter(TRACE_UPDATE, "CCscUpdate::PrepareForSync");
TraceAssert(0 != cNumItems);
TraceAssert(NULL != pItemID);
//
// Copy the list of item ID's
//
if (NULL == m_hSyncItems)
{
m_hSyncItems = DSA_Create(sizeof(SYNCMGRITEMID), 4);
if (NULL == m_hSyncItems)
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create DSA for SYNCMGRITEMID list");
}
else
DSA_DeleteAllItems(m_hSyncItems);
while (cNumItems--)
DSA_AppendItem(m_hSyncItems, pItemID++);
exit_gracefully:
// ISyncMgrSynchronize::PrepareForSync is now an asynchronous call
// so we could create another thread to do the work and return from
// this call immediately. However, since all we do is copy the list
// of Item IDs, let's do it here and call
// m_pSyncMgrCB->PrepareForSyncCompleted before returning.
if (NULL != m_pSyncMgrCB)
m_pSyncMgrCB->PrepareForSyncCompleted(hr);
TraceLeaveResult(hr);
}
STDMETHODIMP
CCscUpdate::Synchronize(HWND hWndParent)
{
HRESULT hr = E_FAIL;
ULONG cItems = 0;
BOOL bConnectionEstablished = FALSE;
TraceEnter(TRACE_UPDATE, "CCscUpdate::Synchronize");
if (NULL != m_hSyncItems)
cItems = DSA_GetItemCount(m_hSyncItems);
//
// Don't want systray UI updates while syncing.
// Whenever the systray UI is updated, the code checks first
// for this global mutex object. If it's non-signaled, the
// systray knows there's a sync in progress and the UI isn't
// updated.
//
TraceAssert(NULL == m_hSyncInProgMutex);
m_hSyncInProgMutex = CreateMutex(NULL, TRUE, c_szSyncInProgMutex);
if (0 == cItems)
{
ExitGracefully(hr, E_UNEXPECTED, "Nothing to synchronize");
}
else if (1 == cItems)
{
SYNCMGRITEMID *pItemID = (SYNCMGRITEMID*)DSA_GetItemPtr(m_hSyncItems, 0);
if (NULL != pItemID && IsEqualGUID(GUID_CscNullSyncItem, *pItemID))
{
//
// A single item in the DSA and it's our "null sync" GUID.
// This means we really have nothing to sync but the invoker
// of the sync wants to see some SyncMgr progress UI. In
// this scenario the update item enumerator already enumerated
// the "null sync" item. Here we set this single item's progress
// UI info to 100% complete and skip any sync activity.
//
SYNCMGRPROGRESSITEM spi = {0};
spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE |
SYNCMGRPROGRESSITEM_STATUSTEXT |
SYNCMGRPROGRESSITEM_PROGVALUE |
SYNCMGRPROGRESSITEM_MAXVALUE;
spi.cbSize = sizeof(spi);
spi.dwStatusType = SYNCMGRSTATUS_SUCCEEDED;
spi.lpcStatusText = L" ";
spi.iProgValue = 1;
spi.iMaxValue = 1;
m_pSyncMgrCB->Progress(GUID_CscNullSyncItem, &spi);
m_pSyncMgrCB->SynchronizeCompleted(S_OK);
if (CSC_SYNC_RECONNECT & m_dwSyncFlags)
{
//
// We have nothing to sync but one or more servers
// may still be offline. The user's expectation is that the
// sync will transition these to online regardless of link
// speed. Add them to the "reconnect" list.
//
_BuildOfflineShareList(&m_ReconnectList);
}
ExitGracefully(hr, NOERROR, "Nothing to sync. Progress UI displayed");
}
}
m_hwndDlgParent = hWndParent;
// We can pin autocached files without a net (no sync required);
// otherwise we need to establish a RAS connection to do anything.
if ((m_dwSyncFlags & CSC_SYNC_NONET) && !(m_dwSyncFlags & CSC_SYNC_PINFILES))
{
hr = m_pSyncMgrCB->EstablishConnection(NULL, 0);
FailGracefully(hr, "Unable to establish RAS connection");
bConnectionEstablished = TRUE;
}
// For each share, kick off a thread to do the work
while (cItems > 0)
{
SYNCMGRITEMID *pItemID;
CSCEntry *pShareEntry;
--cItems;
pItemID = (SYNCMGRITEMID*)DSA_GetItemPtr(m_hSyncItems, cItems);
pShareEntry = m_ShareLog.Get(*pItemID);
// We don't enumerate shares to SyncMgr unless a share entry
// exists in the registry, so m_ShareLog.Get should never fail here.
if (NULL == pShareEntry)
ExitGracefully(hr, E_UNEXPECTED, "No share entry");
hr = SynchronizeShare(pItemID, pShareEntry->Name(), bConnectionEstablished);
DSA_DeleteItem(m_hSyncItems, cItems);
FailGracefully(hr, "Unable to create sync thread");
}
TraceAssert(0 == DSA_GetItemCount(m_hSyncItems));
exit_gracefully:
if (FAILED(hr))
SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED);
TraceLeaveResult(hr);
}
//
// Try to reconnect any server that is currently offline.
//
void
CCscUpdate::_BuildOfflineShareList(
CscFilenameList *pfnl
)
{
WIN32_FIND_DATA fd;
DWORD dwStatus = 0;
CCscFindHandle hFind = CacheFindFirst(NULL, &fd, &dwStatus, NULL, NULL, NULL);
if (hFind.IsValid())
{
do
{
if (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwStatus)
{
CscFilenameList::HSHARE hShare;
pfnl->AddShare(fd.cFileName, &hShare);
}
}
while(CacheFindNext(hFind, &fd, &dwStatus, NULL, NULL, NULL));
}
}
STDMETHODIMP
CCscUpdate::SetItemStatus(REFSYNCMGRITEMID rItemID,
DWORD dwSyncMgrStatus)
{
HRESULT hr = E_FAIL;
ULONG cItems;
BOOL bAllItems;
TraceEnter(TRACE_UPDATE, "CCscUpdate::SetItemStatus");
if (SYNCMGRSTATUS_SKIPPED != dwSyncMgrStatus && SYNCMGRSTATUS_STOPPED != dwSyncMgrStatus)
TraceLeaveResult(E_NOTIMPL);
bAllItems = FALSE;
if (SYNCMGRSTATUS_STOPPED == dwSyncMgrStatus)
{
bAllItems = TRUE;
m_dwSyncFlags |= CSC_SYNC_CANCELLED;
}
// SetItemStatus can be called between PrepareForSync and Synchronize, in
// in which case the correct thing to do is remove the item from m_hSyncItems.
if (NULL != m_hSyncItems)
{
cItems = DSA_GetItemCount(m_hSyncItems);
while (cItems > 0)
{
SYNCMGRITEMID *pItemID;
--cItems;
pItemID = (SYNCMGRITEMID*)DSA_GetItemPtr(m_hSyncItems, cItems);
if (bAllItems || (NULL != pItemID && IsEqualGUID(rItemID, *pItemID)))
{
// Remove the item from the list of items to sync
DSA_DeleteItem(m_hSyncItems, cItems);
if (!bAllItems)
ExitGracefully(hr, S_OK, "Skipping item");
}
}
}
// Lookup the thread for the item ID and set its status
// to cause it to terminate.
hr = SetSyncThreadStatus(SyncStop, bAllItems ? GUID_NULL : rItemID);
exit_gracefully:
TraceLeaveResult(hr);
}
STDMETHODIMP
CCscUpdate::ShowError(HWND /*hWndParent*/ , REFSYNCMGRERRORID /*ErrorID*/)
{
return E_NOTIMPL;
}
HRESULT
CCscUpdate::SynchronizeShare(SYNCMGRITEMID *pItemID, LPCTSTR pszShareName, BOOL bRasConnected)
{
HRESULT hr = S_OK;
DWORD dwThreadID;
PSYNCTHREADDATA pThreadData;
ULONG cbShareName = 0;
TraceEnter(TRACE_UPDATE, "CCscUpdate::SynchronizeShare");
TraceAssert(NULL != pItemID);
TraceAssert(NULL != pszShareName);
TraceAssert(*pszShareName);
EnterCriticalSection(&m_csThreadList);
if (NULL == m_hSyncThreads)
m_hSyncThreads = DPA_Create(4);
LeaveCriticalSection(&m_csThreadList);
if (NULL == m_hSyncThreads)
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create DPA for threads");
cbShareName = StringByteSize(pszShareName);
pThreadData = (PSYNCTHREADDATA)LocalAlloc(LPTR, sizeof(SYNCTHREADDATA) + cbShareName);
if (!pThreadData)
ExitGracefully(hr, E_OUTOFMEMORY, "LocalAlloc failed");
pThreadData->pThis = this;
pThreadData->ItemID = *pItemID;
pThreadData->pszShareName = (LPTSTR)(pThreadData + 1);
CopyMemory(pThreadData->pszShareName, pszShareName, cbShareName);
//
// If we established a RAS connection, then it will go away
// right after the sync completes, so there's no point trying
// to reconnect. That is, only check CSC_SYNC_RECONNECT and
// add the share to the reconnect list if we aren't doing RAS.
//
if (bRasConnected)
{
pThreadData->dwSyncStatus |= SDS_SYNC_RAS_CONNECTED;
}
else if (m_dwSyncFlags & CSC_SYNC_RECONNECT)
{
CscFilenameList::HSHARE hShare;
m_ReconnectList.AddShare(pszShareName, &hShare);
}
// Give the thread a reference to this object and the DLL
AddRef();
LoadLibrary(c_szDllName);
pThreadData->hThread = CreateThread(NULL,
0,
_SyncThread,
pThreadData,
CREATE_SUSPENDED,
&dwThreadID);
if (NULL != pThreadData->hThread)
{
EnterCriticalSection(&m_csThreadList);
DPA_AppendPtr(m_hSyncThreads, pThreadData);
LeaveCriticalSection(&m_csThreadList);
ResumeThread(pThreadData->hThread);
}
else
{
DWORD dwErr = GetLastError();
LocalFree(pThreadData);
LPTSTR pszErr = GetErrorText(GetLastError());
LogError(*pItemID,
SYNCMGRLOGLEVEL_ERROR,
IDS_FILL_SPARSE_FILES_ERROR,
pszShareName,
pszErr);
LocalFreeString(&pszErr);
hr = HRESULT_FROM_WIN32(dwErr);
Release();
FreeLibrary(g_hInstance);
}
exit_gracefully:
TraceLeaveResult(hr);
}
void
CCscUpdate::SetLastSyncTime(LPCTSTR pszShareName)
{
HKEY hKey = NULL;
hKey = m_ShareLog.OpenKey(pszShareName, KEY_SET_VALUE);
if (hKey)
{
FILETIME ft = {0};
GetSystemTimeAsFileTime(&ft);
RegSetValueEx(hKey, c_szLastSync, 0, REG_BINARY, (LPBYTE)&ft, sizeof(ft));
RegCloseKey(hKey);
}
}
DWORD
CCscUpdate::GetLastSyncTime(LPCTSTR pszShareName, LPFILETIME pft)
{
DWORD dwResult = ERROR_PATH_NOT_FOUND;
HKEY hKey = NULL;
hKey = m_ShareLog.OpenKey(pszShareName, KEY_QUERY_VALUE);
if (hKey)
{
DWORD dwSize = sizeof(*pft);
dwResult = RegQueryValueEx(hKey, c_szLastSync, NULL, NULL, (LPBYTE)pft, &dwSize);
RegCloseKey(hKey);
}
return dwResult;
}
void
CCscUpdate::SyncThreadCompleted(PSYNCTHREADDATA pSyncData)
{
int iThread;
TraceEnter(TRACE_UPDATE, "CCscUpdate::SyncThreadCompleted");
TraceAssert(NULL != pSyncData);
TraceAssert(NULL != m_hSyncThreads);
EnterCriticalSection(&m_csThreadList);
iThread = DPA_GetPtrIndex(m_hSyncThreads, pSyncData);
TraceAssert(-1 != iThread);
DPA_DeletePtr(m_hSyncThreads, iThread);
CloseHandle(pSyncData->hThread);
pSyncData->hThread = NULL;
iThread = DPA_GetPtrCount(m_hSyncThreads);
LeaveCriticalSection(&m_csThreadList);
if (0 == iThread)
{
SyncCompleted();
}
TraceLeaveVoid();
}
void
CCscUpdate::SyncCompleted(void)
{
if ((m_dwSyncFlags & CSC_SYNC_RECONNECT) &&
!(m_dwSyncFlags & CSC_SYNC_CANCELLED))
{
m_dwSyncFlags &= ~CSC_SYNC_RECONNECT;
ReconnectServers(&m_ReconnectList, FALSE, FALSE);
}
if (NULL != m_hSyncInProgMutex)
{
// We're not syncing so reset the global event
ReleaseMutex(m_hSyncInProgMutex);
CloseHandle(m_hSyncInProgMutex);
m_hSyncInProgMutex = NULL;
}
if (m_dwSyncFlags & CSC_SYNC_NOTIFY_SYSTRAY)
{
// Notify systray that we're done
PostToSystray(CSCWM_DONESYNCING, 0, 0);
m_dwSyncFlags &= ~CSC_SYNC_NOTIFY_SYSTRAY;
}
// Notify SyncMgr that the sync is done
if (NULL != m_pSyncMgrCB)
{
m_pSyncMgrCB->SynchronizeCompleted(S_OK);
}
HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, c_szSyncCompleteEvent);
if (NULL != hEvent)
{
//
// Let anyone interested know that the sync is complete.
//
SetEvent(hEvent);
CloseHandle(hEvent);
}
}
UINT
GetErrorFormat(DWORD dwErr, BOOL bMerging = FALSE)
{
UINT idString = 0;
// These are all just initial guesses. Not sure
// which error codes we'll get from CSC.
switch (dwErr)
{
case ERROR_DISK_FULL:
// "The server disk is full."
// "The local disk is full."
idString = bMerging ? IDS_SERVER_FULL_ERROR : IDS_LOCAL_DISK_FULL_ERROR;
break;
case ERROR_LOCK_VIOLATION:
case ERROR_SHARING_VIOLATION:
case ERROR_OPEN_FILES:
case ERROR_ACTIVE_CONNECTIONS:
case ERROR_DEVICE_IN_USE:
// "'%1' is in use on %2"
idString = IDS_FILE_OPEN_ERROR;
break;
case ERROR_BAD_NETPATH:
case ERROR_DEV_NOT_EXIST:
case ERROR_NETNAME_DELETED:
case ERROR_BAD_NET_NAME:
case ERROR_SHARING_PAUSED:
case ERROR_REQ_NOT_ACCEP:
case ERROR_REDIR_PAUSED:
case ERROR_BAD_DEVICE:
case ERROR_CONNECTION_UNAVAIL:
case ERROR_NO_NET_OR_BAD_PATH:
case ERROR_NO_NETWORK:
case ERROR_CONNECTION_REFUSED:
case ERROR_GRACEFUL_DISCONNECT:
case ERROR_NETWORK_UNREACHABLE:
case ERROR_HOST_UNREACHABLE:
case ERROR_PROTOCOL_UNREACHABLE:
case ERROR_PORT_UNREACHABLE:
case ERROR_LOGON_FAILURE:
// "Unable to connect to '%1.' %2"
idString = IDS_SHARE_CONNECT_ERROR;
break;
case ERROR_OPEN_FAILED:
case ERROR_UNEXP_NET_ERR:
case ERROR_NETWORK_BUSY:
case ERROR_BAD_NET_RESP:
// "Unable to access '%1' on %2. %3"
idString = IDS_NET_ERROR;
break;
case ERROR_ACCESS_DENIED:
case ERROR_NETWORK_ACCESS_DENIED:
// "Access to '%1' is denied on %2"
idString = IDS_ACCESS_ERROR;
break;
case ERROR_BAD_FORMAT:
// "The Offline Files cache is corrupt. Restart the computer to correct the cache."
idString = IDS_CACHE_CORRUPT;
break;
default:
// "Error accessing '%1' on %2. %3"
idString = IDS_UNKNOWN_SYNC_ERROR;
break;
}
return idString;
}
HRESULT
CCscUpdate::LogError(REFSYNCMGRITEMID rItemID,
LPCTSTR pszText,
DWORD dwLogLevel,
REFSYNCMGRERRORID ErrorID)
{
HRESULT hr;
SYNCMGRLOGERRORINFO slei;
USES_CONVERSION;
TraceEnter(TRACE_UPDATE, "CCscUpdate::LogError");
if (NULL == m_pSyncMgrCB)
TraceLeaveResult(E_UNEXPECTED);
slei.cbSize = sizeof(slei);
slei.mask = SYNCMGRLOGERROR_ITEMID | SYNCMGRLOGERROR_ERRORID;
slei.ItemID = rItemID;
slei.ErrorID = ErrorID;
// if we have a jumptext associated with this item then
// set the enable jumptext flag
if (ErrorID != GUID_NULL)
{
slei.mask |= SYNCMGRLOGERROR_ERRORFLAGS;
slei.dwSyncMgrErrorFlags = SYNCMGRERRORFLAG_ENABLEJUMPTEXT;
}
Trace((pszText));
hr = m_pSyncMgrCB->LogError(dwLogLevel, T2CW(pszText), &slei);
TraceLeaveResult(hr);
}
DWORD
CCscUpdate::LogError(REFSYNCMGRITEMID rItemID,
DWORD dwLogLevel,
UINT nFormatID,
...)
{
LPTSTR pszError = NULL;
va_list args;
va_start(args, nFormatID);
if (vFormatStringID(&pszError, g_hInstance, nFormatID, &args))
{
LogError(rItemID, pszError, dwLogLevel);
LocalFree(pszError);
}
va_end(args);
return 0;
}
DWORD
CCscUpdate::LogError(REFSYNCMGRITEMID rItemID,
UINT nFormatID,
LPCTSTR pszName,
DWORD dwErr,
DWORD dwLogLevel)
{
//
// Break the filename into "file" and "path" components
//
TCHAR szPath[MAX_PATH] = TEXT("\\");
LPCTSTR pszFile = NULL;
if (pszName)
{
pszFile = PathFindFileName(pszName);
lstrcpyn(szPath, pszName, (int)min(ARRAYSIZE(szPath),(int)(pszFile-pszName)));
}
//
// Get the system error text and format the error
//
LPTSTR pszErr = GetErrorText(dwErr);
LogError(rItemID,
dwLogLevel,
nFormatID,
pszFile,
szPath,
pszErr);
LocalFreeString(&pszErr);
return 0;
}
BOOL
MakeDriveLetterPath(LPCTSTR pszUNC,
LPCTSTR pszShare,
LPCTSTR pszDrive,
LPTSTR *ppszResult)
{
BOOL bResult = FALSE;
ULONG cchShare;
if (!pszUNC || !pszShare || !ppszResult)
return FALSE;
*ppszResult = NULL;
cchShare = lstrlen(pszShare);
// If the path is on the share, use the drive letter instead
if (pszDrive && *pszDrive &&
CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT,
NORM_IGNORECASE,
pszUNC,
cchShare,
pszShare,
cchShare))
{
*ppszResult = (LPTSTR)LocalAlloc(LPTR, MAX(StringByteSize(pszUNC), MAX_PATH_BYTES));
if (*ppszResult)
{
PathCombine(*ppszResult, pszDrive, pszUNC + cchShare);
bResult = TRUE;
}
}
return bResult;
}
DWORD
CCscUpdate::CopyLocalFileWithDriveMapping(LPCTSTR pszSrc,
LPCTSTR pszDst,
LPCTSTR pszShare,
LPCTSTR pszDrive,
BOOL bDirectory)
{
DWORD dwErr = NOERROR;
LPTSTR szDst = NULL;
if (!pszSrc || !pszDst || !pszShare)
return ERROR_INVALID_PARAMETER;
// If the destination is on the share, use the drive letter instead
if (MakeDriveLetterPath(pszDst, pszShare, pszDrive, &szDst))
pszDst = szDst;
if (bDirectory)
{
// We don't need to copy the directory contents here, just create
// the tree structure on the server.
if (!CreateDirectory(pszDst, NULL))
{
dwErr = GetLastError();
if (ERROR_ALREADY_EXISTS == dwErr)
dwErr = NOERROR;
}
}
else
{
LPTSTR pszTmpName = NULL;
if (!CSCCopyReplica(pszSrc, &pszTmpName) ||
!MoveFileEx(pszTmpName,
pszDst,
MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
{
dwErr = GetLastError();
}
if (NULL != pszTmpName)
{
DeleteFile(pszTmpName);
LocalFree(pszTmpName);
}
}
if (ERROR_PATH_NOT_FOUND == dwErr)
{
// The parent directory doesn't exist, create it now.
TCHAR szParent[MAX_PATH];
lstrcpyn(szParent, pszDst, ARRAYSIZE(szParent));
PathRemoveFileSpec(szParent);
dwErr = CopyLocalFileWithDriveMapping(pszSrc, szParent, pszShare, NULL, TRUE);
// If that worked, retry the original operation.
if (NOERROR == dwErr)
dwErr = CopyLocalFileWithDriveMapping(pszSrc, pszDst, pszShare, NULL, bDirectory);
}
LocalFreeString(&szDst);
return dwErr;
}
BOOL
HandleConflictLocally(PSYNCTHREADDATA pSyncData,
LPCTSTR pszPath,
DWORD dwCscStatus,
DWORD dwLocalAttr,
DWORD dwRemoteAttr = 0)
{
BOOL bResult = FALSE;
LPTSTR szParent = NULL;
// If it's super-hidden or not modified locally, we can always
// handle the conflict locally.
if (!(dwCscStatus & CSC_LOCALLY_MODIFIED) || IsHiddenSystem(dwLocalAttr) || IsHiddenSystem(dwRemoteAttr))
return TRUE;
// If we're dealing with 2 folders, the worst that happens is that the
// underlying files/folders get merged.
if ((FILE_ATTRIBUTE_DIRECTORY & dwLocalAttr) && (FILE_ATTRIBUTE_DIRECTORY & dwRemoteAttr))
return TRUE;
//
// Next, check whether the parent path is super-hidden.
//
// For example, recycle bin makes super-hidden folders and puts
// metadata files in them.
//
// Do this on the server, since CSC has exclusive access to the database
// while merging, causing GetFileAttributes to fail with Access Denied.
//
//
// Do this on the server, since CSC has exclusive access to the database
// while merging, causing GetFileAttributes to fail with Access Denied.
//
if (MakeDriveLetterPath(pszPath, pSyncData->pszShareName, pSyncData->szDrive, &szParent))
{
//
// Don't check attributes at the root, just stop.
// WinSE 16781.
//
for(PathRemoveFileSpec(szParent); !PathIsRoot(szParent); PathRemoveFileSpec(szParent))
{
dwRemoteAttr = GetFileAttributes(szParent);
if ((DWORD)-1 == dwRemoteAttr)
{
// Path doesn't exist, access denied, etc.
break;
}
if (IsHiddenSystem(dwRemoteAttr))
{
bResult = TRUE;
break;
}
}
}
LocalFreeString(&szParent);
return bResult;
}
DWORD
CCscUpdate::HandleFileConflict(PSYNCTHREADDATA pSyncData,
LPCTSTR pszName,
DWORD dwStatus,
DWORD dwHintFlags,
LPWIN32_FIND_DATA pFind32)
{
DWORD dwResult = CSCPROC_RETURN_CONTINUE;
DWORD dwErr = NOERROR;
int nErrorResolution = RFC_KEEPBOTH;
LPTSTR pszNewName = NULL;
LPTSTR szFullPath = NULL;
BOOL bApplyToAll = FALSE;
TraceEnter(TRACE_UPDATE, "CCscUpdate::HandleFileConflict");
Trace((TEXT("File conflict: %s"), pszName));
TraceAssert(pSyncData->dwSyncStatus & SDS_SYNC_OUT);
szFullPath = (LPTSTR)LocalAlloc(LPTR, StringByteSize(pszName) + MAX_PATH*sizeof(TCHAR));
if (!szFullPath)
{
dwErr = ERROR_OUTOFMEMORY;
ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "LocalAlloc failed");
}
HANDLE hFind;
WIN32_FIND_DATA fdRemote;
PathCombine(szFullPath, pSyncData->szDrive, pszName + lstrlen(pSyncData->pszShareName));
hFind = FindFirstFile(szFullPath, &fdRemote);
// Does the net version still exist?
if (hFind == INVALID_HANDLE_VALUE)
ExitGracefully(dwResult, HandleDeleteConflict(pSyncData, pszName, dwStatus, dwHintFlags, pFind32), "Net file deleted");
// Still exists, continue
FindClose(hFind);
// If only the attributes or file times were modified locally,
// or if the file is hidden+system, keep the server copy and
// don't bother the user. (e.g. desktop.ini)
if (HandleConflictLocally(pSyncData, pszName, dwStatus, pFind32->dwFileAttributes, fdRemote.dwFileAttributes))
{
ExitGracefully(dwResult, CSCPROC_RETURN_FORCE_INWARD, "Ignoring conflict");
}
else if (IsSilentFolder(pszName))
{
// It's in a per-user shell special folder. Last writer wins.
if (CompareFileTime(&pFind32->ftLastWriteTime, &fdRemote.ftLastWriteTime) < 0)
{
ExitGracefully(dwResult, CSCPROC_RETURN_FORCE_INWARD, "Handling special folder conflict - server copy wins");
}
else
{
ExitGracefully(dwResult, CSCPROC_RETURN_FORCE_OUTWARD, "Handling special folder conflict - local copy wins");
}
}
dwErr = GetNewVersionName(pszName,
pSyncData->pszShareName,
pSyncData->szDrive,
&pszNewName);
if (NOERROR != dwErr)
{
ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "GetNewVersionName failed");
}
switch (SDS_SYNC_FILE_CONFLICT_MASK & pSyncData->dwSyncStatus)
{
case 0:
if (CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags)
{
nErrorResolution = ShowConflictDialog(m_hwndDlgParent,
pszName,
pszNewName,
pSyncData->pszShareName,
pSyncData->szDrive,
pFind32,
&fdRemote);
if (RFC_APPLY_TO_ALL & nErrorResolution)
{
bApplyToAll = TRUE;
nErrorResolution &= ~RFC_APPLY_TO_ALL;
}
}
break;
case SDS_SYNC_CONFLICT_KEEPLOCAL:
nErrorResolution = RFC_KEEPLOCAL;
break;
case SDS_SYNC_CONFLICT_KEEPNET:
nErrorResolution = RFC_KEEPNETWORK;
break;
case SDS_SYNC_CONFLICT_KEEPBOTH:
nErrorResolution = RFC_KEEPBOTH;
break;
}
// Self-host notification callback
CSCUI_NOTIFYHOOK((CSCH_UpdateConflict, TEXT("Update conflict: %1, resolution %2!d!"), pszName, nErrorResolution));
switch (nErrorResolution)
{
default:
case RFC_KEEPBOTH:
if (bApplyToAll)
pSyncData->dwSyncStatus |= SDS_SYNC_CONFLICT_KEEPBOTH;
lstrcpy(szFullPath, pszName);
PathRemoveFileSpec(szFullPath);
if (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes)
{
// Rename the local version in the cache and merge again.
lstrcpyn(pFind32->cFileName, pszNewName, ARRAYSIZE(pFind32->cFileName));
if (!CSCDoLocalRenameEx(pszName, szFullPath, pFind32, TRUE, TRUE))
{
dwErr = GetLastError();
ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "CSCDoLocalRenameEx failed");
}
// Because CSCDoLocalRenameEx and CSCMergeShare are separate operations,
// we have to abort the current merge operation and start over.
// Otherwise, the current merge operation fails due to the "left
// hand not knowing what the right hande is doing".
Trace((TEXT("Restarting merge on: %s"), pSyncData->pszShareName));
pSyncData->dwSyncStatus |= SDS_SYNC_RESTART_MERGE;
dwResult = CSCPROC_RETURN_ABORT;
}
else
{
// Note that CSCDoLocalRenameEx would work for files also, but we
// prefer to avoid restarting CSCMergeShare so do these ourselves.
PathAppend(szFullPath, pszNewName);
dwErr = CopyLocalFileWithDriveMapping(pszName,
szFullPath,
pSyncData->pszShareName,
pSyncData->szDrive);
if (NOERROR != dwErr)
ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "CopyLocalFileWithDriveMapping failed");
// If the original file was pinned, we want to pin the copy also.
// Unfortunately, we can't reliably pin during a merge, so we have
// to remember these in a list and pin them later.
if (dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN))
{
if (!m_pConflictPinList)
m_pConflictPinList = new CscFilenameList;
if (m_pConflictPinList)
{
m_pConflictPinList->AddFile(szFullPath,
!!(pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
}
}
// Tell CSCMergeShare to copy the server copy to the cache
// (with the old name). This clears the dirty cache.
dwResult = CSCPROC_RETURN_FORCE_INWARD;
}
break;
case RFC_KEEPNETWORK:
// Tell CSCMergeShare to copy the server copy to the cache
dwResult = CSCPROC_RETURN_FORCE_INWARD;
if (bApplyToAll)
pSyncData->dwSyncStatus |= SDS_SYNC_CONFLICT_KEEPNET;
break;
case RFC_KEEPLOCAL:
// Tell CSCMergeShare to push the local copy to the server
dwResult = CSCPROC_RETURN_FORCE_OUTWARD;
if (bApplyToAll)
pSyncData->dwSyncStatus |= SDS_SYNC_CONFLICT_KEEPLOCAL;
break;
case RFC_CANCEL:
TraceMsg("HandleFileConflict: Cancelling sync - user bailed");
SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED);
dwResult = CSCPROC_RETURN_ABORT;
break;
}
exit_gracefully:
if (CSCPROC_RETURN_FORCE_INWARD == dwResult)
{
// CSCMergeShare truncates (makes sparse) the
// file if we return this. We'd like to fill
// it during this sync.
pSyncData->cFilesToSync++;
pSyncData->dwSyncStatus |= SDS_SYNC_FORCE_INWARD;
}
if (NOERROR != dwErr)
{
pszName += lstrlen(pSyncData->pszShareName);
if (*pszName == TEXT('\\'))
pszName++;
LogError(pSyncData->ItemID,
IDS_NAME_CONFLICT_ERROR,
pszName,
dwErr);
pSyncData->dwSyncStatus |= SDS_SYNC_ERROR;
}
LocalFreeString(&szFullPath);
LocalFreeString(&pszNewName);
TraceLeaveResult(dwResult);
}
// Returns values for the Resolve Delete Conflict dialog
#define RDC_CANCEL 0x00
#define RDC_DELETE 0x01
#define RDC_RESTORE 0x02
#define RDC_APPLY_ALL 0x04
#define RDC_DELETE_ALL (RDC_APPLY_ALL | RDC_DELETE)
#define RDC_RESTORE_ALL (RDC_APPLY_ALL | RDC_RESTORE)
TCHAR const c_szDeleteSelection[] = TEXT("DeleteConflictSelection");
INT_PTR CALLBACK
DeleteConflictProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int nResult;
switch (uMsg)
{
case WM_INITDIALOG:
{
TCHAR szShare[MAX_PATH];
LPCTSTR pszPath = (LPCTSTR)lParam;
LPTSTR pszT = NULL;
szShare[0] = TEXT('\0');
lstrcpyn(szShare, pszPath, ARRAYSIZE(szShare));
PathStripToRoot(szShare);
// Format the file name
PathSetDlgItemPath(hDlg, IDC_FILENAME, pszPath);
// Build the "Do this for all on <this share>" string
FormatStringID(&pszT, g_hInstance, IDS_FMT_DELETE_APPLY_ALL, szShare);
if (pszT)
{
SetDlgItemText(hDlg, IDC_APPLY_TO_ALL, pszT);
LocalFreeString(&pszT);
}
// else default text is OK (no share name)
// Select whatever the user chose last time, default is "restore"
DWORD dwPrevSelection = RDC_RESTORE;
DWORD dwType;
DWORD cbData = sizeof(dwPrevSelection);
SHGetValue(HKEY_CURRENT_USER,
c_szCSCKey,
c_szDeleteSelection,
&dwType,
&dwPrevSelection,
&cbData);
dwPrevSelection = (RDC_DELETE == dwPrevSelection ? IDC_DELETE_LOCAL : IDC_KEEP_LOCAL);
CheckRadioButton(hDlg, IDC_KEEP_LOCAL, IDC_DELETE_LOCAL, dwPrevSelection);
// Get the file-type icon
pszT = PathFindExtension(pszPath);
if (pszT)
{
SHFILEINFO sfi = {0};
SHGetFileInfo(pszT, 0, &sfi, sizeof(sfi), SHGFI_ICON);
if (sfi.hIcon)
{
SendDlgItemMessage(hDlg,
IDC_DLGTYPEICON,
STM_SETICON,
(WPARAM)sfi.hIcon,
0L);
}
}
}
return TRUE;
case WM_COMMAND:
nResult = -1;
switch (LOWORD(wParam))
{
case IDCANCEL:
nResult = RDC_CANCEL;
break;
case IDOK:
if (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_DELETE_LOCAL))
nResult = RDC_DELETE;
else
nResult = RDC_RESTORE;
// Remember the selection for next time
SHSetValue(HKEY_CURRENT_USER,
c_szCSCKey,
c_szDeleteSelection,
REG_DWORD,
&nResult,
sizeof(nResult));
if (BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_APPLY_TO_ALL))
nResult |= RDC_APPLY_ALL;
break;
}
if (-1 != nResult)
{
EndDialog(hDlg, nResult);
return TRUE;
}
break;
}
return FALSE;
}
BOOL CALLBACK
ConflictPurgeCallback(LPCWSTR /*pszFile*/, LPARAM lParam)
{
PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)lParam;
return !(SDS_SYNC_CANCELLED & pSyncData->dwSyncStatus);
}
DWORD
CCscUpdate::HandleDeleteConflict(PSYNCTHREADDATA pSyncData,
LPCTSTR pszName,
DWORD dwStatus,
DWORD dwHintFlags,
LPWIN32_FIND_DATA pFind32)
{
DWORD dwResult = CSCPROC_RETURN_CONTINUE;
int nErrorResolution = RDC_DELETE; // default action
BOOL bDirectory = (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes);
TraceEnter(TRACE_UPDATE, "CCscUpdate::HandleDeleteConflict");
Trace((TEXT("Net file deleted: %s"), pszName));
//
// We already know that the net file was deleted, or HandleDeleteConflict
// wouldn't be called. If the local copy was also deleted, then there
// isn't really a conflict and we can continue without prompting.
//
// Handle the conflict silently if only attributes changed or it's super-hidden.
//
// Finally, if the file lives in certain special folder locations,
// such as AppData, handle the conflict silently.
//
// If we get past all that, ask the user what to do, but only bother
// the user as a last resort.
//
if ( !(dwStatus & FLAG_CSC_COPY_STATUS_LOCALLY_DELETED)
&& !HandleConflictLocally(pSyncData, pszName, dwStatus, pFind32->dwFileAttributes)
&& !IsSilentFolder(pszName)
)
{
// The file is either pinned or modified locally, so
// default action is now "restore".
nErrorResolution = RDC_RESTORE;
switch (SDS_SYNC_DELETE_CONFLICT_MASK & pSyncData->dwSyncStatus)
{
case 0:
if (CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags)
{
int idDialog = (bDirectory ? IDD_FOLDER_CONFLICT_DELETE : IDD_FILE_CONFLICT_DELETE);
nErrorResolution = (int)DialogBoxParam(g_hInstance,
MAKEINTRESOURCE(idDialog),
m_hwndDlgParent,
DeleteConflictProc,
(LPARAM)pszName);
if (RDC_DELETE_ALL == nErrorResolution)
{
pSyncData->dwSyncStatus |= SDS_SYNC_DELETE_DELETE;
nErrorResolution = RDC_DELETE;
}
else if (RDC_RESTORE_ALL == nErrorResolution)
{
pSyncData->dwSyncStatus |= SDS_SYNC_DELETE_RESTORE;
nErrorResolution = RDC_RESTORE;
}
}
break;
case SDS_SYNC_DELETE_DELETE:
nErrorResolution = RDC_DELETE;
break;
case SDS_SYNC_DELETE_RESTORE:
nErrorResolution = RDC_RESTORE;
break;
}
// Self-host notification callback
CSCUI_NOTIFYHOOK((CSCH_DeleteConflict, TEXT("Delete conflict: %1, resolution %2!d!"), pszName, nErrorResolution));
}
switch (nErrorResolution)
{
default:
case RDC_RESTORE:
Trace((TEXT("HandleDeleteConflict: restoring %s"), pszName));
// Tell CSCMergeShare to push the local copy to the server
dwResult = CSCPROC_RETURN_FORCE_OUTWARD;
break;
case RDC_DELETE:
Trace((TEXT("HandleDeleteConflict: deleting %s"), pszName));
if (bDirectory)
{
// Deep delete
CSCUIRemoveFolderFromCache(pszName, 0, ConflictPurgeCallback, (LPARAM)pSyncData);
}
else
{
if (ERROR_SUCCESS == CscDelete(pszName))
{
ShellChangeNotify(pszName, pFind32, TRUE, SHCNE_DELETE);
}
}
dwResult = CSCPROC_RETURN_SKIP;
break;
case RDC_CANCEL:
TraceMsg("HandleDeleteConflict: Cancelling sync - user bailed");
SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED);
dwResult = CSCPROC_RETURN_ABORT;
break;
}
TraceLeaveResult(dwResult);
}
DWORD
CCscUpdate::CscCallback(PSYNCTHREADDATA pSyncData,
LPCTSTR pszName,
DWORD dwStatus,
DWORD dwHintFlags,
DWORD dwPinCount,
LPWIN32_FIND_DATA pFind32,
DWORD dwReason,
DWORD dwParam1,
DWORD dwParam2)
{
DWORD dwResult = CSCPROC_RETURN_CONTINUE;
SYNCMGRPROGRESSITEM spi = { sizeof(spi), 0 };
TraceEnter(TRACE_UPDATE, "CCscUpdate::CscCallback");
TraceAssert(pSyncData != NULL);
TraceAssert(pSyncData->pThis == this);
// Check for Cancel
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
{
TraceMsg("Cancelling sync operation");
TraceLeaveValue(CSCPROC_RETURN_ABORT);
}
switch (dwReason)
{
case CSCPROC_REASON_BEGIN:
// First thing to do is determine if this is for the entire share
// or an individual file in the share.
if (!(pSyncData->dwSyncStatus & SDS_SYNC_STARTED))
{
// SHARE BEGIN
pSyncData->dwSyncStatus |= SDS_SYNC_STARTED;
TraceAssert(!lstrcmpi(pszName, pSyncData->pszShareName));
Trace((TEXT("Share begin: %s"), pszName));
if (pSyncData->dwSyncStatus & SDS_SYNC_OUT)
{
// Save the drive letter to use for net operations
Trace((TEXT("Drive %s"), pFind32->cFileName));
lstrcpyn(pSyncData->szDrive, pFind32->cFileName, ARRAYSIZE(pSyncData->szDrive));
}
else
{
pSyncData->szDrive[0] = TEXT('\0');
}
// Remember whether it's an autocache share or not
switch (dwStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK)
{
case FLAG_CSC_SHARE_STATUS_AUTO_REINT:
case FLAG_CSC_SHARE_STATUS_VDO:
pSyncData->dwSyncStatus |= SDS_SYNC_AUTOCACHE;
break;
}
}
else
{
// FILE BEGIN
BOOL bSkipFile = FALSE;
TraceAssert(lstrlen(pszName) > lstrlen(pSyncData->pszShareName));
if (!(pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
// If we're updating a file selection and this file
// isn't part of the selection, skip it.
if (m_pFileList && !m_pFileList->FileExists(pszName, false))
{
bSkipFile = TRUE;
}
else if (!(pSyncData->dwSyncStatus & (SDS_SYNC_AUTOCACHE | SDS_SYNC_OUT)) &&
!(dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)) &&
!IsSpecialFolder(pszName))
{
// Skip autocached files when filling on a
// non-autocache share. Raid #341786
bSkipFile = TRUE;
}
else if (!(pSyncData->dwSyncStatus & CSC_SYNC_IGNORE_ACCESS))
{
// dwReserved0 is the current user's access mask
// dwReserved1 is the Guest access mask
DWORD dwCurrentAccess = pFind32->dwReserved0 | pFind32->dwReserved1;
if (pSyncData->dwSyncStatus & SDS_SYNC_OUT)
{
//
// If the current user doesn't have sufficient access
// to merge offline changes, then don't bother trying.
// (It must be some other user's file.)
//
// Have the attributes changed offline?
if (FLAG_CSC_COPY_STATUS_ATTRIB_LOCALLY_MODIFIED & dwStatus)
{
// Yes. Continue if the current user has
// write-attribute access.
bSkipFile = !(dwCurrentAccess & FILE_WRITE_ATTRIBUTES);
}
// Have the contents changed offline?
if (!bSkipFile &&
((FLAG_CSC_COPY_STATUS_DATA_LOCALLY_MODIFIED
| FLAG_CSC_COPY_STATUS_LOCALLY_CREATED
| FLAG_CSC_COPY_STATUS_LOCALLY_DELETED) & dwStatus))
{
// Yes. Continue if the current user has
// write-data access.
bSkipFile = !(dwCurrentAccess & FILE_WRITE_DATA);
}
}
else
{
//
// We're filling. Continue if the current user has
// read-data access, otherwise skip.
//
bSkipFile = !(dwCurrentAccess & FILE_READ_DATA);
}
}
}
else if (!(pSyncData->dwSyncStatus & SDS_SYNC_OUT))
{
// It's a directory and we're in CSCFillSparseFiles.
//
// Note that we never skip directories when merging (we may be
// interested in a file further down the tree) although we
// can skip directories when filling.
// If it's not in the file selection, skip it.
if (m_pFileList && !m_pFileList->FileExists(pszName, false))
{
bSkipFile = TRUE;
}
}
if (bSkipFile)
{
Trace((TEXT("Skipping: %s"), pszName));
dwResult = CSCPROC_RETURN_SKIP;
pSyncData->dwSyncStatus |= SDS_SYNC_FILE_SKIPPED;
break;
}
Trace((TEXT("File begin: %s"), pszName));
//
// Since we sometimes don't skip directories, even when it turns
// out they have nothing that the current user is interested in,
// don't display directory names in SyncMgr.
//
// If we sync a file farther down the tree, we will display the
// filename and the intervening directory names will be visible
// at that time.
//
if (!(pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
USES_CONVERSION;
// Tell SyncMgr what we're doing
spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT;
spi.lpcStatusText = T2CW(pszName + lstrlen(pSyncData->pszShareName) + 1);
NotifySyncMgr(pSyncData, &spi);
}
// dwParam1 is non-zero when there is a conflict, i.e. both the
// local and remote versions of the file have been modified.
if (dwParam1)
{
if (dwParam2) // indicates server file deleted
{
Trace((TEXT("Delete conflict: %d"), dwParam2));
dwResult = HandleDeleteConflict(pSyncData, pszName, dwStatus, dwHintFlags, pFind32);
}
else
{
Trace((TEXT("Update conflict: %d"), dwParam1));
dwResult = HandleFileConflict(pSyncData, pszName, dwStatus, dwHintFlags, pFind32);
}
}
}
break;
case CSCPROC_REASON_END:
// dwParam2 == error code (winerror.h)
if (3000 <= dwParam2 && dwParam2 <= 3200)
{
// Private error codes used in cscdll
Trace((TEXT("CSC error: %d"), dwParam2));
dwParam2 = NOERROR;
}
else if (ERROR_OPERATION_ABORTED == dwParam2)
{
// We returned CSCPROC_RETURN_ABORT for some reason.
// Whatever it was, we already reported it.
dwParam2 = NOERROR;
dwResult = CSCPROC_RETURN_ABORT;
}
if (lstrlen(pszName) == lstrlen(pSyncData->pszShareName))
{
// SHARE END
TraceAssert(!lstrcmpi(pszName, pSyncData->pszShareName));
Trace((TEXT("Share end: %s"), pszName));
pSyncData->dwSyncStatus &= ~SDS_SYNC_STARTED;
}
else
{
BOOL bUpdateProgress = FALSE;
// FILE END
if (!(pSyncData->dwSyncStatus & SDS_SYNC_FILE_SKIPPED))
{
Trace((TEXT("File end: %s"), pszName));
bUpdateProgress = TRUE;
// Special case errors
switch (dwParam2)
{
case ERROR_ACCESS_DENIED:
if (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes)
{
// 317751 directories are not per-user, so if a
// different user syncs, we can hit this. Don't want
// to show an error message unless we are ignoring
// access (when the user explicitly selected something
// to pin/sync).
//
// 394362 BrianV hit this running as an admin, so don't
// show this error for admins either.
//
if (!(pSyncData->dwSyncStatus & CSC_SYNC_IGNORE_ACCESS))
{
TraceMsg("Suppressing ERROR_ACCESS_DENIED on folder");
dwParam2 = NOERROR;
}
}
break;
case ERROR_GEN_FAILURE:
TraceMsg("Received ERROR_GEN_FAILURE from cscdll");
if (dwStatus & FLAG_CSC_COPY_STATUS_FILE_IN_USE)
dwParam2 = ERROR_OPEN_FILES;
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
// We either handle the error here or the user is
// prompted, so no need for another error message.
dwParam2 = NOERROR;
// If this is an autocache file and has not been modified
// offline, nuke it now. Otherwise, prompt for action.
if (CSCPROC_RETURN_FORCE_OUTWARD == HandleDeleteConflict(pSyncData,
pszName,
dwStatus,
dwHintFlags,
pFind32))
{
dwParam2 = CopyLocalFileWithDriveMapping(pszName,
pszName,
pSyncData->pszShareName,
pSyncData->szDrive,
(FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes));
}
break;
case ERROR_DISK_FULL:
// There's no point continuing
dwResult = CSCPROC_RETURN_ABORT;
break;
default:
// nothing
break;
}
}
else
{
pSyncData->dwSyncStatus &= ~SDS_SYNC_FILE_SKIPPED;
dwParam2 = NOERROR;
// If doing full sync, then we count progress for skipped
// files as well. Not true for quick fill or merge.
if (pSyncData->dwSyncStatus & SDS_SYNC_IN_FULL)
bUpdateProgress = TRUE;
}
// Update progress in SyncMgr
if (bUpdateProgress)
{
pSyncData->cFilesDone++;
spi.mask = SYNCMGRPROGRESSITEM_PROGVALUE;
spi.iProgValue = min(pSyncData->cFilesDone, pSyncData->cFilesToSync - 1);
Trace((TEXT("%d of %d files done"), spi.iProgValue, pSyncData->cFilesToSync));
NotifySyncMgr(pSyncData, &spi);
}
}
if (dwParam2 != NOERROR)
{
UINT idsError = GetErrorFormat(dwParam2, boolify(pSyncData->dwSyncStatus & SDS_SYNC_OUT));
if (IDS_SHARE_CONNECT_ERROR == idsError)
{
LPTSTR pszErr = GetErrorText(dwParam2);
//
// Special-case the "can't connect to share" error.
// Display only the share name in the error message
// and abort the synchronization of this share.
//
LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_ERROR,
idsError,
pSyncData->pszShareName,
pszErr ? pszErr : TEXT(""));
LocalFreeString(&pszErr);
dwResult = CSCPROC_RETURN_ABORT;
}
else
{
LogError(pSyncData->ItemID,
idsError,
pszName,
dwParam2);
}
pSyncData->dwSyncStatus |= SDS_SYNC_ERROR;
}
break;
}
// Check for Cancel
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
{
TraceMsg("Cancelling sync operation");
dwResult = CSCPROC_RETURN_ABORT;
}
TraceLeaveValue(dwResult);
}
void
CCscUpdate::NotifySyncMgr(PSYNCTHREADDATA pSyncData, LPSYNCMGRPROGRESSITEM pspi)
{
LPSYNCMGRSYNCHRONIZECALLBACK pSyncMgr = pSyncData->pThis->m_pSyncMgrCB;
if (pSyncMgr)
{
HRESULT hr = pSyncMgr->Progress(pSyncData->ItemID, pspi);
if (hr == S_SYNCMGR_CANCELITEM || hr == S_SYNCMGR_CANCELALL)
pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED;
}
}
DWORD WINAPI
CCscUpdate::_CscCallback(LPCTSTR pszName,
DWORD dwStatus,
DWORD dwHintFlags,
DWORD dwPinCount,
LPWIN32_FIND_DATA pFind32,
DWORD dwReason,
DWORD dwParam1,
DWORD dwParam2,
DWORD_PTR dwContext)
{
DWORD dwResult = CSCPROC_RETURN_ABORT;
PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)dwContext;
if (pSyncData != NULL && pSyncData->pThis != NULL)
dwResult = pSyncData->pThis->CscCallback(pSyncData,
pszName,
dwStatus,
dwHintFlags,
dwPinCount,
pFind32,
dwReason,
dwParam1,
dwParam2);
return dwResult;
}
BOOL
CCscUpdate::PinLinkTarget(LPCTSTR pszName, PSYNCTHREADDATA pSyncData)
{
BOOL bResult = FALSE;
LPTSTR pszTarget = NULL;
TraceEnter(TRACE_SHELLEX, "PinLinkTarget");
GetLinkTarget(pszName, &pszTarget);
if (pszTarget)
{
DWORD dwAttr = GetFileAttributes(pszTarget);
if ((DWORD)-1 == dwAttr)
ExitGracefully(bResult, FALSE, "Link target not found");
TraceAssert(!(dwAttr & FILE_ATTRIBUTE_DIRECTORY));
// Check for EFS
if ((FILE_ATTRIBUTE_ENCRYPTED & dwAttr) && SkipEFSPin(pSyncData, pszTarget))
ExitGracefully(bResult, FALSE, "Skipping EFS link target");
if (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED))
{
HRESULT hr = m_NoPinList.IsPinAllowed(pszTarget);
if (S_OK == hr)
{
if (CSCPinFile(pszTarget, pSyncData->dwPinHints, NULL, NULL, NULL))
{
WIN32_FIND_DATA fd = {0};
LPCTSTR pszT = PathFindFileName(pszTarget);
fd.dwFileAttributes = dwAttr;
lstrcpyn(fd.cFileName, pszT ? pszT : pszTarget, ARRAYSIZE(fd.cFileName));
ShellChangeNotify(pszTarget, &fd, FALSE);
bResult = TRUE;
if ((FILE_ATTRIBUTE_ENCRYPTED & dwAttr) && !m_bCacheIsEncrypted)
{
LogError(pSyncData->ItemID,
IDS_PIN_ENCRYPT_WARNING,
pszTarget,
NOERROR,
SYNCMGRLOGLEVEL_WARNING);
}
}
}
else if (S_FALSE == hr)
{
if (FILE_ATTRIBUTE_DIRECTORY & dwAttr)
{
LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_WARNING,
IDS_PIN_NOPINFOLDER_POLICY_WARNING,
pszTarget);
}
else
{
LogError(pSyncData->ItemID,
IDS_PIN_NOPINFILE_POLICY_WARNING,
pszTarget,
NOERROR,
SYNCMGRLOGLEVEL_WARNING);
}
}
}
}
exit_gracefully:
LocalFreeString(&pszTarget);
TraceLeaveValue(bResult);
}
BOOL
CCscUpdate::ShouldPinRecurse(LPCTSTR pszName)
{
//
// NTRAID#NTBUG9-508029-2001/12/18-jeffreys
//
// If CSC_SYNC_PIN_RECURSE is set, the answer is always TRUE. Otherwise,
// if we're not pinning files (typically running the FrankAr code), we
// automatically recurse on special folders.
//
return ((m_dwSyncFlags & CSC_SYNC_PIN_RECURSE) ||
(!(m_dwSyncFlags & CSC_SYNC_PINFILES) && !CConfig::GetSingleton().NoAdminPinSpecialFolders() && IsSpecialFolder(pszName)));
}
DWORD WINAPI
CCscUpdate::_PinNewFilesW32Callback(LPCTSTR pszName,
ENUM_REASON eReason,
LPWIN32_FIND_DATA pFind32,
LPARAM lpContext)
{
DWORD dwResult = CSCPROC_RETURN_CONTINUE;
PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)lpContext;
DWORD dwHintFlags = 0;
DWORD dwErr = NOERROR;
LPTSTR pszConnectionName = NULL;
// This callback is used when enumerating a pinned folder looking
// for new files on the server. Since the parent folder is pinned,
// any files in it that aren't pinned get pinned here.
TraceEnter(TRACE_UPDATE, "CCscUpdate::_PinNewFilesW32Callback");
TraceAssert(pSyncData != NULL);
// Check for Cancel
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
{
TraceMsg("Cancelling sync operation");
TraceLeaveValue(CSCPROC_RETURN_ABORT);
}
// Always ignore folder_end and ignore folder_begin if we
// aren't doing a recursive pin operation.
if (eReason == ENUM_REASON_FOLDER_END ||
(eReason == ENUM_REASON_FOLDER_BEGIN && !pSyncData->pThis->ShouldPinRecurse(pszName)))
{
TraceLeaveValue(CSCPROC_RETURN_SKIP);
}
if (eReason == ENUM_REASON_FOLDER_BEGIN)
{
DWORD dwShareStatus = 0;
// Folders may be DFS junctions, so make sure it's cacheable.
if (!ShareIsCacheable(pszName, FALSE, &pszConnectionName, &dwShareStatus))
{
ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "Skipping no-cache folder");
}
}
if (S_FALSE == pSyncData->pThis->m_NoPinList.IsPinAllowed(pszName))
{
if (FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes)
{
pSyncData->pThis->LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_WARNING,
IDS_PIN_NOPINFOLDER_POLICY_WARNING,
pszName);
}
else
{
pSyncData->pThis->LogError(pSyncData->ItemID,
IDS_PIN_NOPINFILE_POLICY_WARNING,
pszName,
NOERROR,
SYNCMGRLOGLEVEL_WARNING);
}
ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "Skipping per no-pin policy");
}
// At this point, we either have 1) a file or 2) folder_begin + recurse,
// so pin anything that isn't pinned.
// Is this file already pinned?
if (!CSCQueryFileStatus(pszName, NULL, NULL, &dwHintFlags))
dwErr = GetLastError();
if (ERROR_FILE_NOT_FOUND == dwErr ||
(NOERROR == dwErr && !(dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN))))
{
// Check for EFS
BOOL bIsEFSFile = (FILE_ATTRIBUTE_ENCRYPTED & pFind32->dwFileAttributes) &&
!(FILE_ATTRIBUTE_DIRECTORY & pFind32->dwFileAttributes);
if (bIsEFSFile && pSyncData->pThis->SkipEFSPin(pSyncData, pszName))
ExitGracefully(dwResult, CSCPROC_RETURN_SKIP, "Skipping EFS file");
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
ExitGracefully(dwResult, CSCPROC_RETURN_ABORT, "Sync cancelled");
// Pin it now.
if (CSCPinFile(pszName, pSyncData->dwPinHints, NULL, NULL, NULL))
{
pSyncData->cFilesToSync++;
ShellChangeNotify(pszName, pFind32, FALSE);
if (bIsEFSFile && !pSyncData->pThis->m_bCacheIsEncrypted)
{
pSyncData->pThis->LogError(pSyncData->ItemID,
IDS_PIN_ENCRYPT_WARNING,
pszName,
NOERROR,
SYNCMGRLOGLEVEL_WARNING);
}
// If this is a link file, pin the target (if appropriate)
LPTSTR pszExtn = PathFindExtension(pszName);
if (pszExtn && !lstrcmpi(pszExtn, c_szLNK))
{
if (pSyncData->pThis->PinLinkTarget(pszName, pSyncData))
pSyncData->cFilesToSync++;
}
}
else
{
DWORD dwError = GetLastError();
UINT idsError = GetErrorFormat(dwError);
if (IDS_SHARE_CONNECT_ERROR == idsError)
{
LPTSTR pszErr = GetErrorText(dwError);
//
// Special-case the "can't connect to share" error.
// Display only the share name in the error message
// and abort the pinning of this share.
//
pSyncData->pThis->LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_ERROR,
idsError,
pSyncData->pszShareName,
pszErr ? pszErr : TEXT(""));
LocalFreeString(&pszErr);
pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED;
}
else
{
DWORD dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_ERROR;
if (ERROR_INVALID_NAME == dwError)
{
//
// File type is in the exclusion list.
// This is a warning, not an error.
//
dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_WARNING;
}
pSyncData->pThis->LogError(pSyncData->ItemID,
IDS_PIN_FILE_ERROR,
pszName,
dwError,
dwSyncMgrLogLevel);
}
pSyncData->dwSyncStatus |= SDS_SYNC_ERROR;
}
LPTSTR pszScanMsg = NULL;
SYNCMGRPROGRESSITEM spi;
spi.cbSize = sizeof(spi);
spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT;
spi.lpcStatusText = L" ";
// Skip the share name
TraceAssert(PathIsPrefix(pSyncData->pszShareName, pszName));
pszName += lstrlen(pSyncData->pszShareName);
if (*pszName == TEXT('\\'))
pszName++;
LPCTSTR pszFile = PathFindFileName(pszName);
TCHAR szPath[MAX_PATH] = TEXT("\\");
if (*pszName)
lstrcpyn(szPath, pszName, (int)min(ARRAYSIZE(szPath),(int)(pszFile-pszName)));
// If we still have a name, build a string like
// "scanning: dir\foo.txt" to display in SyncMgr
if (FormatStringID(&pszScanMsg, g_hInstance, IDS_NEW_SCAN, pszFile, szPath))
{
USES_CONVERSION;
spi.lpcStatusText = T2CW(pszScanMsg);
}
NotifySyncMgr(pSyncData, &spi);
LocalFreeString(&pszScanMsg);
}
else if ((dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)) &&
(pSyncData->pThis->m_dwSyncFlags & CSC_SYNC_PINFILES))
{
// FLAG_CSC_HINT_PIN_USER being set implies that CSCQueryFileStatus
// succeeded above.
// The item was already pinned. Save it in the undo exclusion list.
if (!pSyncData->pUndoExclusionList)
pSyncData->pUndoExclusionList = new CscFilenameList;
if (pSyncData->pUndoExclusionList)
pSyncData->pUndoExclusionList->AddFile(pszName);
}
exit_gracefully:
if (pszConnectionName)
{
WNetCancelConnection2(pszConnectionName, 0, FALSE);
LocalFreeString(&pszConnectionName);
}
TraceLeaveValue(dwResult);
}
DWORD WINAPI
CCscUpdate::_PinNewFilesCSCCallback(LPCTSTR pszName,
ENUM_REASON eReason,
DWORD /*dwStatus*/,
DWORD dwHintFlags,
DWORD /*dwPinCount*/,
LPWIN32_FIND_DATA /*pFind32*/,
LPARAM lpContext)
{
PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)lpContext;
PCSCUPDATE pThis;
// This callback is used when enumerating the CSC database looking
// for pinned folders, with the intention of pinning new files
// in those folders on the server.
TraceEnter(TRACE_UPDATE, "CCscUpdate::_PinNewFilesCSCCallback");
TraceAssert(pSyncData != NULL);
TraceAssert(pSyncData->pThis != NULL);
pThis = pSyncData->pThis;
// Check for Cancel
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
{
TraceMsg("Cancelling sync operation");
TraceLeaveValue(CSCPROC_RETURN_ABORT);
}
// If this isn't a directory with the user hint flag, keep looking.
if (eReason != ENUM_REASON_FOLDER_BEGIN ||
!(dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)))
{
TraceLeaveValue(CSCPROC_RETURN_CONTINUE);
}
// If we have a file list and this directory isn't in the list,
// continue without doing anything here.
if (pSyncData->pThis->m_pFileList &&
!pSyncData->pThis->m_pFileList->FileExists(pszName, false))
{
TraceLeaveValue(CSCPROC_RETURN_CONTINUE);
}
// Ok, we've found a directory with the user hint flag set. Walk
// this directory on the server, pinning any files that aren't pinned.
pSyncData->dwPinHints = dwHintFlags;
_Win32EnumFolder(pszName,
FALSE,
_PinNewFilesW32Callback,
(LPARAM)pSyncData);
TraceLeaveValue(CSCPROC_RETURN_CONTINUE);
}
DWORD WINAPI
CCscUpdate::_SyncThread(LPVOID pThreadData)
{
PSYNCTHREADDATA pSyncData = (PSYNCTHREADDATA)pThreadData;
PCSCUPDATE pThis;
HRESULT hrComInit = E_FAIL;
SYNCMGRPROGRESSITEM spi = {0};
DWORD dwErr = NOERROR;
CSCSHARESTATS shareStats;
CSCGETSTATSINFO si = { SSEF_NONE,
SSUF_NONE,
false, // No access info reqd (faster).
false };
ULONG cDirtyFiles = 0;
ULONG cStaleFiles = 0;
DWORD dwShareStatus = 0;
BOOL bShareOnline = FALSE;
DWORD dwConnectionSpeed = 0;
TraceEnter(TRACE_UPDATE, "CCscUpdate::_SyncThread");
TraceAssert(pSyncData);
TraceAssert(pSyncData->pThis);
TraceAssert(pSyncData->pszShareName && *pSyncData->pszShareName);
pThis = pSyncData->pThis;
spi.cbSize = sizeof(spi);
hrComInit = CoInitialize(NULL);
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
ExitGracefully(dwErr, NOERROR, "Cancelling sync operation");
// Figure out how many files need updating
pSyncData->cFilesDone = 0;
pSyncData->cFilesToSync = 0;
_GetShareStatisticsForUser(pSyncData->pszShareName, &si, &shareStats);
// Get share status
CSCQueryFileStatus(pSyncData->pszShareName, &dwShareStatus, NULL, NULL);
// The root of a special folder is pinned with a pin count, but
// not the user-hint flag, so _GetShareStats doesn't count it.
// We need to count this for some of the checks below.
// (If the share is manual-cache, these look exactly like the
// Siemens scenario in 341786, but we want to sync them.)
if (shareStats.cTotal && pThis->IsSpecialFolderShare(pSyncData->pszShareName))
{
shareStats.cPinned++;
//
// At logoff, we want to run the FrankAr code on all
// 'special' folder shares.
// Customers expect folder redirection of special folders
// to ensure all contents are cached.
//
if (pThis->m_dwSyncFlags & CSC_SYNC_LOGOFF)
{
pSyncData->dwSyncStatus |= SDS_SYNC_FORCE_INWARD;
}
}
if (pThis->m_dwSyncFlags & CSC_SYNC_OUT)
{
cDirtyFiles = shareStats.cModified;
//
// Force the merge code if there are open files, so we are
// sure to do the open file warning. The danger here is that we
// don't warn because the share with open files has nothing dirty,
// but we merge changes on another share and then transition online.
// We don't want to transition online without warning about open files.
//
if (0 == cDirtyFiles)
{
if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus) &&
(FLAG_CSC_SHARE_STATUS_FILES_OPEN & dwShareStatus))
{
cDirtyFiles++;
}
}
}
if (pThis->m_dwSyncFlags & CSC_SYNC_IN_FULL)
{
// For full inward sync, always set cStaleFiles to at least 1 to force
// a call to CSCFillSparseFiles.
// Also, we get callbacks for each file and folder, even if they
// are not sparse or stale, so go with cTotal here to make
// the progress bar look right.
cStaleFiles = max(shareStats.cTotal, 1);
}
else if (pThis->m_dwSyncFlags & CSC_SYNC_IN_QUICK)
{
cStaleFiles = shareStats.cSparse;
// If we're pinning, then it's possible that nothing is sparse yet,
// but we'll need to call CSCFillSparseFiles.
if (pThis->m_dwSyncFlags & CSC_SYNC_PINFILES)
pSyncData->dwSyncStatus |= SDS_SYNC_FORCE_INWARD;
}
// Self-host notification callback
CSCUI_NOTIFYHOOK((CSCH_SyncShare, TEXT("Sync: %1, Dirty: %2!d!, Stale: %3!d!"), pSyncData->pszShareName, cDirtyFiles, cStaleFiles));
if (dwShareStatus & FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP)
{
// Can't call CSCFillSparseFiles when disconnected (it just fails)
cStaleFiles = 0;
}
else if ((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_MANUAL_REINT
&& 0 == shareStats.cPinned
&& !(pThis->m_dwSyncFlags & CSC_SYNC_PINFILES))
{
// On a manual share, if nothing is pinned (and we aren't pinning)
// then we prefer not to call CSCFillSparseFiles on the share.
// Raid #341786
Trace((TEXT("Manual cache share '%s' has only autocached files"), pSyncData->pszShareName));
cStaleFiles = 0;
}
pSyncData->cFilesToSync = cDirtyFiles + cStaleFiles;
//
// At this point, if pSyncData->cFilesToSync is nonzero, then we are doing
// a sync, and will be calling CSCBeginSynchronization to connect to the
// share (with prompt for credentials if necessary).
//
// If SDS_SYNC_FORCE_INWARD is on, then we are pinning files. We will only
// call CSCFillSparseFiles is the server is in connected mode, and we will
// only call CSCBeginSynchronization if pSyncData->cFilesToSync is nonzero
// (to pin something, you must already have a connection to the share).
//
if (0 == pSyncData->cFilesToSync && !(pSyncData->dwSyncStatus & SDS_SYNC_FORCE_INWARD))
ExitGracefully(dwErr, NOERROR, "Nothing to synchronize");
// Tell SyncMgr how many files we're updating
spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE
| SYNCMGRPROGRESSITEM_PROGVALUE | SYNCMGRPROGRESSITEM_MAXVALUE;
spi.dwStatusType = SYNCMGRSTATUS_UPDATING;
spi.iProgValue = 0;
spi.iMaxValue = pSyncData->cFilesToSync;
Trace((TEXT("%d files to sync on %s"), spi.iMaxValue, pSyncData->pszShareName));
NotifySyncMgr(pSyncData, &spi);
#if 0
// RAS isn't notifying RDR before returning success, so
// do this bogus delay. Remove this when RAS is fixed.
//
// This was fixed a few weeks before Windows 2000 RTM.
// [jeffreys - 1/24/2000]
//
if (pSyncData->dwSyncStatus & SDS_SYNC_RAS_CONNECTED)
{
//
// We've just established a RAS connection, but there's a timing
// problem with RDR. Wait until we can connect to the share, but
// don't wait longer than RAS_CONNECT_DELAY.
//
Trace((TEXT("RAS delay for %s"), pSyncData->pszShareName));
DWORD dwDelayTime = GetTickCount() + RAS_CONNECT_DELAY;
#ifdef DEBUG
int cAttempts = 0;
#endif
while (!bShareOnline &&
GetTickCount() < dwDelayTime &&
!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED))
{
WaitForSingleObject(pThis->m_hSyncMutex, INFINITE);
#ifdef DEBUG
cAttempts++;
#endif
bShareOnline = CSCBeginSynchronization(pSyncData->pszShareName,
&dwConnectionSpeed,
&pSyncData->dwCscContext);
ReleaseMutex(pThis->m_hSyncMutex);
}
Trace((TEXT("%s %s after %d attempts (%dms)"),
pSyncData->pszShareName,
(bShareOnline ? TEXT("connected") : TEXT("not reachable")),
cAttempts,
GetTickCount() - (dwDelayTime - RAS_CONNECT_DELAY)));
}
else
#endif // RAS delay
if (pSyncData->cFilesToSync)
{
//
// CSCBeginSynchronization makes a net connection to the share
// using the "interactive" flag. This causes a credential popup
// if the current user doesn't have access to the share.
// Use the sync mutex to avoid multiple concurrent popups.
//
WaitForSingleObject(pThis->m_hSyncMutex, INFINITE);
bShareOnline = CSCBeginSynchronization(pSyncData->pszShareName,
&dwConnectionSpeed,
&pSyncData->dwCscContext);
ReleaseMutex(pThis->m_hSyncMutex);
}
if (pSyncData->cFilesToSync && !bShareOnline)
{
// The share isn't reachable, so there's no point in continuing.
dwErr = GetLastError();
if (ERROR_CANCELLED == dwErr)
{
// The user cancelled the credential popup
pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED;
ExitGracefully(dwErr, NOERROR, "User cancelled sync");
}
LPTSTR pszErr = GetErrorText(dwErr);
pThis->LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_ERROR,
IDS_SHARE_CONNECT_ERROR,
pSyncData->pszShareName,
pszErr);
LocalFreeString(&pszErr);
ExitGracefully(dwErr, dwErr, "Share not reachable");
}
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
ExitGracefully(dwErr, NOERROR, "Cancelling sync operation");
// Note the time of this sync
pThis->SetLastSyncTime(pSyncData->pszShareName);
// Merge
if (0 != cDirtyFiles)
{
dwErr = pThis->MergeShare(pSyncData);
if (NOERROR != dwErr)
{
LPTSTR pszErr = GetErrorText(dwErr);
pThis->LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_ERROR,
IDS_MERGE_SHARE_ERROR,
pSyncData->pszShareName,
pszErr);
LocalFreeString(&pszErr);
ExitGracefully(dwErr, dwErr, "Aborting due to merge error");
}
}
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
ExitGracefully(dwErr, NOERROR, "Cancelling sync operation");
// Fill
if (0 != cStaleFiles || (pSyncData->dwSyncStatus & SDS_SYNC_FORCE_INWARD))
{
dwErr = pThis->FillShare(pSyncData, shareStats.cPinned, dwConnectionSpeed);
}
exit_gracefully:
// If we called CSCBeginSynchronization and it succeeded,
// we need to call CSCEndSynchronization.
if (bShareOnline)
CSCEndSynchronization(pSyncData->pszShareName, pSyncData->dwCscContext);
// Tell SyncMgr that we're done (succeeded, failed, or stopped)
spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE | SYNCMGRPROGRESSITEM_STATUSTEXT
| SYNCMGRPROGRESSITEM_PROGVALUE | SYNCMGRPROGRESSITEM_MAXVALUE;
spi.dwStatusType = SYNCMGRSTATUS_SUCCEEDED; // Assume success
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
spi.dwStatusType = SYNCMGRSTATUS_STOPPED;
if (NOERROR != dwErr || (pSyncData->dwSyncStatus & SDS_SYNC_ERROR))
spi.dwStatusType = SYNCMGRSTATUS_FAILED;
spi.lpcStatusText = L" ";
spi.iProgValue = spi.iMaxValue = pSyncData->cFilesToSync; // This tells syncmgr that the item is done
NotifySyncMgr(pSyncData, &spi);
if ((pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
&& (pThis->m_dwSyncFlags & CSC_SYNC_PINFILES))
{
// We cancelled a pin operation, roll back to the previous state
CscUnpinFileList(pThis->m_pFileList,
(pThis->m_dwSyncFlags & CSC_SYNC_PIN_RECURSE),
(FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus),
pSyncData->pszShareName,
_UndoProgress,
(LPARAM)pSyncData);
}
// Tell the Update Handler that this thread is exiting
// This may use OLE to notify SyncMgr that the sync is done,
// so do this before CoUninitialize.
Trace((TEXT("%s finished"), pSyncData->pszShareName));
pThis->SyncThreadCompleted(pSyncData);
if (SUCCEEDED(hrComInit))
CoUninitialize();
delete pSyncData->pUndoExclusionList;
LocalFree(pSyncData);
// Release our ref on the object
// (also release our ref on the DLL)
pThis->Release();
TraceLeave();
FreeLibraryAndExitThread(g_hInstance, dwErr);
return 0;
}
DWORD
CCscUpdate::MergeShare(PSYNCTHREADDATA pSyncData)
{
DWORD dwErr = NOERROR;
BOOL bMergeResult = TRUE;
TraceEnter(TRACE_UPDATE, "CCscUpdate::MergeShare");
// CSCMergeShare fails if another thread (or process) is
// currently merging. This is because CSCMergeShare uses
// a drive letter connection to the share to bypass CSC,
// and we don't want to use up all of the drive letters.
// So let's protect the call to CSCMergeShare with a mutex
// (rather than dealing with failure and retrying, etc.)
WaitForSingleObject(m_hSyncMutex, INFINITE);
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
ExitGracefully(dwErr, NOERROR, "Merge cancelled");
//
// It would be nice to skip the open file warning if we knew
// that the open files were on a "silent folder". The best
// we can do, though, is detect that the open files are on
// the same share as a silent folder. There's no guarantee
// that the open files are not from a different folder on
// the same share, so we have to show the warning.
//
//if (!IsSilentShare(pSyncData->pszShareName))
{
DWORD dwShareStatus = 0;
CSCQueryFileStatus(pSyncData->pszShareName, &dwShareStatus, NULL, NULL);
if (FLAG_CSC_SHARE_STATUS_FILES_OPEN & dwShareStatus)
{
if (CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags)
{
// Only show this warning once per sync (not per thread)
if (!(CSC_SYNC_OFWARNINGDONE & m_dwSyncFlags))
{
m_dwSyncFlags |= CSC_SYNC_OFWARNINGDONE;
//
// This dialog we're going to display can use one of
// two templates.
//
// 1. Single user logged on. Tell user to close all files.
// Dialog provides [OK] and [Cancel] options. User can
// choose to continue or cancel.
//
// 2. Multiple users logged on. Tell user that sync cannot
// be performed with multiple users logged on. Dialog
// presents only an [OK] button. However, it's ID is
// IDCANCEL so pressing it will cause us to stop the
// merge.
//
if (IDOK != OpenFilesWarningDialog())
{
TraceMsg("Cancelling sync - user bailed at open file warning");
SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED);
}
}
// else we already put up the warning on another thread. If the
// user cancelled, SDS_SYNC_CANCELLED will be set.
}
else
{
// Don't merge, but continue otherwise.
LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_WARNING,
IDS_OPENFILE_MERGE_WARNING,
pSyncData->pszShareName);
ExitGracefully(dwErr, NOERROR, "Skipping merge due to open files");
}
}
}
//
// Conflict resolution may require stopping and restarting CSCMergeShare,
// so do this in a loop
//
while (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED))
{
Trace((TEXT("Calling CSCMergeShare(%s)"), pSyncData->pszShareName));
pSyncData->dwSyncStatus = SDS_SYNC_OUT;
bMergeResult = CSCMergeShare(pSyncData->pszShareName,
CCscUpdate::_CscCallback,
(DWORD_PTR)pSyncData);
Trace((TEXT("CSCMergeShare(%s) returned"), pSyncData->pszShareName));
// Do we need to merge again?
if (!(SDS_SYNC_RESTART_MERGE & pSyncData->dwSyncStatus))
break;
}
if (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED) && !bMergeResult)
{
dwErr = GetLastError();
if (ERROR_OPERATION_ABORTED == dwErr)
dwErr = NOERROR;
}
exit_gracefully:
ReleaseMutex(m_hSyncMutex);
TraceLeaveValue(dwErr);
}
DWORD
CCscUpdate::FillShare(PSYNCTHREADDATA pSyncData, int cPinned, DWORD dwConnectionSpeed)
{
DWORD dwErr = NOERROR;
DWORD dwShareStatus = 0;
DWORD dwShareHints = 0;
TraceEnter(TRACE_UPDATE, "CCscUpdate::FillShare");
CSCQueryFileStatus(pSyncData->pszShareName, &dwShareStatus, NULL, &dwShareHints);
//
// At logoff, we want to run the FrankAr code on all
// 'special' folder shares.
// Customers expect folder redirection of special folders
// to ensure all contents are cached.
//
if ((m_dwSyncFlags & CSC_SYNC_IN_FULL) ||
((m_dwSyncFlags & CSC_SYNC_LOGOFF) && IsSpecialFolderShare(pSyncData->pszShareName)))
{
pSyncData->dwSyncStatus = SDS_SYNC_IN_FULL;
Trace((TEXT("Full sync at %d00 bps"), dwConnectionSpeed));
//
// Check the server for new files that should be pinned.
//
// We can't do this when disconnected. Also, this is
// time consuming, so don't do it on a slow connection.
//
if (!(FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus)
&& cPinned && !_PathIsSlow(dwConnectionSpeed))
{
//
// Look for pinned folders on this share by enumerating
// in the CSC database. Go out to the server only if/when
// we find a pinned folder.
//
TraceMsg("Running FrankAr code");
//
if (CConfig::GetSingleton().AlwaysPinSubFolders())
{
//
// If the "AlwaysPinSubFolders" policy is set, we
// do a recursive pin. This will cause any content
// (including folders) of a pinned folder to become pinned.
//
pSyncData->pThis->m_dwSyncFlags |= CSC_SYNC_PIN_RECURSE;
}
// First check the root folder
if (_PinNewFilesCSCCallback(pSyncData->pszShareName,
ENUM_REASON_FOLDER_BEGIN,
0,
dwShareHints,
0,
NULL,
(LPARAM)pSyncData) == CSCPROC_RETURN_CONTINUE)
{
_CSCEnumDatabase(pSyncData->pszShareName,
TRUE,
_PinNewFilesCSCCallback,
(LPARAM)pSyncData);
}
TraceMsg("FrankAr code complete");
}
}
else
{
pSyncData->dwSyncStatus = SDS_SYNC_IN_QUICK;
if (m_dwSyncFlags & CSC_SYNC_PINFILES)
{
//
// Enumerate the file list and pin everything, checking with
// SyncMgr periodically.
//
PinFiles(pSyncData);
}
}
if (m_pConflictPinList)
{
// Make sure that any files we created because of merge
// conflicts are pinned.
PinFiles(pSyncData, TRUE);
}
// Can't fill when disconnected
if (!(FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus))
{
// Clear the status text and update the max count in case we
// pinned somthing above
SYNCMGRPROGRESSITEM spi;
spi.cbSize = sizeof(spi);
spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT | SYNCMGRPROGRESSITEM_MAXVALUE;
spi.lpcStatusText = L" ";
spi.iMaxValue = pSyncData->cFilesToSync;
Trace((TEXT("%d files to sync on %s"), spi.iMaxValue, pSyncData->pszShareName));
NotifySyncMgr(pSyncData, &spi);
if (!(pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED))
{
Trace((TEXT("Calling CSCFillSparseFiles(%s, %s)"), pSyncData->pszShareName, (pSyncData->dwSyncStatus & SDS_SYNC_IN_FULL) ? TEXT("full") : TEXT("quick")));
if (!CSCFillSparseFiles(pSyncData->pszShareName,
!!(pSyncData->dwSyncStatus & SDS_SYNC_IN_FULL),
CCscUpdate::_CscCallback,
(DWORD_PTR)pSyncData))
{
dwErr = GetLastError();
if (ERROR_OPERATION_ABORTED == dwErr)
dwErr = NOERROR;
}
Trace((TEXT("CSCFillSparseFiles(%s) complete"), pSyncData->pszShareName));
}
}
else
{
Trace((TEXT("Skipping CSCFillSparseFiles(%s) - server is offline"), pSyncData->pszShareName));
}
TraceLeaveValue(dwErr);
}
void
CCscUpdate::PinFiles(PSYNCTHREADDATA pSyncData, BOOL bConflictPinList)
{
CscFilenameList *pfnl;
CscFilenameList::HSHARE hShare;
LPCTSTR pszFile;
TraceEnter(TRACE_UPDATE, "CCscUpdate::PinFiles");
TraceAssert((m_dwSyncFlags & CSC_SYNC_PINFILES) || bConflictPinList);
pfnl = m_pFileList;
if (bConflictPinList)
{
pfnl = m_pConflictPinList;
}
if (!pfnl ||
!pfnl->GetShareHandle(pSyncData->pszShareName, &hShare))
{
TraceLeaveVoid();
}
CscFilenameList::FileIter fi = pfnl->CreateFileIterator(hShare);
// Iterate over the filenames associated with the share.
while (pszFile = fi.Next())
{
TCHAR szFullPath[MAX_PATH];
TCHAR szRelativePath[MAX_PATH];
WIN32_FIND_DATA fd;
ULONG cchFile = lstrlen(pszFile) + 1; // include NULL
// Check for Cancel
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
break;
ZeroMemory(&fd, sizeof(fd));
// Directories have a trailing "\*"
if (StrChr(pszFile, TEXT('*')))
{
// It's a directory. Trim off the "\*"
cchFile -= 2;
// When pinning at the share level, pszFile points to "*"
// and cchFile is now zero.
}
szRelativePath[0] = TEXT('\0');
lstrcpyn(szRelativePath, pszFile, (int)min(cchFile, ARRAYSIZE(szRelativePath)));
// Build the full path
PathCombine(szFullPath, pSyncData->pszShareName, szRelativePath);
// Get attributes and test for existence
fd.dwFileAttributes = GetFileAttributes(szFullPath);
if ((DWORD)-1 == fd.dwFileAttributes)
continue;
if (S_FALSE == m_NoPinList.IsPinAllowed(szFullPath))
{
//
// Policy says don't pin this file/folder.
//
if (FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes)
{
LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_WARNING,
IDS_PIN_NOPINFOLDER_POLICY_WARNING,
szFullPath);
}
else
{
LogError(pSyncData->ItemID,
IDS_PIN_NOPINFILE_POLICY_WARNING,
szFullPath,
NOERROR,
SYNCMGRLOGLEVEL_WARNING);
}
continue;
}
pszFile = PathFindFileName(szFullPath);
lstrcpyn(fd.cFileName, pszFile ? pszFile : szFullPath, ARRAYSIZE(fd.cFileName));
// Check for EFS
BOOL bIsEFSFile;
bIsEFSFile = (FILE_ATTRIBUTE_ENCRYPTED & fd.dwFileAttributes) &&
!(FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes);
if (bIsEFSFile && SkipEFSPin(pSyncData, szFullPath))
continue;
if (pSyncData->dwSyncStatus & SDS_SYNC_CANCELLED)
break;
// Pin it
pSyncData->dwPinHints = FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_INHERIT_USER;
if (CSCPinFile(szFullPath, pSyncData->dwPinHints, NULL, NULL, NULL))
{
if (bConflictPinList && m_pFileList)
m_pFileList->AddFile(szFullPath, !!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
pSyncData->cFilesToSync++;
ShellChangeNotify(szFullPath, &fd, FALSE);
if (bIsEFSFile && !m_bCacheIsEncrypted)
{
LogError(pSyncData->ItemID,
IDS_PIN_ENCRYPT_WARNING,
szFullPath,
NOERROR,
SYNCMGRLOGLEVEL_WARNING);
}
}
else
{
DWORD dwError = GetLastError();
UINT idsError = GetErrorFormat(dwError);
if (IDS_SHARE_CONNECT_ERROR == idsError)
{
LPTSTR pszErr = GetErrorText(dwError);
//
// Special-case the "can't connect to share" error.
// Display only the share name in the error message
// and abort the pinning of this share.
//
LogError(pSyncData->ItemID,
SYNCMGRLOGLEVEL_ERROR,
idsError,
pSyncData->pszShareName,
pszErr ? pszErr : TEXT(""));
LocalFreeString(&pszErr);
pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED;
}
else
{
DWORD dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_ERROR;
if (ERROR_INVALID_NAME == dwError)
{
//
// File type is in the exclusion list.
// This is a warning, not an error.
//
dwSyncMgrLogLevel = SYNCMGRLOGLEVEL_WARNING;
}
LogError(pSyncData->ItemID,
IDS_PIN_FILE_ERROR,
szFullPath,
dwError,
dwSyncMgrLogLevel);
}
pSyncData->dwSyncStatus |= SDS_SYNC_ERROR;
}
// If it's a directory, pin its contents
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
_Win32EnumFolder(szFullPath,
!bConflictPinList && ShouldPinRecurse(szFullPath),
CCscUpdate::_PinNewFilesW32Callback,
(LPARAM)pSyncData);
}
}
// Flush the shell notify queue
ShellChangeNotify(NULL, TRUE);
TraceLeaveVoid();
}
void
CCscUpdate::NotifyUndo(PSYNCTHREADDATA pSyncData, LPCTSTR pszName)
{
LPTSTR pszMsg;
SYNCMGRPROGRESSITEM spi;
spi.cbSize = sizeof(spi);
spi.mask = SYNCMGRPROGRESSITEM_STATUSTEXT;
spi.lpcStatusText = L" ";
// Skip the share name
if (PathIsPrefix(pSyncData->pszShareName, pszName))
{
pszName += lstrlen(pSyncData->pszShareName);
if (*pszName == TEXT('\\'))
pszName++;
}
LPCTSTR pszFile = PathFindFileName(pszName);
TCHAR szPath[MAX_PATH] = TEXT("\\");
if (*pszName)
lstrcpyn(szPath, pszName, (int)min(ARRAYSIZE(szPath),(int)(pszFile-pszName)));
// If we still have a name, build a string like
// "undo: dir\foo.txt" to display in SyncMgr
if (FormatStringID(&pszMsg, g_hInstance, IDS_UNDO_SCAN, pszFile, szPath))
{
USES_CONVERSION;
spi.lpcStatusText = T2CW(pszMsg);
}
NotifySyncMgr(pSyncData, &spi);
LocalFreeString(&pszMsg);
}
DWORD WINAPI
CCscUpdate::_UndoProgress(LPCTSTR pszItem, LPARAM lpContext)
{
PSYNCTHREADDATA pSyncData = reinterpret_cast<PSYNCTHREADDATA>(lpContext);
if (pSyncData->pUndoExclusionList &&
pSyncData->pUndoExclusionList->FileExists(pszItem))
{
return CSCPROC_RETURN_SKIP;
}
// Update SyncMgr
pSyncData->pThis->NotifyUndo(pSyncData, pszItem);
return CSCPROC_RETURN_CONTINUE;
}
//
// The user's response to the "confirm pin encrypted" dialog is encoded
// to fit into the return value from EndDialog. The PINEFS_XXXXX macros
// describe this encoding.
//
// Bits 0 and 1 represent the user's [Yes][No][Cancel] choice.
//
#define PINEFS_YES 0x00000001
#define PINEFS_NO 0x00000002
#define PINEFS_CANCEL 0x00000003
#define PINEFS_YNC_MASK 0x00000003
//
// Bit 31 indicates if the user checked the "Apply to all" checkbox.
//
#define PINEFS_APPLYTOALL 0x80000000
//
// Convenience macros for indicating yes-to-all and no-to-all.
//
#define PINEFS_NO_TOALL (PINEFS_NO | PINEFS_APPLYTOALL)
#define PINEFS_YES_TOALL (PINEFS_YES | PINEFS_APPLYTOALL)
//
// Returns (by way of EndDialog) one of the PINEFS_XXXXXX codes.
//
// PINEFS_YES - Pin this file but ask again on the next.
// PINEFS_YES_TOALL - Pin this file and all encrypted files encountered.
// PINEFS_NO - Don't pin this file but ask again on the next.
// PINEFS_NO_TOALL - Don't pin this file nor any other encrypted files.
// PINEFS_CANCEL - Don't pin this file. Cancel entire operation.
//
INT_PTR CALLBACK
ConfirmEFSPinProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
INT_PTR nResult = 0;
switch (uMsg)
{
case WM_INITDIALOG:
{
LPTSTR pszMsg = NULL;
LPCTSTR pszFile = PathFindFileName((LPCTSTR)lParam);
FormatStringID(&pszMsg, g_hInstance, IDS_FMT_PIN_EFS_MSG, pszFile);
if (pszMsg)
{
SetDlgItemText(hDlg, IDC_EFS_MSG, pszMsg);
LocalFree(pszMsg);
}
else
{
//
// Let's be safe. On failure we won't pin an encrypted file
// to a non-encrypted cache.
//
EndDialog(hDlg, PINEFS_NO_TOALL);
}
}
nResult = TRUE;
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDYES:
EndDialog(hDlg, BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_PINEFS_APPLYTOALL) ? PINEFS_YES_TOALL : PINEFS_YES);
nResult = TRUE;
break;
case IDNO:
EndDialog(hDlg, BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_PINEFS_APPLYTOALL) ? PINEFS_NO_TOALL : PINEFS_NO);
nResult = TRUE;
break;
case IDCANCEL:
EndDialog(hDlg, PINEFS_CANCEL);
nResult = TRUE;
break;
default:
break;
}
break;
}
return nResult;
}
BOOL
CCscUpdate::SkipEFSPin(PSYNCTHREADDATA pSyncData, LPCTSTR pszItem)
{
BOOL bSkip = FALSE;
if (!m_bCacheIsEncrypted)
{
int iResult;
EnterCriticalSection(&m_csThreadList);
if ((CSC_SYNC_EFS_PIN_NONE & m_dwSyncFlags) ||
!(CSC_SYNC_MAYBOTHERUSER & m_dwSyncFlags))
{
iResult = PINEFS_NO;
}
else if (CSC_SYNC_EFS_PIN_ALL & m_dwSyncFlags)
{
iResult = PINEFS_YES;
}
else
{
// Suspend the other sync threads
SetSyncThreadStatus(SyncPause, pSyncData->ItemID);
iResult = (int)DialogBoxParam(g_hInstance,
MAKEINTRESOURCE(IDD_CONFIRM_PIN_EFS),
m_hwndDlgParent,
ConfirmEFSPinProc,
(LPARAM)pszItem);
if (PINEFS_APPLYTOALL & iResult)
{
//
// User checked the "apply to all" checkbox.
// Persist a [Yes][No] button selection.
//
if (PINEFS_NO == (PINEFS_YNC_MASK & iResult))
{
m_dwSyncFlags |= CSC_SYNC_EFS_PIN_NONE;
}
else if (PINEFS_YES == (PINEFS_YNC_MASK & iResult))
{
m_dwSyncFlags |= CSC_SYNC_EFS_PIN_ALL;
}
}
// Resume syncing
SetSyncThreadStatus(SyncResume, pSyncData->ItemID);
}
LeaveCriticalSection(&m_csThreadList);
switch (PINEFS_YNC_MASK & iResult)
{
default:
case PINEFS_NO:
bSkip = TRUE;
break;
case PINEFS_YES:
// continue
break;
case PINEFS_CANCEL:
// stop all threads
SetItemStatus(GUID_NULL, SYNCMGRSTATUS_STOPPED);
break;
}
}
return bSkip;
}
HRESULT
CCscUpdate::SetSyncThreadStatus(eSetSyncStatus status, REFGUID rItemID)
{
// Assume success here. If we don't find the thread,
// it's probably already finished.
HRESULT hr = S_OK;
BOOL bOneItem;
TraceEnter(TRACE_UPDATE, "CCscUpdate::SetSyncThreadStatus");
bOneItem = (SyncStop == status && !IsEqualGUID(rItemID, GUID_NULL));
EnterCriticalSection(&m_csThreadList);
if (NULL != m_hSyncThreads)
{
int cItems = DPA_GetPtrCount(m_hSyncThreads);
SYNCMGRPROGRESSITEM spi = {0};
DWORD (WINAPI *pfnStartStop)(HANDLE);
pfnStartStop = ResumeThread;
spi.cbSize = sizeof(spi);
spi.mask = SYNCMGRPROGRESSITEM_STATUSTYPE | SYNCMGRPROGRESSITEM_STATUSTEXT;
spi.lpcStatusText = L" ";
spi.dwStatusType = SYNCMGRSTATUS_UPDATING;
if (SyncPause == status)
{
spi.dwStatusType = SYNCMGRSTATUS_PAUSED;
pfnStartStop = SuspendThread;
}
while (cItems > 0)
{
PSYNCTHREADDATA pSyncData;
--cItems;
pSyncData = (PSYNCTHREADDATA)DPA_FastGetPtr(m_hSyncThreads, cItems);
TraceAssert(NULL != pSyncData);
if (SyncStop == status)
{
// Tell the thread to abort
if (!bOneItem || IsEqualGUID(rItemID, pSyncData->ItemID))
{
pSyncData->dwSyncStatus |= SDS_SYNC_CANCELLED;
if (bOneItem)
break;
}
}
else
{
// Suspend or resume the thread if it's not the current thread
if (!IsEqualGUID(rItemID, pSyncData->ItemID))
(*pfnStartStop)(pSyncData->hThread);
m_pSyncMgrCB->Progress(pSyncData->ItemID, &spi);
}
}
}
LeaveCriticalSection(&m_csThreadList);
TraceLeaveResult(hr);
}
HRESULT
CCscUpdate::GetSilentFolderList(void)
{
HRESULT hr = S_OK;
delete m_pSilentFolderList;
m_pSilentFolderList = new CscFilenameList;
delete m_pSpecialFolderList;
m_pSpecialFolderList = new CscFilenameList;
if (NULL == m_pSilentFolderList || NULL == m_pSpecialFolderList)
{
delete m_pSilentFolderList;
m_pSilentFolderList = NULL;
delete m_pSpecialFolderList;
m_pSpecialFolderList = NULL;
hr = E_OUTOFMEMORY;
}
else
{
BuildSilentFolderList(m_pSilentFolderList, m_pSpecialFolderList);
if (0 == m_pSilentFolderList->GetShareCount())
{
delete m_pSilentFolderList;
m_pSilentFolderList = NULL;
}
if (0 == m_pSpecialFolderList->GetShareCount())
{
delete m_pSpecialFolderList;
m_pSpecialFolderList = NULL;
}
}
return hr;
}
void
BuildSilentFolderList(CscFilenameList *pfnlSilentFolders,
CscFilenameList *pfnlSpecialFolders)
{
//
// We will silently handle sync conflicts in any of the folders
// below that have a '1' after them.
//
// If we get complaints about conflicts in folders that we
// think we can handle silently and safely, add them.
//
// Note that CSIDL_PERSONAL (MyDocs) and CSIDL_MYPICTURES
// should probably never be silent, since the user
// interacts with them directly.
//
// This list corresponds to the list of shell folders that may
// be redirected. See also
// HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
//
static const int s_csidlFolders[][2] =
{
{ CSIDL_PROGRAMS, 0 },
{ CSIDL_PERSONAL, 0 },
{ CSIDL_FAVORITES, 0 },
{ CSIDL_STARTUP, 0 },
{ CSIDL_RECENT, 1 },
{ CSIDL_SENDTO, 0 },
{ CSIDL_STARTMENU, 1 },
{ CSIDL_DESKTOPDIRECTORY, 0 },
{ CSIDL_NETHOOD, 0 },
{ CSIDL_TEMPLATES, 0 },
{ CSIDL_APPDATA, 1 },
{ CSIDL_PRINTHOOD, 0 },
{ CSIDL_MYPICTURES, 0 },
{ CSIDL_PROFILE, 1 },
{ CSIDL_ADMINTOOLS, 0 },
};
TCHAR szPath[MAX_PATH];
for (int i = 0; i < ARRAYSIZE(s_csidlFolders); i++)
{
if (SHGetSpecialFolderPath(NULL,
szPath,
s_csidlFolders[i][0] | CSIDL_FLAG_DONT_VERIFY,
FALSE))
{
// We only want UNC net paths
LPTSTR pszUNC = NULL;
GetRemotePath(szPath, &pszUNC);
if (!pszUNC)
continue;
if (s_csidlFolders[i][1])
{
if (pfnlSilentFolders)
pfnlSilentFolders->AddFile(pszUNC, true);
}
else
{
if (pfnlSpecialFolders)
pfnlSpecialFolders->AddFile(pszUNC, true);
}
LocalFreeString(&pszUNC);
}
}
}
///////////////////////////////////////////////////////////////////////////////
// //
// SyncMgr integration (ISyncMgrEnumItems) //
// //
///////////////////////////////////////////////////////////////////////////////
CUpdateEnumerator::CUpdateEnumerator(PCSCUPDATE pUpdate)
: m_cRef(1),
m_pUpdate(pUpdate),
m_hFind(INVALID_HANDLE_VALUE),
m_bEnumFileSelection(FALSE),
m_cCheckedItemsEnumerated(0)
{
DllAddRef();
if (m_pUpdate)
{
m_pUpdate->AddRef();
if (m_pUpdate->m_pFileList)
{
m_bEnumFileSelection = TRUE;
m_SelectionIterator = m_pUpdate->m_pFileList->CreateShareIterator();
}
}
}
CUpdateEnumerator::~CUpdateEnumerator()
{
if (m_hFind != INVALID_HANDLE_VALUE)
CSCFindClose(m_hFind);
DoRelease(m_pUpdate);
DllRelease();
}
STDMETHODIMP CUpdateEnumerator::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CUpdateEnumerator, ISyncMgrEnumItems),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CUpdateEnumerator::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CUpdateEnumerator::Release()
{
if (InterlockedDecrement(&m_cRef))
return m_cRef;
delete this;
return 0;
}
STDMETHODIMP
CUpdateEnumerator::Next(ULONG celt, LPSYNCMGRITEM rgelt, PULONG pceltFetched)
{
HRESULT hr = S_OK;
ULONG cFetched = 0;
LPSYNCMGRITEM pItem = rgelt;
WIN32_FIND_DATA fd = {0};
DWORD dwShareStatus = 0;
DWORD dwSyncFlags;
CscFilenameList::HSHARE hShare;
LPCTSTR pszShareName = NULL;
TraceEnter(TRACE_UPDATE, "CUpdateEnumerator::Next");
TraceAssert(m_pUpdate != NULL);
if (NULL == rgelt)
TraceLeaveResult(E_INVALIDARG);
dwSyncFlags = m_pUpdate->m_dwSyncFlags;
while (cFetched < celt)
{
CSCEntry *pShareEntry;
CSCSHARESTATS shareStats;
CSCGETSTATSINFO si = { SSEF_NONE,
SSUF_TOTAL | SSUF_PINNED | SSUF_MODIFIED | SSUF_SPARSE | SSUF_DIRS,
false,
false };
if (m_bEnumFileSelection)
{
if (!m_SelectionIterator.Next(&hShare))
break;
pszShareName = m_pUpdate->m_pFileList->GetShareName(hShare);
CSCQueryFileStatus(pszShareName, &dwShareStatus, NULL, NULL);
fd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
lstrcpyn(fd.cFileName, pszShareName, ARRAYSIZE(fd.cFileName));
}
else
{
if (m_hFind == INVALID_HANDLE_VALUE)
{
m_hFind = CacheFindFirst(NULL, &fd, &dwShareStatus, NULL, NULL, NULL);
if (m_hFind == INVALID_HANDLE_VALUE)
{
// The database is empty, so there's nothing to enumerate..
break;
}
pszShareName = fd.cFileName;
}
else if (CacheFindNext(m_hFind, &fd, &dwShareStatus, NULL, NULL, NULL))
{
pszShareName = fd.cFileName;
}
else
break;
}
TraceAssert(pszShareName);
//
// This was proposed as a fix for part of 383011. However,
// that bug only applies in a multi-user scenario, and if there
// are more than 3 users using the machine, this would cause a
// user whose SID had been expelled from the CSC database to not
// be able to sync a share where they do indeed have access.
//
// // If the current user has no access to the share, don't enumerate it.
// if (!(CscAccessUser(dwShareStatus) || CscAccessGuest(dwShareStatus)))
// continue;
// Count the # of pinned files, sparse files, etc.
_GetShareStatisticsForUser(pszShareName, &si, &shareStats);
// The root of a special folder is pinned with a pin count, but
// not the user-hint flag, so _GetShareStats doesn't count it.
// We need to count this for some of the checks below.
// (If the share is manual-cache, these look exactly like the
// Siemens scenario in 341786, but we want to sync them.)
if (shareStats.cTotal && m_pUpdate->IsSpecialFolderShare(pszShareName))
{
shareStats.cPinned++;
if (dwSyncFlags & CSC_SYNC_LOGOFF)
{
//
// At logoff, we want to run the FrankAr code on all
// 'special' folder shares.
// Customers expect folder redirection of special folders
// to ensure all contents are cached.
//
dwSyncFlags |= CSC_SYNC_IN_FULL;
}
}
// If we're pinning, then even if nothing is sparse now,
// there will be sparse files after we pin them.
if (dwSyncFlags & CSC_SYNC_PINFILES)
{
shareStats.cSparse++;
shareStats.cTotal++;
}
// If there's nothing cached on this share, then don't even
// enumerate it to SyncMgr. This avoids listing extra junk
// in SyncMgr.
if ((0 == shareStats.cTotal) ||
(shareStats.cTotal == shareStats.cDirs && 0 == shareStats.cPinned))
{
// Either there is nothing cached for this share, or the only
// things found were unpinned dirs (no files, no pinned dirs).
// The second case can happen if you delete files from the viewer,
// in which case you think you deleted everything but the viewer
// doesn't show directories, so they weren't deleted.
Trace((TEXT("Nothing cached on %s, not enumerating"), pszShareName));
continue;
}
if ((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_NO_CACHING)
{
//
// Don't enumerate "no-cache" shares if there's nothing to merge.
//
// These can exist in the cache if the share was previously
// cacheable, but has since been changed to "no caching".
//
// If there's something to merge, we should still sync it to
// get everything squared away.
//
if (!((dwSyncFlags & CSC_SYNC_OUT) && (shareStats.cModified)))
{
Trace((TEXT("Not enumerating no-cache share %s"), pszShareName));
continue;
}
Trace((TEXT("Enumerating no-cache share %s with offline changes."), pszShareName));
}
// Explorer has shut down by the time we do a logoff sync. Hide the
// Properties button at logoff so we don't end up restarting Explorer.
pItem->dwFlags = (dwSyncFlags & CSC_SYNC_LOGOFF) ? 0 : SYNCMGRITEM_HASPROPERTIES;
if ((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_MANUAL_REINT
&& 0 == shareStats.cPinned
&& !(dwSyncFlags & CSC_SYNC_PINFILES))
{
// On a manual share, if nothing is pinned (and we aren't pinning)
// then we don't want to call CSCFillSparseFiles on the share.
// Raid #341786
Trace((TEXT("Manual cache share '%s' has only autocached files"), pszShareName));
// However, if there is something to merge, then we need to sync.
if (!((dwSyncFlags & CSC_SYNC_OUT) && shareStats.cModified))
{
Trace((TEXT("Not enumerating manual-cache share %s"), pszShareName));
continue;
}
// There is something to merge, so enumerate the share but
// tell SyncMgr that it's temporary so it doesn't save state
// for this share.
pItem->dwFlags |= SYNCMGRITEM_TEMPORARY;
}
//
// In some circumstances, we may want to merge even if there
// are no modified files, in order to show the open files warning.
//
// See comments in CCscUpdate::_SyncThread
//
if (0 == shareStats.cModified)
{
if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus) &&
(FLAG_CSC_SHARE_STATUS_FILES_OPEN & dwShareStatus))
{
shareStats.cModified++;
}
}
// Enumerate this share
cFetched++;
// Get existing share entry or create a new one
pShareEntry = m_pUpdate->m_ShareLog.Add(pszShareName);
if (!pShareEntry)
TraceLeaveResult(E_OUTOFMEMORY);
pItem->cbSize = sizeof(SYNCMGRITEM);
pItem->ItemID = pShareEntry->Guid();
pItem->hIcon = g_hCscIcon;
// SYNCMGRITEM_TEMPORARY causes items to not show up in
// SyncMgr's logon/logoff settings page. Raid #237288
//if (0 == shareStats.cPinned)
// pItem->dwFlags |= SYNCMGRITEM_TEMPORARY;
if (ERROR_SUCCESS == m_pUpdate->GetLastSyncTime(pszShareName, &pItem->ftLastUpdate))
pItem->dwFlags |= SYNCMGRITEM_LASTUPDATETIME;
//
// Determine whether this share needs syncing.
//
// At settings time, assume everything needs syncing (check everything)
//
// If outbound, shares with modified files are checked
// If inbound (full), shares with sparse or pinned files are checked
// If inbound (quick), shares with sparse files are checked
//
// Anything else doesn't need to be sync'ed at this time (unchecked)
//
pItem->dwItemState = SYNCMGRITEMSTATE_CHECKED;
if (!(dwSyncFlags & CSC_SYNC_SETTINGS) &&
!((dwSyncFlags & CSC_SYNC_OUT) && shareStats.cModified) &&
!((dwSyncFlags & CSC_SYNC_IN_FULL) && shareStats.cTotal ) &&
!((dwSyncFlags & CSC_SYNC_IN_QUICK) && shareStats.cSparse ))
{
pItem->dwItemState = SYNCMGRITEMSTATE_UNCHECKED;
}
// Get friendly share name here
LPITEMIDLIST pidl = NULL;
SHFILEINFO sfi = {0};
if (SUCCEEDED(SHSimpleIDListFromFindData(pszShareName, &fd, &pidl)))
{
SHGetFileInfo((LPCTSTR)pidl,
0,
&sfi,
sizeof(sfi),
SHGFI_PIDL | SHGFI_DISPLAYNAME);
SHFree(pidl);
}
if (TEXT('\0') != sfi.szDisplayName[0])
pszShareName = sfi.szDisplayName;
SHTCharToUnicode((LPTSTR)pszShareName, pItem->wszItemName, ARRAYSIZE(pItem->wszItemName));
if (SYNCMGRITEMSTATE_CHECKED == pItem->dwItemState)
{
m_cCheckedItemsEnumerated++;
Trace((TEXT("Enumerating %s, checked"), pszShareName));
}
else
{
Trace((TEXT("Enumerating %s, unchecked"), pszShareName));
}
pItem++;
}
if (pceltFetched)
*pceltFetched = cFetched;
if (cFetched != celt)
hr = S_FALSE;
if ((S_FALSE == hr) &&
0 == m_cCheckedItemsEnumerated &&
(CSC_SYNC_SHOWUI_ALWAYS & dwSyncFlags))
{
//
// Special-case where we're synching nothing but still
// want to display SyncMgr progress UI. We enumerate a
// special string rather than a share name for display in
// the status UI. Force hr == S_OK so the caller will accept
// this "dummy" item. Next() will be called once more but
// m_cCheckedItemsEnumerated will be 1 so this block won't be
// entered and we'll return S_FALSE indicating the end of the
// enumeration.
//
pItem->cbSize = sizeof(SYNCMGRITEM);
pItem->hIcon = g_hCscIcon;
pItem->dwFlags = 0;
pItem->dwItemState = SYNCMGRITEMSTATE_CHECKED;
pItem->ItemID = GUID_CscNullSyncItem;
UINT idString = IDS_NULLSYNC_ITEMNAME;
if ((CSC_SYNC_OUT & dwSyncFlags) &&
!((CSC_SYNC_IN_QUICK | CSC_SYNC_IN_FULL) & dwSyncFlags))
{
// Use different text if we are only merging
idString = IDS_NULLMERGE_ITEMNAME;
}
LoadStringW(g_hInstance,
idString,
pItem->wszItemName,
ARRAYSIZE(pItem->wszItemName));
m_cCheckedItemsEnumerated = 1;
TraceMsg("Enumerating NULL item");
hr = S_OK;
}
TraceLeaveResult(hr);
}
STDMETHODIMP
CUpdateEnumerator::Skip(ULONG celt)
{
return Next(celt, NULL, NULL);
}
STDMETHODIMP
CUpdateEnumerator::Reset()
{
m_cCheckedItemsEnumerated = 0;
if (m_bEnumFileSelection)
{
m_SelectionIterator.Reset();
}
else if (m_hFind != INVALID_HANDLE_VALUE)
{
CSCFindClose(m_hFind);
m_hFind = INVALID_HANDLE_VALUE;
}
return S_OK;
}
STDMETHODIMP
CUpdateEnumerator::Clone(LPSYNCMGRENUMITEMS *ppenum)
{
return E_NOTIMPL;
}