//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1997 - 1999
//
//  File:       statdlg.cpp
//
//--------------------------------------------------------------------------

#include "pch.h"
#pragma hdrstop

#include "statdlg.h"
#include "resource.h"
#include "folder.h"
#include "cscst.h"
#include "options.h"
#include "fopendlg.h"
#include "msgbox.h"
#include "strings.h"

// Global used for IsDialogMessage processing
HWND g_hwndStatusDlg = NULL;

CStatusDlg::CStatusDlg(
    HINSTANCE hInstance,
    LPCTSTR pszText,
    eSysTrayState eState,
    Modes mode            // Optional.  Default is MODE_NORMAL
    ) : m_hInstance(hInstance),
        m_hwndDlg(NULL),
        m_hwndLV(NULL),
        m_himl(NULL),
        m_mode(mode),
        m_bExpanded(true),
        m_bSortAscending(true),
        m_iLastColSorted(-1),
        m_eSysTrayState(eState),
        m_cyExpanded(0),
        m_pszText(StrDup(pszText)) 
{ 

}

CStatusDlg::~CStatusDlg(
    void
    ) 
{ 
    if (NULL != m_pszText)
    {
        LocalFree(m_pszText);
    }
}

int
CStatusDlg::Create(
    HWND hwndParent,
    LPCTSTR pszText,
    eSysTrayState eState,
    Modes mode
    )
{
    int iResult = 0;
    CStatusDlg *pdlg = new CStatusDlg(g_hInstance, pszText, eState, mode);
    if (pdlg)
    {
        iResult = pdlg->Run(hwndParent);
        if (!iResult)
            delete pdlg;
        // else pdlg is automatically deleted when the dialog is closed
    }
    return iResult;
}

//
// Run the status dialog as a modeless dialog.
// Activates an existing instance if one is available.
//
int
CStatusDlg::Run(
    HWND hwndParent
    )
{
    //
    // First activate an existing instance if one is already running.
    //
    int iResult = 0;
    TCHAR szDlgTitle[MAX_PATH];
    LoadString(m_hInstance, IDS_STATUSDLG_TITLE, szDlgTitle, ARRAYSIZE(szDlgTitle));

    m_hwndDlg = FindWindow(NULL, szDlgTitle);
    if (NULL != m_hwndDlg && IsWindow(m_hwndDlg) && IsWindowVisible(m_hwndDlg))
    {
        SetForegroundWindow(m_hwndDlg);
    }
    else
    {
        //
        // Otherwise create a new dialog.
        // We need to use CreateDialog rather than DialogBox because
        // sometimes the dialog is hidden.  DialogBox doesn't allow us to
        // change the visibility attributed defined in the dialog template.
        //
        m_hwndDlg = CreateDialogParam(m_hInstance,
                                      MAKEINTRESOURCE(IDD_CSCUI_STATUS),
                                      hwndParent,
                                      DlgProc,
                                      (LPARAM)this);
        if (NULL != m_hwndDlg)
        {
            MSG msg;

            ShowWindow(m_hwndDlg, MODE_NORMAL == m_mode ? SW_NORMAL : SW_HIDE);
            UpdateWindow(m_hwndDlg);

            // We don't need a message loop here. We're on systray's main
            // thread which has a message pump.

            iResult = 1;
        }
    }
    g_hwndStatusDlg = m_hwndDlg;
    return iResult;
}



void
CStatusDlg::Destroy(
    void
    )
{
    if (NULL != m_hwndDlg)
        DestroyWindow(m_hwndDlg);
    g_hwndStatusDlg = NULL;
}



INT_PTR CALLBACK 
CStatusDlg::DlgProc(
    HWND hwnd, 
    UINT uMsg, 
    WPARAM wParam, 
    LPARAM lParam
    )
{
    CStatusDlg *pThis = (CStatusDlg *)GetWindowLongPtr(hwnd, DWLP_USER);

    BOOL bResult = FALSE;
    switch(uMsg)
    {
        case WM_INITDIALOG:
        {
            pThis = (CStatusDlg *)lParam;
            SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)pThis);
            pThis->m_hwndDlg = hwnd;
            bResult = pThis->OnInitDialog(wParam, lParam);
            break;
        }

        case WM_COMMAND:
            if (NULL != pThis)
                bResult = pThis->OnCommand(wParam, lParam);
            break;

        case WM_NOTIFY:
            bResult = pThis->OnNotify(wParam, lParam);
            break;

        case WM_DESTROY:
            pThis->OnDestroy();
            break;
    }
    return bResult;
}


//
// WM_INITDIALOG handler.
//
BOOL
CStatusDlg::OnInitDialog(
    WPARAM wParam,
    LPARAM lParam
    )
{
    TCHAR szScratch[MAX_PATH];
    BOOL bResult = TRUE;
    RECT rcExpanded;
    CConfig& config = CConfig::GetSingleton();

    m_hwndLV = GetDlgItem(m_hwndDlg, IDC_LV_STATUSDLG);
    //
    // Center the dialog on the desktop before contraction.
    //
    CenterWindow(m_hwndDlg, GetDesktopWindow());
    //
    // Start with the dialog not expanded.
    //
    GetWindowRect(m_hwndDlg, &rcExpanded);
    m_cyExpanded = rcExpanded.bottom - rcExpanded.top;
    //
    // Set the cached "expanded" member to be the opposite of the user's
    // preference for expansion.  ExpandDialog will only change the
    // expanded state if it's different from the current state.
    //
    m_bExpanded = !UserLikesDialogExpanded();
    ExpandDialog(!m_bExpanded);
    //
    // Disable buttons as necessary.
    //
    if (config.NoCacheViewer())
        EnableWindow(GetDlgItem(m_hwndDlg, IDC_BTN_VIEWFILES), FALSE);
    if (config.NoConfigCache())
        EnableWindow(GetDlgItem(m_hwndDlg, IDC_BTN_SETTINGS), FALSE);
    //
    // Initialize the message text.
    //
    SetWindowText(GetDlgItem(m_hwndDlg, IDC_TXT_STATUSDLG), m_pszText ? m_pszText : TEXT(""));
    //
    // Turn on checkboxes for column 0.
    //
    EnableListviewCheckboxes(true);
    //
    // Create the imagelist.
    //
    m_himl = CreateImageList();
    if (NULL != m_himl)
        ListView_SetImageList(m_hwndLV, m_himl, LVSIL_SMALL);
    //
    // Create the listview columns.
    //
    CreateListColumns();
    //
    // Fill the listview.
    //
    FillListView();

    if (MODE_AUTOSYNC == m_mode)
    {
        //
        // The dialog is being invoked for it's synchronize function only.
        // The dialog will not be displayed but we'll invoke the synchronize 
        // function just as if it had been displayed.  This feature is used 
        // by the systray context menu to ensure we get the same synchronize 
        // behavior if the action is invoked through either the dialog or 
        // the systray context menu.
        //
        PostMessage(m_hwndDlg, WM_COMMAND, IDOK, 0);
    }
    else
    {
        //
        // Since we're a child of the hidden systray window we need to force ourselves
        // to the forground.
        //
        SetForegroundWindow(m_hwndDlg);
    }

    return bResult;
}


//
// WM_DESTROY handler.
//
BOOL
CStatusDlg::OnDestroy(
    void
    )
{
    RememberUsersDialogSizePref(m_bExpanded);
    DestroyLVEntries();

    //
    // Destroy the CStatusDlg object
    //
    delete this;

    //
    // Image list is automatically destroyed by the listview in comctl32.
    //
    return FALSE;
}


//
// WM_COMMAND handler.
//
BOOL
CStatusDlg::OnCommand(
    WPARAM wParam,
    LPARAM lParam
    )
{
    BOOL bResult = TRUE;
    switch(LOWORD(wParam))
    {
        case IDOK:
            SynchronizeServers();
            // Fall through and destroy the dialog
        case IDCANCEL:
        case IDCLOSE:
            Destroy();
            break;
            
        case IDC_BTN_VIEWFILES:
            COfflineFilesFolder::Open();
            break;

        case IDC_BTN_SETTINGS:
            COfflineFilesSheet::CreateAndRun(g_hInstance, GetDesktopWindow(), &g_cRefCount);
            break;

        case IDC_BTN_DETAILS:
            ExpandDialog(!m_bExpanded);
            break;

        default:
            bResult = FALSE;
            break;  
    }
    return bResult;
}


//
// WM_NOTIFY handler.
//
BOOL
CStatusDlg::OnNotify(
    WPARAM wParam,
    LPARAM lParam
    )
{
    BOOL bResult = TRUE;
  //int idCtl    = int(wParam);
    LPNMHDR pnm  = (LPNMHDR)lParam;

    switch(pnm->code)
    {
        case LVN_GETDISPINFO:
            OnLVN_GetDispInfo((LV_DISPINFO *)lParam);
            break;

        case LVN_COLUMNCLICK:
            OnLVN_ColumnClick((NM_LISTVIEW *)lParam);
            break;

        default:
            bResult = FALSE;
            break;
    }
    return bResult;
}

//
// LVN_GETDISPINFO handler.
//
void
CStatusDlg::OnLVN_GetDispInfo(
    LV_DISPINFO *plvdi
    )
{
    LVEntry *pEntry = (LVEntry *)plvdi->item.lParam;
    if (LVIF_TEXT & plvdi->item.mask)
    {
        static TCHAR szText[MAX_PATH];
        szText[0] = TEXT('\0');
        switch(plvdi->item.iSubItem)
        {
            case iLVSUBITEM_SERVER:
                lstrcpyn(szText, pEntry->Server(), ARRAYSIZE(szText));
                break;
                
            case iLVSUBITEM_STATUS:
                pEntry->GetStatusText(szText, ARRAYSIZE(szText));
                break;

            case iLVSUBITEM_INFO:
                pEntry->GetInfoText(szText, ARRAYSIZE(szText));
                break;
        }
        plvdi->item.pszText = szText;
    }
    if (LVIF_IMAGE & plvdi->item.mask)
    {
        plvdi->item.iImage = pEntry->GetImageIndex();
    }
}

//
// LVN_COLUMNCLICK handler.
//
void
CStatusDlg::OnLVN_ColumnClick(
    NM_LISTVIEW *pnmlv
    )
{
    if (m_iLastColSorted != pnmlv->iSubItem)
    {
        m_bSortAscending = true;
        m_iLastColSorted = pnmlv->iSubItem;
    }
    else
    {
        m_bSortAscending = !m_bSortAscending;
    }

    ListView_SortItems(m_hwndLV, CompareLVItems, LPARAM(this));
}


//
// Create the server listview columns.
//
void
CStatusDlg::CreateListColumns(
    void
    )
{
    //
    // Clear out the listview and header.
    //
    ListView_DeleteAllItems(m_hwndLV);
    HWND hwndHeader = ListView_GetHeader(m_hwndLV);
    if (NULL != hwndHeader)
    {
        while(0 < Header_GetItemCount(hwndHeader))
            ListView_DeleteColumn(m_hwndLV, 0);
    }

    //
    // Create the header titles.
    //
    TCHAR szServer[80] = {0};
    TCHAR szStatus[80] = {0};
    TCHAR szInfo[80]   = {0};
    LoadString(m_hInstance, IDS_STATUSDLG_HDR_SERVER, szServer, ARRAYSIZE(szServer));
    LoadString(m_hInstance, IDS_STATUSDLG_HDR_STATUS, szStatus, ARRAYSIZE(szStatus));
    LoadString(m_hInstance, IDS_STATUSDLG_HDR_INFO,   szInfo,   ARRAYSIZE(szInfo));

    RECT rcList;
    GetClientRect(m_hwndLV, &rcList);
    int cxLV = rcList.right - rcList.left;

#define LVCOLMASK (LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM)

    LV_COLUMN rgCols[] = { 
         { LVCOLMASK, LVCFMT_LEFT, cxLV/4, szServer, 0, iLVSUBITEM_SERVER },
         { LVCOLMASK, LVCFMT_LEFT, cxLV/4, szStatus, 0, iLVSUBITEM_STATUS },
         { LVCOLMASK, LVCFMT_LEFT, cxLV/2, szInfo,   0, iLVSUBITEM_INFO   }
                         };
    //
    // Add the columns to the listview.
    //
    for (INT i = 0; i < ARRAYSIZE(rgCols); i++)
    {
        ListView_InsertColumn(m_hwndLV, i, &rgCols[i]);
    }
}



//
// Populate the listview.
//
void
CStatusDlg::FillListView(
    void
    )
{
    DWORD dwStatus;
    DWORD dwPinCount;
    DWORD dwHintFlags;
    FILETIME ft;
    WIN32_FIND_DATA fd;

    CCscFindHandle hFind = CacheFindFirst(NULL, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft);
    if (hFind.IsValid())
    {
        LPTSTR pszServer = fd.cFileName;
        LPTSTR pszEnd;
        LVEntry *pEntry;
        CSCSHARESTATS stats;
        do
        {
            //
            // Exclude the following:
            //   1. Directories.
            //   2. Files marked as "locally deleted".
            //
            // NOTE:  The filtering done by this function must be the same as 
            //        in several other places throughout the CSCUI code.
            //        To locate these, search the source for the comment
            //        string CSCUI_ITEM_FILTER.
            //
            const DWORD fExclude = SSEF_LOCAL_DELETED | 
                                   SSEF_DIRECTORY;

            CSCGETSTATSINFO si = { fExclude,
                                   SSUF_NONE,
                                   false,      // No access info reqd (faster).
                                   false };     

            if (_GetShareStatisticsForUser(fd.cFileName, &si, &stats) && 
                (0 < stats.cTotal || stats.bOffline))
            {
                bool bReplacedBackslash = false;
                //
                // Extract the server name from the share name returned by CSC.
                //
                while(*pszServer && TEXT('\\') == *pszServer)
                    pszServer++;
                pszEnd = pszServer;
                while(*pszEnd && TEXT('\\') != *pszEnd)
                    pszEnd++;
                if (TEXT('\\') == *pszEnd)
                {
                    *pszEnd = TEXT('\0');
                    bReplacedBackslash = true;
                }
                //
                // Find an existing server entry.  If none found, create a new one.
                //
                if (NULL == (pEntry = FindLVEntry(pszServer)))
                {
                    bool bConnectable = boolify(SendToSystray(CSCWM_ISSERVERBACK, 0, (LPARAM)fd.cFileName));
                    pEntry = CreateLVEntry(pszServer, bConnectable);
                }
                if (NULL != pEntry)
                {
                    if (bReplacedBackslash)
                        *pszEnd = TEXT('\\');

                    //
                    // If we're running in "normal" mode, we 
                    // can't trust the share's "modified offline" bit.
                    // Use the info we got by scanning the cache.
                    // If we're running in "autosync" mode, we can just
                    // use the share's "modified offline" indicator.
                    // If something is truly modified offline, the bit
                    // will be set.
                    //
                    if (MODE_NORMAL == m_mode)
                    {
                        dwStatus &= ~FLAG_CSC_SHARE_STATUS_MODIFIED_OFFLINE;
                        if (0 < stats.cModified)
                            dwStatus |= FLAG_CSC_SHARE_STATUS_MODIFIED_OFFLINE;
                    }
                    //
                    // Add this share and it's statistics to the 
                    // server's list entry.
                    //
                    pEntry->AddShare(fd.cFileName, stats, dwStatus);
                }
            }
        }
        while(CacheFindNext(hFind, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft));
        //
        // Remove those servers that the user won't be interested in.
        // Also place a checkmark next to those servers that are available
        // for reconnection.
        //
        PrepListForDisplay();
    }
}


//
// Build the image list used by the server listview.
//
HIMAGELIST
CStatusDlg::CreateImageList(
    void
    )
{
    HIMAGELIST himl = NULL;

    //
    // Note:  The order of these icon ID's in this array must match with the
    //        iIMAGELIST_ICON_XXXXX enumeration.
    //        The enum values represent the image indices in the image list.
    //
    static const struct IconDef
    {
        LPTSTR szName;
        HINSTANCE hInstance;

    } rgIcons[] = { 
                    { MAKEINTRESOURCE(IDI_SERVER),         m_hInstance },
                    { MAKEINTRESOURCE(IDI_SERVER_OFFLINE), m_hInstance },
                    { MAKEINTRESOURCE(IDI_CSCINFORMATION), m_hInstance },
                    { MAKEINTRESOURCE(IDI_CSCWARNING),     m_hInstance }
                  };
    //
    // Create the image lists for the listview.
    //
    int cxIcon = GetSystemMetrics(SM_CXSMICON);
    int cyIcon = GetSystemMetrics(SM_CYSMICON);

    himl = ImageList_Create(cxIcon,
                            cyIcon,
                            ILC_MASK, 
                            ARRAYSIZE(rgIcons), 
                            10);
    if (NULL != himl)
    {
        for (UINT i = 0; i < ARRAYSIZE(rgIcons); i++)
        {
            HICON hIcon = (HICON)LoadImage(rgIcons[i].hInstance, 
                                           rgIcons[i].szName,
                                           IMAGE_ICON,
                                           cxIcon,
                                           cyIcon,
                                           0);
            if (NULL != hIcon)
            {
                ImageList_AddIcon(himl, hIcon);
                DestroyIcon(hIcon);
            }
        }
        ImageList_SetBkColor(himl, CLR_NONE);  // Transparent background.
    }

    return himl;
}


void
CStatusDlg::EnableListviewCheckboxes(
    bool bEnable
    )
{    
    DWORD dwStyle    = ListView_GetExtendedListViewStyle(m_hwndLV);
    DWORD dwNewStyle = bEnable ? (dwStyle | LVS_EX_CHECKBOXES) :
                                 (dwStyle & ~LVS_EX_CHECKBOXES);
                                 
    ListView_SetExtendedListViewStyle(m_hwndLV, dwNewStyle);
}


//
// The "Details" button changes it's title depending on 
// the dialog state (expanded or not).
//
void
CStatusDlg::UpdateDetailsBtnTitle(
    void
    )
{
    TCHAR szBtnTitle[80];
    int idsBtnTitle = IDS_OPENDETAILS;
    if (m_bExpanded)
        idsBtnTitle = IDS_CLOSEDETAILS;

    LoadString(m_hInstance, idsBtnTitle, szBtnTitle, ARRAYSIZE(szBtnTitle));
    SetWindowText(GetDlgItem(m_hwndDlg, IDC_BTN_DETAILS), szBtnTitle);
}

//
// Expand or contract the dialog vertically.
// When expanded, the server listview is made visible along
// with the "Settings..." and "View Files..." buttons.
//
void 
CStatusDlg::ExpandDialog(
    bool bExpand
    )
{
    if (bExpand != m_bExpanded)
    {
        CConfig& config = CConfig::GetSingleton();
        //
        // Table describing enable/disable state of controls in the lower part
        // of the dialog that are displayed when the dialog is expanded.
        //
        struct
        {
            UINT idCtl;
            bool bEnable;

        } rgidExpanded[] = { { IDC_LV_STATUSDLG,  bExpand },
                             { IDC_BTN_SETTINGS,  bExpand && !config.NoConfigCache() },
                             { IDC_BTN_VIEWFILES, bExpand && !config.NoCacheViewer() }
                           };

        RECT rcDlg;
        GetWindowRect(m_hwndDlg, &rcDlg);
        if (!bExpand)
        {
            //
            // Closing details.
            //
            RECT rcSep;
            GetWindowRect(GetDlgItem(m_hwndDlg, IDC_SEP_STATUSDLG), &rcSep);
            rcDlg.bottom = rcSep.top;
        }
        else
        {
            //
            // Opening details.
            //
            rcDlg.bottom = rcDlg.top + m_cyExpanded;
        }

        //
        // If the dialog is not expanded, we want to disable all of the
        // "tabbable" items in the hidden part so they don't participate
        // in the dialog's tab order.  Note that the "Settings" and
        // "View Files" buttons also have a policy setting involved in
        // the enabling logic.
        //
        for (int i = 0; i < ARRAYSIZE(rgidExpanded); i++)
        {
            EnableWindow(GetDlgItem(m_hwndDlg, rgidExpanded[i].idCtl), rgidExpanded[i].bEnable);
        }

        SetWindowPos(m_hwndDlg,
                     NULL,
                     rcDlg.left,
                     rcDlg.top,
                     rcDlg.right - rcDlg.left,
                     rcDlg.bottom - rcDlg.top,
                     SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);

        m_bExpanded = bExpand;
        UpdateDetailsBtnTitle();
    }
}




//
// Queries the HKCU reg data to see if the user closed the dialog
// expanded or not expanded last time the dialog was used.
// Returns:  true = expanded, false = not expanded.
//
bool
CStatusDlg::UserLikesDialogExpanded(
    void
    )
{
    DWORD dwExpanded = 0;
    HKEY hkey;
    DWORD dwStatus = RegOpenKeyEx(HKEY_CURRENT_USER,
                                  REGSTR_KEY_OFFLINEFILES,
                                  0,
                                  KEY_QUERY_VALUE,
                                  &hkey);
    if (ERROR_SUCCESS == dwStatus)
    {
        DWORD dwType;
        DWORD cbData = sizeof(DWORD);
        RegQueryValueEx(hkey,
                        REGSTR_VAL_EXPANDSTATUSDLG,
                        NULL,
                        &dwType,
                        (LPBYTE)&dwExpanded,
                        &cbData);
        RegCloseKey(hkey);
    }
    return !!dwExpanded;
}


//
// Stores the current state of the status dialog in per-user
// reg data.  Used next time the dialog is opened so that if the
// user likes the dialog expanded, it opens expanded.
//
void
CStatusDlg::RememberUsersDialogSizePref(
    bool bExpanded
    )
{
    HKEY hkey;
    DWORD dwStatus = RegOpenKeyEx(HKEY_CURRENT_USER,
                                  REGSTR_KEY_OFFLINEFILES,
                                  0,
                                  KEY_SET_VALUE,
                                  &hkey);

    if (ERROR_SUCCESS == dwStatus)
    {
        DWORD cbData = sizeof(DWORD);
        DWORD dwExpanded = DWORD(bExpanded);
        RegSetValueEx(hkey,
                      REGSTR_VAL_EXPANDSTATUSDLG,
                      0,
                      REG_DWORD,
                      (CONST BYTE *)&dwExpanded,
                      cbData);

        RegCloseKey(hkey);
    }
}



//
// Build a list of share names for synchronization and
// reconnect.
//
HRESULT 
CStatusDlg::BuildFilenameList(
    CscFilenameList *pfnl
    )
{
    LVEntry *pEntry;
    LVITEM item;

    int cEntries = ListView_GetItemCount(m_hwndLV);
    for (int i = 0; i < cEntries; i++)
    {
        if (ListView_GetCheckState(m_hwndLV, i))
        {
            //
            // Server has checkmark so we add it's
            // shares to the filename list.
            //
            item.mask     = LVIF_PARAM;
            item.iItem    = i;
            item.iSubItem = 0;
            if (ListView_GetItem(m_hwndLV, &item))
            {
                pEntry = (LVEntry *)item.lParam;
                int cShares = pEntry->GetShareCount();
                for (int iShare = 0; iShare < cShares; iShare++)
                {
                    LPCTSTR pszShare = pEntry->GetShareName(iShare);
                    if (NULL != pszShare)
                    {
                        if (!pfnl->AddFile(pszShare, TRUE))
                            return E_OUTOFMEMORY;
                    }
                }
            }
        }
    }
    return NOERROR;
}


//
// Synchronize all of the checked servers from the listview and
// reconnect them.
//
HRESULT
CStatusDlg::SynchronizeServers(
    void
    )
{
    HRESULT hr = NOERROR;
    const bool bSkipTheSync = BST_CHECKED == IsDlgButtonChecked(m_hwndDlg, IDC_CBX_STATUSDLG);
    
    if (::IsSyncInProgress())
    {
        CscMessageBox(m_hwndDlg, 
                      MB_OK | MB_ICONINFORMATION, 
                      m_hInstance, 
                      bSkipTheSync ? IDS_CANTRECONN_SYNCINPROGRESS : IDS_CANTSYNC_SYNCINPROGRESS);
    }
    else
    {
        //
        // First build the FilenameList containing shares to sync.
        //
        CscFilenameList fnl;
        hr = BuildFilenameList(&fnl);
        if (SUCCEEDED(hr))
        {
            if (bSkipTheSync)
            {
                //
                // User has checked "reconnect without sync" checkbox.
                // Therefore, we skip the sync and go straight to reconnect.
                //
                hr = ReconnectServers(&fnl, TRUE, FALSE);
                if (S_OK == hr)
                {
                    PostToSystray(CSCWM_UPDATESTATUS, STWM_STATUSCHECK, 0);
                }

            }
            else
            {
                const DWORD dwUpdateFlags = CSC_UPDATE_STARTNOW |
                                            CSC_UPDATE_SELECTION |
                                            CSC_UPDATE_REINT |
                                            CSC_UPDATE_NOTIFY_DONE |
                                            CSC_UPDATE_SHOWUI_ALWAYS |
                                            CSC_UPDATE_RECONNECT;
                //
                // Syncing is an asynchronous operation involving
                // mobsync.exe.  The code in CscUpdateCache will check for open
                // files and notify the user.  When the sync is done, it will
                // transition everything in the file list to online mode.
                //
                hr = CscUpdateCache(dwUpdateFlags, &fnl);
            }
        }
    }
    return hr;
}


//
// Create an entry for the listview.
// Returns ptr to new entry on success.  NULL on failure.
//
CStatusDlg::LVEntry *
CStatusDlg::CreateLVEntry(
    LPCTSTR pszServer,
    bool bConnectable
    )
{
    LVEntry *pEntry = new LVEntry(m_hInstance, pszServer, bConnectable);
    if (NULL != pEntry)
    {
        LVITEM item;
        item.mask     = LVIF_PARAM | LVIF_TEXT | LVIF_IMAGE;
        item.lParam   = (LPARAM)pEntry;
        item.iItem    = ListView_GetItemCount(m_hwndLV);
        item.iSubItem = 0;
        item.pszText  = LPSTR_TEXTCALLBACK;
        item.iImage   = I_IMAGECALLBACK;
        if (-1 == ListView_InsertItem(m_hwndLV, &item))
        {
            delete pEntry;
            pEntry = NULL;
        }
    }
    return pEntry;
}

//
// Find an entry in the listview using the servername as the key.
// Return ptr to entry on success.  NULL on failure.
//
CStatusDlg::LVEntry *
CStatusDlg::FindLVEntry(
    LPCTSTR pszServer
    )
{
    LVEntry *pEntry = NULL;
    LVITEM item;

    int cEntries = ListView_GetItemCount(m_hwndLV);
    for (int i = 0; i < cEntries; i++)
    {
        item.mask     = LVIF_PARAM;
        item.iItem    = i;
        item.iSubItem = 0;
        if (ListView_GetItem(m_hwndLV, &item))
        {
            pEntry = (LVEntry *)item.lParam;
            //
            // This comparison must be case-INSENSITIVE.  Entries
            // in the CSC database are on a "\\server\share" basis and
            // are at the mercy of what was passed in through the CSC APIs.
            // Therefore, the database can contain "\\Foo\bar" and
            // "\\foo\bar2".  We must treat "Foo" and "foo" as the single
            // server they represent.
            //
            if (0 == lstrcmpi(pEntry->Server(), pszServer))
                break;
            pEntry = NULL;
        }
    }
    return pEntry;
}

//
// Clear out the listview.  Ensures all listview item objects
// are destroyed.
//
void
CStatusDlg::DestroyLVEntries(
    void
    )
{
    LVITEM item;

    int cEntries = ListView_GetItemCount(m_hwndLV);
    for (int i = 0; i < cEntries; i++)
    {
        item.mask     = LVIF_PARAM;
        item.iItem    = i;
        item.iSubItem = 0;
        if (ListView_GetItem(m_hwndLV, &item))
        {
            delete (LVEntry *)item.lParam;
            if (ListView_DeleteItem(m_hwndLV, i))
            {
                i--;
                cEntries--;
            }
        }
    }
}

//
// Determine if a listview entry should remain visible in the listview.
// Currently we include servers that currently connected through the 
// network redirector and are offline OR those that are dirty.
// 
bool
CStatusDlg::ShouldIncludeLVEntry(
    const CStatusDlg::LVEntry& entry
    )
{
    DWORD dwCscStatus;
    entry.GetStats(NULL, &dwCscStatus);

    return (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwCscStatus) || 
           (FLAG_CSC_SHARE_STATUS_MODIFIED_OFFLINE & dwCscStatus);
}


//
// Determine if a checkmark should be placed next to an item in 
// the listview.
//
bool
CStatusDlg::ShouldCheckLVEntry(
    const CStatusDlg::LVEntry& entry
    )
{
    return true;
}


//
// Remove all entries not to be displayed from the listview.
// Initially we create LV entries for each server in the CSC cache.  
// After all servers have been entered and their statistics tallied,
// we call PrepListForDisplay to remove the ones that the
// user won't want to see.
// 
void
CStatusDlg::PrepListForDisplay(
    void
    )
{
    LVEntry *pEntry;
    LVITEM item;

    int cEntries = ListView_GetItemCount(m_hwndLV);
    for (int i = 0; i < cEntries; i++)
    {
        item.mask     = LVIF_PARAM;
        item.iItem    = i;
        item.iSubItem = 0;
        if (ListView_GetItem(m_hwndLV, &item))
        {
            pEntry = (LVEntry *)item.lParam;
            if (ShouldIncludeLVEntry(*pEntry))
            {
                ListView_SetCheckState(m_hwndLV, i, ShouldCheckLVEntry(*pEntry));
            }
            else
            {
                delete pEntry;
                if (ListView_DeleteItem(m_hwndLV, i))
                {
                    i--;
                    cEntries--;
                }
            }
        }
    }
}


//
// Listview item comparison callback.
//
int CALLBACK 
CStatusDlg::CompareLVItems(
    LPARAM lParam1, 
    LPARAM lParam2,
    LPARAM lParamSort
    )
{
    CStatusDlg *pdlg = reinterpret_cast<CStatusDlg *>(lParamSort);
    CStatusDlg::LVEntry *pEntry1 = reinterpret_cast<CStatusDlg::LVEntry *>(lParam1);
    CStatusDlg::LVEntry *pEntry2 = reinterpret_cast<CStatusDlg::LVEntry *>(lParam2);
    int diff = 0;
    TCHAR szText[2][MAX_PATH];

    //
    // This array controls the comparison column IDs used when
    // values for the selected column are equal.  These should
    // remain in order of the iLVSUBITEM_xxxxx enumeration with
    // respect to the first element in each row.
    //
    static const int rgColComp[3][3] = { 
        { iLVSUBITEM_SERVER, iLVSUBITEM_STATUS, iLVSUBITEM_INFO   },
        { iLVSUBITEM_STATUS, iLVSUBITEM_SERVER, iLVSUBITEM_INFO   },
        { iLVSUBITEM_INFO,   iLVSUBITEM_SERVER, iLVSUBITEM_STATUS }
                                       };
    int iCompare = 0;
    while(0 == diff && iCompare < ARRAYSIZE(rgColComp))
    {
        switch(rgColComp[pdlg->m_iLastColSorted][iCompare++])
        {
            case iLVSUBITEM_SERVER:
                lstrcpyn(szText[0], pEntry1->Server(), ARRAYSIZE(szText[0]));
                lstrcpyn(szText[1], pEntry2->Server(), ARRAYSIZE(szText[1]));
                break;

            case iLVSUBITEM_STATUS:
                pEntry1->GetStatusText(szText[0], ARRAYSIZE(szText[0]));
                pEntry2->GetStatusText(szText[1], ARRAYSIZE(szText[1]));
                break;

            case iLVSUBITEM_INFO:
                pEntry1->GetInfoText(szText[0], ARRAYSIZE(szText[0]));
                pEntry2->GetInfoText(szText[1], ARRAYSIZE(szText[1]));
                break;

            default:
                //
                // If you hit this, you need to update this function
                // to handle the new column you've added to the listview.
                //
                *szText[0] = TEXT('\0');
                *szText[1] = TEXT('\0');
                TraceAssert(false);
                break;
        }
        //
        // This comparison should be case-sensitive since it is controlling
        // sort order of display columns.
        //
        diff = lstrcmp(szText[0], szText[1]);
    }

    return pdlg->m_bSortAscending ? diff : -1 * diff;
}



const TCHAR CStatusDlg::LVEntry::s_szBlank[] = TEXT("");
//
// There are 3 binary conditions that control the selection of the entry 
// display information.  Therefore we can use a simple 8-element map of string resource
// IDs and icon image list indices to determine the correct display information
// string for the corresponding LV entry state.  GetDispInfoIndex() is called
// to retrieve the index for a particular LVEntry.
//
const CStatusDlg::LVEntry::DispInfo 
CStatusDlg::LVEntry::s_rgDispInfo[] = {                                                                   // online available modified
    { IDS_SHARE_STATUS_OFFLINE, IDS_SHARE_INFO_UNAVAIL,     CStatusDlg::iIMAGELIST_ICON_SERVER_OFFLINE }, //    0       0         0      
    { IDS_SHARE_STATUS_OFFLINE, IDS_SHARE_INFO_UNAVAIL_MOD, CStatusDlg::iIMAGELIST_ICON_SERVER_OFFLINE }, //    0       0         1
    { IDS_SHARE_STATUS_OFFLINE, IDS_SHARE_INFO_AVAIL,       CStatusDlg::iIMAGELIST_ICON_SERVER_BACK    }, //    0       1         0
    { IDS_SHARE_STATUS_OFFLINE, IDS_SHARE_INFO_AVAIL_MOD,   CStatusDlg::iIMAGELIST_ICON_SERVER_BACK    }, //    0       1         1 
    { IDS_SHARE_STATUS_ONLINE,  IDS_SHARE_INFO_BLANK,       CStatusDlg::iIMAGELIST_ICON_SERVER         }, //    1       0         0
    { IDS_SHARE_STATUS_ONLINE,  IDS_SHARE_INFO_DIRTY,       CStatusDlg::iIMAGELIST_ICON_SERVER_DIRTY   }, //    1       0         1
    { IDS_SHARE_STATUS_ONLINE,  IDS_SHARE_INFO_BLANK,       CStatusDlg::iIMAGELIST_ICON_SERVER         }, //    1       1         0
    { IDS_SHARE_STATUS_ONLINE,  IDS_SHARE_INFO_DIRTY,       CStatusDlg::iIMAGELIST_ICON_SERVER_DIRTY   }  //    1       1         1
    };

CStatusDlg::LVEntry::LVEntry(
    HINSTANCE hInstance,
    LPCTSTR pszServer,
    bool bConnectable
    ) : m_hInstance(hInstance),
        m_pszServer(StrDup(pszServer)),
        m_dwCscStatus(0),
        m_hdpaShares(DPA_Create(4)),
        m_bConnectable(bConnectable),
        m_iDispInfo(-1)
{
    m_stats.cTotal    = 0;
    m_stats.cPinned   = 0;
    m_stats.cModified = 0;
    m_stats.cSparse   = 0;

    if (NULL == m_pszServer)
        m_pszServer = const_cast<LPTSTR>(s_szBlank);
}


CStatusDlg::LVEntry::~LVEntry(
    void
    )
{
    if (s_szBlank != m_pszServer && NULL != m_pszServer)
    {
        LocalFree(m_pszServer);
    }
    if (NULL != m_hdpaShares)
    {
        int cShares = DPA_GetPtrCount(m_hdpaShares);
        for (int i = 0; i < cShares; i++)
        {
            LPTSTR psz = (LPTSTR)DPA_GetPtr(m_hdpaShares, i);
            if (NULL != psz)
            {
                LocalFree(psz);
            }
        }
        DPA_Destroy(m_hdpaShares);
    }
}


bool
CStatusDlg::LVEntry::AddShare(
    LPCTSTR pszShare,
    const CSCSHARESTATS& s,
    DWORD dwCscStatus
    )
{
    bool bResult = false;
    if (NULL != m_hdpaShares)
    {
        LPTSTR pszCopy = StrDup(pszShare);
        if (NULL != pszCopy)
        {
            if (-1 != DPA_AppendPtr(m_hdpaShares, pszCopy))
            {
                m_stats.cTotal    += s.cTotal;
                m_stats.cPinned   += s.cPinned;
                m_stats.cModified += s.cModified;
                m_stats.cSparse   += s.cSparse;

                m_dwCscStatus |= dwCscStatus;

                bResult = true;
            }
            else
            {
                LocalFree(pszCopy);
            }
        }
    }
    return bResult;
}


int 
CStatusDlg::LVEntry::GetShareCount(
    void
    ) const
{
    if (NULL != m_hdpaShares)
        return DPA_GetPtrCount(m_hdpaShares);
    return 0;
}


LPCTSTR 
CStatusDlg::LVEntry::GetShareName(
    int iShare
    ) const
{
    if (NULL != m_hdpaShares)
        return (LPCTSTR)DPA_GetPtr(m_hdpaShares, iShare);
    return NULL;
}


void 
CStatusDlg::LVEntry::GetStats(
    CSCSHARESTATS *ps,
    DWORD *pdwCscStatus
    ) const
{
    if (NULL != ps)
        *ps = m_stats;
    if (NULL != pdwCscStatus)
        *pdwCscStatus = m_dwCscStatus;
}

//
// Determines the index into s_rgDispInfo[] for obtaining display information
// for the LV entry.
//
int
CStatusDlg::LVEntry::GetDispInfoIndex(
    void
    ) const
{
    if (-1 == m_iDispInfo)
    {
        m_iDispInfo = 0;

        if (IsModified())
            m_iDispInfo |= DIF_MODIFIED;

        if (!IsOffline())
            m_iDispInfo |= DIF_ONLINE;

        if (IsConnectable())
            m_iDispInfo |= DIF_AVAILABLE;
    }
    return m_iDispInfo;
}


//
// Retrieve the entry's text for the "Status" column in the listview.
//
void 
CStatusDlg::LVEntry::GetStatusText(
    LPTSTR pszStatus, 
    int cchStatus
    ) const
{
    UINT idsStatusText = s_rgDispInfo[GetDispInfoIndex()].idsStatusText;
    LoadString(m_hInstance, idsStatusText, pszStatus, cchStatus);
}


//
// Retrieve entry's text for the "Information" column in the listview.
//
void 
CStatusDlg::LVEntry::GetInfoText(
    LPTSTR pszInfo, 
    int cchInfo
    ) const
{
    int iInfoText = GetDispInfoIndex();
    int idsInfoText = s_rgDispInfo[iInfoText].idsInfoText;
    if (iInfoText & DIF_MODIFIED)
    {
        //
        // Info text has embedded modified file count.
        // Requires a little more work.
        //
        TCHAR szTemp[MAX_PATH];
        INT_PTR rgcModified[] = { m_stats.cModified };
        LoadString(m_hInstance, idsInfoText, szTemp, ARRAYSIZE(szTemp));
        FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                      FORMAT_MESSAGE_ARGUMENT_ARRAY,
                      szTemp,
                      0,
                      0,
                      pszInfo,
                      cchInfo,
                      (va_list *)rgcModified);
    }
    else
    {
        LoadString(m_hInstance, idsInfoText, pszInfo, cchInfo);
    }
}


//
// Retrieve entry's imagelist index for the image displayed next to the
// entry in the listview.
//
int
CStatusDlg::LVEntry::GetImageIndex(
    void
    ) const
{
    return s_rgDispInfo[GetDispInfoIndex()].iImage;
}


//
// Wrapper for CSCTransitionServerOnline
//
//
// CAUTION!
//
// TransitionShareOnline is called from ReconnectServers (below) which
// means we may be running in either explorer or mobsync.  It's also
// called directly from the systray code when auto-reconnecting a
// server.
//
// 1. Be careful with SendMessage, since it may go out of process. Note that
// STDBGOUT uses SendMessage.
// 2. Be careful not to do anything that could cause a transition to offline,
// which results in a SendMessage from WinLogon, which deadlocks if we are
// running on the systray thread (the recipient of the SendMessage).
// For example, use SHSimpleIDListFromFindData instead of ILCreateFromPath.
//
BOOL
TransitionShareOnline(LPCTSTR pszShare,
                      BOOL  bShareIsAlive,  // TRUE skips CheckShareOnline
                      BOOL  bCheckSpeed,    // FALSE skips slow link check
                      DWORD dwPathSpeed)    // Used if (bShareIsAlive && bCheckSpeed)
{
    BOOL bShareTransitioned = FALSE;
    DWORD dwShareStatus;

    if (!pszShare || !*pszShare)
        return FALSE;

    //
    // Protect against calling CSCCheckShareOnline & CSCTransitionServerOnline
    // for shares that are already online. In particular, CSCCheckShareOnline
    // establishes a net connection so it can be slow.
    //
    // This also means that we call CSCTransitionServerOnline for only one
    // share on a given server, since all shares transition online/offline
    // at the same time.
    //
    if (CSCQueryFileStatus(pszShare, &dwShareStatus, NULL, NULL)
        && (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus))
    {
        //
        // Only try to transition the server online if the share is
        // truly available on the net.  Otherwise we'll put the server online
        // and the next call for net resources on that server will cause a net
        // timeout.
        //
        if (!bShareIsAlive)
        {
            bShareIsAlive = CSCCheckShareOnlineEx(pszShare, &dwPathSpeed);
            if (!bShareIsAlive)
            {
                DWORD dwErr = GetLastError();
                if (ERROR_ACCESS_DENIED == dwErr ||
                    ERROR_LOGON_FAILURE == dwErr)
                {
                    // The share is reachable, but we don't have valid
                    // credentials.  We don't have a valid path speed,
                    // so the best we can do is assume fast link.
                    //
                    // Currently, this function is always called with
                    // bShareIsAlive and bCheckSpeed both TRUE or both FALSE
                    // so it doesn't make any difference.  Could change in
                    // the future, though.
                    //
                    bShareIsAlive = TRUE;
                    bCheckSpeed = FALSE;
                }
            }
        }
        if (bShareIsAlive)
        {
            //
            // Also, for auto-reconnection, we only transition if not on a
            // slow link.
            //
            if (!bCheckSpeed || !_PathIsSlow(dwPathSpeed))
            {
                //
                // Transition to online
                //
                STDBGOUT((2, TEXT("Transitioning server \"%s\" to online"), pszShare));
                if (CSCTransitionServerOnline(pszShare))
                {
                    STDBGOUT((1, TEXT("Server \"%s\" reconnected."), pszShare));
                    LPTSTR pszTemp;
                    if (LocalAllocString(&pszTemp, pszShare))
                    {
	                //
                        // Strip the path to only the "\\server" part.
                        //
                        PathStripToRoot(pszTemp);
                        PathRemoveFileSpec(pszTemp);
                        SendCopyDataToSystray(PWM_REFRESH_SHELL, StringByteSize(pszTemp), pszTemp);
                        LocalFreeString(&pszTemp);
                    }
                    bShareTransitioned = TRUE;
                }
                else
                {
                    STDBGOUT((1, TEXT("Error %d reconnecting \"%s\""), GetLastError(), pszShare));
                }
            }
            else
            {
                STDBGOUT((1, TEXT("Path to \"%s\" is SLOW.  No reconnect."), pszShare));
            }
        }
        else
        {
            STDBGOUT((1, TEXT("Unable to connect to \"%s\".  No reconnect."), pszShare));
        }
    }

    return bShareTransitioned;
}


//
// CAUTION!
//
// ReconnectServers is called from both the Status Dialog (explorer) and
// the sync update handler (mobsync.exe). Any communication with systray
// must be done in a process-safe manner.
//
// See comments for TransitionShareOnline above.
//
HRESULT
ReconnectServers(CscFilenameList *pfnl,
                 BOOL bCheckForOpenFiles,
                 BOOL bCheckSpeed)          // FALSE skips slow link check
{
    if (pfnl)
    {
        CscFilenameList::ShareIter si = pfnl->CreateShareIterator();
        CscFilenameList::HSHARE hShare;
        BOOL bRefreshShell = FALSE;

        if (bCheckForOpenFiles)
        {
            //
            // First scan the shares to see if any have open files.
            //
            while(si.Next(&hShare))
            {
                DWORD dwShareStatus;
                if (CSCQueryFileStatus(pfnl->GetShareName(hShare), &dwShareStatus, NULL, NULL))
                {
                    if ((FLAG_CSC_SHARE_STATUS_FILES_OPEN & dwShareStatus) &&
                        (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwShareStatus))
                    {
                        if (IDOK != OpenFilesWarningDialog())
                        {
                            return S_FALSE; // User cancelled.
                        }

                        break;
                    }
                }
            }
            si.Reset();
        }

        //
        // Walk through the list, transitioning everything to online.
        //
        while(si.Next(&hShare))
        {
            if (TransitionShareOnline(pfnl->GetShareName(hShare), FALSE, bCheckSpeed, 0))
                bRefreshShell = TRUE;
        }
    }

    return S_OK;
}
