reactos/dll/shellext/cabview/folder.cpp
2024-12-05 15:14:55 +01:00

857 lines
27 KiB
C++

/*
* PROJECT: ReactOS CabView Shell Extension
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Shell folder implementation
* COPYRIGHT: Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
*/
#include "cabview.h"
#include "util.h"
enum FOLDERCOLUMNS
{
COL_NAME, // PKEY_ItemNameDisplay
COL_SIZE, // PKEY_Size
COL_TYPE, // PKEY_ItemTypeText
COL_MDATE, // PKEY_DateModified
COL_PATH, // PKEY_?: Archive-relative path
COL_ATT, // PKEY_FileAttributes
COLCOUNT
};
static const struct FOLDERCOLUMN
{
BYTE TextId;
BYTE LvcFmt;
BYTE LvcChars;
BYTE ColFlags;
const GUID *pkg;
BYTE pki;
} g_Columns[] =
{
{ IDS_COL_NAME, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_NAME },
{ IDS_COL_SIZE, LVCFMT_RIGHT, 16, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_SIZE },
{ IDS_COL_TYPE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_STORAGETYPE },
{ IDS_COL_MDATE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_WRITETIME },
{ IDS_COL_PATH, LVCFMT_LEFT, 30, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &CLSID_CabFolder, 0 },
{ IDS_COL_ATT, LVCFMT_RIGHT, 10, SHCOLSTATE_TYPE_STR, &FMTID_Storage, PID_STG_ATTRIBUTES },
};
#include <pshpack1.h>
struct CABITEM
{
WORD cb;
WORD Unknown; // Not sure what Windows uses this for, we always store 0
UINT Size;
WORD Date, Time; // DOS
WORD Attrib;
WORD NameOffset;
WCHAR Path[ANYSIZE_ARRAY];
#if FLATFOLDER
inline bool IsFolder() const { return false; }
#else
inline BOOL IsFolder() const { return Attrib & FILE_ATTRIBUTE_DIRECTORY; }
#endif
enum { FSATTS = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DIRECTORY };
WORD GetFSAttributes() const { return Attrib & FSATTS; }
LPCWSTR GetName() const { return Path + NameOffset; }
template<class PIDL> static CABITEM* Validate(PIDL pidl)
{
CABITEM *p = (CABITEM*)pidl;
return p && p->cb > FIELD_OFFSET(CABITEM, Path[1]) && p->Unknown == 0 ? p : NULL;
}
};
#include <poppack.h>
static CABITEM* CreateItem(LPCWSTR Path, UINT Attrib, UINT Size, UINT DateTime)
{
const SIZE_T len = lstrlenW(Path), cb = FIELD_OFFSET(CABITEM, Path[len + 1]);
if (cb > 0xffff)
return NULL;
CABITEM *p = (CABITEM*)SHAlloc(cb + sizeof(USHORT));
if (p)
{
p->cb = (USHORT)cb;
p->Unknown = 0;
p->Size = Size;
p->Attrib = Attrib;
p->Date = HIWORD(DateTime);
p->Time = LOWORD(DateTime);
p->NameOffset = 0;
for (UINT i = 0;; ++i)
{
WCHAR c = Path[i];
if (c == L':') // Don't allow absolute paths
c = L'_';
if (c == L'/') // Normalize
c = L'\\';
if (c == '\\')
p->NameOffset = i + 1;
p->Path[i] = c;
if (!c)
break;
}
((SHITEMID*)((BYTE*)p + cb))->cb = 0;
}
return p;
}
static CABITEM* CreateItem(LPCSTR Path, UINT Attrib, UINT Size = 0, UINT DateTime = 0)
{
WCHAR buf[MAX_PATH * 2];
UINT codepage = (Attrib & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP;
if (MultiByteToWideChar(codepage, 0, Path, -1, buf, _countof(buf)))
return CreateItem(buf, Attrib, Size, DateTime);
return NULL;
}
static HRESULT CALLBACK ItemMenuCallback(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj,
UINT uMsg, WPARAM wParam, LPARAM lParam)
{
enum { IDC_EXTRACT, IDC_EXTRACTALL };
HRESULT hr = E_NOTIMPL;
const BOOL Background = !pdtobj;
switch (uMsg)
{
case DFM_MODIFYQCMFLAGS:
{
*((UINT*)lParam) = wParam | CMF_NOVERBS | (Background ? 0 : CMF_VERBSONLY);
return S_OK;
}
case DFM_MERGECONTEXTMENU:
{
QCMINFO &qcmi = *(QCMINFO*)lParam;
UINT pos = qcmi.indexMenu, id = 0;
if (Background || SUCCEEDED(hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACT, IDS_EXTRACT, MFS_DEFAULT)))
{
hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACTALL, IDS_EXTRACTALL);
if (SUCCEEDED(hr) && !Background)
{
--pos;
InsertMenuItem(qcmi, pos, id, 0, -1); // Separator
}
}
if (SUCCEEDED(hr))
{
qcmi.idCmdFirst = id + 1;
hr = S_FALSE; // Don't add verbs
}
break;
}
case DFM_INVOKECOMMAND:
{
hr = S_FALSE;
CCabFolder *pCabFolder = static_cast<CCabFolder*>(psf);
switch (wParam)
{
case IDC_EXTRACT:
case IDC_EXTRACTALL:
hr = pCabFolder->ExtractFilesUI(hwnd, wParam == IDC_EXTRACT ? pdtobj : NULL);
break;
}
break;
}
}
return hr;
}
static HRESULT CALLBACK FolderBackgroundMenuCallback(IShellFolder *psf, HWND hwnd,
IDataObject *pdtobj, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
return ItemMenuCallback(psf, hwnd, NULL, uMsg, wParam, lParam);
}
int CEnumIDList::FindNamedItem(PCUITEMID_CHILD pidl) const
{
CABITEM *needle = (CABITEM*)pidl;
for (ULONG i = 0, c = GetCount(); i < c; ++i)
{
CABITEM *item = (CABITEM*)DPA_FastGetPtr(m_Items, i);
if (!lstrcmpiW(needle->Path, item->Path))
return i;
}
return -1;
}
struct FILLCALLBACKDATA
{
CEnumIDList *pEIDL;
SHCONTF ContF;
};
static HRESULT CALLBACK EnumFillCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie)
{
FILLCALLBACKDATA &data = *(FILLCALLBACKDATA*)cookie;
switch ((UINT)msg)
{
case ECM_FILE:
{
const FDINOTIFICATION &fdin = *ecd.pfdin;
HRESULT hr = S_FALSE;
SFGAOF attr = MapFSToSFAttributes(fdin.attribs & CABITEM::FSATTS);
if (IncludeInEnumIDList(data.ContF, attr))
{
UINT datetime = MAKELONG(fdin.time, fdin.date);
CABITEM *item = CreateItem(fdin.psz1, fdin.attribs, fdin.cb, datetime);
if (!item)
return E_OUTOFMEMORY;
if (FAILED(hr = data.pEIDL->Append((LPCITEMIDLIST)item)))
SHFree(item);
}
return SUCCEEDED(hr) ? S_FALSE : hr; // Never extract
}
}
return E_NOTIMPL;
}
HRESULT CEnumIDList::Fill(LPCWSTR path, HWND hwnd, SHCONTF contf)
{
FILLCALLBACKDATA data = { this, contf };
return ExtractCabinet(path, NULL, EnumFillCallback, &data);
}
HRESULT CEnumIDList::Fill(PCIDLIST_ABSOLUTE pidl, HWND hwnd, SHCONTF contf)
{
WCHAR path[MAX_PATH];
if (SHGetPathFromIDListW(pidl, path))
return Fill(path, hwnd, contf);
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
IFACEMETHODIMP CCabFolder::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
{
if (pSort)
*pSort = COL_NAME;
if (pDisplay)
*pDisplay = COL_NAME;
return S_OK;
}
IFACEMETHODIMP CCabFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
{
if (!pcsFlags || iColumn >= _countof(g_Columns))
return E_INVALIDARG;
*pcsFlags = g_Columns[iColumn].ColFlags;
return S_OK;
}
IFACEMETHODIMP CCabFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName)
{
CABITEM *item = CABITEM::Validate(pidl);
if (!item || !pName)
return E_INVALIDARG;
if (dwFlags & SHGDN_FORPARSING)
{
if (dwFlags & SHGDN_INFOLDER)
return StrTo(FLATFOLDER ? item->Path : item->GetName(), *pName);
WCHAR parent[MAX_PATH];
if (!SHGetPathFromIDListW(m_CurDir, parent))
return E_FAIL;
UINT cch = lstrlenW(parent) + 1 + lstrlenW(item->Path) + 1;
pName->uType = STRRET_WSTR;
pName->pOleStr = (LPWSTR)SHAlloc(cch * sizeof(WCHAR));
if (!pName->pOleStr)
return E_OUTOFMEMORY;
lstrcpyW(pName->pOleStr, parent);
PathAppendW(pName->pOleStr, item->Path);
return S_OK;
}
SHFILEINFO fi;
DWORD attr = item->IsFolder() ? FILE_ATTRIBUTE_DIRECTORY : 0;
UINT flags = SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES;
if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags))
return StrTo(fi.szDisplayName, *pName);
return StrTo(item->GetName(), *pName);
}
HRESULT CCabFolder::GetItemDetails(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd, VARIANT *pv)
{
HRESULT hr = E_FAIL;
STRRET *psr = &psd->str, srvar;
CABITEM *item = CABITEM::Validate(pidl);
if (!item)
return E_INVALIDARG;
switch (iColumn)
{
case COL_NAME:
{
hr = GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, pv ? &srvar : psr);
return SUCCEEDED(hr) && pv ? StrRetToVariantBSTR(&srvar, *pv) : hr;
}
case COL_SIZE:
{
UINT data = item->Size;
if (pv)
{
V_VT(pv) = VT_UI4;
V_UI4(pv) = data;
}
else
{
psr->uType = STRRET_CSTR;
StrFormatByteSizeA(data, psr->cStr, _countof(psr->cStr));
}
return S_OK;
}
case COL_TYPE:
{
SHFILEINFO fi;
LPCWSTR data = fi.szTypeName;
DWORD attr = item->GetFSAttributes();
UINT flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES;
if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags))
return pv ? StrTo(data, *pv) : StrTo(data, *psr);
break;
}
case COL_MDATE:
{
if (pv)
{
if (DosDateTimeToVariantTime(item->Date, item->Time, &V_DATE(pv)))
{
V_VT(pv) = VT_DATE;
return S_OK;
}
}
else
{
FILETIME utc, loc;
if (DosDateTimeToFileTime(item->Date, item->Time, &utc) && FileTimeToLocalFileTime(&utc, &loc))
{
psr->uType = STRRET_CSTR;
if (SHFormatDateTimeA(&loc, NULL, psr->cStr, _countof(psr->cStr)))
{
return S_OK;
}
}
}
break;
}
case COL_PATH:
{
UINT len = item->NameOffset ? item->NameOffset - 1 : 0;
return pv ? StrTo(item->Path, len, *pv) : StrTo(item->Path, len, *psr);
}
case COL_ATT:
{
UINT data = item->GetFSAttributes();
if (pv)
{
V_VT(pv) = VT_UI4;
V_UI4(pv) = data;
}
else
{
UINT i = 0;
psr->uType = STRRET_CSTR;
if (data & FILE_ATTRIBUTE_READONLY) psr->cStr[i++] = 'R';
if (data & FILE_ATTRIBUTE_HIDDEN) psr->cStr[i++] = 'H';
if (data & FILE_ATTRIBUTE_SYSTEM) psr->cStr[i++] = 'S';
if (data & FILE_ATTRIBUTE_ARCHIVE) psr->cStr[i++] = 'A';
psr->cStr[i++] = '\0';
}
return S_OK;
}
}
return hr;
}
IFACEMETHODIMP CCabFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
{
if (!pscid || !pv)
return E_INVALIDARG;
CABITEM *item;
int col = MapSCIDToColumn(*pscid);
if (col >= 0)
{
return GetItemDetails(pidl, col, NULL, pv);
}
else if ((item = CABITEM::Validate(pidl)) == NULL)
{
return E_INVALIDARG;
}
else if (IsEqual(*pscid, FMTID_ShellDetails, PID_FINDDATA))
{
WIN32_FIND_DATA wfd;
ZeroMemory(&wfd, sizeof(wfd));
wfd.dwFileAttributes = item->GetFSAttributes();
wfd.nFileSizeLow = item->Size;
DosDateTimeToFileTime(item->Date, item->Time, &wfd.ftLastWriteTime);
lstrcpyn(wfd.cFileName, item->GetName(), MAX_PATH);
return InitVariantFromBuffer(&wfd, sizeof(wfd), pv);
}
return E_FAIL;
}
IFACEMETHODIMP CCabFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
{
if (!psd || iColumn >= _countof(g_Columns))
{
return E_INVALIDARG;
}
else if (!pidl)
{
psd->fmt = g_Columns[iColumn].LvcFmt;
psd->cxChar = g_Columns[iColumn].LvcChars;
WCHAR buf[MAX_PATH];
if (LoadStringW(_AtlBaseModule.GetResourceInstance(), g_Columns[iColumn].TextId, buf, _countof(buf)))
return StrTo(buf, psd->str);
return E_FAIL;
}
return GetItemDetails(pidl, iColumn, psd, NULL);
}
int CCabFolder::MapSCIDToColumn(const SHCOLUMNID &scid)
{
for (UINT i = 0; i < _countof(g_Columns); ++i)
{
if (g_Columns[i].pkg && IsEqual(scid, *g_Columns[i].pkg, g_Columns[i].pki))
return i;
}
return -1;
}
IFACEMETHODIMP CCabFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
{
if (column < _countof(g_Columns) && g_Columns[column].pkg)
{
pscid->fmtid = *g_Columns[column].pkg;
pscid->pid = g_Columns[column].pki;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP CCabFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
{
CEnumIDList *p = CEnumIDList::CreateInstance();
*ppEnumIDList = static_cast<LPENUMIDLIST>(p);
return p ? p->Fill(m_CurDir, hwndOwner, dwFlags) : E_OUTOFMEMORY;
}
IFACEMETHODIMP CCabFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
{
UNIMPLEMENTED;
return E_NOTIMPL;
}
HRESULT CCabFolder::CompareID(LPARAM lParam, PCUITEMID_CHILD pidl1, PCUITEMID_CHILD pidl2)
{
CABITEM *p1 = (CABITEM*)pidl1, *p2 = (CABITEM*)pidl2;
HRESULT hr = S_OK;
int ret = 0;
if (lParam & (SHCIDS_ALLFIELDS | SHCIDS_CANONICALONLY))
{
ret = lstrcmpiW(p1->Path, p2->Path);
if (ret && (lParam & SHCIDS_ALLFIELDS))
{
for (UINT i = 0; ret && SUCCEEDED(hr) && i < COLCOUNT; ++i)
{
hr = (i == COL_NAME) ? 0 : CompareID(i, pidl1, pidl2);
ret = (short)HRESULT_CODE(hr);
}
}
}
else
{
UINT col = lParam & SHCIDS_COLUMNMASK;
switch (col)
{
case COL_NAME:
ret = StrCmpLogicalW(p1->GetName(), p2->GetName());
break;
case COL_SIZE:
ret = p1->Size - p2->Size;
break;
case COL_MDATE:
ret = MAKELONG(p1->Time, p1->Date) - MAKELONG(p2->Time, p2->Date);
break;
default:
{
if (col < COLCOUNT)
{
PWSTR str1, str2;
if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl1, col, str1)))
{
if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl2, col, str2)))
{
ret = StrCmpLogicalW(str1, str2);
SHFree(str2);
}
SHFree(str1);
}
}
else
{
hr = E_INVALIDARG;
}
}
}
}
return SUCCEEDED(hr) ? MAKE_COMPARE_HRESULT(ret) : hr;
}
IFACEMETHODIMP CCabFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
{
C_ASSERT(FLATFOLDER);
if (!pidl1 || !ILIsSingle(pidl1) || !pidl2 || !ILIsSingle(pidl2))
return E_UNEXPECTED;
return CompareID(lParam, pidl1, pidl2);
}
IFACEMETHODIMP CCabFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppv)
{
if (riid == IID_IShellView)
{
SFV_CREATE sfvc = { sizeof(SFV_CREATE), static_cast<IShellFolder*>(this) };
return SHCreateShellFolderView(&sfvc, (IShellView**)ppv);
}
if (riid == IID_IContextMenu)
{
LPFNDFMCALLBACK func = FolderBackgroundMenuCallback;
IContextMenu **ppCM = (IContextMenu**)ppv;
return CDefFolderMenu_Create2(m_CurDir, hwndOwner, 0, NULL, this, func, 0, NULL, ppCM);
}
return E_NOINTERFACE;
}
IFACEMETHODIMP CCabFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, SFGAOF *rgfInOut)
{
if (!cidl)
{
const SFGAOF ThisFolder = (SFGAO_FOLDER | SFGAO_BROWSABLE | SFGAO_CANLINK);
*rgfInOut = *rgfInOut & ThisFolder;
return S_OK;
}
else if (!apidl)
{
return E_INVALIDARG;
}
HRESULT hr = S_OK;
const SFGAOF filemask = SFGAO_READONLY | SFGAO_HIDDEN | SFGAO_SYSTEM | SFGAO_ISSLOW;
SFGAOF remain = *rgfInOut & filemask, validate = *rgfInOut & SFGAO_VALIDATE;
CComPtr<CEnumIDList> list;
for (UINT i = 0; i < cidl && (remain || validate); ++i)
{
CABITEM *item = CABITEM::Validate(apidl[i]);
if (!item)
{
hr = E_INVALIDARG;
break;
}
else if (validate)
{
if (!list && FAILED_UNEXPECTEDLY(hr = CreateEnum(&list)))
return hr;
if (list->FindNamedItem((PCUITEMID_CHILD)item) == -1)
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
SFGAOF att = MapFSToSFAttributes(item->GetFSAttributes()) | SFGAO_ISSLOW;
remain &= att & ~(FLATFOLDER ? SFGAO_FOLDER : 0);
}
*rgfInOut = remain;
return hr;
}
IFACEMETHODIMP CCabFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT *prgfInOut, LPVOID *ppvOut)
{
HRESULT hr = E_NOINTERFACE;
if (riid == IID_IExtractIconA || riid == IID_IExtractIconW)
{
if (cidl != 1)
return E_INVALIDARG;
CABITEM *item = CABITEM::Validate(apidl[0]);
if (!item)
return E_INVALIDARG;
DWORD attr = item->GetFSAttributes();
return SHCreateFileExtractIconW(item->GetName(), attr, riid, ppvOut);
}
else if (riid == IID_IContextMenu && cidl)
{
LPFNDFMCALLBACK func = ItemMenuCallback;
IContextMenu **ppCM = (IContextMenu**)ppvOut;
return CDefFolderMenu_Create2(NULL, hwndOwner, cidl, apidl, this, func, 0, NULL, ppCM);
}
else if (riid == IID_IDataObject && cidl)
{
// Note: This IDataObject is only compatible with IContextMenu, it cannot handle drag&drop of virtual items!
return CIDLData_CreateFromIDArray(m_CurDir, cidl, apidl, (IDataObject**)ppvOut);
}
return hr;
}
IFACEMETHODIMP CCabFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case SFVM_WINDOWCREATED:
m_ShellViewWindow = (HWND)wParam;
return S_OK;
case SFVM_WINDOWCLOSING:
m_ShellViewWindow = NULL;
return S_OK;
}
return E_NOTIMPL;
}
IFACEMETHODIMP CCabFolder::GetIconOf(PCUITEMID_CHILD pidl, UINT flags, int *pIconIndex)
{
if (CABITEM *item = CABITEM::Validate(pidl))
{
int index = MapPIDLToSystemImageListIndex(this, pidl, flags);
if (index == -1 && item->IsFolder())
index = (flags & GIL_OPENICON) ? SIID_FOLDEROPEN : SIID_FOLDER;
if (index != -1)
{
*pIconIndex = index;
return S_OK;
}
}
return S_FALSE;
}
static HRESULT GetFsPathFromIDList(PCIDLIST_ABSOLUTE pidl, PWSTR pszPath)
{
BOOL ret = SHGetPathFromIDListW(pidl, pszPath);
if (!ret && ILIsEmpty(pidl))
ret = SHGetSpecialFolderPathW(NULL, pszPath, CSIDL_DESKTOPDIRECTORY, TRUE);
return ret ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
static int CALLBACK FolderBrowseCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
WCHAR buf[MAX_PATH];
switch (uMsg)
{
case BFFM_INITIALIZED:
{
if (LoadStringW(_AtlBaseModule.GetResourceInstance(), IDS_EXTRACT, buf, _countof(buf)))
{
// Remove leading and trailing dots
WCHAR *s = buf, *e = s + lstrlenW(s);
while (*s == '.') ++s;
while (e > s && e[-1] == '.') *--e = UNICODE_NULL;
SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)s);
SendMessageW(GetDlgItem(hwnd, IDOK), WM_SETTEXT, 0, (LPARAM)s);
}
if (lpData)
{
SendMessageW(hwnd, BFFM_SETEXPANDED, FALSE, lpData);
SendMessageW(hwnd, BFFM_SETSELECTION, FALSE, lpData);
}
break;
}
case BFFM_SELCHANGED:
{
SFGAOF wanted = SFGAO_FILESYSTEM | SFGAO_FOLDER, query = wanted | SFGAO_STREAM;
PCIDLIST_ABSOLUTE pidl = (PCIDLIST_ABSOLUTE)lParam;
BOOL enable = ILIsEmpty(pidl); // Allow the desktop
PCUITEMID_CHILD child;
IShellFolder *pSF;
if (SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &child)))
{
SFGAOF attrib = query;
if (SUCCEEDED(pSF->GetAttributesOf(1, &child, &attrib)))
enable = (attrib & query) == wanted;
pSF->Release();
}
if (enable)
{
// We don't trust .zip folders, check the file-system to make sure
UINT attrib = SUCCEEDED(GetFsPathFromIDList(pidl, buf)) ? GetFileAttributesW(buf) : 0;
enable = (attrib & FILE_ATTRIBUTE_DIRECTORY) && attrib != INVALID_FILE_ATTRIBUTES;
}
PostMessageW(hwnd, BFFM_ENABLEOK, 0, enable);
break;
}
}
return 0;
}
struct EXTRACTFILESDATA
{
CCabFolder *pLifetimeCF;
HWND hWndOwner;
CIDA *pCIDA;
STGMEDIUM cidamedium;
IDataObject *pDO;
IStream *pMarshalDO;
IProgressDialog *pPD;
UINT cabfiles, completed;
WCHAR path[MAX_PATH], cab[MAX_PATH];
};
static HWND GetUiOwner(const EXTRACTFILESDATA &data)
{
HWND hWnd;
if (SUCCEEDED(IUnknown_GetWindow(data.pPD, &hWnd)) && IsWindowVisible(hWnd))
return hWnd;
return IsWindowVisible(data.hWndOwner) ? data.hWndOwner : NULL;
}
static HRESULT CALLBACK ExtractFilesCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie)
{
EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)cookie;
switch ((UINT)msg)
{
case ECM_BEGIN:
{
data.cabfiles = (UINT)(SIZE_T)ecd.pfdin->hf;
return S_OK;
}
case ECM_FILE:
{
if (data.pPD && data.pPD->HasUserCancelled())
return HRESULT_FROM_WIN32(ERROR_CANCELLED);
HRESULT hr = data.pCIDA ? S_FALSE : S_OK; // Filtering or all items?
if (hr != S_OK)
{
CABITEM *needle = CreateItem(ecd.pfdin->psz1, ecd.pfdin->attribs);
if (!needle)
return E_OUTOFMEMORY;
for (UINT i = 0; i < data.pCIDA->cidl && hr == S_FALSE; ++i)
{
C_ASSERT(FLATFOLDER);
LPCITEMIDLIST pidlChild = ILFindLastID(HIDA_GetPIDLItem(data.pCIDA, i));
CABITEM *haystack = CABITEM::Validate(pidlChild);
if (!haystack && FAILED_UNEXPECTEDLY(hr = E_FAIL))
break;
if (!lstrcmpiW(needle->Path, haystack->Path))
{
if (data.pPD)
data.pPD->SetLine(1, needle->Path, TRUE, NULL);
hr = S_OK; // Found it in the list of files to extract
}
}
SHFree(needle);
}
if (data.pPD)
data.pPD->SetProgress(data.completed++, data.cabfiles);
return hr;
}
case ECM_PREPAREPATH:
{
UINT flags = SHPPFW_DIRCREATE | SHPPFW_IGNOREFILENAME;
return SHPathPrepareForWriteW(GetUiOwner(data), NULL, ecd.Path, flags);
}
case ECM_ERROR:
{
return ErrorBox(GetUiOwner(data), ecd.hr);
}
}
return E_NOTIMPL;
}
static void Free(EXTRACTFILESDATA &data)
{
if (data.pPD)
{
data.pPD->StopProgressDialog();
data.pPD->Release();
}
CDataObjectHIDA::DestroyCIDA(data.pCIDA, data.cidamedium);
IUnknown_Set((IUnknown**)&data.pDO, NULL);
IUnknown_Set((IUnknown**)&data.pMarshalDO, NULL);
IUnknown_Set((IUnknown**)&data.pLifetimeCF, NULL);
SHFree(&data);
}
static DWORD CALLBACK ExtractFilesThread(LPVOID pParam)
{
EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)pParam;
HRESULT hr = S_OK;
if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_ProgressDialog, NULL, IID_PPV_ARG(IProgressDialog, &data.pPD))))
{
// TODO: IActionProgress SPACTION_COPYING
if (SUCCEEDED(data.pPD->StartProgressDialog(data.hWndOwner, NULL, PROGDLG_NOTIME, NULL)))
{
data.pPD->SetTitle(data.cab);
data.pPD->SetLine(2, data.path, TRUE, NULL);
data.pPD->SetAnimation(GetModuleHandleW(L"SHELL32"), 161);
data.pPD->SetProgress(0, 0);
}
}
if (data.pMarshalDO)
{
hr = CoGetInterfaceAndReleaseStream(data.pMarshalDO, IID_PPV_ARG(IDataObject, &data.pDO));
data.pMarshalDO = NULL;
if (SUCCEEDED(hr))
hr = CDataObjectHIDA::CreateCIDA(data.pDO, &data.pCIDA, data.cidamedium);
}
if (SUCCEEDED(hr))
{
ExtractCabinet(data.cab, data.path, ExtractFilesCallback, &data);
}
Free(data);
return 0;
}
HRESULT CCabFolder::ExtractFilesUI(HWND hWnd, IDataObject *pDO)
{
if (!IsWindowVisible(hWnd) && IsWindowVisible(m_ShellViewWindow))
hWnd = m_ShellViewWindow;
EXTRACTFILESDATA *pData = (EXTRACTFILESDATA*)SHAlloc(sizeof(*pData));
if (!pData)
return E_OUTOFMEMORY;
ZeroMemory(pData, sizeof(*pData));
pData->hWndOwner = hWnd;
pData->pLifetimeCF = this;
pData->pLifetimeCF->AddRef();
HRESULT hr = GetFsPathFromIDList(m_CurDir, pData->cab);
if (SUCCEEDED(hr) && pDO)
{
hr = CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDO, &pData->pMarshalDO);
}
if (SUCCEEDED(hr))
{
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
LPITEMIDLIST pidlInitial = ILClone(m_CurDir);
ILRemoveLastID(pidlInitial); // Remove the "name.cab" part (we can't extract into ourselves)
UINT bif = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
BROWSEINFO bi = { hWnd, NULL, NULL, pData->cab, bif, FolderBrowseCallback, (LPARAM)pidlInitial };
if (PIDLIST_ABSOLUTE folder = SHBrowseForFolderW(&bi))
{
hr = GetFsPathFromIDList(folder, pData->path);
ILFree(folder);
if (SUCCEEDED(hr))
{
UINT ctf = CTF_COINIT | CTF_PROCESS_REF | CTF_THREAD_REF | CTF_FREELIBANDEXIT;
hr = SHCreateThread(ExtractFilesThread, pData, ctf, NULL) ? S_OK : E_OUTOFMEMORY;
}
}
ILFree(pidlInitial);
}
if (hr != S_OK)
Free(*pData);
return hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ? S_OK : hr;
}