//+-------------------------------------------------------------------------
//
//  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;
}
