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  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include <precomp.h>
23 
24 #include <mmsystem.h>
25 #include <ntquery.h>
26 
27 WINE_DEFAULT_DEBUG_CHANNEL(CRecycleBin);
28 
29 typedef struct
30 {
31     int column_name_id;
32     const GUID *fmtId;
33     DWORD pid;
34     int pcsFlags;
35     int fmt;
36     int cxChars;
37 } columninfo;
38 
39 static const columninfo RecycleBinColumns[] =
40 {
41     {IDS_SHV_COLUMN_NAME,        &FMTID_Storage,   PID_STG_NAME,       SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_LEFT,  30},
42     {IDS_SHV_COLUMN_DELFROM, &FMTID_Displaced, PID_DISPLACED_FROM, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_LEFT,  30},
43     {IDS_SHV_COLUMN_DELDATE, &FMTID_Displaced, PID_DISPLACED_DATE, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT,  20},
44     {IDS_SHV_COLUMN_SIZE,        &FMTID_Storage,   PID_STG_SIZE,       SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_RIGHT, 20},
45     {IDS_SHV_COLUMN_TYPE,        &FMTID_Storage,   PID_STG_STORAGETYPE, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT,  LVCFMT_LEFT,  20},
46     {IDS_SHV_COLUMN_MODIFIED,        &FMTID_Storage,   PID_STG_WRITETIME,  SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT,  20},
47     /*    {"creation time",  &FMTID_Storage,   PID_STG_CREATETIME, SHCOLSTATE_TYPE_DATE,                        LVCFMT_LEFT,  20}, */
48     /*    {"attribs",        &FMTID_Storage,   PID_STG_ATTRIBUTES, SHCOLSTATE_TYPE_STR,                         LVCFMT_LEFT,  20},       */
49 };
50 
51 #define COLUMN_NAME    0
52 #define COLUMN_DELFROM 1
53 #define COLUMN_DATEDEL 2
54 #define COLUMN_SIZE    3
55 #define COLUMN_TYPE    4
56 #define COLUMN_MTIME   5
57 
58 #define COLUMNS_COUNT  6
59 
60 /*
61  * Recycle Bin folder
62  */
63 
64 HRESULT CRecyclerExtractIcon_CreateInstance(LPCITEMIDLIST pidl, REFIID riid, LPVOID * ppvOut)
65 {
66     CComPtr<IDefaultExtractIconInit> initIcon;
67     HRESULT hr = SHCreateDefaultExtractIcon(IID_PPV_ARG(IDefaultExtractIconInit, &initIcon));
68     if (FAILED_UNEXPECTEDLY(hr))
69         return hr;
70 
71     /* FIXME: This is completely unimplemented */
72     initIcon->SetNormalIcon(swShell32Name, 0);
73 
74     return initIcon->QueryInterface(riid, ppvOut);
75 }
76 
77 class CRecycleBinEnum :
78     public CEnumIDListBase
79 {
80     private:
81     public:
82         CRecycleBinEnum();
83         ~CRecycleBinEnum();
84         HRESULT WINAPI Initialize(DWORD dwFlags);
85         static BOOL WINAPI CBEnumRecycleBin(IN PVOID Context, IN HANDLE hDeletedFile);
86         BOOL WINAPI CBEnumRecycleBin(IN HANDLE hDeletedFile);
87 
88         BEGIN_COM_MAP(CRecycleBinEnum)
89         COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
90         END_COM_MAP()
91 };
92 
93 class CRecycleBinItemContextMenu :
94     public CComObjectRootEx<CComMultiThreadModelNoCS>,
95     public IContextMenu2
96 {
97     private:
98         LPITEMIDLIST                        apidl;
99     public:
100         CRecycleBinItemContextMenu();
101         ~CRecycleBinItemContextMenu();
102         HRESULT WINAPI Initialize(LPCITEMIDLIST pidl);
103 
104         // IContextMenu
105         virtual HRESULT WINAPI QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
106         virtual HRESULT WINAPI InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi);
107         virtual HRESULT WINAPI GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen);
108 
109         // IContextMenu2
110         virtual HRESULT WINAPI HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam);
111 
112         BEGIN_COM_MAP(CRecycleBinItemContextMenu)
113         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
114         COM_INTERFACE_ENTRY_IID(IID_IContextMenu2, IContextMenu2)
115         END_COM_MAP()
116 };
117 
118 typedef struct
119 {
120     PIDLRecycleStruct *pFileDetails;
121     HANDLE hDeletedFile;
122     BOOL bFound;
123 } SEARCH_CONTEXT, *PSEARCH_CONTEXT;
124 
125 BOOL WINAPI CBSearchRecycleBin(IN PVOID Context, IN HANDLE hDeletedFile)
126 {
127     PSEARCH_CONTEXT pContext = (PSEARCH_CONTEXT)Context;
128 
129     PDELETED_FILE_DETAILS_W pFileDetails;
130     DWORD dwSize;
131     BOOL ret;
132 
133     if (!GetDeletedFileDetailsW(hDeletedFile,
134                                 0,
135                                 NULL,
136                                 &dwSize) &&
137             GetLastError() != ERROR_INSUFFICIENT_BUFFER)
138     {
139         ERR("GetDeletedFileDetailsW failed\n");
140         return FALSE;
141     }
142 
143     pFileDetails = (DELETED_FILE_DETAILS_W *)SHAlloc(dwSize);
144     if (!pFileDetails)
145     {
146         ERR("No memory\n");
147         return FALSE;
148     }
149 
150     if (!GetDeletedFileDetailsW(hDeletedFile,
151                                 dwSize,
152                                 pFileDetails,
153                                 NULL))
154     {
155         ERR("GetDeletedFileDetailsW failed\n");
156         SHFree(pFileDetails);
157         return FALSE;
158     }
159 
160     ret = memcmp(pFileDetails, pContext->pFileDetails, dwSize);
161     if (!ret)
162     {
163         pContext->hDeletedFile = hDeletedFile;
164         pContext->bFound = TRUE;
165     }
166     else
167         CloseRecycleBinHandle(hDeletedFile);
168 
169     SHFree(pFileDetails);
170     return ret;
171 }
172 
173 static PIDLRecycleStruct * _ILGetRecycleStruct(LPCITEMIDLIST pidl)
174 {
175     LPPIDLDATA pdata = _ILGetDataPointer(pidl);
176 
177     if (pdata && pdata->type == 0x00)
178         return (PIDLRecycleStruct*) & (pdata->u.crecycle);
179 
180     return NULL;
181 }
182 
183 CRecycleBinEnum::CRecycleBinEnum()
184 {
185 }
186 
187 CRecycleBinEnum::~CRecycleBinEnum()
188 {
189 }
190 
191 HRESULT WINAPI CRecycleBinEnum::Initialize(DWORD dwFlags)
192 {
193     static LPCWSTR szDrive = L"C:\\";
194 
195     if (dwFlags & SHCONTF_NONFOLDERS)
196     {
197         TRACE("Starting Enumeration\n");
198 
199         if (!EnumerateRecycleBinW(szDrive /* FIXME */ , CBEnumRecycleBin, (PVOID)this))
200         {
201             WARN("Error: EnumerateCRecycleBinW failed\n");
202             return E_FAIL;
203         }
204     }
205     else
206     {
207         // do nothing
208     }
209     return S_OK;
210 }
211 
212 static LPITEMIDLIST _ILCreateRecycleItem(PDELETED_FILE_DETAILS_W pFileDetails)
213 {
214     PIDLDATA tmp;
215     LPITEMIDLIST pidl;
216     PIDLRecycleStruct * p;
217     int size0 = (char*)&tmp.u.crecycle.szName - (char*)&tmp.u.crecycle;
218     int size = size0;
219 
220     tmp.type = 0x00;
221     size += (wcslen(pFileDetails->FileName) + 1) * sizeof(WCHAR);
222 
223     pidl = (LPITEMIDLIST)SHAlloc(size + 4);
224     if (!pidl)
225         return pidl;
226 
227     pidl->mkid.cb = size + 2;
228     memcpy(pidl->mkid.abID, &tmp, 2 + size0);
229 
230     p = &((PIDLDATA*)pidl->mkid.abID)->u.crecycle;
231     RtlCopyMemory(p, pFileDetails, sizeof(DELETED_FILE_DETAILS_W));
232     wcscpy(p->szName, pFileDetails->FileName);
233     *(WORD*)((char*)pidl + (size + 2)) = 0;
234     return pidl;
235 }
236 
237 BOOL WINAPI CRecycleBinEnum::CBEnumRecycleBin(IN PVOID Context, IN HANDLE hDeletedFile)
238 {
239     return static_cast<CRecycleBinEnum *>(Context)->CBEnumRecycleBin(hDeletedFile);
240 }
241 
242 BOOL WINAPI CRecycleBinEnum::CBEnumRecycleBin(IN HANDLE hDeletedFile)
243 {
244     PDELETED_FILE_DETAILS_W pFileDetails;
245     DWORD dwSize;
246     LPITEMIDLIST pidl = NULL;
247     BOOL ret;
248 
249     if (!GetDeletedFileDetailsW(hDeletedFile,
250                                 0,
251                                 NULL,
252                                 &dwSize) &&
253             GetLastError() != ERROR_INSUFFICIENT_BUFFER)
254     {
255         ERR("GetDeletedFileDetailsW failed\n");
256         return FALSE;
257     }
258 
259     pFileDetails = (DELETED_FILE_DETAILS_W *)SHAlloc(dwSize);
260     if (!pFileDetails)
261     {
262         ERR("No memory\n");
263         return FALSE;
264     }
265 
266     if (!GetDeletedFileDetailsW(hDeletedFile,
267                                 dwSize,
268                                 pFileDetails,
269                                 NULL))
270     {
271         ERR("GetDeletedFileDetailsW failed\n");
272         SHFree(pFileDetails);
273         return FALSE;
274     }
275 
276     pidl = _ILCreateRecycleItem(pFileDetails);
277     if (!pidl)
278     {
279         SHFree(pFileDetails);
280         return FALSE;
281     }
282 
283     ret = AddToEnumList(pidl);
284 
285     if (!ret)
286         SHFree(pidl);
287     SHFree(pFileDetails);
288     TRACE("Returning %d\n", ret);
289     CloseRecycleBinHandle(hDeletedFile);
290     return ret;
291 }
292 
293 /**************************************************************************
294 * IContextMenu2 Bitbucket Item Implementation
295 */
296 
297 CRecycleBinItemContextMenu::CRecycleBinItemContextMenu()
298 {
299     apidl = NULL;
300 }
301 
302 CRecycleBinItemContextMenu::~CRecycleBinItemContextMenu()
303 {
304     ILFree(apidl);
305 }
306 
307 HRESULT WINAPI CRecycleBinItemContextMenu::Initialize(LPCITEMIDLIST pidl)
308 {
309     apidl = ILClone(pidl);
310     if (apidl == NULL)
311         return E_OUTOFMEMORY;
312     return S_OK;
313 }
314 
315 HRESULT WINAPI CRecycleBinItemContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
316 {
317     WCHAR szBuffer[30] = {0};
318     ULONG Count = 1;
319 
320     TRACE("(%p)->(hmenu=%p indexmenu=%x cmdfirst=%x cmdlast=%x flags=%x )\n", this, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
321 
322     if (LoadStringW(shell32_hInstance, IDS_RESTORE, szBuffer, sizeof(szBuffer) / sizeof(WCHAR)))
323     {
324         szBuffer[(sizeof(szBuffer)/sizeof(WCHAR))-1] = L'\0';
325         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + Count, MFT_STRING, szBuffer, MFS_ENABLED);
326         Count++;
327     }
328 
329     if (LoadStringW(shell32_hInstance, IDS_CUT, szBuffer, sizeof(szBuffer) / sizeof(WCHAR)))
330     {
331         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + Count++, MFT_SEPARATOR, NULL, MFS_ENABLED);
332         szBuffer[(sizeof(szBuffer)/sizeof(WCHAR))-1] = L'\0';
333         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + Count++, MFT_STRING, szBuffer, MFS_ENABLED);
334     }
335 
336     if (LoadStringW(shell32_hInstance, IDS_DELETE, szBuffer, sizeof(szBuffer) / sizeof(WCHAR)))
337     {
338         szBuffer[(sizeof(szBuffer)/sizeof(WCHAR))-1] = L'\0';
339         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + Count++, MFT_SEPARATOR, NULL, MFS_ENABLED);
340         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + Count++, MFT_STRING, szBuffer, MFS_ENABLED);
341     }
342 
343     if (LoadStringW(shell32_hInstance, IDS_PROPERTIES, szBuffer, sizeof(szBuffer) / sizeof(WCHAR)))
344     {
345         szBuffer[(sizeof(szBuffer)/sizeof(WCHAR))-1] = L'\0';
346         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + Count++, MFT_SEPARATOR, NULL, MFS_ENABLED);
347         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst + Count, MFT_STRING, szBuffer, MFS_DEFAULT);
348     }
349 
350     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, Count);
351 }
352 
353 HRESULT WINAPI CRecycleBinItemContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
354 {
355     SEARCH_CONTEXT Context;
356     static LPCWSTR szDrive = L"C:\\";
357 
358     TRACE("(%p)->(invcom=%p verb=%p wnd=%p)\n", this, lpcmi, lpcmi->lpVerb, lpcmi->hwnd);
359 
360     if (lpcmi->lpVerb == MAKEINTRESOURCEA(1) || lpcmi->lpVerb == MAKEINTRESOURCEA(5))
361     {
362         Context.pFileDetails = _ILGetRecycleStruct(apidl);
363         Context.bFound = FALSE;
364 
365         EnumerateRecycleBinW(szDrive, CBSearchRecycleBin, (PVOID)&Context);
366         if (!Context.bFound)
367             return E_FAIL;
368 
369         if (lpcmi->lpVerb == MAKEINTRESOURCEA(1))
370         {
371             /* restore file */
372             if (RestoreFile(Context.hDeletedFile))
373                 return S_OK;
374             else
375                 return E_FAIL;
376         }
377         else
378         {
379             DeleteFileHandleToRecycleBin(Context.hDeletedFile);
380             return E_NOTIMPL;
381         }
382     }
383     else if (lpcmi->lpVerb == MAKEINTRESOURCEA(3))
384     {
385         FIXME("implement cut\n");
386         return E_NOTIMPL;
387     }
388     else if (lpcmi->lpVerb == MAKEINTRESOURCEA(7))
389     {
390         FIXME("implement properties\n");
391         return E_NOTIMPL;
392     }
393 
394     return S_OK;
395 }
396 
397 HRESULT WINAPI CRecycleBinItemContextMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
398 {
399     TRACE("(%p)->(idcom=%lx flags=%x %p name=%p len=%x)\n", this, idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
400 
401     return E_FAIL;
402 }
403 
404 HRESULT WINAPI CRecycleBinItemContextMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
405 {
406     TRACE("CRecycleBin_IContextMenu2Item_HandleMenuMsg (%p)->(msg=%x wp=%lx lp=%lx)\n", this, uMsg, wParam, lParam);
407 
408     return E_NOTIMPL;
409 }
410 
411 CRecycleBin::CRecycleBin()
412 {
413     pidl = NULL;
414 }
415 
416 CRecycleBin::~CRecycleBin()
417 {
418     SHFree(pidl);
419 }
420 
421 /*************************************************************************
422  * RecycleBin IPersistFolder2 interface
423  */
424 
425 HRESULT WINAPI CRecycleBin::GetClassID(CLSID *pClassID)
426 {
427     TRACE("(%p, %p)\n", this, pClassID);
428     if (pClassID == NULL)
429         return E_INVALIDARG;
430     memcpy(pClassID, &CLSID_RecycleBin, sizeof(CLSID));
431     return S_OK;
432 }
433 
434 HRESULT WINAPI CRecycleBin::Initialize(LPCITEMIDLIST pidl)
435 {
436     TRACE("(%p, %p)\n", this, pidl);
437 
438     SHFree((LPVOID)this->pidl);
439     this->pidl = ILClone(pidl);
440     if (this->pidl == NULL)
441         return E_OUTOFMEMORY;
442     return S_OK;
443 }
444 
445 HRESULT WINAPI CRecycleBin::GetCurFolder(LPITEMIDLIST *ppidl)
446 {
447     TRACE("\n");
448     *ppidl = ILClone(pidl);
449     return S_OK;
450 }
451 
452 /*************************************************************************
453  * RecycleBin IShellFolder2 interface
454  */
455 
456 HRESULT WINAPI CRecycleBin::ParseDisplayName(HWND hwnd, LPBC pbc,
457         LPOLESTR pszDisplayName, ULONG *pchEaten, PIDLIST_RELATIVE *ppidl,
458         ULONG *pdwAttributes)
459 {
460     FIXME("stub\n");
461     return E_NOTIMPL;
462 }
463 
464 
465 PDELETED_FILE_DETAILS_W
466 UnpackDetailsFromPidl(LPCITEMIDLIST pidl)
467 {
468     return (PDELETED_FILE_DETAILS_W)&pidl->mkid.abID;
469 }
470 
471 HRESULT WINAPI CRecycleBin::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
472 {
473     return ShellObjectCreatorInit<CRecycleBinEnum>(dwFlags, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
474 }
475 
476 HRESULT WINAPI CRecycleBin::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbc, REFIID riid, void **ppv)
477 {
478     FIXME("(%p, %p, %p, %s, %p) - stub\n", this, pidl, pbc, debugstr_guid(&riid), ppv);
479     return E_NOTIMPL;
480 }
481 
482 HRESULT WINAPI CRecycleBin::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbc, REFIID riid, void **ppv)
483 {
484     FIXME("(%p, %p, %p, %s, %p) - stub\n", this, pidl, pbc, debugstr_guid(&riid), ppv);
485     return E_NOTIMPL;
486 }
487 
488 HRESULT WINAPI CRecycleBin::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
489 {
490     PIDLRecycleStruct* pData1 = _ILGetRecycleStruct(pidl1);
491     PIDLRecycleStruct* pData2 = _ILGetRecycleStruct(pidl2);
492     LPWSTR pName1, pName2;
493 
494     if(!pData1 || !pData2 || LOWORD(lParam) >= COLUMNS_COUNT)
495         return E_INVALIDARG;
496 
497     SHORT result;
498     LONGLONG diff;
499     switch (LOWORD(lParam))
500     {
501         case 0: /* Name */
502             pName1 = PathFindFileNameW(pData1->szName);
503             pName2 = PathFindFileNameW(pData2->szName);
504             result = wcsicmp(pName1, pName2);
505             break;
506         case 1: /* Orig. Location */
507             result = wcsicmp(pData1->szName, pData2->szName);
508             break;
509         case 2: /* Date Deleted */
510             result = CompareFileTime(&pData1->DeletionTime, &pData2->DeletionTime);
511             break;
512         case 3: /* Size */
513             diff = pData1->FileSize.QuadPart - pData2->FileSize.QuadPart;
514             return MAKE_COMPARE_HRESULT(diff);
515         case 4: /* Type */
516             pName1 = PathFindExtensionW(pData1->szName);
517             pName2 = PathFindExtensionW(pData2->szName);
518             result = wcsicmp(pName1, pName2);
519             break;
520         case 5: /* Modified */
521             result = CompareFileTime(&pData1->LastModification, &pData2->LastModification);
522             break;
523     }
524     return MAKE_COMPARE_HRESULT(result);
525 }
526 
527 HRESULT WINAPI CRecycleBin::CreateViewObject(HWND hwndOwner, REFIID riid, void **ppv)
528 {
529     CComPtr<IShellView> pShellView;
530     HRESULT hr = E_NOINTERFACE;
531 
532     TRACE("(%p, %p, %s, %p)\n", this, hwndOwner, debugstr_guid(&riid), ppv);
533 
534     if (!ppv)
535         return hr;
536 
537     *ppv = NULL;
538 
539     if (IsEqualIID (riid, IID_IDropTarget))
540     {
541         hr = CRecyclerDropTarget_CreateInstance(riid, ppv);
542     }
543     else if (IsEqualIID (riid, IID_IContextMenu) || IsEqualIID (riid, IID_IContextMenu2))
544     {
545         hr = this->QueryInterface(riid, ppv);
546     }
547     else if (IsEqualIID (riid, IID_IShellView))
548     {
549         SFV_CREATE sfvparams = {sizeof(SFV_CREATE), this};
550         hr = SHCreateShellFolderView(&sfvparams, (IShellView**)ppv);
551     }
552     else
553         return hr;
554 
555     TRACE ("-- (%p)->(interface=%p)\n", this, ppv);
556     return hr;
557 
558 }
559 
560 HRESULT WINAPI CRecycleBin::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
561         SFGAOF *rgfInOut)
562 {
563     TRACE("(%p, %d, {%p, ...}, {%x})\n", this, cidl, apidl ? apidl[0] : NULL, (unsigned int)*rgfInOut);
564     *rgfInOut &= SFGAO_FOLDER|SFGAO_DROPTARGET|SFGAO_HASPROPSHEET|SFGAO_CANLINK;
565     return S_OK;
566 }
567 
568 HRESULT WINAPI CRecycleBin::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
569         REFIID riid, UINT *prgfInOut, void **ppv)
570 {
571     LPVOID pObj = NULL;
572     HRESULT hr = E_INVALIDARG;
573 
574     TRACE ("(%p)->(%p,%u,apidl=%p, %p %p)\n", this,
575            hwndOwner, cidl, apidl, prgfInOut, ppv);
576 
577     if (!ppv)
578         return hr;
579 
580     *ppv = NULL;
581 
582     if ((IsEqualIID (riid, IID_IContextMenu) || IsEqualIID(riid, IID_IContextMenu2)) && (cidl >= 1))
583     {
584         hr = ShellObjectCreatorInit<CRecycleBinItemContextMenu>(apidl[0], riid, &pObj);
585     }
586     else if((IsEqualIID(riid, IID_IExtractIconA) || IsEqualIID(riid, IID_IExtractIconW)) && (cidl == 1))
587     {
588         hr = CRecyclerExtractIcon_CreateInstance(apidl[0], riid, &pObj);
589     }
590     else
591         hr = E_NOINTERFACE;
592 
593     if (SUCCEEDED(hr) && !pObj)
594         hr = E_OUTOFMEMORY;
595 
596     *ppv = pObj;
597     TRACE ("(%p)->hr=0x%08x\n", this, hr);
598     return hr;
599 }
600 
601 HRESULT WINAPI CRecycleBin::GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF uFlags, STRRET *pName)
602 {
603     PIDLRecycleStruct *pFileDetails;
604     LPWSTR pFileName;
605 
606     TRACE("(%p, %p, %x, %p)\n", this, pidl, (unsigned int)uFlags, pName);
607 
608     pFileDetails = _ILGetRecycleStruct(pidl);
609     if (!pFileDetails)
610     {
611         pName->cStr[0] = 0;
612         pName->uType = STRRET_CSTR;
613         return E_INVALIDARG;
614     }
615 
616     pFileName = wcsrchr(pFileDetails->szName, L'\\');
617     if (!pFileName)
618     {
619         pName->cStr[0] = 0;
620         pName->uType = STRRET_CSTR;
621         return E_UNEXPECTED;
622     }
623 
624     pName->pOleStr = StrDupW(pFileName + 1);
625     if (pName->pOleStr == NULL)
626         return E_OUTOFMEMORY;
627 
628     pName->uType = STRRET_WSTR;
629     return S_OK;
630 }
631 
632 HRESULT WINAPI CRecycleBin::SetNameOf(HWND hwnd, PCUITEMID_CHILD pidl, LPCOLESTR pszName,
633                                       SHGDNF uFlags, PITEMID_CHILD *ppidlOut)
634 {
635     TRACE("\n");
636     return E_FAIL; /* not supported */
637 }
638 
639 HRESULT WINAPI CRecycleBin::GetDefaultSearchGUID(GUID *pguid)
640 {
641     FIXME("stub\n");
642     return E_NOTIMPL;
643 }
644 
645 HRESULT WINAPI CRecycleBin::EnumSearches(IEnumExtraSearch **ppEnum)
646 {
647     FIXME("stub\n");
648     *ppEnum = NULL;
649     return E_NOTIMPL;
650 }
651 
652 HRESULT WINAPI CRecycleBin::GetDefaultColumn(DWORD dwReserved, ULONG *pSort, ULONG *pDisplay)
653 {
654     TRACE("(%p, %x, %p, %p)\n", this, (unsigned int)dwReserved, pSort, pDisplay);
655     if (pSort)
656         *pSort = 0;
657     if (pDisplay)
658         *pDisplay = 0;
659     return S_OK;
660 }
661 
662 HRESULT WINAPI CRecycleBin::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
663 {
664     TRACE("(%p, %d, %p)\n", this, iColumn, pcsFlags);
665     if (iColumn >= COLUMNS_COUNT)
666         return E_INVALIDARG;
667     *pcsFlags = RecycleBinColumns[iColumn].pcsFlags;
668     return S_OK;
669 }
670 
671 HRESULT WINAPI CRecycleBin::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
672 {
673     FIXME("stub\n");
674     return E_NOTIMPL;
675 }
676 
677 static HRESULT FormatDateTime(LPWSTR buffer, int size, FILETIME * ft)
678 {
679     FILETIME lft;
680     SYSTEMTIME time;
681     int ret;
682 
683     FileTimeToLocalFileTime(ft, &lft);
684     FileTimeToSystemTime(&lft, &time);
685 
686     ret = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &time, NULL, buffer, size);
687     if (ret > 0 && ret < size)
688     {
689         /* Append space + time without seconds */
690         buffer[ret-1] = ' ';
691         GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &time, NULL, &buffer[ret], size - ret);
692     }
693 
694     return (ret != 0 ? E_FAIL : S_OK);
695 }
696 
697 HRESULT WINAPI CRecycleBin::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, LPSHELLDETAILS pDetails)
698 {
699     PIDLRecycleStruct * pFileDetails;
700     WCHAR buffer[MAX_PATH];
701     WCHAR szTypeName[100];
702     LPWSTR pszBackslash;
703     UINT Length;
704 
705     TRACE("(%p, %p, %d, %p)\n", this, pidl, iColumn, pDetails);
706     if (iColumn >= COLUMNS_COUNT)
707         return E_FAIL;
708     pDetails->fmt = RecycleBinColumns[iColumn].fmt;
709     pDetails->cxChar = RecycleBinColumns[iColumn].cxChars;
710     if (pidl == NULL)
711         return SHSetStrRet(&pDetails->str, RecycleBinColumns[iColumn].column_name_id);
712 
713     if (iColumn == COLUMN_NAME)
714         return GetDisplayNameOf(pidl, SHGDN_NORMAL, &pDetails->str);
715 
716     pFileDetails = _ILGetRecycleStruct(pidl);
717     switch (iColumn)
718     {
719         case COLUMN_DATEDEL:
720             FormatDateTime(buffer, MAX_PATH, &pFileDetails->DeletionTime);
721             break;
722         case COLUMN_DELFROM:
723             pszBackslash = wcsrchr(pFileDetails->szName, L'\\');
724             Length = (pszBackslash - pFileDetails->szName);
725             memcpy((LPVOID)buffer, pFileDetails->szName, Length * sizeof(WCHAR));
726             buffer[Length] = L'\0';
727             break;
728         case COLUMN_SIZE:
729             StrFormatKBSizeW(pFileDetails->FileSize.QuadPart, buffer, MAX_PATH);
730             break;
731         case COLUMN_MTIME:
732             FormatDateTime(buffer, MAX_PATH, &pFileDetails->LastModification);
733             break;
734         case COLUMN_TYPE:
735             // FIXME: We should in fact use a UNICODE version of _ILGetFileType
736             szTypeName[0] = L'\0';
737             wcscpy(buffer, PathFindExtensionW(pFileDetails->szName));
738             if (!( HCR_MapTypeToValueW(buffer, buffer, _countof(buffer), TRUE) &&
739                     HCR_MapTypeToValueW(buffer, szTypeName, _countof(szTypeName), FALSE )))
740             {
741                 /* load localized file string */
742                 szTypeName[0] = '\0';
743                 if(LoadStringW(shell32_hInstance, IDS_ANY_FILE, szTypeName, _countof(szTypeName)))
744                 {
745                     szTypeName[63] = '\0';
746                     StringCchPrintfW(buffer, _countof(buffer), szTypeName, PathFindExtensionW(pFileDetails->szName));
747                 }
748             }
749             return SHSetStrRet(&pDetails->str, szTypeName);
750         default:
751             return E_FAIL;
752     }
753 
754     return SHSetStrRet(&pDetails->str, buffer);
755 }
756 
757 HRESULT WINAPI CRecycleBin::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
758 {
759     TRACE("(%p, %d, %p)\n", this, iColumn, pscid);
760     if (iColumn >= COLUMNS_COUNT)
761         return E_INVALIDARG;
762     pscid->fmtid = *RecycleBinColumns[iColumn].fmtId;
763     pscid->pid = RecycleBinColumns[iColumn].pid;
764     return S_OK;
765 }
766 
767 BOOL CRecycleBin::RecycleBinIsEmpty()
768 {
769     CComPtr<IEnumIDList> spEnumFiles;
770     HRESULT hr = EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &spEnumFiles);
771     if (FAILED(hr))
772         return TRUE;
773     CComHeapPtr<ITEMIDLIST> spPidl;
774     ULONG itemcount;
775     return spEnumFiles->Next(1, &spPidl, &itemcount) != S_OK;
776     }
777 
778 /*************************************************************************
779  * RecycleBin IContextMenu interface
780  */
781 
782 HRESULT WINAPI CRecycleBin::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
783 {
784     WCHAR szBuffer[100];
785     MENUITEMINFOW mii;
786     int id = 1;
787 
788     TRACE("QueryContextMenu %p %p %u %u %u %u\n", this, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags );
789 
790     if (!hMenu)
791         return E_INVALIDARG;
792 
793     memset(&mii, 0, sizeof(mii));
794     mii.cbSize = sizeof(mii);
795     mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE;
796     mii.fState = RecycleBinIsEmpty() ? MFS_DISABLED : MFS_ENABLED;
797     szBuffer[0] = L'\0';
798     LoadStringW(shell32_hInstance, IDS_EMPTY_BITBUCKET, szBuffer, sizeof(szBuffer) / sizeof(WCHAR));
799     mii.dwTypeData = szBuffer;
800     mii.cch = wcslen(mii.dwTypeData);
801     mii.wID = idCmdFirst + id++;
802     mii.fType = MFT_STRING;
803     iIdEmpty = 1;
804 
805     if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
806         return E_FAIL;
807 
808     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, id);
809 }
810 
811 HRESULT WINAPI CRecycleBin::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
812 {
813     HRESULT hr;
814     LPSHELLBROWSER lpSB;
815     IShellView * lpSV = NULL;
816 
817     TRACE("%p %p verb %p\n", this, lpcmi, lpcmi->lpVerb);
818 
819     if (LOWORD(lpcmi->lpVerb) == iIdEmpty)
820     {
821         // FIXME
822         // path & flags
823         hr = SHEmptyRecycleBinW(lpcmi->hwnd, L"C:\\", 0);
824         TRACE("result %x\n", hr);
825         if (hr != S_OK)
826             return hr;
827 
828         lpSB = (LPSHELLBROWSER)SendMessageA(lpcmi->hwnd, CWM_GETISHELLBROWSER, 0, 0);
829         if (lpSB && SUCCEEDED(lpSB->QueryActiveShellView(&lpSV)))
830             lpSV->Refresh();
831     }
832     return S_OK;
833 }
834 
835 HRESULT WINAPI CRecycleBin::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
836 {
837     FIXME("%p %lu %u %p %p %u\n", this, idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
838 
839     return E_NOTIMPL;
840 }
841 
842 /*************************************************************************
843  * RecycleBin IShellPropSheetExt interface
844  */
845 
846 HRESULT WINAPI CRecycleBin::AddPages(LPFNSVADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
847 {
848     FIXME("%p %p %lu\n", this, pfnAddPage, lParam);
849 
850     return E_NOTIMPL;
851 }
852 
853 HRESULT WINAPI CRecycleBin::ReplacePage(EXPPS uPageID, LPFNSVADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam)
854 {
855     FIXME("%p %lu %p %lu\n", this, uPageID, pfnReplaceWith, lParam);
856 
857     return E_NOTIMPL;
858 }
859 
860 /*************************************************************************
861  * RecycleBin IShellExtInit interface
862  */
863 
864 HRESULT WINAPI CRecycleBin::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
865 {
866     TRACE("%p %p %p %p\n", this, pidlFolder, pdtobj, hkeyProgID );
867     return S_OK;
868 }
869 
870 BOOL
871 TRASH_CanTrashFile(LPCWSTR wszPath)
872 {
873     LONG ret;
874     DWORD dwNukeOnDelete, dwType, VolSerialNumber, MaxComponentLength;
875     DWORD FileSystemFlags, dwSize, dwDisposition;
876     HKEY hKey;
877     WCHAR szBuffer[10];
878     WCHAR szKey[150] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Bitbucket\\Volume\\";
879 
880     if (wszPath[1] != L':')
881     {
882         /* path is UNC */
883         return FALSE;
884     }
885 
886     // Only keep the base path.
887     WCHAR wszRootPathName[MAX_PATH];
888     strcpyW(wszRootPathName, wszPath);
889     PathRemoveFileSpecW(wszRootPathName);
890     PathAddBackslashW(wszRootPathName);
891 
892     if (GetDriveTypeW(wszRootPathName) != DRIVE_FIXED)
893     {
894         /* no bitbucket on removable media */
895         return FALSE;
896     }
897 
898     if (!GetVolumeInformationW(wszRootPathName, NULL, 0, &VolSerialNumber, &MaxComponentLength, &FileSystemFlags, NULL, 0))
899     {
900         ERR("GetVolumeInformationW failed with %u\n", GetLastError());
901         return FALSE;
902     }
903 
904     swprintf(szBuffer, L"%04X-%04X", LOWORD(VolSerialNumber), HIWORD(VolSerialNumber));
905     wcscat(szKey, szBuffer);
906 
907     if (RegCreateKeyExW(HKEY_CURRENT_USER, szKey, 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition) != ERROR_SUCCESS)
908     {
909         ERR("RegCreateKeyExW failed\n");
910         return FALSE;
911     }
912 
913     if (dwDisposition  & REG_CREATED_NEW_KEY)
914     {
915         /* per default move to bitbucket */
916         dwNukeOnDelete = 0;
917         RegSetValueExW(hKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&dwNukeOnDelete, sizeof(DWORD));
918         /* per default unlimited size */
919         dwSize = -1;
920         RegSetValueExW(hKey, L"MaxCapacity", 0, REG_DWORD, (LPBYTE)&dwSize, sizeof(DWORD));
921         RegCloseKey(hKey);
922         return TRUE;
923     }
924     else
925     {
926         dwSize = sizeof(dwNukeOnDelete);
927         ret = RegQueryValueExW(hKey, L"NukeOnDelete", NULL, &dwType, (LPBYTE)&dwNukeOnDelete, &dwSize);
928         if (ret != ERROR_SUCCESS)
929         {
930             if (ret ==  ERROR_FILE_NOT_FOUND)
931             {
932                 /* restore key and enable bitbucket */
933                 dwNukeOnDelete = 0;
934                 RegSetValueExW(hKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&dwNukeOnDelete, sizeof(DWORD));
935             }
936             RegCloseKey(hKey);
937             return TRUE;
938         }
939         else if (dwNukeOnDelete)
940         {
941             /* do not delete to bitbucket */
942             RegCloseKey(hKey);
943             return FALSE;
944         }
945         /* FIXME
946          * check if bitbucket is full
947          */
948         RegCloseKey(hKey);
949         return TRUE;
950     }
951 }
952 
953 BOOL
954 TRASH_TrashFile(LPCWSTR wszPath)
955 {
956     TRACE("(%s)\n", debugstr_w(wszPath));
957     return DeleteFileToRecycleBin(wszPath);
958 }
959 
960 /*************************************************************************
961  * SHUpdateCRecycleBinIcon                                [SHELL32.@]
962  *
963  * Undocumented
964  */
965 EXTERN_C HRESULT WINAPI SHUpdateRecycleBinIcon(void)
966 {
967     FIXME("stub\n");
968 
969     return S_OK;
970 }
971 
972 /*************************************************************************
973  *              SHEmptyRecycleBinA (SHELL32.@)
974  */
975 HRESULT WINAPI SHEmptyRecycleBinA(HWND hwnd, LPCSTR pszRootPath, DWORD dwFlags)
976 {
977     LPWSTR szRootPathW = NULL;
978     int len;
979     HRESULT hr;
980 
981     TRACE("%p, %s, 0x%08x\n", hwnd, debugstr_a(pszRootPath), dwFlags);
982 
983     if (pszRootPath)
984     {
985         len = MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, NULL, 0);
986         if (len == 0)
987             return HRESULT_FROM_WIN32(GetLastError());
988         szRootPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
989         if (!szRootPathW)
990             return E_OUTOFMEMORY;
991         if (MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, szRootPathW, len) == 0)
992         {
993             HeapFree(GetProcessHeap(), 0, szRootPathW);
994             return HRESULT_FROM_WIN32(GetLastError());
995         }
996     }
997 
998     hr = SHEmptyRecycleBinW(hwnd, szRootPathW, dwFlags);
999     HeapFree(GetProcessHeap(), 0, szRootPathW);
1000 
1001     return hr;
1002 }
1003 
1004 HRESULT WINAPI SHEmptyRecycleBinW(HWND hwnd, LPCWSTR pszRootPath, DWORD dwFlags)
1005 {
1006     WCHAR szPath[MAX_PATH] = {0}, szBuffer[MAX_PATH];
1007     DWORD dwSize, dwType, count;
1008     LONG ret;
1009     IShellFolder *pDesktop, *pRecycleBin;
1010     PIDLIST_ABSOLUTE pidlRecycleBin;
1011     PITEMID_CHILD pidl;
1012     HRESULT hr = S_OK;
1013     LPENUMIDLIST penumFiles;
1014     STRRET StrRet;
1015 
1016     TRACE("%p, %s, 0x%08x\n", hwnd, debugstr_w(pszRootPath), dwFlags);
1017 
1018     if (!(dwFlags & SHERB_NOCONFIRMATION))
1019     {
1020         hr = SHGetDesktopFolder(&pDesktop);
1021         if (FAILED(hr))
1022             return hr;
1023         hr = SHGetFolderLocation(NULL, CSIDL_BITBUCKET, NULL, 0, &pidlRecycleBin);
1024         if (FAILED(hr))
1025         {
1026             pDesktop->Release();
1027             return hr;
1028         }
1029         hr = pDesktop->BindToObject(pidlRecycleBin, NULL, IID_PPV_ARG(IShellFolder, &pRecycleBin));
1030         CoTaskMemFree(pidlRecycleBin);
1031         pDesktop->Release();
1032         if (FAILED(hr))
1033             return hr;
1034         hr = pRecycleBin->EnumObjects(hwnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &penumFiles);
1035         if (FAILED(hr))
1036         {
1037             pRecycleBin->Release();
1038             return hr;
1039         }
1040 
1041         count = 0;
1042         if (hr != S_FALSE)
1043         {
1044             while (penumFiles->Next(1, &pidl, NULL) == S_OK)
1045             {
1046                 count++;
1047                 pRecycleBin->GetDisplayNameOf(pidl, SHGDN_NORMAL, &StrRet);
1048                 StrRetToBuf(&StrRet, pidl, szBuffer, _countof(szBuffer));
1049                 CoTaskMemFree(pidl);
1050             }
1051             penumFiles->Release();
1052         }
1053         pRecycleBin->Release();
1054 
1055         switch (count)
1056         {
1057             case 0:
1058                 /* no files, don't need confirmation */
1059                 break;
1060 
1061             case 1:
1062                 /* we have only one item inside the bin, so show a message box with its name */
1063                 if (ShellMessageBoxW(shell32_hInstance, hwnd, MAKEINTRESOURCEW(IDS_DELETEITEM_TEXT), MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET),
1064                                    MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2, szBuffer) == IDNO)
1065                 {
1066                     return S_OK;
1067                 }
1068                 break;
1069 
1070             default:
1071                 /* we have more than one item, so show a message box with the count of the items */
1072                 StringCbPrintfW(szBuffer, sizeof(szBuffer), L"%u", count);
1073                 if (ShellMessageBoxW(shell32_hInstance, hwnd, MAKEINTRESOURCEW(IDS_DELETEMULTIPLE_TEXT), MAKEINTRESOURCEW(IDS_EMPTY_BITBUCKET),
1074                                    MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2, szBuffer) == IDNO)
1075                 {
1076                     return S_OK;
1077                 }
1078                 break;
1079         }
1080     }
1081 
1082     if (dwFlags & SHERB_NOPROGRESSUI)
1083     {
1084         ret = EmptyRecycleBinW(pszRootPath);
1085     }
1086     else
1087     {
1088        /* FIXME
1089         * show a progress dialog
1090         */
1091         ret = EmptyRecycleBinW(pszRootPath);
1092     }
1093 
1094     if (!ret)
1095         return HRESULT_FROM_WIN32(GetLastError());
1096 
1097     if (!(dwFlags & SHERB_NOSOUND))
1098     {
1099         dwSize = sizeof(szPath);
1100         ret = RegGetValueW(HKEY_CURRENT_USER,
1101                            L"AppEvents\\Schemes\\Apps\\Explorer\\EmptyRecycleBin\\.Current",
1102                            NULL,
1103                            RRF_RT_REG_SZ,
1104                            &dwType,
1105                            (PVOID)szPath,
1106                            &dwSize);
1107         if (ret != ERROR_SUCCESS)
1108             return S_OK;
1109 
1110         if (dwType != REG_EXPAND_SZ) /* type dismatch */
1111             return S_OK;
1112 
1113         szPath[(sizeof(szPath)/sizeof(WCHAR))-1] = L'\0';
1114         PlaySoundW(szPath, NULL, SND_FILENAME);
1115     }
1116     return S_OK;
1117 }
1118 
1119 HRESULT WINAPI SHQueryRecycleBinA(LPCSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
1120 {
1121     LPWSTR szRootPathW = NULL;
1122     int len;
1123     HRESULT hr;
1124 
1125     TRACE("%s, %p\n", debugstr_a(pszRootPath), pSHQueryRBInfo);
1126 
1127     if (pszRootPath)
1128     {
1129         len = MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, NULL, 0);
1130         if (len == 0)
1131             return HRESULT_FROM_WIN32(GetLastError());
1132         szRootPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
1133         if (!szRootPathW)
1134             return E_OUTOFMEMORY;
1135         if (MultiByteToWideChar(CP_ACP, 0, pszRootPath, -1, szRootPathW, len) == 0)
1136         {
1137             HeapFree(GetProcessHeap(), 0, szRootPathW);
1138             return HRESULT_FROM_WIN32(GetLastError());
1139         }
1140     }
1141 
1142     hr = SHQueryRecycleBinW(szRootPathW, pSHQueryRBInfo);
1143     HeapFree(GetProcessHeap(), 0, szRootPathW);
1144 
1145     return hr;
1146 }
1147 
1148 HRESULT WINAPI SHQueryRecycleBinW(LPCWSTR pszRootPath, LPSHQUERYRBINFO pSHQueryRBInfo)
1149 {
1150     FIXME("%s, %p - stub\n", debugstr_w(pszRootPath), pSHQueryRBInfo);
1151 
1152     if (!(pszRootPath) || (pszRootPath[0] == 0) ||
1153         !(pSHQueryRBInfo) || (pSHQueryRBInfo->cbSize < sizeof(SHQUERYRBINFO)))
1154     {
1155         return E_INVALIDARG;
1156     }
1157 
1158     pSHQueryRBInfo->i64Size = 0;
1159     pSHQueryRBInfo->i64NumItems = 0;
1160 
1161     return S_OK;
1162 }
1163