/*****************************************************************************\ FILE: copyfgd.cpp DESCRIPTION: Copy a FileGroupDescriptor. \*****************************************************************************/ #include "shellprv.h" #include #include "ids.h" #include "pidl.h" #include "fstreex.h" #include "copy.h" #include #include #include // InternetGetLastResponseInfo #include "ynlist.h" #include "datautil.h" class CCopyThread; HRESULT CreateInstance_CopyThread(HWND hwnd, LPCTSTR pszPath, IDataObject *pdtobj, DWORD *pdwEffect, BOOL fIsBkDropTarget, CCopyThread ** ppct); extern "C" { #include "undo.h" #include "defview.h" }; BOOL GetWininetError(DWORD dwError, BOOL fCopy, LPTSTR pszErrorMsg, DWORD cchSize) { TCHAR szErrorMsg[MAX_PATH]; BOOL fIsWininetError = ((dwError >= INTERNET_ERROR_BASE) && (dwError <= INTERNET_ERROR_LAST)); // Default message if FormatMessage doesn't recognize hres szErrorMsg[0] = 0; LoadString(HINST_THISDLL, (fCopy ? IDS_COPYERROR : IDS_MOVEERROR), szErrorMsg, ARRAYSIZE(szErrorMsg)); BOOL fCopyOut = TRUE; if (fIsWininetError) { static HINSTANCE s_hinst = NULL; if (!s_hinst) s_hinst = GetModuleHandle(TEXT("WININET")); // It's okay if we leak it. // Can wininet give us extended error messages? // We ignore them because it's too late to call InternetGetLastResponseInfo. if (ERROR_INTERNET_EXTENDED_ERROR != dwError) { DWORD dwResult; TCHAR szDetails[MAX_PATH*2]; dwResult = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, (LPCVOID)s_hinst, dwError, 0, szDetails, ARRAYSIZE(szDetails), NULL); if (dwResult != 0) { HRESULT hr = StringCchCat(szErrorMsg, ARRAYSIZE(szErrorMsg), TEXT("%s")); if (SUCCEEDED(hr)) { hr = StringCchPrintf(pszErrorMsg, cchSize, szErrorMsg, szDetails); if (SUCCEEDED(hr)) { fCopyOut = FALSE; } } } } } if (fCopyOut) { StringCchCopy(pszErrorMsg, cchSize, szErrorMsg); } return TRUE; } // thunk A/W funciton to access A/W FILEGROUPDESCRIPTOR // this relies on the fact that the first part of the A/W structures are // identical. only the string buffer part is different. so all accesses to the // cFileName field need to go through this function. // FILEDESCRIPTOR *GetFileDescriptor(FILEGROUPDESCRIPTOR *pfgd, BOOL fUnicode, int nIndex, LPTSTR pszName) { if (fUnicode) { // Yes, so grab the data because it matches. FILEGROUPDESCRIPTORW * pfgdW = (FILEGROUPDESCRIPTORW *)pfgd; // cast to what this really is // If the filename starts with a leading / we're going to be in trouble, since the rest // of the code assumes its going to be a \. Web folders does the leading /, so just lop it // off right here. WCHAR *pwz = pfgdW->fgd[nIndex].cFileName; if (pfgdW->fgd[nIndex].cFileName[0] == '/') { memmove(pwz, pwz+1, sizeof(pfgdW->fgd[nIndex].cFileName) - sizeof(WCHAR)); } // Now flip all the /'s to \'s. No dbcs issues, we're unicode! for (; *pwz; ++pwz) { if (*pwz == '/') { *pwz = '\\'; } } if (pszName) SHUnicodeToTChar(pfgdW->fgd[nIndex].cFileName, pszName, MAX_PATH); return (FILEDESCRIPTOR *)&pfgdW->fgd[nIndex]; // cast assume the non string parts are the same! } else { FILEGROUPDESCRIPTORA *pfgdA = (FILEGROUPDESCRIPTORA *)pfgd; // cast to what this really is if (pfgdA->fgd[nIndex].cFileName[0] == '/' && CharNextA(pfgdA->fgd[nIndex].cFileName) == pfgdA->fgd[nIndex].cFileName+1) { memmove (pfgdA->fgd[nIndex].cFileName, pfgdA->fgd[nIndex].cFileName+1, sizeof(pfgdA->fgd[nIndex].cFileName)-sizeof(char)); } if (pszName) SHAnsiToTChar(pfgdA->fgd[nIndex].cFileName, pszName, MAX_PATH); return (FILEDESCRIPTOR *)&pfgdA->fgd[nIndex]; // cast assume the non string parts are the same! } } void CreateProgressStatusStr(LPCTSTR pszDirTo, LPWSTR pwzProgressStr, DWORD cchSize) { // IDS_COPYTO also works in move operations. (It doesn't use the work "Copy") LPTSTR pszMsg = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_COPYTO), pszDirTo); if (pszMsg) { SHTCharToUnicode(pszMsg, pwzProgressStr, cchSize); LocalFree(pszMsg); } else pwzProgressStr[0] = 0; } void CalcBytesInFileGrpDescriptor(FILEGROUPDESCRIPTOR *pfgd, BOOL fUnicode, ULARGE_INTEGER * puliTotal) { puliTotal->QuadPart = 0; // Init. for (UINT i = 0; i < pfgd->cItems; i++) { // WARNING: This may point to a FILEDESCRIPTOR *A or W, but that's ok as long as we ignore the filename. ULARGE_INTEGER uliFileSize; FILEDESCRIPTOR *pfd = GetFileDescriptor(pfgd, fUnicode, i, NULL); if (pfd->dwFlags & FD_FILESIZE) { uliFileSize.HighPart = pfd->nFileSizeHigh; uliFileSize.LowPart = pfd->nFileSizeLow; puliTotal->QuadPart += uliFileSize.QuadPart; } } } BOOL IsNameInDescriptor(FILEGROUPDESCRIPTOR *pfgd, BOOL fUnicode, LPCTSTR pszName, UINT iMax) { for (UINT i = 0; i < iMax; i++) { TCHAR szName[MAX_PATH]; // WARNING: This may point to a FILEDESCRIPTOR *A or W, but that's ok as long as we ignore the filename. FILEDESCRIPTOR *pfd = GetFileDescriptor(pfgd, fUnicode, i, szName); if (lstrcmpi(szName, pszName) == 0) return TRUE; } return FALSE; } BOOL ShowProgressUI(FILEGROUPDESCRIPTOR *pfgd) { return (0 < pfgd->cItems) && (FD_PROGRESSUI & pfgd->fgd->dwFlags); } class CCopyThread : public IUnknown { public: // *** IUnknown *** STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); HRESULT DoCopy(void) {return _DoCopy(_pdtobj);}; HRESULT DoAsynchCopy(void); HRESULT GetEffect(DWORD *pdwEffect) {*pdwEffect = _dwEffect; return S_OK;}; // For synchronous case only. friend HRESULT CreateInstance_CopyThread(HWND hwnd, LPCTSTR pszPath, IDataObject *pdtobj, DWORD *pdwEffect, BOOL fIsBkDropTarget, CCopyThread ** ppct); protected: CCopyThread(HWND hwnd, LPCTSTR pszPath, IDataObject *pdtobj, DWORD *pdwEffect, BOOL fIsBkDropTarget); ~CCopyThread(); private: HRESULT _CopyThreadProc(void); static DWORD CALLBACK CopyThreadProc(void *pvThis) { return ((CCopyThread *) pvThis)->_CopyThreadProc(); }; HRESULT _DoCopy(IDataObject * pdo); LONG _cRef; HWND _hwnd; LPCTSTR _pszPath; IDataObject * _pdtobj; // Unmarshalled IStream * _pstmDataObjMarshal; // Carrying the IDataObject across threads DWORD _dwEffect; BOOL _fWindowIsTarget; }; CCopyThread::CCopyThread(HWND hwnd, LPCTSTR pszPath, IDataObject *pdtobj, DWORD *pdwEffect, BOOL fIsBkDropTarget) : _cRef(1) { DllAddRef(); // Assert this class was zero inited. ASSERT(!_pdtobj); ASSERT(!_pstmDataObjMarshal); ASSERT(!_dwEffect); ASSERT(!_hwnd); ASSERT(!_pszPath); _hwnd = hwnd; // If we are dropping onto the background of the window, we can assume that the window we // are passed is the target window of the copy. _fWindowIsTarget = fIsBkDropTarget; Str_SetPtr((LPTSTR *) &_pszPath, pszPath); IUnknown_Set((IUnknown **)&_pdtobj, (IUnknown *)pdtobj); // The caller doesn't get the return value in pdwEffect because it happens on a background thread. // take these out of this because we don't want a cancel operation to not make it back to a caller // and have them delete the files anyway. We also need to make sure moves are done in such a way that the // destination (us) moves the files and not the caller. This is the caller will return from ::Drop() before // the files finish copying (moving). _dwEffect = *pdwEffect; } CCopyThread::~CCopyThread() { IUnknown_Set((IUnknown **)&_pdtobj, NULL); IUnknown_Set((IUnknown **)&_pstmDataObjMarshal, NULL); Str_SetPtr((LPTSTR *) &_pszPath, NULL); DllRelease(); } HRESULT CCopyThread::DoAsynchCopy(void) { HRESULT hr = CoMarshalInterThreadInterfaceInStream(IID_IDataObject, _pdtobj, &_pstmDataObjMarshal); if (SUCCEEDED(hr)) { IUnknown_Set((IUnknown **)&_pdtobj, NULL); AddRef(); // pass to thread if (SHCreateThread(CCopyThread::CopyThreadProc, this, CTF_COINIT, NULL)) { hr = S_OK; } else { hr = E_OUTOFMEMORY; Release(); // thread did not take, we need to release } } return hr; } HRESULT CCopyThread::_CopyThreadProc(void) { IDataObject * pdo; HRESULT hr = CoGetInterfaceAndReleaseStream(_pstmDataObjMarshal, IID_PPV_ARG(IDataObject, &pdo)); _pstmDataObjMarshal = NULL; // CoGetInterfaceAndReleaseStream() released the ref. if (S_OK == hr) { hr = _DoCopy(pdo); IAsyncOperation * pao; if (SUCCEEDED(pdo->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pao)))) { pao->EndOperation(hr, NULL, _dwEffect); pao->Release(); } pdo->Release(); } Release(); // Releae the background thread's ref. return hr; } HRESULT ConvertCallerFDToTCharFD(const FILEDESCRIPTOR * pfdSource, BOOL fUnicode, FILEDESCRIPTOR * pfdDest) { #ifdef UNICODE if (fUnicode) { *pfdDest = *pfdSource; } else { // pfdSource is really ansi. const FILEDESCRIPTORA * pfdSourceA = (const FILEDESCRIPTORA *) pfdSource; // We need to convert, so copy the small part. *(FILEDESCRIPTORA *)pfdDest = *pfdSourceA; SHAnsiToUnicode(pfdSourceA->cFileName, pfdDest->cFileName, ARRAYSIZE(pfdDest->cFileName)); } #else // UNICODE if (fUnicode) { // pfdSource is really unicode. const FILEDESCRIPTORW * pfdSourceW = (const FILEDESCRIPTORW *) pfdSource; // We need to convert, so copy the small part. *(FILEDESCRIPTORA *)pfdDest = *(const FILEDESCRIPTORA *)pfdSource; SHUnicodeToAnsi(pfdSourceW->cFileName, pfdDest->cFileName, ARRAYSIZE(pfdDest->cFileName)); } else { *pfdDest = *pfdSource; } #endif // UNICODE return S_OK; } BOOL IsWininetHRESULT(IN HRESULT hr) { // One way to tell if the error is from wininet is to check for the FACILITY_INTERNET // facility. BOOL fIsWininet = (FACILITY_INTERNET == HRESULT_FACILITY(hr)); DWORD dwError = HRESULT_CODE(hr); // However, sometimes that facility won't be set but it will still be a wininet error. if (!fIsWininet && (FACILITY_WIN32 == HRESULT_FACILITY(hr)) && ((dwError >= INTERNET_ERROR_BASE) && (dwError <= INTERNET_ERROR_LAST))) { // Win #147295. The root of the problem is that an API that returns a dwError can only // return error numbers in one FACILITY. However, wininet APIs will return error values // in the FACILITY_INTERNET facility (ERROR_INTERNET_* which is 12000 to INTERNET_ERROR_LAST) // AND from the FACILITY_WIN32 facility (like ERROR_NO_MORE_FILES). Therefore the caller // can't just blindly set the FACILITY_INTERNET facility when converting the dwError to an // HRESULT. // // If wininet was smart, they would just use the WIN32 facility since they reserved the 12000 // range. fIsWininet = TRUE; } return fIsWininet; } // Puts up a message box if necessary. // we don't want to see any error with FACILITY_ITF, since we // can't make sense of those errors anyway. void MessageBoxIfNecessary(HRESULT hr, DWORD dwEffect, HWND hwndDlgParent) { TCHAR szTitle[MAX_PATH]; LoadString(HINST_THISDLL, ((DROPEFFECT_COPY == dwEffect) ? IDS_FILEERRORCOPY : IDS_FILEERRORCOPY), szTitle, ARRAYSIZE(szTitle)); if (IsWininetHRESULT(hr)) { TCHAR szErrorMsg[MAX_PATH]; if (GetWininetError(HRESULT_CODE(hr), (DROPEFFECT_COPY == dwEffect), szErrorMsg, ARRAYSIZE(szErrorMsg))) { MessageBox(hwndDlgParent, szErrorMsg, szTitle, (MB_ICONERROR | MB_OK | MB_SYSTEMMODAL)); } } else if (HRESULT_FACILITY(hr) != FACILITY_ITF) { // dont display an error if caused by a user cancel. if (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr) { TCHAR szErrorMsg[MAX_PATH]; if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, HRESULT_CODE(hr), 0L, szErrorMsg, ARRAYSIZE(szErrorMsg), NULL)) { MessageBox(hwndDlgParent, szErrorMsg, szTitle, (MB_ICONERROR | MB_OK | MB_SYSTEMMODAL)); } } } // All other errors don't make any sense to the user, so don't put // up any UI. } HRESULT CCopyThread::_DoCopy(IDataObject * pdo) { HRESULT hr = S_OK; FORMATETC fmteA = {g_cfFileGroupDescriptorA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; FORMATETC fmteW = {g_cfFileGroupDescriptorW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; FORMATETC fmteOffset = {g_cfOFFSETS, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM mediumFGD = {0}, mediumOffset = {0}; BOOL fUnicode = FALSE; PROGRESSINFO progInfo = {0}; // We should have only one bit set. ASSERT(_dwEffect==DROPEFFECT_COPY || _dwEffect==DROPEFFECT_LINK || _dwEffect==DROPEFFECT_MOVE); // Display the progress now because pdo->GetData may be slow, especially if we have to call it // twice.IDA_FILEMOVE progInfo.ppd = CProgressDialog_CreateInstance(((DROPEFFECT_COPY == _dwEffect) ? IDS_ACTIONTITLECOPY : IDS_ACTIONTITLEMOVE), ((DROPEFFECT_COPY == _dwEffect) ? IDA_FILECOPY : IDA_FILEMOVE), g_hinst); if (progInfo.ppd) { WCHAR wzCalcTime[MAX_PATH]; progInfo.uliBytesCompleted.QuadPart = progInfo.uliBytesTotal.QuadPart = 0; progInfo.ppd->StartProgressDialog(_hwnd, NULL, PROGDLG_AUTOTIME, NULL); LoadStringW(HINST_THISDLL, ((DROPEFFECT_COPY == _dwEffect) ? IDS_CALCCOPYTIME : IDS_CALCMOVETIME), wzCalcTime, ARRAYSIZE(wzCalcTime)); progInfo.ppd->SetLine(2, wzCalcTime, FALSE, NULL); } // First try to massage the IDataObject into a virtual storage that we can use as the // source of a storage engine copy bool bPerformManually = true; if (bPerformManually) { // Try for UNICODE group descriptor first. If that succeeds, we won't bother trying to // ASCII since UNICODE is the "preferred" format. For ANSI builds, we only try for ANSI hr = pdo->GetData(&fmteW, &mediumFGD); if (SUCCEEDED(hr)) fUnicode = TRUE; else hr = pdo->GetData(&fmteA, &mediumFGD); if (SUCCEEDED(hr)) { UINT i, iConflict = 1; UINT iTopLevelItem = 0; YNLIST ynl; DROPHISTORY dh = {0}; // WARNING: pfgd is really an A or W struct. to deal with this all code needs to use // the GetFileDescriptor() function FILEGROUPDESCRIPTOR *pfgd = (FILEGROUPDESCRIPTOR *)GlobalLock(mediumFGD.hGlobal); DECLAREWAITCURSOR; SetWaitCursor(); BOOL fCantUseDialogHWND = FALSE; if (progInfo.ppd) { CalcBytesInFileGrpDescriptor(pfgd, fUnicode, &progInfo.uliBytesTotal); // We displayed progress above because pdo->GetData() and CalcBytesInFileGrpDescriptor are slow, but most likely it // was just eating into the delay time before the progress appears. If the caller // didn't want UI, we will close it down now. if (!ShowProgressUI(pfgd)) { progInfo.ppd->StopProgressDialog(); fCantUseDialogHWND = TRUE; } else progInfo.ppd->Timer(PDTIMER_RESET, NULL); } CreateYesNoList(&ynl); // Try & get the offsets too. HRESULT hrOffset = pdo->GetData(&fmteOffset, &mediumOffset); if (SUCCEEDED(hrOffset)) { dh.pptOffset = (POINT *)GlobalLock(mediumOffset.hGlobal); dh.pptOffset++; // First item is the anchor } for (i = 0; i < pfgd->cItems; i++) { BOOL fTopLevel; TCHAR szFullPath[MAX_PATH], szFileName[MAX_PATH]; FILEDESCRIPTOR *pfd = GetFileDescriptor(pfgd, fUnicode, i, szFileName); HRESULT hr; hr = StringCchCopy(szFullPath, ARRAYSIZE(szFullPath), _pszPath); if (FAILED(hr)) { // skip files which might cause problems continue; } // if the source gave us duplicate file names we make them unique here // foo (1).txt, foo (2).txt, etc // name conflicts with targets still get the replace file confirm UI if (IsNameInDescriptor(pfgd, fUnicode, szFileName, i)) { TCHAR szBuf[MAX_PATH], *pszExt = PathFindExtension(szFileName); hr = StringCchPrintf(szBuf, ARRAYSIZE(szBuf), TEXT(" (%d)%s"), iConflict++, pszExt); // " (xxx).msg" if (FAILED(hr)) { // skip files which might cause problems continue; } // make sure it will fit if (((int)ARRAYSIZE(szFileName) - lstrlen(szFileName)) > (lstrlen(szBuf) - lstrlen(pszExt))) { hr = StringCchCopy(pszExt, szFileName + ARRAYSIZE(szFileName) - pszExt, szBuf); if (FAILED(hr)) { // skip files which might cause problems continue; } } } // do PathCleanupSpec on the filespec part of the filename because names // can be relative paths "Folder\foo.txt", "Folder\Folder2\foo.txt" PathCleanupSpec(szFullPath, PathFindFileName(szFileName)); // the filename in the descriptor should not be a fully qualified path if (PathIsRelative(szFileName)) { if (!PathAppend(szFullPath, szFileName)) { // skip files which might cause problems continue; } fTopLevel = (StrChr(szFileName, TEXT('\\')) == NULL && StrChr(szFileName, TEXT('/')) == NULL); } else { TraceMsg(TF_WARNING, "CopyFGD: FGD contains full path - ignoring path"); if (!PathAppend(szFullPath, PathFindFileName(szFileName))) { // skip files which might cause problems continue; } fTopLevel = TRUE; } if (IsInNoList(&ynl, szFullPath)) { continue; } HWND hwndDlgParent; if (fCantUseDialogHWND || FAILED(IUnknown_GetWindow(progInfo.ppd, &hwndDlgParent))) { hwndDlgParent = _hwnd; } BOOL fDirectory = (pfd->dwFlags & FD_ATTRIBUTES) && (pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); // NOTE: SHPathPrepareForWrite() was moved here to insure that we check for replace operations // against a real directory (not an empty a: drive, for example). However, the result is not checked // until later - make sure that we don't overwrite 'hr' between here and there. hr = SHPathPrepareForWrite(hwndDlgParent, NULL, szFullPath, SHPPFW_DEFAULT | SHPPFW_IGNOREFILENAME); FILEDESCRIPTOR fd = {0}; ConvertCallerFDToTCharFD(pfd, fUnicode, &fd); switch (ValidateCreateFileFromClip(hwndDlgParent, &fd, szFullPath, &ynl)) { case IDYES: break; case IDNO: // mil bug 127038 // when somebody says "no don't overwrite", we don't want the performed // drop effect to get set to DROPEFFECT_MOVE, so we set it to DROPEFFECT_NONE // here. _dwEffect = 0; continue; case IDCANCEL: // NOTE: This doesn't do anything because the caller never gets this back // in the asynch case. _dwEffect = 0; i = (int)pfgd->cItems - 1; hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); continue; } if (progInfo.ppd) { WCHAR wzFileName[MAX_PATH]; WCHAR wzTemplateStr[MAX_PATH]; WCHAR wzProgressStr[MAX_PATH]; if (DROPEFFECT_COPY == _dwEffect) LoadStringW(HINST_THISDLL, IDS_COPYING, wzTemplateStr, ARRAYSIZE(wzTemplateStr)); else LoadStringW(HINST_THISDLL, IDS_MOVING, wzTemplateStr, ARRAYSIZE(wzTemplateStr)); // Display "Copying 'filename'" or "Moving 'filename'" on line 1 SHTCharToUnicode(szFileName, wzFileName, ARRAYSIZE(wzFileName)); StringCchPrintf(wzProgressStr, ARRAYSIZE(wzProgressStr), wzTemplateStr, wzFileName); // ok to truncate progInfo.ppd->SetLine(1, wzProgressStr, FALSE, NULL); // Display the dir on line 2 CreateProgressStatusStr(_pszPath, wzProgressStr, ARRAYSIZE(wzProgressStr)); progInfo.ppd->SetLine(2, wzProgressStr, FALSE, NULL); } if (fDirectory) { // Call SHPathPrepareForWrite() again without SHPPFW_IGNOREFILENAME so that it // will create the directory if it doesn't already exist. hr = SHPathPrepareForWrite(hwndDlgParent, NULL, szFullPath, SHPPFW_DEFAULT); if (FAILED(hr)) { // NOTE: This doesn't do anything because the caller never gets this back // in the asynch case. _dwEffect = 0; break; } } else { // We want to prepare the path both before and after errors in order to catch different cases. // NOTE: We should be checking the result of SHPathPrepareForWrite() here if (SUCCEEDED(hr)) { hr = DataObj_SaveToFile(pdo, g_cfFileContents, i, szFullPath, pfd, &progInfo); } if (FAILED(hr)) { MessageBoxIfNecessary(hr, _dwEffect, hwndDlgParent); // NOTE: This doesn't do anything because the caller never gets this back // in the asynch case. _dwEffect = 0; break; } } // Only position item if it was created successfully // and it is not tucked in a subdir. // The last condition is because there is some confusion about whether _hwnd is the // target window of the copy or not. If it is, we should tell the window // to position the item we just dropped. if (SUCCEEDED(hr) && fTopLevel && _fWindowIsTarget) { dh.iItem = iTopLevelItem; if (SUCCEEDED(hrOffset) && dh.pptOffset) { // need to range check the points in case # of items in the list does not match the # // of points. this happens when defview does not know the right number of items // in the data object. SIZE_T cbSize = GlobalSize(mediumOffset.hGlobal); if (((dh.iItem + 1) * sizeof(dh.pptOffset[0])) > cbSize) { dh.pptOffset = NULL; ReleaseStgMediumHGLOBAL(NULL, &mediumOffset); hrOffset = E_FAIL; // for test below } } PositionFileFromDrop(_hwnd, szFullPath, &dh); iTopLevelItem++; } if (progInfo.ppd) { ULARGE_INTEGER uliFileSize; uliFileSize.HighPart = pfd->nFileSizeHigh; uliFileSize.LowPart = pfd->nFileSizeLow; progInfo.uliBytesCompleted.QuadPart += uliFileSize.QuadPart; progInfo.ppd->SetProgress64(progInfo.uliBytesCompleted.QuadPart, progInfo.uliBytesTotal.QuadPart); if (progInfo.ppd->HasUserCancelled()) break; // Cancel the copy. } } DestroyYesNoList(&ynl); if (SUCCEEDED(hrOffset)) ReleaseStgMediumHGLOBAL(NULL, &mediumOffset); ResetWaitCursor(); ReleaseStgMediumHGLOBAL(pfgd, &mediumFGD); } } if (SUCCEEDED(hr)) { // Inform the caller of what we did. We don't do optimized moves // so the caller is responsible for the delete half of the move and // this is now we notify them of that. DataObj_SetDWORD(pdo, g_cfPerformedDropEffect, _dwEffect); DataObj_SetDWORD(pdo, g_cfLogicalPerformedDropEffect, _dwEffect); } if (progInfo.ppd) { progInfo.ppd->StopProgressDialog(); progInfo.ppd->Release(); } return hr; } //=========================== // *** IUnknown Interface *** HRESULT CCopyThread::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CCopyThread, IUnknown), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } ULONG CCopyThread::AddRef(void) { return InterlockedIncrement(&_cRef); } ULONG CCopyThread::Release(void) { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CreateInstance_CopyThread(HWND hwnd, LPCTSTR pszPath, IDataObject *pdtobj, DWORD *pdwEffect, BOOL fIsBkDropTarget, CCopyThread ** ppct) { *ppct = new CCopyThread(hwnd, pszPath, pdtobj, pdwEffect, fIsBkDropTarget); return (*ppct ? S_OK : E_FAIL); } /*****************************************************************************\ We know that the IDataObject (pdo) supports the CF_FILEGROUPDESCRIPTOR clipboard format, so copy that data to the file system directory pszPath. The caller will want to know if this completed or if it was cancelled or errored out. This result will come in the DROPEFFECT out param (pdwEffect). Zero (0) will indicate either error or cancel and this code will take care of displaying error messages. \*****************************************************************************/ HRESULT CFSFolder_AsyncCreateFileFromClip(HWND hwnd, LPCTSTR pszPath, IDataObject *pdo, POINTL pt, DWORD *pdwEffect, BOOL fIsBkDropTarget) { CCopyThread * pct; HRESULT hr = CreateInstance_CopyThread(hwnd, pszPath, pdo, pdwEffect, fIsBkDropTarget, &pct); if (SUCCEEDED(hr)) { if (DataObj_CanGoAsync(pdo)) hr = pct->DoAsynchCopy(); else hr = pct->DoCopy(); pct->Release(); } if (FAILED(hr)) *pdwEffect = DROPEFFECT_NONE; return hr; }