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

1546 lines
42 KiB
C++

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