Skip to content

Commit

Permalink
Add support for IFileOperation interface (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
medusalix authored and sindresorhus committed May 29, 2019
1 parent c2ba062 commit 36d9568
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 24 deletions.
2 changes: 1 addition & 1 deletion build.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

set filename=recycle-bin

gcc "%filename%".c -municode -O2 -s -o "%filename%".exe -std=c99
gcc "%filename%".c -municode -O2 -lole32 -luuid -s -o "%filename%".exe -std=c17
189 changes: 189 additions & 0 deletions progress.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#include <initguid.h>
#include <shlobj.h>

HRESULT STDMETHODCALLTYPE QueryInterface(
IFileOperationProgressSink *this,
REFIID riid,
void **ppv
) {
if (riid == &IID_IUnknown || riid == &IID_IFileOperationProgressSink) {
*ppv = this;

return S_OK;
}

*ppv = NULL;

return E_NOINTERFACE;
}

ULONG STDMETHODCALLTYPE AddRef(IFileOperationProgressSink *this) {
return S_OK;
}

ULONG STDMETHODCALLTYPE Release(IFileOperationProgressSink *this) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE StartOperations(IFileOperationProgressSink *this) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE FinishOperations(
IFileOperationProgressSink *this,
HRESULT hrResult
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PreRenameItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem,
LPCWSTR pszNewName
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PostRenameItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem,
LPCWSTR pszNewName,
HRESULT hrRename,
IShellItem *psiNewlyCreated
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PreMoveItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem,
IShellItem *psiDestinationFolder,
LPCWSTR pszNewNam
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PostMoveItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem,
IShellItem *psiDestinationFolder,
LPCWSTR pszNewName,
HRESULT hrMove,
IShellItem *psiNewlyCreated
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PreCopyItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem,
IShellItem *psiDestinationFolder,
LPCWSTR pszNewName
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PostCopyItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem,
IShellItem *psiDestinationFolder,
LPCWSTR pszNewName,
HRESULT hrCopy,
IShellItem *psiNewlyCreated
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PreDeleteItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem
) {
if (dwFlags & TSF_DELETE_RECYCLE_IF_POSSIBLE) {
return S_OK;
}

return E_ABORT;
}

HRESULT STDMETHODCALLTYPE PostDeleteItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiItem,
HRESULT hrDelete,
IShellItem *psiNewlyCreated
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PreNewItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiDestinationFolder,
LPCWSTR pszNewName
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PostNewItem(
IFileOperationProgressSink *this,
DWORD dwFlags,
IShellItem *psiDestinationFolder,
LPCWSTR pszNewName,
LPCWSTR pszTemplateName,
DWORD dwFileAttributes,
HRESULT hrNew,
IShellItem *psiNewItem
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE UpdateProgress(
IFileOperationProgressSink *this,
UINT iWorkTotal,
UINT iWorkSoFar
) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE ResetTimer(IFileOperationProgressSink *this) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE PauseTimer(IFileOperationProgressSink *this) {
return S_OK;
}

HRESULT STDMETHODCALLTYPE ResumeTimer(IFileOperationProgressSink *this) {
return S_OK;
}

static IFileOperationProgressSinkVtbl progressSinkVtbl = {
QueryInterface,
AddRef,
Release,
StartOperations,
FinishOperations,
PreRenameItem,
PostRenameItem,
PreMoveItem,
PostMoveItem,
PreCopyItem,
PostCopyItem,
PreDeleteItem,
PostDeleteItem,
PreNewItem,
PostNewItem,
UpdateProgress,
ResetTimer,
PauseTimer,
ResumeTimer
};

static IFileOperationProgressSink progressSink = { &progressSinkVtbl };
91 changes: 68 additions & 23 deletions recycle-bin.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
#define _WIN32_IE _WIN32_IE_IE70

#include "progress.h"

#include <stdio.h>
#include <windows.h>
#include <assert.h>
#include <versionhelpers.h>
#include <initguid.h>
#include <shlobj.h>

#define CHECK(result) if (FAILED(result)) {\
return result;\
}
#define CHECK_OBJ(obj, result) if (FAILED(result)) {\
obj->lpVtbl->Release(obj);\
return result;\
}

int wmain(int argc, wchar_t **argv) {
if (argc == 2) {
Expand All @@ -13,43 +28,73 @@ int wmain(int argc, wchar_t **argv) {
puts("\n Move files and folders to the recycle bin\n\n Usage: recycle-bin <path> [...]\n\n Created by Sindre Sorhus");
return 0;
}
}

if (argc == 1) {
} else if (argc == 1) {
puts("Specify at least one path");
return 1;
}

size_t len = argc;
int count = argc - 1;
wchar_t **files = argv + 1;

for (int i = 1; i < argc; i++) {
len += wcslen(argv[i]);
}
CHECK(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));

wchar_t *from = malloc(len * sizeof(wchar_t));
IFileOperation *op;

int pos = 0;
CHECK(CoCreateInstance(
&CLSID_FileOperation,
NULL,
CLSCTX_ALL,
&IID_IFileOperation,
(void**)&op
));

for (int i = 1; i < argc; i++) {
wcscpy(&from[pos], argv[i]);
pos += wcslen(argv[i]) + 1;
if (IsWindows8OrGreater()) {
CHECK_OBJ(op, op->lpVtbl->SetOperationFlags(
op,
FOFX_ADDUNDORECORD |
FOFX_RECYCLEONDELETE |
FOF_NOERRORUI |
FOF_NOCONFIRMATION |
FOF_SILENT |
FOFX_EARLYFAILURE
));
} else {
CHECK_OBJ(op, op->lpVtbl->SetOperationFlags(
op,
FOF_NO_UI |
FOF_ALLOWUNDO |
FOF_NOERRORUI |
FOF_SILENT |
FOFX_EARLYFAILURE
));
}

from[pos] = '\0';
PCIDLIST_ABSOLUTE list[count];

for (int i = 0; i < count; i++) {
int len = GetFullPathName(files[i], 0, NULL, NULL);

if (len == 0) {
op->lpVtbl->Release(op);

assert(++pos == len && "position/length mismatch");
return 1;
}

wchar_t *buf = malloc((len + 1) * sizeof(wchar_t));

SHFILEOPSTRUCTW op;
GetFullPathName(files[i], len, buf, NULL);
list[i] = ILCreateFromPath(buf);

op.hwnd = NULL;
op.wFunc = FO_DELETE;
op.pFrom = from;
op.pTo = NULL;
op.fFlags = FOF_ALLOWUNDO | FOF_NO_UI;
free(buf);
}

int ret = SHFileOperationW(&op);
IShellItemArray *items;
DWORD cookie;

free(from);
CHECK_OBJ(op, SHCreateShellItemArrayFromIDLists(count, list, &items));
CHECK_OBJ(op, op->lpVtbl->Advise(op, &progressSink, &cookie));
CHECK_OBJ(op, op->lpVtbl->DeleteItems(op, (IUnknown*)items));
CHECK_OBJ(op, op->lpVtbl->PerformOperations(op));

return ret;
return op->lpVtbl->Release(op);
}
55 changes: 55 additions & 0 deletions test.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@echo off

setlocal enabledelayedexpansion

:: Global settings
set program=recycle-bin
set dir=test
set name=a

:: Test cases
<nul set /p="Test case #1: Long directory names: "
call :test 100 5

<nul set /p="Test case #2: Deep folder hierarchy: "
call :test 20 20

<nul set /p="Test case #3: Deep hierarchy with long paths: "
call :test 200 20

exit /b

:: Test case procedure
:: Parameters: <filename-length> <hierarchy-depth>
:test
:: Repeat filename
set file=
for /l %%i in (1, 1, %~1) do set file=!file!%name%

:: Create initial directory
set target=%dir%
mkdir %target%

:: Create folder hierarchy
for /l %%i in (1, 1, %~2) do (
set target=!target!\%file%
mkdir %file%
robocopy %file% !target! /MOVE >nul
)

:: Delete long path
"%program%".exe !target!

if %errorlevel% == 0 (
if exist !target! (
echo FAILED
) else (
echo PASSED
)
) else (
echo FAILED
)

:: Clean up
"%program%".exe %dir%
goto :eof

0 comments on commit 36d9568

Please sign in to comment.