//+------------------------------------------------------------------------- // // 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(lParamSort); CStatusDlg::LVEntry *pEntry1 = reinterpret_cast(lParam1); CStatusDlg::LVEntry *pEntry2 = reinterpret_cast(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(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; }