1 /*
2 * Trash virtual folder support. The trashing engine is implemented in trash.c
3 *
4 * Copyright (C) 2006 Mikolaj Zalewski
5 * Copyright (C) 2009 Andrew Hill
6 * Copyright (C) 2018 Russell Johnson
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23 #include <precomp.h>
24
25 #include <mmsystem.h>
26 #include <ntquery.h>
27
28 WINE_DEFAULT_DEBUG_CHANNEL(CRecycleBin);
29
30 typedef struct
31 {
32 int column_name_id;
33 const GUID *fmtId;
34 DWORD pid;
35 int pcsFlags;
36 int fmt;
37 int cxChars;
38 } columninfo;
39
40 static const columninfo RecycleBinColumns[] =
41 {
42 {IDS_SHV_COLUMN_NAME, &FMTID_Storage, PID_STG_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 25},
43 {IDS_SHV_COLUMN_DELFROM, &FMTID_Displaced, PID_DISPLACED_FROM, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 35},
44 {IDS_SHV_COLUMN_DELDATE, &FMTID_Displaced, PID_DISPLACED_DATE, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 15},
45 {IDS_SHV_COLUMN_SIZE, &FMTID_Storage, PID_STG_SIZE, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, LVCFMT_RIGHT, 10},
46 {IDS_SHV_COLUMN_TYPE, &FMTID_Storage, PID_STG_STORAGETYPE, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 15},
47 {IDS_SHV_COLUMN_MODIFIED, &FMTID_Storage, PID_STG_WRITETIME, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 15},
48 /* {"creation time", &FMTID_Storage, PID_STG_CREATETIME, SHCOLSTATE_TYPE_DATE, LVCFMT_LEFT, 20}, */
49 /* {"attribs", &FMTID_Storage, PID_STG_ATTRIBUTES, SHCOLSTATE_TYPE_STR, LVCFMT_LEFT, 20}, */
50 };
51
52 #define COLUMN_NAME 0
53 #define COLUMN_DELFROM 1
54 #define COLUMN_DATEDEL 2
55 #define COLUMN_SIZE 3
56 #define COLUMN_TYPE 4
57 #define COLUMN_MTIME 5
58
59 #define COLUMNS_COUNT 6
60
61 // The ROS Recycle Bin PIDL format starts with a NT4/2000 Unicode FS PIDL followed by
62 // BBITEMDATA and BBITEMFOOTER. This makes it compatible with SHChangeNotify listeners.
63 #include "pshpack1.h"
64 #define BBITEMFILETYPE (PT_FS | PT_FS_UNICODE_FLAG | PT_FS_FILE_FLAG)
65 #define BBITEMFOLDERTYPE (PT_FS | PT_FS_UNICODE_FLAG | PT_FS_FOLDER_FLAG)
66 struct BBITEMDATA
67 {
68 FILETIME DeletionTime;
69 #ifdef COLUMN_FATTS
70 WORD AttribsHi; // Nobody needs this yet
71 #endif
72 WORD RecycledPathOffset;
73 WCHAR OriginalLocation[ANYSIZE_ARRAY];
74 // ... @RecycledPathOffset WCHAR RecycledPath[ANYSIZE_ARRAY];
75 };
76 struct BBITEMFOOTER
77 {
78 enum { ENDSIG = MAKEWORD('K', 'I') }; // "Killed item". MUST have the low bit set so _ILGetFileStructW returns NULL.
79 WORD DataSize;
80 WORD EndSignature;
81 };
82 #include "poppack.h"
83
IsFolder(LPCITEMIDLIST pidl)84 static inline BOOL IsFolder(LPCITEMIDLIST pidl)
85 {
86 return _ILGetFSType(pidl) & PT_FS_FOLDER_FLAG;
87 }
88
ValidateItem(LPCITEMIDLIST pidl)89 static BBITEMDATA* ValidateItem(LPCITEMIDLIST pidl)
90 {
91 const UINT minstringsize = sizeof(L"X") + sizeof(""); // PT_FS strings
92 const UINT minfs = sizeof(WORD) + FIELD_OFFSET(PIDLDATA, u.file.szNames) + minstringsize;
93 const UINT mindatasize = FIELD_OFFSET(BBITEMDATA, OriginalLocation) + (sizeof(L"C:\\X") * 2);
94 const UINT minsize = minfs + mindatasize + sizeof(BBITEMFOOTER);
95 const BYTE type = _ILGetType(pidl);
96 if ((type == BBITEMFILETYPE || type == BBITEMFOLDERTYPE) && pidl->mkid.cb >= minsize)
97 {
98 BBITEMFOOTER *pEnd = (BBITEMFOOTER*)((BYTE*)pidl + pidl->mkid.cb - sizeof(BBITEMFOOTER));
99 if (pEnd->EndSignature == BBITEMFOOTER::ENDSIG && pEnd->DataSize >= mindatasize)
100 return (BBITEMDATA*)((BYTE*)pEnd - pEnd->DataSize);
101 }
102 return NULL;
103 }
104
CreateItem(LPCWSTR pszTrash,LPCWSTR pszOrig,const DELETED_FILE_INFO & Details)105 static LPITEMIDLIST CreateItem(LPCWSTR pszTrash, LPCWSTR pszOrig, const DELETED_FILE_INFO &Details)
106 {
107 const BOOL folder = Details.Attributes & FILE_ATTRIBUTE_DIRECTORY;
108 LPCWSTR pszName = PathFindFileNameW(pszTrash);
109 SIZE_T ofsName = (SIZE_T)(pszName - pszTrash);
110 SIZE_T cchName = wcslen(pszName) + 1, cbName = cchName * sizeof(WCHAR);
111 SIZE_T cbFSNames = cbName + sizeof("") + 1; // Empty short name + 1 for WORD alignment
112 SIZE_T cbFS = sizeof(WORD) + FIELD_OFFSET(PIDLDATA, u.file.szNames) + cbFSNames;
113 SIZE_T cchTrash = ofsName + cchName, cbTrash = cchTrash * sizeof(WCHAR);
114 SIZE_T cchOrig = wcslen(pszOrig) + 1, cbOrig = cchOrig * sizeof(WCHAR);
115 SIZE_T cbData = FIELD_OFFSET(BBITEMDATA, OriginalLocation) + cbOrig + cbTrash;
116 SIZE_T cb = cbFS + cbData + sizeof(BBITEMFOOTER);
117 if (cb > 0xffff)
118 return NULL;
119 LPITEMIDLIST pidl = (LPITEMIDLIST)SHAlloc(cb + sizeof(WORD));
120 if (!pidl)
121 return pidl;
122
123 pidl->mkid.cb = cb;
124 pidl->mkid.abID[0] = folder ? BBITEMFOLDERTYPE : BBITEMFILETYPE;
125 ILGetNext(pidl)->mkid.cb = 0; // Terminator
126 FileStruct &fsitem = ((PIDLDATA*)pidl->mkid.abID)->u.file;
127 fsitem.dummy = 0;
128 C_ASSERT(sizeof(RECYCLEBINFILESIZETYPE) <= sizeof(fsitem.dwFileSize));
129 fsitem.dwFileSize = Details.FileSize;
130 fsitem.uFileAttribs = LOWORD(Details.Attributes);
131 FileTimeToDosDateTime(&Details.LastModification, &fsitem.uFileDate, &fsitem.uFileTime);
132 CopyMemory(fsitem.szNames, pszName, cbName);
133 LPSTR pszShort = const_cast<LPSTR>(&fsitem.szNames[cbName]);
134 pszShort[0] = '\0';
135 pszShort[1] = '\0'; // Fill alignment padding (for ILIsEqual memcmp)
136
137 BBITEMFOOTER *footer = (BBITEMFOOTER*)((BYTE*)pidl + cb - sizeof(BBITEMFOOTER));
138 footer->DataSize = cbData;
139 footer->EndSignature = BBITEMFOOTER::ENDSIG;
140
141 BBITEMDATA *data = (BBITEMDATA*)((BYTE*)footer - footer->DataSize);
142 data->DeletionTime = Details.DeletionTime;
143 #ifdef COLUMN_FATTS
144 data->AttribsHi = HIWORD(Details.Attributes);
145 #endif
146 data->RecycledPathOffset = FIELD_OFFSET(BBITEMDATA, OriginalLocation) + cbOrig;
147 CopyMemory(data->OriginalLocation, pszOrig, cbOrig);
148 CopyMemory((BYTE*)data + data->RecycledPathOffset, pszTrash, cbTrash);
149
150 assert(!(((SIZE_T)&fsitem.szNames) & 1)); // WORD aligned please
151 C_ASSERT(!(FIELD_OFFSET(BBITEMDATA, OriginalLocation) & 1)); // WORD aligned please
152 assert(!(((SIZE_T)data) & 1)); // WORD aligned please
153 assert(_ILGetFSType(pidl));
154 assert(_ILIsPidlSimple(pidl));
155 assert(*(WORD*)((BYTE*)pidl + pidl->mkid.cb - sizeof(WORD)) & 1); // ENDSIG bit
156 assert(_ILGetFileStructW(pidl) == NULL); // Our custom footer is incompatible with WinXP pidl data
157 assert(ValidateItem(pidl) == data);
158 return pidl;
159 }
160
GetItemFileSize(LPCITEMIDLIST pidl)161 static inline UINT GetItemFileSize(LPCITEMIDLIST pidl)
162 {
163 return _ILGetFSType(pidl) ? ((PIDLDATA*)pidl->mkid.abID)->u.file.dwFileSize : 0;
164 }
165
GetItemOriginalFullPath(const BBITEMDATA & Data)166 static inline LPCWSTR GetItemOriginalFullPath(const BBITEMDATA &Data)
167 {
168 return Data.OriginalLocation;
169 }
170
GetItemOriginalFolder(const BBITEMDATA & Data,LPWSTR & Out)171 static HRESULT GetItemOriginalFolder(const BBITEMDATA &Data, LPWSTR &Out)
172 {
173 HRESULT hr = SHStrDupW(GetItemOriginalFullPath(Data), &Out);
174 if (SUCCEEDED(hr))
175 PathRemoveFileSpecW(Out);
176 return hr;
177 }
178
GetItemOriginalFileName(const BBITEMDATA & Data)179 static LPCWSTR GetItemOriginalFileName(const BBITEMDATA &Data)
180 {
181 return PathFindFileNameW(GetItemOriginalFullPath(Data));
182 }
183
GetItemRecycledFullPath(const BBITEMDATA & Data)184 static inline LPCWSTR GetItemRecycledFullPath(const BBITEMDATA &Data)
185 {
186 return (LPCWSTR)((BYTE*)&Data + Data.RecycledPathOffset);
187 }
188
GetItemRecycledFileName(LPCITEMIDLIST pidl,const BBITEMDATA & Data)189 static inline LPCWSTR GetItemRecycledFileName(LPCITEMIDLIST pidl, const BBITEMDATA &Data)
190 {
191 C_ASSERT(BBITEMFILETYPE & PT_FS_UNICODE_FLAG);
192 return (LPCWSTR)((LPPIDLDATA)pidl->mkid.abID)->u.file.szNames;
193 }
194
GetItemDriveNumber(LPCITEMIDLIST pidl)195 static int GetItemDriveNumber(LPCITEMIDLIST pidl)
196 {
197 if (BBITEMDATA *pData = ValidateItem(pidl))
198 return PathGetDriveNumberW(GetItemRecycledFullPath(*pData));
199 WCHAR buf[MAX_PATH];
200 return _ILSimpleGetTextW(pidl, buf, _countof(buf)) ? PathGetDriveNumberW(buf) : -1;
201 }
202
GetItemTypeName(PCUITEMID_CHILD pidl,const BBITEMDATA & Data,SHFILEINFOW & shfi)203 static HRESULT GetItemTypeName(PCUITEMID_CHILD pidl, const BBITEMDATA &Data, SHFILEINFOW &shfi)
204 {
205 LPCWSTR path = GetItemRecycledFullPath(Data);
206 UINT attribs = ((PIDLDATA*)pidl->mkid.abID)->u.file.uFileAttribs;
207 if (SHGetFileInfoW(path, attribs, &shfi, sizeof(shfi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES))
208 return S_OK;
209 shfi.szTypeName[0] = UNICODE_NULL;
210 return E_FAIL;
211 }
212
GetRecycleBinFileHandleFromItem(const BBITEMDATA & Data)213 static HDELFILE GetRecycleBinFileHandleFromItem(const BBITEMDATA &Data)
214 {
215 RECYCLEBINFILEIDENTITY identity = { Data.DeletionTime, GetItemRecycledFullPath(Data) };
216 return GetRecycleBinFileHandle(NULL, &identity);
217 }
218
219 /*
220 * Recycle Bin folder
221 */
222
GetDefaultRecycleDriveNumber()223 static UINT GetDefaultRecycleDriveNumber()
224 {
225 int drive = 0;
226 WCHAR buf[MAX_PATH];
227 if (GetWindowsDirectoryW(buf, _countof(buf)))
228 drive = PathGetDriveNumberW(buf);
229 return max(0, drive);
230 }
231
GetGlobalRecycleBinPath()232 static inline LPCWSTR GetGlobalRecycleBinPath()
233 {
234 return NULL;
235 }
236
IsRecycleBinEmpty(IShellFolder * pSF)237 static BOOL IsRecycleBinEmpty(IShellFolder *pSF)
238 {
239 CComPtr<IEnumIDList> spEnumFiles;
240 HRESULT hr = pSF->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &spEnumFiles);
241 CComHeapPtr<ITEMIDLIST> spPidl;
242 ULONG itemcount;
243 return FAILED(hr) || spEnumFiles->Next(1, &spPidl, &itemcount) != S_OK;
244 }
245
CRecycleBin_ChangeNotifyBBItem(_In_ LONG Event,_In_opt_ LPCITEMIDLIST BBItem)246 static void CRecycleBin_ChangeNotifyBBItem(_In_ LONG Event, _In_opt_ LPCITEMIDLIST BBItem)
247 {
248 LPITEMIDLIST pidlFolder = SHCloneSpecialIDList(NULL, CSIDL_BITBUCKET, FALSE);
249 if (!pidlFolder)
250 return;
251 if (BBItem)
252 {
253 assert(ValidateItem(BBItem));
254 if (LPITEMIDLIST pidlFull = ILCombine(pidlFolder, BBItem))
255 {
256 // Send notification for [Desktop][RecycleBin][BBItem]
257 // FIXME: Windows monitors each RecycleBin FS folder on every drive
258 // instead of manually sending these?
259 SHChangeNotify(Event, SHCNF_IDLIST, pidlFull, NULL);
260 ILFree(pidlFull);
261 }
262 }
263 else
264 {
265 SHChangeNotify(Event, SHCNF_IDLIST, pidlFolder, NULL);
266 }
267 ILFree(pidlFolder);
268 }
269
CRecycleBin_NotifyRecycled(LPCWSTR OrigPath,const WIN32_FIND_DATAW * pFind,const RECYCLEBINFILEIDENTITY * pFI)270 EXTERN_C void CRecycleBin_NotifyRecycled(LPCWSTR OrigPath, const WIN32_FIND_DATAW *pFind,
271 const RECYCLEBINFILEIDENTITY *pFI)
272 {
273 DELETED_FILE_INFO info;
274 info.LastModification = pFind->ftLastWriteTime;
275 info.DeletionTime = pFI->DeletionTime;
276 info.FileSize = pFind->nFileSizeLow;
277 info.Attributes = pFind->dwFileAttributes;
278 if (LPITEMIDLIST pidl = CreateItem(pFI->RecycledFullPath, OrigPath, info))
279 {
280 CRecycleBin_ChangeNotifyBBItem(IsFolder(pidl) ? SHCNE_MKDIR : SHCNE_CREATE, pidl);
281 ILFree(pidl);
282 }
283 }
284
CRecycleBin_NotifyRemovedFromRecycleBin(LPCITEMIDLIST BBItem)285 static void CRecycleBin_NotifyRemovedFromRecycleBin(LPCITEMIDLIST BBItem)
286 {
287 CRecycleBin_ChangeNotifyBBItem(IsFolder(BBItem) ? SHCNE_RMDIR : SHCNE_DELETE, BBItem);
288
289 CComHeapPtr<ITEMIDLIST> pidlBB(SHCloneSpecialIDList(NULL, CSIDL_BITBUCKET, FALSE));
290 CComPtr<IShellFolder> pSF;
291 if (pidlBB && SUCCEEDED(SHBindToObject(NULL, pidlBB, IID_PPV_ARG(IShellFolder, &pSF))))
292 {
293 if (IsRecycleBinEmpty(pSF))
294 SHUpdateRecycleBinIcon();
295 }
296 }
297
CRecyclerExtractIcon_CreateInstance(IShellFolder & FSFolder,LPCITEMIDLIST pidl,REFIID riid,LPVOID * ppvOut)298 static HRESULT CRecyclerExtractIcon_CreateInstance(
299 IShellFolder &FSFolder, LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppvOut)
300 {
301 HRESULT hr = FSFolder.GetUIObjectOf(NULL, 1, &pidl, riid, NULL, ppvOut);
302 if (SUCCEEDED(hr))
303 return hr;
304
305 // In case the search fails we use a default icon
306 ERR("Recycler could not retrieve the icon, this shouldn't happen\n");
307
308 if (IsFolder(pidl))
309 return SHELL_CreateFallbackExtractIconForFolder(riid, ppvOut);
310 else
311 return SHELL_CreateFallbackExtractIconForNoAssocFile(riid, ppvOut);
312 }
313
314 class CRecycleBinItemContextMenu :
315 public CComObjectRootEx<CComMultiThreadModelNoCS>,
316 public IContextMenu2
317 {
318 private:
319 LPITEMIDLIST apidl;
320 public:
321 CRecycleBinItemContextMenu();
322 ~CRecycleBinItemContextMenu();
323 HRESULT WINAPI Initialize(LPCITEMIDLIST pidl);
324
325 // IContextMenu
326 STDMETHOD(QueryContextMenu)(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override;
327 STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpcmi) override;
328 STDMETHOD(GetCommandString)(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen) override;
329
330 // IContextMenu2
331 STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
332
333 BEGIN_COM_MAP(CRecycleBinItemContextMenu)
334 COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
335 COM_INTERFACE_ENTRY_IID(IID_IContextMenu2, IContextMenu2)
336 END_COM_MAP()
337 };
338
339 class CRecycleBinEnum :
340 public CEnumIDListBase
341 {
342 public:
343 CRecycleBinEnum();
344 ~CRecycleBinEnum();
345 HRESULT WINAPI Initialize(DWORD dwFlags);
346 BOOL CBEnumRecycleBin(IN HDELFILE hDeletedFile);
CBEnumRecycleBin(IN PVOID Context,IN HDELFILE hDeletedFile)347 static BOOL CALLBACK CBEnumRecycleBin(IN PVOID Context, IN HDELFILE hDeletedFile)
348 {
349 return static_cast<CRecycleBinEnum*>(Context)->CBEnumRecycleBin(hDeletedFile);
350 }
351
352 BEGIN_COM_MAP(CRecycleBinEnum)
353 COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
354 END_COM_MAP()
355 };
356
CRecycleBinEnum()357 CRecycleBinEnum::CRecycleBinEnum()
358 {
359 }
360
~CRecycleBinEnum()361 CRecycleBinEnum::~CRecycleBinEnum()
362 {
363 }
364
Initialize(DWORD dwFlags)365 HRESULT WINAPI CRecycleBinEnum::Initialize(DWORD dwFlags)
366 {
367 LPCWSTR szDrive = GetGlobalRecycleBinPath();
368 if (dwFlags & SHCONTF_NONFOLDERS)
369 {
370 TRACE("Starting Enumeration\n");
371
372 if (!EnumerateRecycleBinW(szDrive, CBEnumRecycleBin, this))
373 {
374 WARN("Error: EnumerateCRecycleBinW failed\n");
375 return E_FAIL;
376 }
377 }
378 else
379 {
380 // do nothing
381 }
382 return S_OK;
383 }
384
CBEnumRecycleBin(IN HDELFILE hDeletedFile)385 BOOL CRecycleBinEnum::CBEnumRecycleBin(IN HDELFILE hDeletedFile)
386 {
387 LPITEMIDLIST pidl = NULL;
388 DELETED_FILE_INFO info;
389 IRecycleBinFile *pRBF = IRecycleBinFileFromHDELFILE(hDeletedFile);
390 BOOL ret = SUCCEEDED(pRBF->GetInfo(&info));
391 if (ret)
392 {
393 pidl = CreateItem(info.RecycledFullPath.String, info.OriginalFullPath.String, info);
394 ret = pidl != NULL;
395 FreeRecycleBinString(&info.OriginalFullPath);
396 FreeRecycleBinString(&info.RecycledFullPath);
397 }
398 if (pidl)
399 {
400 ret = AddToEnumList(pidl);
401 if (!ret)
402 ILFree(pidl);
403 }
404 CloseRecycleBinHandle(hDeletedFile);
405 return ret;
406 }
407
408 /**************************************************************************
409 * IContextMenu2 Bitbucket Item Implementation
410 */
411
CRecycleBinItemContextMenu()412 CRecycleBinItemContextMenu::CRecycleBinItemContextMenu()
413 {
414 apidl = NULL;
415 }
416
~CRecycleBinItemContextMenu()417 CRecycleBinItemContextMenu::~CRecycleBinItemContextMenu()
418 {
419 ILFree(apidl);
420 }
421
Initialize(LPCITEMIDLIST pidl)422 HRESULT WINAPI CRecycleBinItemContextMenu::Initialize(LPCITEMIDLIST pidl)
423 {
424 apidl = ILClone(pidl);
425 if (apidl == NULL)
426 return E_OUTOFMEMORY;
427 return S_OK;
428 }
429
430 enum { IDC_BB_RESTORE = 1, IDC_BB_CUT, IDC_BB_DELETE, IDC_BB_PROPERTIES };
431 static const CMVERBMAP g_BBItemVerbMap[] =
432 {
433 { "undelete", IDC_BB_RESTORE },
434 { "cut", IDC_BB_CUT },
435 { "delete", IDC_BB_DELETE },
436 { "properties", IDC_BB_PROPERTIES },
437 { NULL }
438 };
439
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)440 HRESULT WINAPI CRecycleBinItemContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
441 {
442 UINT idHigh = 0, id;
443
444 TRACE("(%p)->(hmenu=%p indexmenu=%x cmdfirst=%x cmdlast=%x flags=%x )\n", this, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
445
446 id = idCmdFirst + IDC_BB_RESTORE;
447 if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_RESTORE), 0))
448 {
449 idHigh = max(idHigh, id);
450 indexMenu++;
451 }
452 id = idCmdFirst + IDC_BB_CUT;
453 if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_CUT), MFS_DISABLED))
454 {
455 idHigh = max(idHigh, id);
456 if (_InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0))
457 indexMenu++;
458 }
459 id = idCmdFirst + IDC_BB_DELETE;
460 if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_DELETE), 0))
461 {
462 idHigh = max(idHigh, id);
463 if (_InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0))
464 indexMenu++;
465 }
466 id = idCmdFirst + IDC_BB_PROPERTIES;
467 if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_PROPERTIES), 0))
468 {
469 idHigh = max(idHigh, id);
470 if (_InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0))
471 indexMenu++;
472 }
473 return idHigh ? MAKE_HRESULT(SEVERITY_SUCCESS, 0, idHigh - idCmdFirst + 1) : S_OK;
474 }
475
ConfirmDelete(LPCMINVOKECOMMANDINFO lpcmi,UINT cidl,LPCITEMIDLIST pidl,const BBITEMDATA & Data)476 static BOOL ConfirmDelete(LPCMINVOKECOMMANDINFO lpcmi, UINT cidl, LPCITEMIDLIST pidl, const BBITEMDATA &Data)
477 {
478 if (lpcmi->fMask & CMIC_MASK_FLAG_NO_UI)
479 {
480 return TRUE;
481 }
482 else if (cidl == 1)
483 {
484 const UINT ask = IsFolder(pidl) ? ASK_DELETE_FOLDER : ASK_DELETE_FILE;
485 return SHELL_ConfirmYesNoW(lpcmi->hwnd, ask, GetItemOriginalFileName(Data));
486 }
487 WCHAR buf[MAX_PATH];
488 wsprintfW(buf, L"%d", cidl);
489 return SHELL_ConfirmYesNoW(lpcmi->hwnd, ASK_DELETE_MULTIPLE_ITEM, buf);
490 }
491
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)492 HRESULT WINAPI CRecycleBinItemContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
493 {
494 TRACE("(%p)->(invcom=%p verb=%p wnd=%p)\n", this, lpcmi, lpcmi->lpVerb, lpcmi->hwnd);
495
496 int CmdId = SHELL_MapContextMenuVerbToCmdId(lpcmi, g_BBItemVerbMap);
497
498 // Handle DefView accelerators
499 if ((SIZE_T)lpcmi->lpVerb == FCIDM_SHVIEW_CUT)
500 CmdId = IDC_BB_CUT;
501 if ((SIZE_T)lpcmi->lpVerb == FCIDM_SHVIEW_DELETE)
502 CmdId = IDC_BB_DELETE;
503 if ((SIZE_T)lpcmi->lpVerb == FCIDM_SHVIEW_PROPERTIES)
504 CmdId = IDC_BB_PROPERTIES;
505
506 if (CmdId == IDC_BB_RESTORE || CmdId == IDC_BB_DELETE)
507 {
508 BBITEMDATA *pData = ValidateItem(apidl);
509 if (!pData && FAILED_UNEXPECTEDLY(E_FAIL))
510 return E_FAIL;
511 HDELFILE hDelFile = GetRecycleBinFileHandleFromItem(*pData);
512 if (!hDelFile && FAILED_UNEXPECTEDLY(E_FAIL))
513 return E_FAIL;
514
515 HRESULT hr = S_FALSE;
516 if (CmdId == IDC_BB_RESTORE)
517 hr = RestoreFileFromRecycleBin(hDelFile) ? S_OK : E_FAIL;
518 else if (ConfirmDelete(lpcmi, 1, apidl, *pData))
519 hr = DeleteFileInRecycleBin(hDelFile) ? S_OK : E_FAIL;
520
521 if (hr == S_OK)
522 CRecycleBin_NotifyRemovedFromRecycleBin(apidl);
523
524 CloseRecycleBinHandle(hDelFile);
525 return hr;
526 }
527 else if (CmdId == IDC_BB_CUT)
528 {
529 FIXME("implement cut\n");
530 SHELL_ErrorBox(lpcmi->hwnd, ERROR_NOT_SUPPORTED);
531 return E_NOTIMPL;
532 }
533 else if (CmdId == IDC_BB_PROPERTIES)
534 {
535 FIXME("implement properties\n");
536 SHELL_ErrorBox(lpcmi->hwnd, ERROR_NOT_SUPPORTED);
537 return E_NOTIMPL;
538 }
539 return E_UNEXPECTED;
540 }
541
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)542 HRESULT WINAPI CRecycleBinItemContextMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
543 {
544 TRACE("(%p)->(idcom=%lx flags=%x %p name=%p len=%x)\n", this, idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
545 return SHELL_GetCommandStringImpl(idCommand, uFlags, lpszName, uMaxNameLen, g_BBItemVerbMap);
546 }
547
HandleMenuMsg(UINT uMsg,WPARAM wParam,LPARAM lParam)548 HRESULT WINAPI CRecycleBinItemContextMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
549 {
550 TRACE("CRecycleBin_IContextMenu2Item_HandleMenuMsg (%p)->(msg=%x wp=%lx lp=%lx)\n", this, uMsg, wParam, lParam);
551
552 return E_NOTIMPL;
553 }
554
CRecycleBin()555 CRecycleBin::CRecycleBin()
556 {
557 pidl = NULL;
558 ZeroMemory(m_pFSFolders, sizeof(m_pFSFolders));
559 }
560
~CRecycleBin()561 CRecycleBin::~CRecycleBin()
562 {
563 SHFree(pidl);
564 for (SIZE_T i = 0; i < _countof(m_pFSFolders); ++i)
565 {
566 if (m_pFSFolders[i])
567 m_pFSFolders[i]->Release();
568 }
569 }
570
GetFSFolderForItem(LPCITEMIDLIST pidl)571 IShellFolder* CRecycleBin::GetFSFolderForItem(LPCITEMIDLIST pidl)
572 {
573 int drive = GetItemDriveNumber(pidl);
574 if (drive < 0)
575 drive = GetDefaultRecycleDriveNumber();
576 if ((UINT)drive >= _countof(m_pFSFolders) && FAILED_UNEXPECTEDLY(E_FAIL))
577 return NULL;
578
579 if (!m_pFSFolders[drive])
580 {
581 HRESULT hr;
582 PERSIST_FOLDER_TARGET_INFO pfti = {};
583 if (FAILED_UNEXPECTEDLY(hr = GetRecycleBinPathFromDriveNumber(drive, pfti.szTargetParsingName)))
584 return NULL;
585 pfti.dwAttributes = FILE_ATTRIBUTE_DIRECTORY;
586 pfti.csidl = -1;
587 CComHeapPtr<ITEMIDLIST> pidlRoot;
588 pidlRoot.Attach(SHELL32_CreateSimpleIDListFromPath(pfti.szTargetParsingName, pfti.dwAttributes));
589 if (!pidlRoot && FAILED_UNEXPECTEDLY(E_FAIL))
590 return NULL;
591 IShellFolder *psf;
592 hr = SHELL32_CoCreateInitSF(pidlRoot, &pfti, NULL, &CLSID_ShellFSFolder, IID_PPV_ARG(IShellFolder, &psf));
593 if (FAILED(hr))
594 return NULL;
595 m_pFSFolders[drive] = psf; // Reference count is 1 (for the m_pFSFolders cache)
596 }
597 m_pFSFolders[drive]->AddRef(); // AddRef for the caller
598 return m_pFSFolders[drive];
599 }
600
601 /*************************************************************************
602 * RecycleBin IPersistFolder2 interface
603 */
604
GetClassID(CLSID * pClassID)605 HRESULT WINAPI CRecycleBin::GetClassID(CLSID *pClassID)
606 {
607 TRACE("(%p, %p)\n", this, pClassID);
608 if (pClassID == NULL)
609 return E_INVALIDARG;
610 *pClassID = GetClassID();
611 return S_OK;
612 }
613
Initialize(PCIDLIST_ABSOLUTE pidl)614 HRESULT WINAPI CRecycleBin::Initialize(PCIDLIST_ABSOLUTE pidl)
615 {
616 TRACE("(%p, %p)\n", this, pidl);
617
618 SHFree((LPVOID)this->pidl);
619 this->pidl = ILClone(pidl);
620 if (this->pidl == NULL)
621 return E_OUTOFMEMORY;
622 return S_OK;
623 }
624
GetCurFolder(PIDLIST_ABSOLUTE * ppidl)625 HRESULT WINAPI CRecycleBin::GetCurFolder(PIDLIST_ABSOLUTE *ppidl)
626 {
627 TRACE("\n");
628 return SHILClone((LPCITEMIDLIST)pidl, ppidl);
629 }
630
631 /*************************************************************************
632 * RecycleBin IShellFolder2 interface
633 */
634
ParseDisplayName(HWND hwnd,LPBC pbc,LPOLESTR pszDisplayName,ULONG * pchEaten,PIDLIST_RELATIVE * ppidl,ULONG * pdwAttributes)635 HRESULT WINAPI CRecycleBin::ParseDisplayName(HWND hwnd, LPBC pbc,
636 LPOLESTR pszDisplayName, ULONG *pchEaten, PIDLIST_RELATIVE *ppidl,
637 ULONG *pdwAttributes)
638 {
639 FIXME("stub\n");
640 return E_NOTIMPL; // FIXME: Parse "D<Drive><UniqueId>.ext"
641 }
642
EnumObjects(HWND hwndOwner,DWORD dwFlags,LPENUMIDLIST * ppEnumIDList)643 HRESULT WINAPI CRecycleBin::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
644 {
645 return ShellObjectCreatorInit<CRecycleBinEnum>(dwFlags, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
646 }
647
BindToObject(PCUIDLIST_RELATIVE pidl,LPBC pbc,REFIID riid,void ** ppv)648 HRESULT WINAPI CRecycleBin::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbc, REFIID riid, void **ppv)
649 {
650 FIXME("(%p, %p, %p, %s, %p) - stub\n", this, pidl, pbc, debugstr_guid(&riid), ppv);
651 return E_NOTIMPL;
652 }
653
BindToStorage(PCUIDLIST_RELATIVE pidl,LPBC pbc,REFIID riid,void ** ppv)654 HRESULT WINAPI CRecycleBin::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbc, REFIID riid, void **ppv)
655 {
656 FIXME("(%p, %p, %p, %s, %p) - stub\n", this, pidl, pbc, debugstr_guid(&riid), ppv);
657 return E_NOTIMPL;
658 }
659
CompareCanonical(const BBITEMDATA & Data1,const BBITEMDATA & Data2)660 static HRESULT CompareCanonical(const BBITEMDATA &Data1, const BBITEMDATA &Data2)
661 {
662 // This assumes two files with the same original path cannot be deleted at
663 // the same time (within the FAT/NTFS FILETIME resolution).
664 int result = CompareFileTime(&Data1.DeletionTime, &Data2.DeletionTime);
665 if (result == 0)
666 result = _wcsicmp(GetItemOriginalFullPath(Data1), GetItemOriginalFullPath(Data2));
667 return MAKE_COMPARE_HRESULT(result);
668 }
669
CompareIDs(LPARAM lParam,PCUIDLIST_RELATIVE pidl1,PCUIDLIST_RELATIVE pidl2)670 HRESULT WINAPI CRecycleBin::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
671 {
672 UINT column = UINT(lParam & SHCIDS_COLUMNMASK);
673 if (column >= COLUMNS_COUNT || !_ILGetFSType(pidl1) || !_ILGetFSType(pidl2))
674 return E_INVALIDARG;
675 BBITEMDATA *pData1 = ValidateItem(pidl1), *pData2 = ValidateItem(pidl2);
676 if ((!pData1 || !pData2) && column != COLUMN_NAME)
677 return E_INVALIDARG;
678
679 LPCWSTR pName1, pName2;
680 FILETIME ft1, ft2;
681 SHFILEINFOW shfi1, shfi2;
682 int result;
683 HRESULT hr = CFSFolder::CompareSortFoldersFirst(pidl1, pidl2);
684 if (SUCCEEDED(hr))
685 return hr;
686 switch (column)
687 {
688 case COLUMN_NAME:
689 if (pData1 && pData2)
690 {
691 if (lParam & SHCIDS_CANONICALONLY)
692 return CompareCanonical(*pData1, *pData2);
693 pName1 = GetItemOriginalFileName(*pData1);
694 pName2 = GetItemOriginalFileName(*pData2);
695 result = CFSFolder::CompareUiStrings(pName1, pName2);
696 }
697 else
698 {
699 // We support comparing names even for non-Recycle items because
700 // SHChangeNotify can broadcast regular FS items.
701 if (IShellFolder *pSF = GetFSFolderForItem(pidl1))
702 {
703 hr = pSF->CompareIDs(lParam, pidl1, pidl2);
704 pSF->Release();
705 return hr;
706 }
707 return E_INVALIDARG;
708 }
709 break;
710 case COLUMN_DELFROM:
711 if (SUCCEEDED(hr = GetItemOriginalFolder(*pData1, const_cast<LPWSTR&>(pName1))))
712 {
713 if (SUCCEEDED(hr = GetItemOriginalFolder(*pData2, const_cast<LPWSTR&>(pName2))))
714 {
715 result = CFSFolder::CompareUiStrings(pName1, pName2);
716 SHFree(const_cast<LPWSTR>(pName2));
717 }
718 SHFree(const_cast<LPWSTR>(pName1));
719 }
720 return SUCCEEDED(hr) ? MAKE_COMPARE_HRESULT(result) : hr;
721 case COLUMN_DATEDEL:
722 result = CompareFileTime(&pData1->DeletionTime, &pData2->DeletionTime);
723 break;
724 case COLUMN_SIZE:
725 result = GetItemFileSize(pidl1) - GetItemFileSize(pidl2);
726 break;
727 case COLUMN_TYPE:
728 GetItemTypeName(pidl1, *pData1, shfi1);
729 GetItemTypeName(pidl2, *pData2, shfi2);
730 result = CFSFolder::CompareUiStrings(shfi1.szTypeName, shfi2.szTypeName);
731 break;
732 case COLUMN_MTIME:
733 _ILGetFileDateTime(pidl1, &ft1);
734 _ILGetFileDateTime(pidl2, &ft2);
735 result = CompareFileTime(&ft1, &ft2);
736 break;
737 }
738 return MAKE_COMPARE_HRESULT(result);
739 }
740
CreateViewObject(HWND hwndOwner,REFIID riid,void ** ppv)741 HRESULT WINAPI CRecycleBin::CreateViewObject(HWND hwndOwner, REFIID riid, void **ppv)
742 {
743 CComPtr<IShellView> pShellView;
744 HRESULT hr = E_NOINTERFACE;
745
746 TRACE("(%p, %p, %s, %p)\n", this, hwndOwner, debugstr_guid(&riid), ppv);
747
748 if (!ppv)
749 return hr;
750 *ppv = NULL;
751
752 if (IsEqualIID (riid, IID_IDropTarget))
753 {
754 hr = CRecyclerDropTarget_CreateInstance(riid, ppv);
755 }
756 else if (IsEqualIID (riid, IID_IContextMenu) || IsEqualIID (riid, IID_IContextMenu2))
757 {
758 m_IsBackgroundMenu = true;
759 hr = this->QueryInterface(riid, ppv);
760 }
761 else if (IsEqualIID (riid, IID_IShellView))
762 {
763 SFV_CREATE sfvparams = { sizeof(SFV_CREATE), this };
764 hr = SHCreateShellFolderView(&sfvparams, (IShellView**)ppv);
765 }
766 else
767 return hr;
768
769 TRACE ("-- (%p)->(interface=%p)\n", this, ppv);
770 return hr;
771
772 }
773
GetAttributesOf(UINT cidl,PCUITEMID_CHILD_ARRAY apidl,SFGAOF * rgfInOut)774 HRESULT WINAPI CRecycleBin::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
775 SFGAOF *rgfInOut)
776 {
777 TRACE("(%p, %d, {%p, ...}, {%x})\n", this, cidl, apidl ? apidl[0] : NULL, (unsigned int)*rgfInOut);
778 HRESULT hr = S_OK;
779 const SFGAOF ThisFolder = SFGAO_FOLDER | SFGAO_HASPROPSHEET | SFGAO_DROPTARGET | SFGAO_CANRENAME | SFGAO_CANLINK;
780 if (!cidl)
781 {
782 *rgfInOut &= ThisFolder;
783 if (SHRestricted(REST_BITBUCKNOPROP))
784 *rgfInOut &= ~SFGAO_HASPROPSHEET;
785 return hr;
786 }
787 SFGAOF remain = SFGAO_LINK & *rgfInOut;
788 *rgfInOut &= remain | SFGAO_HASPROPSHEET | SFGAO_CANDELETE | SFGAO_FILESYSTEM; // TODO: SFGAO_CANMOVE
789 for (UINT i = 0; (*rgfInOut & remain) && i < cidl && SUCCEEDED(hr); ++i)
790 {
791 if (IShellFolder* pSF = GetFSFolderForItem(apidl[i]))
792 {
793 hr = pSF->GetAttributesOf(1, &apidl[i], rgfInOut);
794 pSF->Release();
795 }
796 }
797 return hr;
798 }
799
GetUIObjectOf(HWND hwndOwner,UINT cidl,PCUITEMID_CHILD_ARRAY apidl,REFIID riid,UINT * prgfInOut,void ** ppv)800 HRESULT WINAPI CRecycleBin::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
801 REFIID riid, UINT *prgfInOut, void **ppv)
802 {
803 LPVOID pObj = NULL;
804 HRESULT hr = E_INVALIDARG;
805
806 TRACE ("(%p)->(%p,%u,apidl=%p, %p %p)\n", this,
807 hwndOwner, cidl, apidl, prgfInOut, ppv);
808
809 if (!ppv)
810 return hr;
811
812 *ppv = NULL;
813 assert(!cidl || (apidl && apidl[0]));
814
815 if ((IsEqualIID (riid, IID_IContextMenu) || IsEqualIID(riid, IID_IContextMenu2)) && (cidl >= 1))
816 {
817 // FIXME: Handle multiple items
818 hr = ShellObjectCreatorInit<CRecycleBinItemContextMenu>(apidl[0], riid, &pObj);
819 }
820 else if((IsEqualIID(riid, IID_IExtractIconA) || IsEqualIID(riid, IID_IExtractIconW)) && (cidl == 1))
821 {
822 if (IShellFolder *pSF = GetFSFolderForItem(apidl[0]))
823 {
824 hr = CRecyclerExtractIcon_CreateInstance(*pSF, apidl[0], riid, &pObj);
825 pSF->Release();
826 }
827 }
828 else
829 hr = E_NOINTERFACE;
830
831 if (SUCCEEDED(hr) && !pObj)
832 hr = E_OUTOFMEMORY;
833
834 *ppv = pObj;
835 TRACE ("(%p)->hr=0x%08x\n", this, hr);
836 return hr;
837 }
838
GetDisplayNameOf(PCUITEMID_CHILD pidl,SHGDNF uFlags,STRRET * pName)839 HRESULT WINAPI CRecycleBin::GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF uFlags, STRRET *pName)
840 {
841 TRACE("(%p, %p, %x, %p)\n", this, pidl, (unsigned int)uFlags, pName);
842 const BBITEMDATA *pData = ValidateItem(pidl);
843 if (!pData)
844 return E_INVALIDARG;
845
846 if (IS_SHGDN_FOR_PARSING(uFlags))
847 {
848 LPCWSTR pszName = GetItemRecycledFullPath(*pData);
849 if (uFlags & SHGDN_INFOLDER)
850 pszName = PathFindFileNameW(pszName);
851 pName->pOleStr = SHStrDupW(pszName);
852 }
853 else
854 {
855 if (uFlags & SHGDN_INFOLDER)
856 pName->pOleStr = SHStrDupW(GetItemOriginalFileName(*pData));
857 else
858 pName->pOleStr = SHStrDupW(GetItemOriginalFullPath(*pData));
859 }
860
861 if (pName->pOleStr)
862 {
863 pName->uType = STRRET_WSTR;
864 if (!IsFolder(pidl))
865 SHELL_FS_ProcessDisplayFilename(pName->pOleStr, uFlags);
866 return S_OK;
867 }
868 pName->uType = STRRET_CSTR;
869 pName->cStr[0] = '\0';
870 return E_OUTOFMEMORY;
871 }
872
SetNameOf(HWND hwnd,PCUITEMID_CHILD pidl,LPCOLESTR pszName,SHGDNF uFlags,PITEMID_CHILD * ppidlOut)873 HRESULT WINAPI CRecycleBin::SetNameOf(HWND hwnd, PCUITEMID_CHILD pidl, LPCOLESTR pszName,
874 SHGDNF uFlags, PITEMID_CHILD *ppidlOut)
875 {
876 TRACE("\n");
877 return E_FAIL; /* not supported */
878 }
879
GetDefaultSearchGUID(GUID * pguid)880 HRESULT WINAPI CRecycleBin::GetDefaultSearchGUID(GUID *pguid)
881 {
882 FIXME("stub\n");
883 return E_NOTIMPL;
884 }
885
EnumSearches(IEnumExtraSearch ** ppEnum)886 HRESULT WINAPI CRecycleBin::EnumSearches(IEnumExtraSearch **ppEnum)
887 {
888 FIXME("stub\n");
889 *ppEnum = NULL;
890 return E_NOTIMPL;
891 }
892
GetDefaultColumn(DWORD dwReserved,ULONG * pSort,ULONG * pDisplay)893 HRESULT WINAPI CRecycleBin::GetDefaultColumn(DWORD dwReserved, ULONG *pSort, ULONG *pDisplay)
894 {
895 TRACE("(%p, %x, %p, %p)\n", this, (unsigned int)dwReserved, pSort, pDisplay);
896 if (pSort)
897 *pSort = 0;
898 if (pDisplay)
899 *pDisplay = 0;
900 return S_OK;
901 }
902
GetDefaultColumnState(UINT iColumn,SHCOLSTATEF * pcsFlags)903 HRESULT WINAPI CRecycleBin::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
904 {
905 TRACE("(%p, %d, %p)\n", this, iColumn, pcsFlags);
906 if (iColumn >= COLUMNS_COUNT)
907 return E_INVALIDARG;
908 *pcsFlags = RecycleBinColumns[iColumn].pcsFlags;
909 return S_OK;
910 }
911
GetDetailsEx(PCUITEMID_CHILD pidl,const SHCOLUMNID * pscid,VARIANT * pv)912 HRESULT WINAPI CRecycleBin::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
913 {
914 FIXME("stub\n");
915 return E_NOTIMPL;
916 }
917
GetDetailsOf(PCUITEMID_CHILD pidl,UINT iColumn,LPSHELLDETAILS pDetails)918 HRESULT WINAPI CRecycleBin::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, LPSHELLDETAILS pDetails)
919 {
920 HRESULT hr;
921 FILETIME ft;
922 SHFILEINFOW shfi;
923 WCHAR buffer[MAX_PATH];
924
925 TRACE("(%p, %p, %d, %p)\n", this, pidl, iColumn, pDetails);
926 if (iColumn >= COLUMNS_COUNT)
927 return E_FAIL;
928
929 if (pidl == NULL)
930 {
931 pDetails->fmt = RecycleBinColumns[iColumn].fmt;
932 pDetails->cxChar = RecycleBinColumns[iColumn].cxChars;
933 return SHSetStrRet(&pDetails->str, RecycleBinColumns[iColumn].column_name_id);
934 }
935
936 if (iColumn == COLUMN_NAME)
937 return GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, &pDetails->str);
938
939 const BBITEMDATA *pData = ValidateItem(pidl);
940 if (!pData && FAILED_UNEXPECTEDLY(E_INVALIDARG))
941 return E_INVALIDARG;
942
943 switch (iColumn)
944 {
945 case COLUMN_DATEDEL:
946 CFSFolder::FormatDateTime(pData->DeletionTime, buffer, _countof(buffer));
947 break;
948 case COLUMN_DELFROM:
949 if (SUCCEEDED(hr = GetItemOriginalFolder(*pData, pDetails->str.pOleStr)))
950 pDetails->str.uType = STRRET_WSTR;
951 return hr;
952 case COLUMN_SIZE:
953 *buffer = UNICODE_NULL;
954 if (!IsFolder(pidl))
955 CFSFolder::FormatSize(GetItemFileSize(pidl), buffer, _countof(buffer));
956 break;
957 case COLUMN_MTIME:
958 _ILGetFileDateTime(pidl, &ft);
959 CFSFolder::FormatDateTime(ft, buffer, _countof(buffer));
960 break;
961 case COLUMN_TYPE:
962 GetItemTypeName(pidl, *pData, shfi);
963 return SHSetStrRet(&pDetails->str, shfi.szTypeName);
964 default:
965 return E_FAIL;
966 }
967 return SHSetStrRet(&pDetails->str, buffer);
968 }
969
MapColumnToSCID(UINT iColumn,SHCOLUMNID * pscid)970 HRESULT WINAPI CRecycleBin::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
971 {
972 TRACE("(%p, %d, %p)\n", this, iColumn, pscid);
973 if (iColumn >= COLUMNS_COUNT)
974 return E_INVALIDARG;
975 pscid->fmtid = *RecycleBinColumns[iColumn].fmtId;
976 pscid->pid = RecycleBinColumns[iColumn].pid;
977 return S_OK;
978 }
979
980 /*************************************************************************
981 * RecycleBin IContextMenu interface
982 */
983
984 enum { IDC_EMPTYRECYCLEBIN = 1, IDC_PROPERTIES };
985 static const CMVERBMAP g_BBFolderVerbMap[] =
986 {
987 { "empty", IDC_EMPTYRECYCLEBIN },
988 { "properties", IDC_PROPERTIES },
989 { NULL }
990 };
991
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)992 HRESULT WINAPI CRecycleBin::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
993 {
994 TRACE("QueryContextMenu %p %p %u %u %u %u\n", this, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags );
995
996 if (!hMenu)
997 return E_INVALIDARG;
998
999 UINT idHigh = 0, id;
1000
1001 WORD state = IsRecycleBinEmpty(this) ? MFS_DISABLED : MFS_ENABLED;
1002 id = idCmdFirst + IDC_EMPTYRECYCLEBIN;
1003 if (_InsertMenuItemW(hMenu, indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET), state))
1004 {
1005 idHigh = max(idHigh, id);
1006 if (m_IsBackgroundMenu && !SHRestricted(REST_BITBUCKNOPROP))
1007 {
1008 id = idCmdFirst + IDC_PROPERTIES;
1009 if (_InsertMenuItemW(hMenu, ++indexMenu, TRUE, id, MFT_STRING, MAKEINTRESOURCEW(IDS_PROPERTIES), 0))
1010 {
1011 idHigh = max(idHigh, id);
1012 _InsertMenuItemW(hMenu, indexMenu++, TRUE, -1, MFT_SEPARATOR, NULL, 0);
1013 }
1014 }
1015 }
1016 return idHigh ? MAKE_HRESULT(SEVERITY_SUCCESS, 0, idHigh - idCmdFirst + 1) : S_OK;
1017 }
1018
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)1019 HRESULT WINAPI CRecycleBin::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1020 {
1021 TRACE("%p %p verb %p\n", this, lpcmi, lpcmi ? lpcmi->lpVerb : NULL);
1022 int CmdId = SHELL_MapContextMenuVerbToCmdId(lpcmi, g_BBFolderVerbMap);
1023 if (CmdId == IDC_EMPTYRECYCLEBIN)
1024 {
1025 HRESULT hr = SHEmptyRecycleBinW(lpcmi->hwnd, NULL, 0);
1026 TRACE("result %x\n", hr);
1027 if (hr != S_OK)
1028 return hr;
1029 #if 0 // This is a nasty hack because lpcmi->hwnd might not be a shell browser.
1030 // Not required with working SHChangeNotify.
1031 CComPtr<IShellView> pSV;
1032 LPSHELLBROWSER lpSB = (LPSHELLBROWSER)SendMessage(lpcmi->hwnd, CWM_GETISHELLBROWSER, 0, 0);
1033 if (lpSB && SUCCEEDED(lpSB->QueryActiveShellView(&pSV)))
1034 pSV->Refresh();
1035 #endif
1036 return hr;
1037 }
1038 else if (CmdId == IDC_PROPERTIES)
1039 {
1040 return SHELL_ShowItemIDListProperties((LPITEMIDLIST)CSIDL_BITBUCKET);
1041 }
1042 return E_INVALIDARG;
1043 }
1044
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)1045 HRESULT WINAPI CRecycleBin::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
1046 {
1047 TRACE("%p %lu %u %p %p %u\n", this, idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
1048 return SHELL_GetCommandStringImpl(idCommand, uFlags, lpszName, uMaxNameLen, g_BBFolderVerbMap);
1049 }
1050
1051 /*************************************************************************
1052 * RecycleBin IShellPropSheetExt interface
1053 */
1054
AddPages(LPFNSVADDPROPSHEETPAGE pfnAddPage,LPARAM lParam)1055 HRESULT WINAPI CRecycleBin::AddPages(LPFNSVADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
1056 {
1057 extern HRESULT RecycleBin_AddPropSheetPages(LPFNSVADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
1058 return RecycleBin_AddPropSheetPages(pfnAddPage, lParam);
1059 }
1060
ReplacePage(EXPPS uPageID,LPFNSVADDPROPSHEETPAGE pfnReplaceWith,LPARAM lParam)1061 HRESULT WINAPI CRecycleBin::ReplacePage(EXPPS uPageID, LPFNSVADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam)
1062 {
1063 FIXME("%p %lu %p %lu\n", this, uPageID, pfnReplaceWith, lParam);
1064
1065 return E_NOTIMPL;
1066 }
1067
1068 /*************************************************************************
1069 * RecycleBin IShellExtInit interface
1070 */
1071
Initialize(PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pdtobj,HKEY hkeyProgID)1072 HRESULT WINAPI CRecycleBin::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
1073 {
1074 TRACE("%p %p %p %p\n", this, pidlFolder, pdtobj, hkeyProgID );
1075 m_IsBackgroundMenu = false;
1076 return S_OK;
1077 }
1078
1079 /**
1080 * Tests whether a file can be trashed
1081 * @param wszPath Path to the file to be trash
1082 * @returns TRUE if the file can be trashed, FALSE otherwise
1083 */
1084 BOOL
TRASH_CanTrashFile(LPCWSTR wszPath)1085 TRASH_CanTrashFile(LPCWSTR wszPath)
1086 {
1087 LONG ret;
1088 DWORD dwNukeOnDelete, dwType, VolSerialNumber, MaxComponentLength;
1089 DWORD FileSystemFlags, dwSize, dwDisposition;
1090 HKEY hKey;
1091 WCHAR szBuffer[10];
1092 WCHAR szKey[150] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\BitBucket\\Volume\\";
1093
1094 if (wszPath[1] != L':')
1095 {
1096 /* path is UNC */
1097 return FALSE;
1098 }
1099
1100 // Copy and retrieve the root path from get given string
1101 WCHAR wszRootPathName[MAX_PATH];
1102 StringCbCopyW(wszRootPathName, sizeof(wszRootPathName), wszPath);
1103 PathStripToRootW(wszRootPathName);
1104
1105 // Test to see if the drive is fixed (non removable)
1106 if (GetDriveTypeW(wszRootPathName) != DRIVE_FIXED)
1107 {
1108 /* no bitbucket on removable media */
1109 return FALSE;
1110 }
1111
1112 if (!GetVolumeInformationW(wszRootPathName, NULL, 0, &VolSerialNumber, &MaxComponentLength, &FileSystemFlags, NULL, 0))
1113 {
1114 ERR("GetVolumeInformationW failed with %u wszRootPathName=%s\n", GetLastError(), debugstr_w(wszRootPathName));
1115 return FALSE;
1116 }
1117
1118 swprintf(szBuffer, L"%04X-%04X", LOWORD(VolSerialNumber), HIWORD(VolSerialNumber));
1119 wcscat(szKey, szBuffer);
1120
1121 if (RegCreateKeyExW(HKEY_CURRENT_USER, szKey, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hKey, &dwDisposition) != ERROR_SUCCESS)
1122 {
1123 ERR("RegCreateKeyExW failed\n");
1124 return FALSE;
1125 }
1126
1127 if (dwDisposition & REG_CREATED_NEW_KEY)
1128 {
1129 /* per default move to bitbucket */
1130 dwNukeOnDelete = 0;
1131 RegSetValueExW(hKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&dwNukeOnDelete, sizeof(DWORD));
1132 /* per default unlimited size */
1133 dwSize = -1;
1134 RegSetValueExW(hKey, L"MaxCapacity", 0, REG_DWORD, (LPBYTE)&dwSize, sizeof(DWORD));
1135 }
1136 else
1137 {
1138 dwSize = sizeof(dwNukeOnDelete);
1139 ret = RegQueryValueExW(hKey, L"NukeOnDelete", NULL, &dwType, (LPBYTE)&dwNukeOnDelete, &dwSize);
1140 if (ret != ERROR_SUCCESS)
1141 {
1142 dwNukeOnDelete = 0;
1143 if (ret == ERROR_FILE_NOT_FOUND)
1144 {
1145 /* restore key and enable bitbucket */
1146 RegSetValueExW(hKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&dwNukeOnDelete, sizeof(DWORD));
1147 }
1148 }
1149 }
1150 BOOL bCanTrash = !dwNukeOnDelete;
1151 // FIXME: Check if bitbucket is full (CORE-13743)
1152 RegCloseKey(hKey);
1153 return bCanTrash;
1154 }
1155
1156 BOOL
TRASH_TrashFile(LPCWSTR wszPath)1157 TRASH_TrashFile(LPCWSTR wszPath)
1158 {
1159 TRACE("(%s)\n", debugstr_w(wszPath));
1160 return DeleteFileToRecycleBin(wszPath);
1161 }
1162
TRASH_PlayEmptyRecycleBinSound()1163 static void TRASH_PlayEmptyRecycleBinSound()
1164 {
1165 CRegKey regKey;
1166 CHeapPtr<WCHAR> pszValue;
1167 CHeapPtr<WCHAR> pszSndPath;
1168 DWORD dwType, dwSize;
1169 LONG lError;
1170
1171 lError = regKey.Open(HKEY_CURRENT_USER,
1172 L"AppEvents\\Schemes\\Apps\\Explorer\\EmptyRecycleBin\\.Current",
1173 KEY_READ);
1174 if (lError != ERROR_SUCCESS)
1175 return;
1176
1177 lError = regKey.QueryValue(NULL, &dwType, NULL, &dwSize);
1178 if (lError != ERROR_SUCCESS)
1179 return;
1180
1181 if (!pszValue.AllocateBytes(dwSize))
1182 return;
1183
1184 lError = regKey.QueryValue(NULL, &dwType, pszValue, &dwSize);
1185 if (lError != ERROR_SUCCESS)
1186 return;
1187
1188 if (dwType == REG_EXPAND_SZ)
1189 {
1190 dwSize = ExpandEnvironmentStringsW(pszValue, NULL, 0);
1191 if (dwSize == 0)
1192 return;
1193
1194 if (!pszSndPath.Allocate(dwSize))
1195 return;
1196
1197 if (ExpandEnvironmentStringsW(pszValue, pszSndPath, dwSize) == 0)
1198 return;
1199 }
1200 else if (dwType == REG_SZ)
1201 {
1202 /* The type is REG_SZ, no need to expand */
1203 pszSndPath.Attach(pszValue.Detach());
1204 }
1205 else
1206 {
1207 /* Invalid type */
1208 return;
1209 }
1210
1211 PlaySoundW(pszSndPath, NULL, SND_FILENAME | SND_ASYNC | SND_NODEFAULT);
1212 }
1213
1214 /*************************************************************************
1215 * SHUpdateCRecycleBinIcon [SHELL32.@]
1216 *
1217 * Undocumented
1218 */
SHUpdateRecycleBinIcon(void)1219 EXTERN_C HRESULT WINAPI SHUpdateRecycleBinIcon(void)
1220 {
1221 FIXME("stub\n");
1222
1223 // HACK! This dwItem2 should be the icon index in the system image list that has changed.
1224 // FIXME: Call SHMapPIDLToSystemImageListIndex
1225 DWORD dwItem2 = -1;
1226
1227 SHChangeNotify(SHCNE_UPDATEIMAGE, SHCNF_DWORD, NULL, &dwItem2);
1228 return S_OK;
1229 }
1230
1231 /*************************************************************************
1232 * SHEmptyRecycleBinA (SHELL32.@)
1233 */
SHEmptyRecycleBinA(HWND hwnd,LPCSTR pszRootPath,DWORD dwFlags)1234 HRESULT WINAPI SHEmptyRecycleBinA(HWND hwnd, LPCSTR pszRootPath, DWORD dwFlags)
1235 {
1236 LPWSTR szRootPathW = NULL;
1237 int len;
1238 HRESULT hr;
1239
1240 TRACE("%p, %s, 0x%08x\n", hwnd, debugstr_a(pszRootPath), dwFlags);
1241
1242 if (pszRootPath)
1243 {
1244 len = MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, NULL, 0);
1245 if (len == 0)
1246 return HRESULT_FROM_WIN32(GetLastError());
1247 szRootPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1248 if (!szRootPathW)
1249 return E_OUTOFMEMORY;
1250 if (MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, szRootPathW, len) == 0)
1251 {
1252 HeapFree(GetProcessHeap(), 0, szRootPathW);
1253 return HRESULT_FROM_WIN32(GetLastError());
1254 }
1255 }
1256
1257 hr = SHEmptyRecycleBinW(hwnd, szRootPathW, dwFlags);
1258 HeapFree(GetProcessHeap(), 0, szRootPathW);
1259
1260 return hr;
1261 }
1262
SHEmptyRecycleBinW(HWND hwnd,LPCWSTR pszRootPath,DWORD dwFlags)1263 HRESULT WINAPI SHEmptyRecycleBinW(HWND hwnd, LPCWSTR pszRootPath, DWORD dwFlags)
1264 {
1265 WCHAR szBuffer[MAX_PATH];
1266 DWORD count;
1267 LONG ret;
1268 IShellFolder *pDesktop, *pRecycleBin;
1269 PIDLIST_ABSOLUTE pidlRecycleBin;
1270 PITEMID_CHILD pidl;
1271 HRESULT hr = S_OK;
1272 LPENUMIDLIST penumFiles;
1273 STRRET StrRet;
1274
1275 TRACE("%p, %s, 0x%08x\n", hwnd, debugstr_w(pszRootPath), dwFlags);
1276
1277 if (!(dwFlags & SHERB_NOCONFIRMATION))
1278 {
1279 hr = SHGetDesktopFolder(&pDesktop);
1280 if (FAILED(hr))
1281 return hr;
1282 hr = SHGetFolderLocation(NULL, CSIDL_BITBUCKET, NULL, 0, &pidlRecycleBin);
1283 if (FAILED(hr))
1284 {
1285 pDesktop->Release();
1286 return hr;
1287 }
1288 hr = pDesktop->BindToObject(pidlRecycleBin, NULL, IID_PPV_ARG(IShellFolder, &pRecycleBin));
1289 CoTaskMemFree(pidlRecycleBin);
1290 pDesktop->Release();
1291 if (FAILED(hr))
1292 return hr;
1293 hr = pRecycleBin->EnumObjects(hwnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &penumFiles);
1294 if (FAILED(hr))
1295 {
1296 pRecycleBin->Release();
1297 return hr;
1298 }
1299
1300 count = 0;
1301 if (hr != S_FALSE)
1302 {
1303 while (penumFiles->Next(1, &pidl, NULL) == S_OK)
1304 {
1305 count++;
1306 pRecycleBin->GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, &StrRet);
1307 StrRetToBuf(&StrRet, pidl, szBuffer, _countof(szBuffer));
1308 CoTaskMemFree(pidl);
1309 }
1310 penumFiles->Release();
1311 }
1312 pRecycleBin->Release();
1313
1314 switch (count)
1315 {
1316 case 0:
1317 /* no files, don't need confirmation */
1318 break;
1319
1320 case 1:
1321 /* we have only one item inside the bin, so show a message box with its name */
1322 if (ShellMessageBoxW(shell32_hInstance, hwnd, MAKEINTRESOURCEW(IDS_DELETEITEM_TEXT), MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET),
1323 MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2, szBuffer) == IDNO)
1324 {
1325 return S_OK;
1326 }
1327 break;
1328
1329 default:
1330 /* we have more than one item, so show a message box with the count of the items */
1331 StringCbPrintfW(szBuffer, sizeof(szBuffer), L"%u", count);
1332 if (ShellMessageBoxW(shell32_hInstance, hwnd, MAKEINTRESOURCEW(IDS_DELETEMULTIPLE_TEXT), MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET),
1333 MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2, szBuffer) == IDNO)
1334 {
1335 return S_OK;
1336 }
1337 break;
1338 }
1339 }
1340
1341 if (dwFlags & SHERB_NOPROGRESSUI)
1342 {
1343 ret = EmptyRecycleBinW(pszRootPath);
1344 }
1345 else
1346 {
1347 /* FIXME
1348 * show a progress dialog
1349 */
1350 ret = EmptyRecycleBinW(pszRootPath);
1351 }
1352
1353 if (!ret)
1354 return HRESULT_FROM_WIN32(GetLastError());
1355
1356 CRecycleBin_ChangeNotifyBBItem(SHCNE_UPDATEDIR, NULL);
1357 if (!(dwFlags & SHERB_NOSOUND))
1358 {
1359 TRASH_PlayEmptyRecycleBinSound();
1360 }
1361 return S_OK;
1362 }
1363
SHQueryRecycleBinA(LPCSTR pszRootPath,LPSHQUERYRBINFO pSHQueryRBInfo)1364 HRESULT WINAPI SHQueryRecycleBinA(LPCSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
1365 {
1366 LPWSTR szRootPathW = NULL;
1367 int len;
1368 HRESULT hr;
1369
1370 TRACE("%s, %p\n", debugstr_a(pszRootPath), pSHQueryRBInfo);
1371
1372 if (pszRootPath)
1373 {
1374 len = MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, NULL, 0);
1375 if (len == 0)
1376 return HRESULT_FROM_WIN32(GetLastError());
1377 szRootPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1378 if (!szRootPathW)
1379 return E_OUTOFMEMORY;
1380 if (MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, szRootPathW, len) == 0)
1381 {
1382 HeapFree(GetProcessHeap(), 0, szRootPathW);
1383 return HRESULT_FROM_WIN32(GetLastError());
1384 }
1385 }
1386
1387 hr = SHQueryRecycleBinW(szRootPathW, pSHQueryRBInfo);
1388 HeapFree(GetProcessHeap(), 0, szRootPathW);
1389
1390 return hr;
1391 }
1392
SHQueryRecycleBinW(LPCWSTR pszRootPath,LPSHQUERYRBINFO pSHQueryRBInfo)1393 HRESULT WINAPI SHQueryRecycleBinW(LPCWSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
1394 {
1395 TRACE("%s, %p\n", debugstr_w(pszRootPath), pSHQueryRBInfo);
1396
1397 if (!pszRootPath || (pszRootPath[0] == 0) ||
1398 !pSHQueryRBInfo || (pSHQueryRBInfo->cbSize < sizeof(SHQUERYRBINFO)))
1399 {
1400 return E_INVALIDARG;
1401 }
1402
1403 pSHQueryRBInfo->i64Size = 0;
1404 pSHQueryRBInfo->i64NumItems = 0;
1405
1406 CComPtr<IRecycleBin> spRecycleBin;
1407 HRESULT hr;
1408 if (FAILED_UNEXPECTEDLY((hr = GetDefaultRecycleBin(pszRootPath, &spRecycleBin))))
1409 return hr;
1410
1411 CComPtr<IRecycleBinEnumList> spEnumList;
1412 hr = spRecycleBin->EnumObjects(&spEnumList);
1413 if (!SUCCEEDED(hr))
1414 return hr;
1415
1416 while (TRUE)
1417 {
1418 CComPtr<IRecycleBinFile> spFile;
1419 hr = spEnumList->Next(1, &spFile, NULL);
1420 if (hr == S_FALSE)
1421 return S_OK;
1422
1423 if (FAILED_UNEXPECTEDLY(hr))
1424 return hr;
1425
1426 ULARGE_INTEGER Size = {};
1427 if (FAILED_UNEXPECTEDLY((hr = spFile->GetFileSize(&Size))))
1428 return hr;
1429
1430 pSHQueryRBInfo->i64Size += Size.QuadPart;
1431 pSHQueryRBInfo->i64NumItems++;
1432 }
1433
1434 return S_OK;
1435 }
1436