1830 lines
53 KiB
C++
1830 lines
53 KiB
C++
#include "pch.h"
|
|
#pragma hdrstop
|
|
|
|
#include "ownerdlg.h"
|
|
#include "ownerlst.h"
|
|
#include "resource.h"
|
|
#include "uihelp.h"
|
|
#include "uiutils.h"
|
|
|
|
//
|
|
// Private message used to indicate that the owner list thread
|
|
// has completed it's work.
|
|
//
|
|
const UINT PWM_OWNERLIST_COMPLETE = WM_USER + 1;
|
|
//
|
|
// Mask bits indicating what operations are allowed for a given
|
|
// file selection set in the listview.
|
|
//
|
|
const DWORD ACTION_NONE = 0x00000000;
|
|
const DWORD ACTION_TAKEOWNERSHIP = 0x00000001;
|
|
const DWORD ACTION_MOVE = 0x00000002;
|
|
const DWORD ACTION_DELETE = 0x00000004;
|
|
const DWORD ACTION_ANY = 0x00000007;
|
|
|
|
|
|
const static DWORD rgFileOwnerDialogHelpIDs[] =
|
|
{
|
|
IDC_CMB_OWNERDLG_OWNERS, IDH_CMB_OWNERDLG_OWNERS,
|
|
IDC_LV_OWNERDLG, IDH_LV_OWNERDLG,
|
|
IDC_BTN_OWNERDLG_DELETE, IDH_BTN_OWNERDLG_DELETE,
|
|
IDC_BTN_OWNERDLG_MOVETO, IDH_BTN_OWNERDLG_MOVETO,
|
|
IDC_BTN_OWNERDLG_TAKE, IDH_BTN_OWNERDLG_TAKE,
|
|
IDC_BTN_OWNERDLG_BROWSE, IDH_BTN_OWNERDLG_BROWSE,
|
|
IDC_EDIT_OWNERDLG_MOVETO, IDH_EDIT_OWNERDLG_MOVETO,
|
|
0,0
|
|
};
|
|
|
|
|
|
CFileOwnerDialog::CFileOwnerDialog(HINSTANCE hInstance,
|
|
HWND hwndParent,
|
|
LPCTSTR pszVolumeRoot,
|
|
const CArray<IDiskQuotaUser *>& rgpOwners
|
|
) : m_hInstance(hInstance),
|
|
m_hwndParent(hwndParent),
|
|
m_hwndDlg(NULL),
|
|
m_hwndLV(NULL),
|
|
m_hwndOwnerCombo(NULL),
|
|
m_hwndEditMoveTo(NULL),
|
|
m_iLastColSorted(-1),
|
|
m_bSortAscending(true),
|
|
m_bAbort(false),
|
|
m_hOwnerListThread(NULL),
|
|
m_rgpOwners(rgpOwners),
|
|
m_strVolumeRoot(pszVolumeRoot)
|
|
{
|
|
}
|
|
|
|
CFileOwnerDialog::~CFileOwnerDialog(
|
|
void
|
|
)
|
|
{
|
|
if (NULL != m_hOwnerListThread)
|
|
{
|
|
CloseHandle(m_hOwnerListThread);
|
|
}
|
|
}
|
|
|
|
|
|
INT_PTR
|
|
CFileOwnerDialog::Run(
|
|
void
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_HIGH, TEXT("CFileOwnerDialog::Run")));
|
|
return DialogBoxParam(m_hInstance,
|
|
MAKEINTRESOURCE(IDD_OWNERSANDFILES),
|
|
m_hwndParent,
|
|
DlgProc,
|
|
(LPARAM)this);
|
|
}
|
|
|
|
|
|
INT_PTR CALLBACK
|
|
CFileOwnerDialog::DlgProc(
|
|
HWND hwnd,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
//
|
|
// Retrieve the dialog object's ptr from the window's userdata.
|
|
// Place there in response to WM_INITDIALOG.
|
|
//
|
|
CFileOwnerDialog *pThis = (CFileOwnerDialog *)GetWindowLongPtr(hwnd, DWLP_USER);
|
|
|
|
try
|
|
{
|
|
switch(uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
//
|
|
// Store "this" ptr in window's userdata.
|
|
//
|
|
SetWindowLongPtr(hwnd, DWLP_USER, (INT_PTR)lParam);
|
|
pThis = (CFileOwnerDialog *)lParam;
|
|
//
|
|
// Save the HWND in our object. We'll need it later.
|
|
//
|
|
pThis->m_hwndDlg = hwnd;
|
|
return pThis->OnInitDialog(hwnd);
|
|
|
|
case WM_DESTROY:
|
|
return pThis->OnDestroy(hwnd);
|
|
|
|
case WM_COMMAND:
|
|
return pThis->OnCommand(hwnd, wParam, lParam);
|
|
|
|
case WM_NOTIFY:
|
|
return pThis->OnNotify(hwnd, wParam, lParam);
|
|
|
|
case WM_CONTEXTMENU:
|
|
return pThis->OnContextMenu((HWND)wParam, LOWORD(lParam), HIWORD(lParam));
|
|
break;
|
|
|
|
case PWM_OWNERLIST_COMPLETE:
|
|
pThis->OnOwnerListComplete();
|
|
break;
|
|
|
|
case WM_SETCURSOR:
|
|
return pThis->OnSetCursor(hwnd);
|
|
break;
|
|
|
|
case WM_HELP:
|
|
WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, STR_DSKQUOUI_HELPFILE,
|
|
HELP_WM_HELP, (DWORD_PTR)(LPTSTR) rgFileOwnerDialogHelpIDs);
|
|
return TRUE;
|
|
}
|
|
}
|
|
catch(CAllocException& me)
|
|
{
|
|
//
|
|
// Announce any out-of-memory errors associated with running the dlg.
|
|
//
|
|
DiskQuotaMsgBox(GetDesktopWindow(),
|
|
IDS_OUTOFMEMORY,
|
|
IDS_TITLE_DISK_QUOTA,
|
|
MB_ICONERROR | MB_OK);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
INT_PTR
|
|
CFileOwnerDialog::OnInitDialog(
|
|
HWND hwnd
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_HIGH, TEXT("CFileOwnerDialog::OnInitDialog")));
|
|
|
|
//
|
|
// Save HWNDs of controls we'll need later.
|
|
//
|
|
m_hwndLV = GetDlgItem(hwnd, IDC_LV_OWNERDLG);
|
|
m_hwndOwnerCombo = GetDlgItem(hwnd, IDC_CMB_OWNERDLG_OWNERS);
|
|
m_hwndEditMoveTo = GetDlgItem(hwnd, IDC_EDIT_OWNERDLG_MOVETO);
|
|
//
|
|
// We want these controls to be disabled until the owner list
|
|
// has been populated.
|
|
//
|
|
EnableWindow(m_hwndLV, FALSE);
|
|
EnableWindow(m_hwndOwnerCombo, FALSE);
|
|
EnableWindow(m_hwndEditMoveTo, FALSE);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BTN_OWNERDLG_BROWSE), FALSE);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_CBX_OWNERDLG_EXCLUDEDIRS), FALSE);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_CBX_OWNERDLG_EXCLUDEFILES), FALSE);
|
|
//
|
|
// Build the list of owners and filenames on the volume.
|
|
// This can take a while depending on how many owners
|
|
// are in m_rgpOwners, the size of the volume and how many
|
|
// files each owner owns. First clear the owner list in case Run()
|
|
// is being called multiple times on the same dialog object.
|
|
// Note that we build this list on a background thread so that
|
|
// we don't block the UI while it's building.
|
|
//
|
|
m_OwnerList.Clear();
|
|
DWORD idThread;
|
|
m_hOwnerListThread = CreateThread(NULL,
|
|
0,
|
|
OwnerListThreadProc,
|
|
this,
|
|
0,
|
|
&idThread);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
INT_PTR
|
|
CFileOwnerDialog::OnSetCursor(
|
|
HWND hwnd
|
|
)
|
|
{
|
|
if (m_hOwnerListThread && (WAIT_TIMEOUT == WaitForSingleObject(m_hOwnerListThread, 0)))
|
|
{
|
|
SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
INT_PTR
|
|
CFileOwnerDialog::OnDestroy(
|
|
HWND hwnd
|
|
)
|
|
{
|
|
m_bAbort = true;
|
|
if (NULL != m_hOwnerListThread)
|
|
{
|
|
WaitForSingleObject(m_hOwnerListThread, INFINITE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
DWORD
|
|
CFileOwnerDialog::OwnerListThreadProc( // [static]
|
|
LPVOID pvParam
|
|
)
|
|
{
|
|
CFileOwnerDialog *pThis = (CFileOwnerDialog *)pvParam;
|
|
|
|
pThis->BuildFileOwnerList(pThis->m_strVolumeRoot,
|
|
pThis->m_rgpOwners,
|
|
&(pThis->m_OwnerList));
|
|
|
|
PostMessage(pThis->m_hwndDlg, PWM_OWNERLIST_COMPLETE, 0, 0);
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Called in response to PWM_OWNERLIST_COMPLETE which is posted
|
|
// to the dialog when the OwnerListThreadProc has completed
|
|
// it's processing.
|
|
//
|
|
void
|
|
CFileOwnerDialog::OnOwnerListComplete(
|
|
void
|
|
)
|
|
{
|
|
//
|
|
// Set the message in the top of the dialog.
|
|
//
|
|
CString s(m_hInstance, IDS_FMT_OWNERDLG_HEADER, m_OwnerList.OwnerCount());
|
|
SetWindowText(GetDlgItem(m_hwndDlg, IDC_TXT_OWNERDLG_HEADER), s);
|
|
//
|
|
// Populate the listview and owner combo.
|
|
//
|
|
InitializeList(m_OwnerList, m_hwndLV);
|
|
InitializeOwnerCombo(m_OwnerList, m_hwndOwnerCombo);
|
|
//
|
|
// Now we can enable the controls we disabled in OnInitDialog().
|
|
//
|
|
EnableWindow(m_hwndLV, TRUE);
|
|
EnableWindow(m_hwndOwnerCombo, TRUE);
|
|
EnableWindow(m_hwndEditMoveTo, TRUE);
|
|
EnableWindow(GetDlgItem(m_hwndDlg, IDC_BTN_OWNERDLG_BROWSE), TRUE);
|
|
EnableWindow(GetDlgItem(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEDIRS), TRUE);
|
|
EnableWindow(GetDlgItem(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEFILES), TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
INT_PTR
|
|
CFileOwnerDialog::OnCommand(
|
|
HWND hwnd,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
BOOL bResult = TRUE; // Assume not handled.
|
|
WORD wID = LOWORD(wParam);
|
|
WORD wNotifyCode = HIWORD(wParam);
|
|
HWND hCtl = (HWND)lParam;
|
|
|
|
switch(wID)
|
|
{
|
|
case IDCANCEL:
|
|
EndDialog(hwnd, 0);
|
|
bResult = FALSE;
|
|
break;
|
|
|
|
case IDC_CMB_OWNERDLG_OWNERS:
|
|
if (CBN_SELCHANGE == wNotifyCode)
|
|
{
|
|
int iOwner = ComboBox_GetCurSel(m_hwndOwnerCombo);
|
|
if (1 < m_OwnerList.OwnerCount())
|
|
{
|
|
//
|
|
// Owner list contains more than one owner. The combo
|
|
// contains a leading "All owners" entry.
|
|
//
|
|
iOwner--;
|
|
}
|
|
|
|
DBGASSERT((-1 <= iOwner));
|
|
CAutoSetRedraw autoredraw(m_hwndLV, false);
|
|
//
|
|
// Only show "owner" column if user has selected "all owners" combo item.
|
|
//
|
|
CreateListColumns(m_hwndLV, -1 == iOwner);
|
|
FillListView(m_OwnerList, m_hwndLV, iOwner);
|
|
}
|
|
bResult = FALSE;
|
|
break;
|
|
|
|
case IDC_BTN_OWNERDLG_BROWSE:
|
|
{
|
|
CString s;
|
|
if (BrowseForFolder(hwnd, &s))
|
|
SetWindowText(m_hwndEditMoveTo, s);
|
|
break;
|
|
}
|
|
|
|
case IDC_BTN_OWNERDLG_DELETE:
|
|
DeleteSelectedFiles(m_hwndLV);
|
|
bResult = FALSE;
|
|
break;
|
|
|
|
case IDC_BTN_OWNERDLG_MOVETO:
|
|
{
|
|
CPath strDest;
|
|
CPath strRoot;
|
|
int cchEdit = Edit_GetTextLength(m_hwndEditMoveTo);
|
|
Edit_GetText(m_hwndEditMoveTo,
|
|
strDest.GetBuffer(cchEdit + 1),
|
|
cchEdit + 1);
|
|
strDest.ReleaseBuffer();
|
|
strDest.Trim();
|
|
strDest.GetRoot(&strRoot);
|
|
|
|
if (IsSameVolume(strRoot, m_strVolumeRoot))
|
|
{
|
|
//
|
|
// Don't let operator move files to a folder
|
|
// on the same volume.
|
|
//
|
|
DiskQuotaMsgBox(m_hwndDlg,
|
|
IDS_ERROR_MOVETO_SAMEVOL,
|
|
IDS_TITLE_DISK_QUOTA,
|
|
MB_ICONINFORMATION | MB_OK);
|
|
|
|
SetWindowText(m_hwndEditMoveTo, strDest);
|
|
SetFocus(m_hwndEditMoveTo);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Eveything looks OK. Try to move the files.
|
|
//
|
|
MoveSelectedFiles(m_hwndLV, strDest);
|
|
}
|
|
bResult = FALSE;
|
|
break;
|
|
}
|
|
|
|
case IDC_BTN_OWNERDLG_TAKE:
|
|
{
|
|
HRESULT hr = TakeOwnershipOfSelectedFiles(m_hwndLV);
|
|
if (FAILED(hr))
|
|
{
|
|
DBGERROR((TEXT("TakeOwnershipOfSelectedFiles failed with hr = 0x%08X"), hr));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IDC_EDIT_OWNERDLG_MOVETO:
|
|
if (EN_UPDATE == wNotifyCode)
|
|
{
|
|
//
|
|
// Disable the "Move" button if the destination edit field
|
|
// is blank.
|
|
//
|
|
HWND hwnd = GetDlgItem(m_hwndDlg, IDC_BTN_OWNERDLG_MOVETO);
|
|
bool bEnable = ShouldEnableControl(IDC_BTN_OWNERDLG_MOVETO);
|
|
if (bEnable != boolify(IsWindowEnabled(hwnd)))
|
|
{
|
|
EnableWindow(hwnd, bEnable);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case IDC_CBX_OWNERDLG_EXCLUDEFILES:
|
|
case IDC_CBX_OWNERDLG_EXCLUDEDIRS:
|
|
if (BN_CLICKED == wNotifyCode)
|
|
{
|
|
//
|
|
// The allowable states for these two checkboxes are:
|
|
//
|
|
// Excl Files Excl Dirs
|
|
// --------------- ----------------
|
|
// Checked Unchecked
|
|
// Unchecked Checked
|
|
// Unchecked Unchecked
|
|
//
|
|
// It makes no sense to have both checkboxes checked.
|
|
// This would cause the list to be empty and might
|
|
// generate user confusion.
|
|
//
|
|
if (IsDlgButtonChecked(m_hwndDlg, wID))
|
|
{
|
|
UINT idOther = IDC_CBX_OWNERDLG_EXCLUDEFILES;
|
|
if (IDC_CBX_OWNERDLG_EXCLUDEFILES == wID)
|
|
{
|
|
idOther = IDC_CBX_OWNERDLG_EXCLUDEDIRS;
|
|
}
|
|
CheckDlgButton(m_hwndDlg, idOther, BST_UNCHECKED);
|
|
}
|
|
FillListView(m_OwnerList, m_hwndLV, ComboBox_GetCurSel(m_hwndOwnerCombo) - 1);
|
|
}
|
|
break;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
|
|
INT_PTR
|
|
CFileOwnerDialog::OnContextMenu(
|
|
HWND hwndItem,
|
|
int xPos,
|
|
int yPos
|
|
)
|
|
{
|
|
int idCtl = GetDlgCtrlID(hwndItem);
|
|
WinHelp(hwndItem,
|
|
UseWindowsHelp(idCtl) ? NULL : STR_DSKQUOUI_HELPFILE,
|
|
HELP_CONTEXTMENU,
|
|
(DWORD_PTR)((LPTSTR)rgFileOwnerDialogHelpIDs));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Determine what actions are allowed for the current selection.
|
|
//
|
|
DWORD
|
|
CFileOwnerDialog::GetAllowedActions(
|
|
HWND hwndLV
|
|
)
|
|
{
|
|
CArray<COwnerListItemHandle> rgItemHandles;
|
|
|
|
BuildListOfSelectedFiles(hwndLV, NULL, &rgItemHandles);
|
|
if (0 != rgItemHandles.Count())
|
|
{
|
|
const int cHandles = rgItemHandles.Count();
|
|
for(int i = 0; i < cHandles; i++)
|
|
{
|
|
COwnerListItemHandle handle = rgItemHandles[i];
|
|
int iOwner = handle.OwnerIndex();
|
|
int iFile = handle.FileIndex();
|
|
if (m_OwnerList.IsFileDirectory(iOwner, iFile))
|
|
{
|
|
//
|
|
// If any directory exists in the selection,
|
|
// "take ownership" is the only allowed action.
|
|
//
|
|
return ACTION_TAKEOWNERSHIP;
|
|
}
|
|
}
|
|
}
|
|
return ACTION_ANY;
|
|
}
|
|
|
|
|
|
//
|
|
// Determine if one of the move/delete/take buttons should be enabled
|
|
// or disabled.
|
|
//
|
|
bool
|
|
CFileOwnerDialog::ShouldEnableControl(
|
|
UINT idCtl
|
|
)
|
|
{
|
|
bool bEnable = true;
|
|
int cLVSel = ListView_GetSelectedCount(m_hwndLV);
|
|
DWORD actions = GetAllowedActions(m_hwndLV);
|
|
|
|
switch(idCtl)
|
|
{
|
|
case IDC_BTN_OWNERDLG_DELETE:
|
|
bEnable = (0 != (ACTION_DELETE & actions)) && (0 < cLVSel);
|
|
break;
|
|
|
|
case IDC_BTN_OWNERDLG_TAKE:
|
|
bEnable = (0 != (ACTION_TAKEOWNERSHIP & actions)) && (0 < cLVSel);
|
|
break;
|
|
|
|
case IDC_BTN_OWNERDLG_MOVETO:
|
|
bEnable = (0 != (ACTION_MOVE & actions));
|
|
if (bEnable && 0 < cLVSel)
|
|
{
|
|
CPath s;
|
|
int cch = Edit_GetTextLength(m_hwndEditMoveTo);
|
|
Edit_GetText(m_hwndEditMoveTo, s.GetBuffer(cch + 1), cch + 1);
|
|
s.ReleaseBuffer();
|
|
s.Trim();
|
|
bEnable = 0 < s.Length();
|
|
}
|
|
break;
|
|
|
|
case IDC_BTN_OWNERDLG_BROWSE:
|
|
case IDC_EDIT_OWNERDLG_MOVETO:
|
|
bEnable = (0 != (ACTION_MOVE & actions));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return bEnable;
|
|
}
|
|
|
|
|
|
INT_PTR
|
|
CFileOwnerDialog::OnNotify(
|
|
HWND hwnd,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
BOOL bResult = TRUE;
|
|
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;
|
|
|
|
case LVN_ITEMCHANGED:
|
|
OnLVN_ItemChanged((NM_LISTVIEW *)lParam);
|
|
break;
|
|
|
|
case LVN_KEYDOWN:
|
|
OnLVN_KeyDown((NMLVKEYDOWN *)lParam);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
void
|
|
CFileOwnerDialog::OnLVN_GetDispInfo(
|
|
LV_DISPINFO *plvdi
|
|
)
|
|
{
|
|
static CPath strPath;
|
|
static CString strOwner;
|
|
|
|
COwnerListItemHandle hItem(plvdi->item.lParam);
|
|
int iOwner = hItem.OwnerIndex();
|
|
int iFile = hItem.FileIndex();
|
|
|
|
if (LVIF_TEXT & plvdi->item.mask)
|
|
{
|
|
switch(plvdi->item.iSubItem)
|
|
{
|
|
case iLVSUBITEM_FILE:
|
|
{
|
|
CPath s;
|
|
m_OwnerList.GetFileName(iOwner, iFile, &s);
|
|
if (m_OwnerList.IsFileDirectory(iOwner, iFile))
|
|
{
|
|
strPath.Format(m_hInstance, IDS_FMT_OWNERDLG_FOLDERNAME, s.Cstr());
|
|
}
|
|
else
|
|
{
|
|
strPath = s;
|
|
}
|
|
plvdi->item.pszText = (LPTSTR)strPath.Cstr();
|
|
}
|
|
break;
|
|
|
|
case iLVSUBITEM_FOLDER:
|
|
m_OwnerList.GetFolderName(iOwner, iFile, &strPath);
|
|
plvdi->item.pszText = (LPTSTR)strPath.Cstr();
|
|
break;
|
|
|
|
case iLVSUBITEM_OWNER:
|
|
m_OwnerList.GetOwnerName(iOwner, &strOwner);
|
|
plvdi->item.pszText = (LPTSTR)strOwner.Cstr();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (LVIF_IMAGE & plvdi->item.mask)
|
|
{
|
|
//
|
|
// Not displaying any images. This is just a placeholder.
|
|
// Should be optimized out by compiler.
|
|
//
|
|
}
|
|
}
|
|
|
|
|
|
int CALLBACK
|
|
CFileOwnerDialog::CompareLVItems(
|
|
LPARAM lParam1,
|
|
LPARAM lParam2,
|
|
LPARAM lParamSort
|
|
)
|
|
{
|
|
CFileOwnerDialog *pdlg = reinterpret_cast<CFileOwnerDialog *>(lParamSort);
|
|
int diff = 0;
|
|
try
|
|
{
|
|
COwnerListItemHandle h1(lParam1);
|
|
COwnerListItemHandle h2(lParam2);
|
|
int iOwner1 = h1.OwnerIndex();
|
|
int iOwner2 = h2.OwnerIndex();
|
|
int iFile1 = h1.FileIndex();
|
|
int iFile2 = h2.FileIndex();
|
|
static CPath s1, s2;
|
|
|
|
//
|
|
// 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_FILE, iLVSUBITEM_FOLDER, iLVSUBITEM_OWNER },
|
|
{ iLVSUBITEM_FOLDER, iLVSUBITEM_FILE, iLVSUBITEM_OWNER },
|
|
{ iLVSUBITEM_OWNER, iLVSUBITEM_FILE, iLVSUBITEM_FOLDER }
|
|
};
|
|
int iCompare = 0;
|
|
while(0 == diff && iCompare < ARRAYSIZE(rgColComp))
|
|
{
|
|
switch(rgColComp[pdlg->m_iLastColSorted][iCompare++])
|
|
{
|
|
case iLVSUBITEM_FILE:
|
|
pdlg->m_OwnerList.GetFileName(iOwner1, iFile1, &s1);
|
|
pdlg->m_OwnerList.GetFileName(iOwner2, iFile2, &s2);
|
|
break;
|
|
|
|
case iLVSUBITEM_FOLDER:
|
|
pdlg->m_OwnerList.GetFolderName(iOwner1, iFile1, &s1);
|
|
pdlg->m_OwnerList.GetFolderName(iOwner2, iFile2, &s2);
|
|
break;
|
|
|
|
case iLVSUBITEM_OWNER:
|
|
//
|
|
// Can use CPath (s1 and s2) in place of CString arg since
|
|
// CPath is derived from CString.
|
|
//
|
|
pdlg->m_OwnerList.GetOwnerName(iOwner1, &s1);
|
|
pdlg->m_OwnerList.GetOwnerName(iOwner2, &s2);
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// If you hit this, you need to update this function
|
|
// to handle the new column you've added to the listview.
|
|
//
|
|
DBGASSERT((false));
|
|
break;
|
|
}
|
|
diff = s1.Compare(s2);
|
|
}
|
|
//
|
|
// Don't need contents of static strings between function invocations.
|
|
// The strings are static to avoid repeated construction/destruction.
|
|
// It's only a minor optimization.
|
|
//
|
|
s1.Empty();
|
|
s2.Empty();
|
|
}
|
|
catch(CAllocException& e)
|
|
{
|
|
//
|
|
// Do nothing. Just return diff "as is".
|
|
// Don't want to throw an exception back into comctl32.
|
|
//
|
|
}
|
|
return pdlg->m_bSortAscending ? diff : -1 * diff;
|
|
}
|
|
|
|
|
|
void
|
|
CFileOwnerDialog::OnLVN_ColumnClick(
|
|
NM_LISTVIEW *pnmlv
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_LOW, TEXT("CFileOwnerDialog::OnLVN_ColumnClick")));
|
|
|
|
if (m_iLastColSorted != pnmlv->iSubItem)
|
|
{
|
|
m_bSortAscending = true;
|
|
m_iLastColSorted = pnmlv->iSubItem;
|
|
}
|
|
else
|
|
{
|
|
m_bSortAscending = !m_bSortAscending;
|
|
}
|
|
|
|
ListView_SortItems(m_hwndLV, CompareLVItems, LPARAM(this));
|
|
}
|
|
|
|
|
|
//
|
|
// Called whenever a listview item has changed state.
|
|
// I'm using this to update the "enabledness" of the
|
|
// dialog buttons. If there's nothing selected in the listview,
|
|
// the move/delete/take buttons are disabled.
|
|
//
|
|
void
|
|
CFileOwnerDialog::OnLVN_ItemChanged(
|
|
NM_LISTVIEW *pnmlv
|
|
)
|
|
{
|
|
static const int rgCtls[] = { IDC_BTN_OWNERDLG_DELETE,
|
|
IDC_BTN_OWNERDLG_TAKE,
|
|
IDC_BTN_OWNERDLG_MOVETO,
|
|
IDC_BTN_OWNERDLG_BROWSE,
|
|
IDC_EDIT_OWNERDLG_MOVETO};
|
|
|
|
//
|
|
// LVN_ITEMCHANGED is sent multiple times when you move the
|
|
// highlight bar in a listview.
|
|
// Only run this code when the "focused" state bit is set
|
|
// for the "new state". This should be the last call in
|
|
// the series.
|
|
//
|
|
if (LVIS_FOCUSED & pnmlv->uNewState)
|
|
{
|
|
for (int i = 0; i < ARRAYSIZE(rgCtls); i++)
|
|
{
|
|
HWND hwnd = GetDlgItem(m_hwndDlg, rgCtls[i]);
|
|
bool bEnable = ShouldEnableControl(rgCtls[i]);
|
|
if (bEnable != boolify(IsWindowEnabled(hwnd)))
|
|
{
|
|
EnableWindow(hwnd, bEnable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CFileOwnerDialog::OnLVN_KeyDown(
|
|
NMLVKEYDOWN *plvkd
|
|
)
|
|
{
|
|
if (VK_DELETE == plvkd->wVKey)
|
|
{
|
|
DeleteSelectedFiles(m_hwndLV);
|
|
FocusOnSomethingInListview(m_hwndLV);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CFileOwnerDialog::FocusOnSomethingInListview(
|
|
HWND hwndLV
|
|
)
|
|
{
|
|
//
|
|
// Focus on something.
|
|
//
|
|
int iFocus = ListView_GetNextItem(hwndLV, -1, LVNI_FOCUSED);
|
|
if (-1 == iFocus)
|
|
iFocus = 0;
|
|
|
|
ListView_SetItemState(hwndLV, iFocus, LVIS_FOCUSED | LVIS_SELECTED,
|
|
LVIS_FOCUSED | LVIS_SELECTED);
|
|
}
|
|
|
|
|
|
//
|
|
// Creates the listview columns and populates the listview
|
|
// with filenames.
|
|
//
|
|
void
|
|
CFileOwnerDialog::InitializeList(
|
|
const COwnerList& fol, // file & owner list
|
|
HWND hwndList
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::InitializeList")));
|
|
|
|
CreateListColumns(hwndList, 1 < m_OwnerList.OwnerCount());
|
|
FillListView(fol, hwndList);
|
|
ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT);
|
|
}
|
|
|
|
|
|
void
|
|
CFileOwnerDialog::CreateListColumns(
|
|
HWND hwndList,
|
|
bool bShowOwner // Default is true.
|
|
)
|
|
{
|
|
//
|
|
// Clear out the listview and header.
|
|
//
|
|
ListView_DeleteAllItems(hwndList);
|
|
HWND hwndHeader = ListView_GetHeader(hwndList);
|
|
if (NULL != hwndHeader)
|
|
{
|
|
while(0 < Header_GetItemCount(hwndHeader))
|
|
ListView_DeleteColumn(hwndList, 0);
|
|
}
|
|
|
|
//
|
|
// Create the header titles.
|
|
//
|
|
CString strFile(m_hInstance, IDS_OWNERDLG_HDR_FILE);
|
|
CString strFolder(m_hInstance, IDS_OWNERDLG_HDR_FOLDER);
|
|
CString strOwner(m_hInstance, IDS_OWNERDLG_HDR_OWNER);
|
|
|
|
//
|
|
// FEATURE: Should probably allow for vertical scroll bar also.
|
|
//
|
|
RECT rcList;
|
|
GetClientRect(hwndList, &rcList);
|
|
int cxCol = (rcList.right - rcList.left) / (bShowOwner ? 3 : 2);
|
|
|
|
#define LVCOLMASK (LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM)
|
|
|
|
LV_COLUMN rgCols[] = {
|
|
{ LVCOLMASK, LVCFMT_LEFT, cxCol, strFile, 0, iLVSUBITEM_FILE },
|
|
{ LVCOLMASK, LVCFMT_LEFT, cxCol, strFolder, 0, iLVSUBITEM_FOLDER },
|
|
{ LVCOLMASK, LVCFMT_LEFT, cxCol, strOwner, 0, iLVSUBITEM_OWNER }
|
|
};
|
|
//
|
|
// Add the columns to the listview.
|
|
//
|
|
int cCols = bShowOwner ? ARRAYSIZE(rgCols) : ARRAYSIZE(rgCols) - 1;
|
|
for (INT i = 0; i < cCols; i++)
|
|
{
|
|
if (-1 == ListView_InsertColumn(hwndList, i, &rgCols[i]))
|
|
{
|
|
DBGERROR((TEXT("CFileOwnerDialog::CreateListColumns failed to add column %d"), i));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CFileOwnerDialog::FillListView(
|
|
const COwnerList& fol, // file & owner list
|
|
HWND hwndList,
|
|
int iOwner // default is -1 (all owners)
|
|
)
|
|
{
|
|
ListView_DeleteAllItems(hwndList);
|
|
|
|
LV_ITEM item;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE | LVIF_PARAM;
|
|
item.state = 0;
|
|
item.stateMask = 0;
|
|
item.pszText = LPSTR_TEXTCALLBACK;
|
|
item.iImage = I_IMAGECALLBACK;
|
|
|
|
int iFirst = iOwner;
|
|
int iLast = iOwner;
|
|
if (-1 == iOwner)
|
|
{
|
|
iFirst = 0;
|
|
iLast = fol.OwnerCount() - 1;
|
|
}
|
|
int iItem = 0;
|
|
const bool bExclFiles = IsDlgButtonChecked(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEFILES);
|
|
const bool bExclDirs = IsDlgButtonChecked(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEDIRS);
|
|
//
|
|
// WARNING: Reusing formal arg iOwner. It's safe to do, but you
|
|
// should be aware that I'm doing it.
|
|
//
|
|
for (iOwner = iFirst; iOwner <= iLast; iOwner++)
|
|
{
|
|
int cFiles = fol.FileCount(iOwner, true);
|
|
for (int iFile = 0; iFile < cFiles; iFile++)
|
|
{
|
|
bool bDirectory = fol.IsFileDirectory(iOwner, iFile);
|
|
bool bFile = !bDirectory;
|
|
|
|
if ((bDirectory && !bExclDirs) || (bFile && !bExclFiles))
|
|
{
|
|
if (!fol.IsFileDeleted(iOwner, iFile))
|
|
{
|
|
item.lParam = COwnerListItemHandle(iOwner, iFile);
|
|
item.iItem = iItem++;
|
|
if (-1 == ListView_InsertItem(hwndList, &item))
|
|
DBGERROR((TEXT("Error adding LV item for owner %d, file %d"), iOwner, iFile));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CFileOwnerDialog::InitializeOwnerCombo(
|
|
const COwnerList& fol, // file & owner list
|
|
HWND hwndCombo
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::InitializeList")));
|
|
|
|
int iSelected = ComboBox_GetCurSel(hwndCombo);
|
|
ComboBox_ResetContent(hwndCombo);
|
|
|
|
CString s, s2;
|
|
int cOwners = fol.OwnerCount();
|
|
if (1 < cOwners)
|
|
{
|
|
//
|
|
// Add "all owners" entry.
|
|
//
|
|
s.Format(m_hInstance, IDS_FMT_ALLOWNERS, fol.FileCount());
|
|
ComboBox_InsertString(hwndCombo, -1, s);
|
|
}
|
|
|
|
for (int iOwner = 0; iOwner < cOwners; iOwner++)
|
|
{
|
|
fol.GetOwnerName(iOwner, &s2);
|
|
s.Format(m_hInstance, IDS_FMT_OWNER, s2.Cstr(), fol.FileCount(iOwner));
|
|
ComboBox_InsertString(hwndCombo, -1, s);
|
|
}
|
|
|
|
ComboBox_SetCurSel(hwndCombo, CB_ERR != iSelected ? iSelected : 0);
|
|
|
|
//
|
|
// Set the max height of the owner combo
|
|
//
|
|
RECT rcCombo;
|
|
GetClientRect(m_hwndOwnerCombo, &rcCombo);
|
|
SetWindowPos(m_hwndOwnerCombo,
|
|
NULL,
|
|
0, 0,
|
|
rcCombo.right - rcCombo.left,
|
|
200,
|
|
SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
|
|
}
|
|
|
|
|
|
//
|
|
// Determine if two volume root strings refer to the same volume.
|
|
// With volume mount points, "C:\" and "D:\DriveC" could refer to the
|
|
// same physical volume. To differentiate we need to examine the unique
|
|
// volume name GUID strings.
|
|
//
|
|
bool
|
|
CFileOwnerDialog::IsSameVolume(
|
|
LPCTSTR pszRoot1,
|
|
LPCTSTR pszRoot2
|
|
)
|
|
{
|
|
TCHAR szVolGUID1[MAX_PATH];
|
|
TCHAR szTemp[MAX_PATH];
|
|
bool bSameVolume = false;
|
|
|
|
//
|
|
// GetVolumeNameForVolumeMountPoint requires trailing backslash on paths.
|
|
//
|
|
lstrcpyn(szTemp, pszRoot1, ARRAYSIZE(szTemp));
|
|
PathAddBackslash(szTemp);
|
|
|
|
if (GetVolumeNameForVolumeMountPoint(szTemp, szVolGUID1, ARRAYSIZE(szVolGUID1)))
|
|
{
|
|
TCHAR szVolGUID2[MAX_PATH];
|
|
lstrcpyn(szTemp, pszRoot2, ARRAYSIZE(szTemp));
|
|
PathAddBackslash(szTemp);
|
|
|
|
if (GetVolumeNameForVolumeMountPoint(szTemp, szVolGUID2, ARRAYSIZE(szVolGUID2)))
|
|
{
|
|
if (0 == lstrcmpi(szVolGUID1, szVolGUID2))
|
|
bSameVolume = true;
|
|
}
|
|
}
|
|
return bSameVolume;
|
|
}
|
|
|
|
//
|
|
// Let the user browse for a folder.
|
|
// The selected folder path is returned in *pstrFolder.
|
|
//
|
|
bool
|
|
CFileOwnerDialog::BrowseForFolder(
|
|
HWND hwndParent,
|
|
CString *pstrFolder
|
|
)
|
|
{
|
|
bool bResult = false;
|
|
BROWSEINFO bi;
|
|
ZeroMemory(&bi, sizeof(bi));
|
|
|
|
CString strTitle(m_hInstance, IDS_BROWSEFORFOLDER);
|
|
|
|
bi.hwndOwner = hwndParent;
|
|
bi.pidlRoot = NULL; // Start at desktop.
|
|
bi.pszDisplayName = NULL;
|
|
bi.lpszTitle = strTitle.Cstr();
|
|
//
|
|
// FEATURE: Setting the BIF_EDITBOX flag causes SHBrowseForFolder to invoke
|
|
// autocomplete through SHAutoComplete (in shlwapi). SHAutoComplete
|
|
// loads browseui.dll to implement the autocomplete feature. The bad
|
|
// part is that SHAutoComplete also unloads browseui.dll before it
|
|
// returns, resulting in calls to the unloaded WndProc. I've notified
|
|
// ReinerF about this. Turning off the BIF_EDITBOX bit prevents
|
|
// autocomplete from being used and thus prevents the problem.
|
|
// I want the edit box. Turn it back on once they fix this bug.
|
|
//
|
|
// brianau [1/30/97]
|
|
//
|
|
bi.ulFlags = BIF_RETURNONLYFSDIRS; // | BIF_EDITBOX;
|
|
bi.lpfn = BrowseForFolderCallback;
|
|
bi.lParam = (LPARAM)pstrFolder;
|
|
bi.iImage = 0;
|
|
|
|
bResult = boolify(SHBrowseForFolder(&bi));
|
|
return bResult;
|
|
}
|
|
|
|
|
|
//
|
|
// Callback called by SHBrowseForFolder. Writes selected folder path
|
|
// to CString object who's pointer is passed in lpData arg.
|
|
//
|
|
int
|
|
CFileOwnerDialog::BrowseForFolderCallback(
|
|
HWND hwnd,
|
|
UINT uMsg,
|
|
LPARAM lParam,
|
|
LPARAM lpData
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::BrowseForFolderCallback")));
|
|
CString *pstrFolder = (CString *)lpData;
|
|
|
|
if (BFFM_SELCHANGED == uMsg)
|
|
{
|
|
SHGetPathFromIDList((LPCITEMIDLIST)lParam, pstrFolder->GetBuffer(MAX_PATH));
|
|
pstrFolder->ReleaseBuffer();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Builds a double-nul terminated list of file paths from the listview
|
|
// along with an array of "item handle" objects that acts as a cross-
|
|
// reference between the list items, items in the listview and items
|
|
// in the file owner list. Each handle contains an owner index and
|
|
// file index into the file owner list. Each handle is also the value
|
|
// stored as the lParam in the listview items.
|
|
// Both pList and prgItemHandles arguments are optional. Although,
|
|
// calling with neither non-null is sort of useless.
|
|
//
|
|
void
|
|
CFileOwnerDialog::BuildListOfSelectedFiles(
|
|
HWND hwndLV,
|
|
DblNulTermList *pList,
|
|
CArray<COwnerListItemHandle> *prgItemHandles
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::BuildListOfSelectedFiles")));
|
|
int iItem = -1;
|
|
CPath strPath;
|
|
LV_ITEM item;
|
|
|
|
if (NULL != prgItemHandles)
|
|
prgItemHandles->Clear();
|
|
while(-1 != (iItem = ListView_GetNextItem(hwndLV, iItem, LVNI_SELECTED)))
|
|
{
|
|
item.iSubItem = 0;
|
|
item.iItem = iItem;
|
|
item.mask = LVIF_PARAM;
|
|
if (-1 != ListView_GetItem(hwndLV, &item))
|
|
{
|
|
COwnerListItemHandle hItem(item.lParam);
|
|
m_OwnerList.GetFileFullPath(hItem.OwnerIndex(),
|
|
hItem.FileIndex(),
|
|
&strPath);
|
|
if (pList)
|
|
pList->AddString(strPath);
|
|
if (prgItemHandles)
|
|
prgItemHandles->Append(hItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Given an item "handle", find it's entry in the listview.
|
|
//
|
|
int
|
|
CFileOwnerDialog::FindItemFromHandle(
|
|
HWND hwndLV,
|
|
const COwnerListItemHandle& handle
|
|
)
|
|
{
|
|
LV_FINDINFO lvfi;
|
|
lvfi.flags = LVFI_PARAM;
|
|
lvfi.lParam = handle;
|
|
return ListView_FindItem(hwndLV, -1, &lvfi);
|
|
}
|
|
|
|
|
|
//
|
|
// Scans an array of item handles and removes all corresponding
|
|
// items from the listview.
|
|
//
|
|
void
|
|
CFileOwnerDialog::RemoveListViewItems(
|
|
HWND hwndLV,
|
|
const CArray<COwnerListItemHandle>& rgItemHandles
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::RemoveListViewItems")));
|
|
LV_ITEM item;
|
|
CPath strPath;
|
|
|
|
CAutoSetRedraw autoredraw(hwndLV, false);
|
|
int cHandles = rgItemHandles.Count();
|
|
for (int iHandle = 0; iHandle < cHandles; iHandle++)
|
|
{
|
|
COwnerListItemHandle handle = rgItemHandles[iHandle];
|
|
int iItem = FindItemFromHandle(hwndLV, handle);
|
|
if (-1 != iItem)
|
|
{
|
|
int iOwner = handle.OwnerIndex();
|
|
int iFile = handle.FileIndex();
|
|
m_OwnerList.GetFileFullPath(iOwner, iFile, &strPath);
|
|
|
|
if ((DWORD)-1 == GetFileAttributes(strPath))
|
|
{
|
|
//
|
|
// File doesn't exist any more.
|
|
// Delete from the listview.
|
|
// Mark it as "deleted" in the ownerlist container.
|
|
//
|
|
ListView_DeleteItem(hwndLV, iItem);
|
|
m_OwnerList.MarkFileDeleted(iOwner, iFile);
|
|
DBGPRINT((DM_VIEW, DL_LOW, TEXT("Removed item %d \"%s\""),
|
|
iItem, strPath.Cstr()));
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Refresh the owner combo to update the file counts.
|
|
//
|
|
InitializeOwnerCombo(m_OwnerList, m_hwndOwnerCombo);
|
|
}
|
|
|
|
|
|
//
|
|
// Delete the files selected in the listview.
|
|
// Files deleted are removed from the listview.
|
|
//
|
|
void
|
|
CFileOwnerDialog::DeleteSelectedFiles(
|
|
HWND hwndLV
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::DeleteSelectedFiles")));
|
|
DblNulTermList list(1024); // 1024 is the buffer growth size in chars.
|
|
CArray<COwnerListItemHandle> rgItemHandles;
|
|
|
|
BuildListOfSelectedFiles(hwndLV, &list, &rgItemHandles);
|
|
if (0 < list.Count())
|
|
{
|
|
SHFILEOPSTRUCT fo;
|
|
fo.hwnd = m_hwndDlg;
|
|
fo.wFunc = FO_DELETE;
|
|
fo.pFrom = list;
|
|
fo.pTo = NULL;
|
|
fo.fFlags = 0;
|
|
|
|
if (0 != SHFileOperation(&fo))
|
|
{
|
|
DBGERROR((TEXT("SHFileOperation [FO_DELETE] failed")));
|
|
}
|
|
//
|
|
// Remove listview items if their files were really deleted.
|
|
//
|
|
RemoveListViewItems(hwndLV, rgItemHandles);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Move the selected files to a new location.
|
|
// Moved files are removed from the listview.
|
|
//
|
|
void
|
|
CFileOwnerDialog::MoveSelectedFiles(
|
|
HWND hwndLV,
|
|
LPCTSTR pszDest
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::DeleteSelectedFiles")));
|
|
DblNulTermList list(1024); // 1024 is the buffer growth size in chars.
|
|
CArray<COwnerListItemHandle> rgItemHandles;
|
|
|
|
BuildListOfSelectedFiles(hwndLV, &list, &rgItemHandles);
|
|
if (0 < list.Count())
|
|
{
|
|
CPath strDest(pszDest);
|
|
if (1 == list.Count())
|
|
{
|
|
//
|
|
// If we have only a single file we MUST create a fully-qualified
|
|
// path to the destination file. Oddities in the shell's move/copy
|
|
// engine won't let us pass merely a destination folder in the
|
|
// case where that folder doesn't exist. If we give the full path
|
|
// including filename we'll get the "folder doesn't exist, create
|
|
// now?" messagebox as we would expect. If we're moving multiple
|
|
// files the shell accepts a single directory path.
|
|
//
|
|
LPCTSTR psz;
|
|
DblNulTermListIter iter(list);
|
|
if (iter.Next(&psz))
|
|
{
|
|
CPath strSrc(psz); // Copy the source
|
|
CPath strFile;
|
|
strSrc.GetFileSpec(&strFile);// Extract the filename.
|
|
strDest.Append(strFile); // Append to the dest path.
|
|
}
|
|
}
|
|
|
|
SHFILEOPSTRUCT fo;
|
|
fo.hwnd = m_hwndDlg;
|
|
fo.wFunc = FO_MOVE;
|
|
fo.pFrom = list;
|
|
fo.pTo = strDest;
|
|
fo.fFlags = FOF_RENAMEONCOLLISION;
|
|
|
|
if (0 != SHFileOperation(&fo))
|
|
{
|
|
DBGERROR((TEXT("SHFileOperation [FO_MOVE] failed")));
|
|
}
|
|
//
|
|
// Remove listview items if their file was really deleted.
|
|
//
|
|
RemoveListViewItems(hwndLV, rgItemHandles);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Get the SID to use for taking ownership of files.
|
|
// First try to get the first group SID with the SE_GROUP_OWNER attribute.
|
|
// If none found, use the operator's account SID. The SID is in a
|
|
// dynamic buffer attached to the ptrSid autoptr argument.
|
|
//
|
|
HRESULT
|
|
CFileOwnerDialog::GetOwnershipSid(
|
|
array_autoptr<BYTE> *ptrSid
|
|
)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
DWORD dwErr = 0;
|
|
|
|
//
|
|
// Get the token handle. First try the thread token then the process
|
|
// token. If these fail we return early. No sense in continuing
|
|
// on if we can't get a user token.
|
|
//
|
|
CWin32Handle hToken;
|
|
if (!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_READ,
|
|
TRUE,
|
|
hToken.HandlePtr()))
|
|
{
|
|
if (ERROR_NO_TOKEN == GetLastError())
|
|
{
|
|
if (!OpenProcessToken(GetCurrentProcess(),
|
|
TOKEN_READ,
|
|
hToken.HandlePtr()))
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d opening process token"), dwErr));
|
|
return HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d opening thread token"), dwErr));
|
|
return HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the required size of the group token information buffer.
|
|
//
|
|
array_autoptr<BYTE> ptrTokenInfo;
|
|
DWORD cbTokenInfo = 0;
|
|
|
|
if (!GetTokenInformation(hToken,
|
|
TokenGroups,
|
|
NULL,
|
|
cbTokenInfo,
|
|
&cbTokenInfo))
|
|
{
|
|
dwErr = GetLastError();
|
|
if (ERROR_INSUFFICIENT_BUFFER == dwErr)
|
|
{
|
|
ptrTokenInfo = new BYTE[cbTokenInfo];
|
|
}
|
|
else
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d getting TokenGroups info [for size]"), dwErr));
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the group token information.
|
|
//
|
|
if (NULL != ptrTokenInfo.get())
|
|
{
|
|
if (!GetTokenInformation(hToken,
|
|
TokenGroups,
|
|
ptrTokenInfo.get(),
|
|
cbTokenInfo,
|
|
&cbTokenInfo))
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d getting TokenGroups info"), dwErr));
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Extract the first SID with the GROUP_OWNER bit set.
|
|
//
|
|
TOKEN_GROUPS *ptg = (TOKEN_GROUPS *)ptrTokenInfo.get();
|
|
DBGASSERT((NULL != ptg));
|
|
for (DWORD i = 0; i < ptg->GroupCount; i++)
|
|
{
|
|
SID_AND_ATTRIBUTES *psa = (SID_AND_ATTRIBUTES *)&ptg->Groups[i];
|
|
DBGASSERT((NULL != psa));
|
|
if (SE_GROUP_OWNER & psa->Attributes)
|
|
{
|
|
int cbSid = GetLengthSid(psa->Sid);
|
|
*ptrSid = new BYTE[cbSid];
|
|
CopySid(cbSid, ptrSid->get(), psa->Sid);
|
|
hr = NOERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
//
|
|
// Didn't find a SID from the group information.
|
|
// Use the operator's SID.
|
|
//
|
|
cbTokenInfo = 0;
|
|
if (!GetTokenInformation(hToken,
|
|
TokenUser,
|
|
NULL,
|
|
cbTokenInfo,
|
|
&cbTokenInfo))
|
|
{
|
|
dwErr = GetLastError();
|
|
if (ERROR_INSUFFICIENT_BUFFER == dwErr)
|
|
{
|
|
ptrTokenInfo = new BYTE[cbTokenInfo];
|
|
}
|
|
else
|
|
{
|
|
DBGERROR((TEXT("Error %d getting TokenUser info [for size]"), dwErr));
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Get the user token information.
|
|
//
|
|
if (!GetTokenInformation(hToken,
|
|
TokenUser,
|
|
ptrTokenInfo.get(),
|
|
cbTokenInfo,
|
|
&cbTokenInfo))
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d getting TokenUser info"), dwErr));
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
else
|
|
{
|
|
SID_AND_ATTRIBUTES *psa = (SID_AND_ATTRIBUTES *)ptrTokenInfo.get();
|
|
DBGASSERT((NULL != psa));
|
|
int cbSid = GetLengthSid(psa->Sid);
|
|
*ptrSid = new BYTE[cbSid];
|
|
CopySid(cbSid, ptrSid->get(), psa->Sid);
|
|
hr = NOERROR;
|
|
}
|
|
}
|
|
}
|
|
if (SUCCEEDED(hr) && NULL != ptrSid->get() && !IsValidSid(ptrSid->get()))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_SID);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// Transfers ownership of selected files in the listview to the
|
|
// currently logged-on user.
|
|
//
|
|
HRESULT
|
|
CFileOwnerDialog::TakeOwnershipOfSelectedFiles(
|
|
HWND hwndLV
|
|
)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
DWORD dwErr = 0;
|
|
CArray<COwnerListItemHandle> rgItemHandles;
|
|
|
|
BuildListOfSelectedFiles(hwndLV, NULL, &rgItemHandles);
|
|
if (0 == rgItemHandles.Count())
|
|
return S_OK;
|
|
|
|
array_autoptr<BYTE> ptrSid;
|
|
hr = GetOwnershipSid(&ptrSid);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
CPath strFile;
|
|
int cHandles = rgItemHandles.Count();
|
|
for (int i = 0; i < cHandles; i++)
|
|
{
|
|
COwnerListItemHandle handle = rgItemHandles[i];
|
|
int iItem = FindItemFromHandle(hwndLV, handle);
|
|
if (-1 != iItem)
|
|
{
|
|
SECURITY_DESCRIPTOR sd;
|
|
if (InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
|
|
{
|
|
int iOwner = handle.OwnerIndex();
|
|
int iFile = handle.FileIndex();
|
|
m_OwnerList.GetFileFullPath(iOwner, iFile, &strFile);
|
|
if (SetSecurityDescriptorOwner(&sd, ptrSid.get(), FALSE))
|
|
{
|
|
if (SetFileSecurity(strFile, OWNER_SECURITY_INFORMATION, &sd))
|
|
{
|
|
ListView_DeleteItem(hwndLV, iItem);
|
|
m_OwnerList.MarkFileDeleted(iOwner, iFile);
|
|
}
|
|
else
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d setting new owner for \"%s\""),
|
|
dwErr, strFile.Cstr()));
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d setting security descriptor owner"), dwErr));
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwErr = GetLastError();
|
|
DBGERROR((TEXT("Error %d initing security descriptor"), GetLastError()));
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBGERROR((TEXT("Can't find listview item for owner %d, file %d"),
|
|
handle.OwnerIndex(), handle.FileIndex()));
|
|
}
|
|
}
|
|
//
|
|
// Refresh the owner combo with the new file counts.
|
|
//
|
|
InitializeOwnerCombo(m_OwnerList, m_hwndOwnerCombo);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// The original code for listing files owned by a user was
|
|
// contributed by MarkZ. I made some minor modifications
|
|
// to fit it into the diskquota project and make it more
|
|
// exception safe.
|
|
//
|
|
inline VOID *
|
|
Add2Ptr(VOID *pv, ULONG cb)
|
|
{
|
|
return((BYTE *) pv + cb);
|
|
}
|
|
|
|
inline ULONG
|
|
QuadAlign( ULONG Value )
|
|
{
|
|
return (Value + 7) & ~7;
|
|
}
|
|
|
|
|
|
//
|
|
// Add files owned by a particular user on a particular volume.
|
|
//
|
|
HRESULT
|
|
CFileOwnerDialog::AddFilesToOwnerList(
|
|
LPCTSTR pszVolumeRoot,
|
|
HANDLE hVolumeRoot,
|
|
IDiskQuotaUser *pOwner,
|
|
COwnerList *pOwnerList
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::AddFilesToOwnerList")));
|
|
DBGASSERT((NULL != hVolumeRoot));
|
|
DBGASSERT((NULL != pOwner));
|
|
DBGASSERT((NULL != pOwnerList));
|
|
|
|
struct
|
|
{
|
|
ULONG Restart;
|
|
BYTE Sid[MAX_SID_LEN];
|
|
}FsCtlInput;
|
|
|
|
NTSTATUS status = ERROR_SUCCESS;
|
|
|
|
if (m_bAbort)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// Get owner's SID.
|
|
//
|
|
HRESULT hr = pOwner->GetSid(FsCtlInput.Sid, sizeof(FsCtlInput.Sid));
|
|
if (FAILED(hr))
|
|
{
|
|
DBGERROR((TEXT("IDiskQuotaUser::GetSid failed with hr = 0x%08X"), hr));
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Add the owner to the owner-file list.
|
|
//
|
|
int iOwner = pOwnerList->AddOwner(pOwner);
|
|
|
|
IO_STATUS_BLOCK iosb;
|
|
FsCtlInput.Restart = 1;
|
|
BYTE Output[1024];
|
|
bool bPathIsRemote = false;
|
|
FILE_FS_DEVICE_INFORMATION DeviceInfo;
|
|
|
|
//
|
|
// Determine if the volume is a remote device. This will affect
|
|
// our handling of the paths returned by NtQueryInformationFile.
|
|
//
|
|
status = NtQueryVolumeInformationFile(
|
|
hVolumeRoot,
|
|
&iosb,
|
|
&DeviceInfo,
|
|
sizeof(DeviceInfo),
|
|
FileFsDeviceInformation);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
bPathIsRemote = (FILE_REMOTE_DEVICE == DeviceInfo.Characteristics);
|
|
}
|
|
|
|
while (!m_bAbort)
|
|
{
|
|
status = NtFsControlFile(hVolumeRoot,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&iosb,
|
|
FSCTL_FIND_FILES_BY_SID,
|
|
&FsCtlInput,
|
|
sizeof(FsCtlInput),
|
|
Output,
|
|
sizeof(Output));
|
|
|
|
FsCtlInput.Restart = 0;
|
|
|
|
if (!NT_SUCCESS(status) && STATUS_BUFFER_OVERFLOW != status)
|
|
{
|
|
DBGERROR((TEXT("NtFsControlFile failed with status 0x%08X"), status));
|
|
return HRESULT_FROM_NT(status);
|
|
}
|
|
|
|
if (0 == iosb.Information)
|
|
{
|
|
//
|
|
// No more data.
|
|
//
|
|
break;
|
|
}
|
|
|
|
PFILE_NAME_INFORMATION pFileNameInfo = (PFILE_NAME_INFORMATION)Output;
|
|
|
|
while (!m_bAbort && ((PBYTE)pFileNameInfo < Output + iosb.Information))
|
|
{
|
|
ULONG Length = sizeof(FILE_NAME_INFORMATION) - sizeof(WCHAR) +
|
|
pFileNameInfo->FileNameLength;
|
|
|
|
CNtHandle hChild;
|
|
WCHAR szChild[MAX_PATH];
|
|
|
|
RtlMoveMemory(szChild, pFileNameInfo->FileName, pFileNameInfo->FileNameLength);
|
|
szChild[pFileNameInfo->FileNameLength / sizeof(WCHAR)] = L'\0';
|
|
status = OpenNtObject(szChild,
|
|
hVolumeRoot,
|
|
FILE_SYNCHRONOUS_IO_NONALERT,
|
|
FILE_READ_ATTRIBUTES,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_OPEN,
|
|
hChild.HandlePtr());
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DBGERROR((TEXT("Unable to open file \"%s\". Status = 0x%08X"),
|
|
szChild, status));
|
|
}
|
|
else if (!m_bAbort)
|
|
{
|
|
//
|
|
// Directory entries get a slightly different treatment so
|
|
// we need to know if an entry is a directory or not.
|
|
//
|
|
bool bIsDirectory = false;
|
|
IO_STATUS_BLOCK iosb2;
|
|
FILE_BASIC_INFORMATION fbi;
|
|
status = NtQueryInformationFile(hChild,
|
|
&iosb2,
|
|
&fbi,
|
|
sizeof(fbi),
|
|
FileBasicInformation);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DBGERROR((TEXT("NtQueryInformationFile failed with status 0x%08X for \"%s\""),
|
|
status, szChild));
|
|
}
|
|
else if (0 != (FILE_ATTRIBUTE_DIRECTORY & fbi.FileAttributes))
|
|
{
|
|
bIsDirectory = true;
|
|
}
|
|
|
|
//
|
|
// Get the file's name (full path).
|
|
//
|
|
WCHAR szFile[MAX_PATH + 10];
|
|
status = NtQueryInformationFile(hChild,
|
|
&iosb2,
|
|
szFile,
|
|
sizeof(szFile),
|
|
FileNameInformation);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DBGERROR((TEXT("NtQueryInformation file failed with status 0x%08X for \"%s\""),
|
|
status, szChild));
|
|
}
|
|
else if (!m_bAbort)
|
|
{
|
|
PFILE_NAME_INFORMATION pfn = (PFILE_NAME_INFORMATION)szFile;
|
|
pfn->FileName[pfn->FileNameLength / sizeof(WCHAR)] = L'\0';
|
|
CPath path;
|
|
|
|
//
|
|
// If the path is remote, NtQueryInformationFile returns
|
|
// a string like this:
|
|
//
|
|
// \server\share\dir1\dir2\file.ext
|
|
//
|
|
// If the path is local, NtQueryInformationFile returns
|
|
// a string like this:
|
|
//
|
|
// \dir1\dir2\file.ext
|
|
//
|
|
// For remote paths we merely prepend a '\' to create a
|
|
// valid UNC path. For local paths we prepend the local
|
|
// drive specification.
|
|
//
|
|
if (bPathIsRemote)
|
|
{
|
|
path = L"\\";
|
|
path += CString(pfn->FileName);
|
|
}
|
|
else
|
|
{
|
|
path = pszVolumeRoot;
|
|
path.Append(pfn->FileName);
|
|
}
|
|
DBGPRINT((DM_VIEW, DL_LOW, TEXT("Adding \"%s\""), path.Cstr()));
|
|
pOwnerList->AddFile(iOwner, path, bIsDirectory);
|
|
}
|
|
}
|
|
hChild.Close();
|
|
|
|
pFileNameInfo =
|
|
(PFILE_NAME_INFORMATION) Add2Ptr(pFileNameInfo, QuadAlign(Length));
|
|
}
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
//
|
|
// Build a list of files owned by a set of users on a particular volume.
|
|
// pszVolumeRoot is the volume root directory (i.e. "C:\").
|
|
// rgpOwners is an array of user object pointers, one for each owner.
|
|
// pOwnerList is the container where the resulting filenames are placed.
|
|
// Calls AddFilesToOwnerList() for each owner in rgpOwners.
|
|
//
|
|
HRESULT
|
|
CFileOwnerDialog::BuildFileOwnerList(
|
|
LPCTSTR pszVolumeRoot,
|
|
const CArray<IDiskQuotaUser *>& rgpOwners,
|
|
COwnerList *pOwnerList
|
|
)
|
|
{
|
|
DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::BuildFileOwnerList")));
|
|
HRESULT hr = NOERROR;
|
|
CNtHandle hVolumeRoot;
|
|
NTSTATUS status = OpenNtObject(pszVolumeRoot,
|
|
NULL,
|
|
FILE_SYNCHRONOUS_IO_NONALERT,
|
|
FILE_READ_ATTRIBUTES,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_OPEN,
|
|
hVolumeRoot.HandlePtr());
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return HRESULT_FROM_NT(status);
|
|
|
|
int cOwners = rgpOwners.Count();
|
|
for (int i = 0; i < cOwners && !m_bAbort; i++)
|
|
{
|
|
hr = AddFilesToOwnerList(pszVolumeRoot, hVolumeRoot, rgpOwners[i], pOwnerList);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// MarkZ had this function in his original implementation so I just
|
|
// kept it. I did need to fix a bug in the original code. He was
|
|
// calling RtlFreeHeap() on str.Buffer for all cases. This is was
|
|
// not applicable in the RtlInitUnicodeString() case where the
|
|
// unicode string is merely bound to the pszFile argument.
|
|
//
|
|
NTSTATUS
|
|
CFileOwnerDialog::OpenNtObject (
|
|
LPCWSTR pszFile,
|
|
HANDLE RelatedObject,
|
|
ULONG CreateOptions,
|
|
ULONG DesiredAccess,
|
|
ULONG ShareAccess,
|
|
ULONG CreateDisposition,
|
|
HANDLE *ph)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES oa;
|
|
UNICODE_STRING str;
|
|
IO_STATUS_BLOCK isb;
|
|
bool bFreeString = false;
|
|
|
|
if (NULL == RelatedObject)
|
|
{
|
|
RtlDosPathNameToNtPathName_U(pszFile, &str, NULL, NULL);
|
|
bFreeString = true;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This just attaches pszFile to the rtl string.
|
|
// We don't free it.
|
|
//
|
|
RtlInitUnicodeString(&str, pszFile);
|
|
}
|
|
|
|
InitializeObjectAttributes(&oa,
|
|
&str,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelatedObject,
|
|
NULL);
|
|
|
|
status = NtCreateFile(ph,
|
|
DesiredAccess | SYNCHRONIZE,
|
|
&oa,
|
|
&isb,
|
|
NULL, // pallocationsize (none!)
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
ShareAccess,
|
|
CreateDisposition,
|
|
CreateOptions,
|
|
NULL, // EA buffer (none!)
|
|
0);
|
|
|
|
if (bFreeString)
|
|
RtlFreeHeap(RtlProcessHeap(), 0, str.Buffer);
|
|
return(status);
|
|
}
|