1546 lines
42 KiB
C++
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;
|
||
|
}
|