1173 lines
27 KiB
C
1173 lines
27 KiB
C
/*
|
|
* copy.c - File copy handler module.
|
|
*/
|
|
|
|
|
|
/* Headers
|
|
**********/
|
|
|
|
#include "project.h"
|
|
#pragma hdrstop
|
|
|
|
#include "stub.h"
|
|
#include "oleutil.h"
|
|
|
|
|
|
/* Constants
|
|
************/
|
|
|
|
/* size of file copy buffer in bytes */
|
|
|
|
#define COPY_BUF_SIZE (64 * 1024)
|
|
|
|
|
|
/* Module Variables
|
|
*******************/
|
|
|
|
#pragma data_seg(DATA_SEG_SHARED)
|
|
|
|
/* lock count for file copy buffer */
|
|
|
|
PRIVATE_DATA ULONG MulcCopyBufLock = 0;
|
|
|
|
/* buffer for file copying */
|
|
|
|
PRIVATE_DATA PBYTE MpbyteCopyBuf = NULL;
|
|
|
|
/* length of file copy buffer in bytes */
|
|
|
|
PRIVATE_DATA UINT MucbCopyBufLen = 0;
|
|
|
|
#pragma data_seg()
|
|
|
|
|
|
/***************************** Private Functions *****************************/
|
|
|
|
/* Module Prototypes
|
|
********************/
|
|
|
|
PRIVATE_CODE TWINRESULT SimpleCopy(PRECNODE, RECSTATUSPROC, LPARAM);
|
|
PRIVATE_CODE TWINRESULT CreateDestinationFolders(PCRECNODE);
|
|
PRIVATE_CODE TWINRESULT CreateCopyBuffer(void);
|
|
PRIVATE_CODE void DestroyCopyBuffer(void);
|
|
PRIVATE_CODE TWINRESULT CopyFileByHandle(HANDLE, HANDLE, RECSTATUSPROC, LPARAM, ULONG, PULONG);
|
|
PRIVATE_CODE TWINRESULT CopyFileByName(PCRECNODE, PRECNODE, RECSTATUSPROC, LPARAM, ULONG, PULONG);
|
|
PRIVATE_CODE ULONG DetermineCopyScale(PCRECNODE);
|
|
PRIVATE_CODE BOOL IsCopyDestination(PCRECNODE);
|
|
PRIVATE_CODE BOOL SetDestinationTimeStamps(PCRECNODE);
|
|
PRIVATE_CODE BOOL DeleteFolderProc(LPCTSTR, PCWIN32_FIND_DATA, PVOID);
|
|
|
|
#ifdef DEBUG
|
|
|
|
PRIVATE_CODE BOOL CopyBufferIsOk(void);
|
|
PRIVATE_CODE BOOL VerifyRECITEMAndSrcRECNODE(PCRECNODE);
|
|
|
|
#endif
|
|
|
|
|
|
/*
|
|
** SimpleCopy()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE TWINRESULT SimpleCopy(PRECNODE prnSrc, RECSTATUSPROC rsp,
|
|
LPARAM lpCallbackData)
|
|
{
|
|
TWINRESULT tr;
|
|
ULONG ulScale;
|
|
PRECNODE prnDest;
|
|
ULONG ulCurrent = 0;
|
|
|
|
/* lpCallbackData may be any value. */
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(prnSrc, CRECNODE));
|
|
ASSERT(! rsp ||
|
|
IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
|
|
|
|
ulScale = DetermineCopyScale(prnSrc);
|
|
|
|
/* Copy the source file to each destination file. */
|
|
|
|
tr = TR_SUCCESS;
|
|
|
|
BeginCopy();
|
|
|
|
for (prnDest = prnSrc->priParent->prnFirst;
|
|
prnDest;
|
|
prnDest = prnDest->prnNext)
|
|
{
|
|
if (prnDest != prnSrc)
|
|
{
|
|
if (IsCopyDestination(prnDest))
|
|
{
|
|
tr = CopyFileByName(prnSrc, prnDest, rsp, lpCallbackData,
|
|
ulScale, &ulCurrent);
|
|
|
|
if (tr != TR_SUCCESS)
|
|
break;
|
|
|
|
ASSERT(ulCurrent <= ulScale);
|
|
}
|
|
}
|
|
}
|
|
|
|
EndCopy();
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** CreateDestinationFolders()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE TWINRESULT CreateDestinationFolders(PCRECNODE pcrnSrc)
|
|
{
|
|
TWINRESULT tr = TR_SUCCESS;
|
|
PCRECNODE pcrnDest;
|
|
|
|
for (pcrnDest = pcrnSrc->priParent->prnFirst;
|
|
pcrnDest;
|
|
pcrnDest = pcrnDest->prnNext)
|
|
{
|
|
if (pcrnDest->rnaction == RNA_COPY_TO_ME)
|
|
{
|
|
tr = CreateFolders(pcrnDest->pcszFolder,
|
|
((PCOBJECTTWIN)(pcrnDest->hObjectTwin))->hpath);
|
|
|
|
if (tr != TR_SUCCESS)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** CreateCopyBuffer()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE TWINRESULT CreateCopyBuffer(void)
|
|
{
|
|
TWINRESULT tr;
|
|
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
/* Has the copy buffer already been allocated? */
|
|
|
|
if (MpbyteCopyBuf)
|
|
/* Yes. */
|
|
tr = TR_SUCCESS;
|
|
else
|
|
{
|
|
/* No. Allocate it. */
|
|
|
|
if (AllocateMemory(COPY_BUF_SIZE, &MpbyteCopyBuf))
|
|
{
|
|
MucbCopyBufLen = COPY_BUF_SIZE;
|
|
tr = TR_SUCCESS;
|
|
|
|
TRACE_OUT((TEXT("CreateCopyBuffer(): %u byte file copy buffer allocated."),
|
|
MucbCopyBufLen));
|
|
}
|
|
else
|
|
tr = TR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** DestroyCopyBuffer()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE void DestroyCopyBuffer(void)
|
|
{
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
/* Has the copy buffer already been allocated? */
|
|
|
|
if (MpbyteCopyBuf)
|
|
{
|
|
/* Yes. Free it. */
|
|
|
|
FreeMemory(MpbyteCopyBuf);
|
|
MpbyteCopyBuf = NULL;
|
|
TRACE_OUT((TEXT("DestroyCopyBuffer(): %u byte file copy buffer freed."),
|
|
MucbCopyBufLen));
|
|
MucbCopyBufLen = 0;
|
|
}
|
|
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
** CopyFileByHandle()
|
|
**
|
|
** Copies one file to another.
|
|
**
|
|
** Arguments: hfSrc - file handle to open source file
|
|
** hfDest - file handle to open destination file
|
|
**
|
|
** Returns: TWINRESULT
|
|
**
|
|
** Side Effects: Leaves the file pointer of each file at the end of the file.
|
|
*/
|
|
PRIVATE_CODE TWINRESULT CopyFileByHandle(HANDLE hfSrc, HANDLE hfDest,
|
|
RECSTATUSPROC rsp, LPARAM lpCallbackData,
|
|
ULONG ulScale, PULONG pulcbTotal)
|
|
{
|
|
TWINRESULT tr;
|
|
|
|
/* lpCallbackData may be any value. */
|
|
/* ulScale may be any value. */
|
|
|
|
ASSERT(IS_VALID_HANDLE(hfSrc, FILE));
|
|
ASSERT(IS_VALID_HANDLE(hfDest, FILE));
|
|
ASSERT(! rsp ||
|
|
IS_VALID_CODE_PTR(rsp, RECSTATUSROC));
|
|
ASSERT(IS_VALID_WRITE_PTR(pulcbTotal, ULONG));
|
|
|
|
/* Make sure the copy buffer has been created. */
|
|
|
|
tr = CreateCopyBuffer();
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
BeginCopy();
|
|
|
|
/* Move to the beginning of the files. */
|
|
|
|
if (SetFilePointer(hfSrc, 0, NULL, FILE_BEGIN) != INVALID_SEEK_POSITION)
|
|
{
|
|
if (SetFilePointer(hfDest, 0, NULL, FILE_BEGIN) != INVALID_SEEK_POSITION)
|
|
{
|
|
do
|
|
{
|
|
DWORD dwcbRead;
|
|
|
|
if (ReadFile(hfSrc, MpbyteCopyBuf, MucbCopyBufLen, &dwcbRead,
|
|
NULL))
|
|
{
|
|
if (dwcbRead)
|
|
{
|
|
DWORD dwcbWritten;
|
|
|
|
if (WriteFile(hfDest, MpbyteCopyBuf, dwcbRead,
|
|
&dwcbWritten, NULL) &&
|
|
dwcbWritten == dwcbRead)
|
|
{
|
|
RECSTATUSUPDATE rsu;
|
|
|
|
ASSERT(*pulcbTotal <= ULONG_MAX - dwcbRead);
|
|
|
|
*pulcbTotal += dwcbRead;
|
|
|
|
rsu.ulProgress = *pulcbTotal;
|
|
rsu.ulScale = ulScale;
|
|
|
|
if (! NotifyReconciliationStatus(rsp, RS_DELTA_COPY,
|
|
(LPARAM)&rsu,
|
|
lpCallbackData))
|
|
tr = TR_ABORT;
|
|
}
|
|
else
|
|
tr = TR_DEST_WRITE_FAILED;
|
|
}
|
|
else
|
|
/* Hit EOF. Stop. */
|
|
break;
|
|
}
|
|
else
|
|
tr = TR_SRC_READ_FAILED;
|
|
} while (tr == TR_SUCCESS);
|
|
}
|
|
else
|
|
tr = TR_DEST_WRITE_FAILED;
|
|
}
|
|
else
|
|
tr = TR_SRC_READ_FAILED;
|
|
|
|
EndCopy();
|
|
}
|
|
|
|
return(tr);
|
|
}
|
|
|
|
// MakeAnsiPath
|
|
//
|
|
// Copys path pszIn to pszOut, ensuring that pszOut has a valid ANSI mapping
|
|
|
|
void MakeAnsiPath(LPTSTR pszIn, LPTSTR pszOut)
|
|
{
|
|
#ifdef UNICODE
|
|
CHAR szAnsi[MAX_PATH];
|
|
pszOut[0] = L'\0';
|
|
|
|
WideCharToMultiByte(CP_ACP, 0, pszIn, -1, szAnsi, ARRAYSIZE(szAnsi), NULL, NULL);
|
|
MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, pszOut, MAX_PATH);
|
|
if (lstrcmp(pszOut, pszIn))
|
|
{
|
|
// Cannot convert losslessly from Unicode -> Ansi, so get the short path
|
|
|
|
lstrcpyn(pszOut, pszIn, MAX_PATH);
|
|
SheShortenPath(pszOut, TRUE);
|
|
}
|
|
#else
|
|
lstrcpyn(pszOut, pszIn, MAX_PATH);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
** CopyFileByName()
|
|
**
|
|
** Copies one file over another.
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: TWINRESULT
|
|
**
|
|
** Side Effects: Copies source file's time stamp to destination file.
|
|
*/
|
|
PRIVATE_CODE TWINRESULT CopyFileByName(PCRECNODE pcrnSrc, PRECNODE prnDest,
|
|
RECSTATUSPROC rsp, LPARAM lpCallbackData,
|
|
ULONG ulScale, PULONG pulcbTotal)
|
|
{
|
|
TWINRESULT tr;
|
|
TCHAR rgchSrcPath[MAX_PATH_LEN];
|
|
TCHAR rgchDestPath[MAX_PATH_LEN];
|
|
|
|
/* lpCallbackData may be any value. */
|
|
/* ulScale may be any value. */
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcrnSrc, CRECNODE));
|
|
ASSERT(IS_VALID_STRUCT_PTR(prnDest, CRECNODE));
|
|
ASSERT(! rsp ||
|
|
IS_VALID_CODE_PTR(rsp, RECSTATUSROC));
|
|
ASSERT(IS_VALID_WRITE_PTR(pulcbTotal, ULONG));
|
|
|
|
/* Create source path string. */
|
|
|
|
ComposePath(rgchSrcPath, pcrnSrc->pcszFolder, pcrnSrc->priParent->pcszName);
|
|
ASSERT(lstrlen(rgchSrcPath) < ARRAYSIZE(rgchSrcPath));
|
|
|
|
/* Create destination path string. */
|
|
|
|
ComposePath(rgchDestPath, prnDest->pcszFolder, prnDest->priParent->pcszName);
|
|
ASSERT(lstrlen(rgchDestPath) < ARRAYSIZE(rgchDestPath));
|
|
|
|
/* Check volumes. */
|
|
|
|
if (MyIsPathOnVolume(rgchSrcPath, (HPATH)(pcrnSrc->hvid)) &&
|
|
MyIsPathOnVolume(rgchDestPath, (HPATH)(prnDest->hvid)))
|
|
{
|
|
FILESTAMP fsSrc;
|
|
FILESTAMP fsDest;
|
|
|
|
/* Compare current file stamps with recorded file stamps. */
|
|
|
|
MyGetFileStamp(rgchSrcPath, &fsSrc);
|
|
MyGetFileStamp(rgchDestPath, &fsDest);
|
|
|
|
if (! MyCompareFileStamps(&(pcrnSrc->fsCurrent), &fsSrc) &&
|
|
! MyCompareFileStamps(&(prnDest->fsCurrent), &fsDest))
|
|
{
|
|
HANDLE hfSrc;
|
|
|
|
/* Open source file. Assume source file will be read sequentially. */
|
|
|
|
hfSrc = CreateFile(rgchSrcPath, GENERIC_READ, FILE_SHARE_READ, NULL,
|
|
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
|
|
|
if (hfSrc != INVALID_HANDLE_VALUE)
|
|
{
|
|
HANDLE hfDest;
|
|
|
|
/*
|
|
* Create destination file. Assume destination file will be
|
|
* written sequentially.
|
|
*/
|
|
|
|
TCHAR szAnsiPath[MAX_PATH];
|
|
MakeAnsiPath(rgchDestPath, szAnsiPath);
|
|
|
|
hfDest = CreateFile(szAnsiPath, GENERIC_WRITE, 0, NULL,
|
|
CREATE_ALWAYS,
|
|
(FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN),
|
|
NULL);
|
|
|
|
if (hfDest != INVALID_HANDLE_VALUE)
|
|
{
|
|
/* Everything is cool. Copy the file. */
|
|
|
|
tr = CopyFileByHandle(hfSrc, hfDest, rsp,
|
|
lpCallbackData, ulScale,
|
|
pulcbTotal);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
/*
|
|
* Set the destination file's time stamp to the source
|
|
* file's time stamp to assist clients that don't maintain
|
|
* a persistent briefcase database, like MPR. Failure to
|
|
* set the time stamp is not fatal.
|
|
*/
|
|
|
|
if (! SetFileTime(hfDest, NULL, NULL,
|
|
&(pcrnSrc->fsCurrent.ftMod)))
|
|
WARNING_OUT((TEXT("CopyFileByName(): Failed to set last modification time stamp of destination file %s to match source file %s."),
|
|
rgchDestPath,
|
|
rgchSrcPath));
|
|
}
|
|
|
|
/* Failing to close the destination file is fatal here. */
|
|
|
|
if (! CloseHandle(hfDest) && tr == TR_SUCCESS)
|
|
tr = TR_DEST_WRITE_FAILED;
|
|
}
|
|
else
|
|
tr = TR_DEST_OPEN_FAILED;
|
|
|
|
/* Failing to close the source file successfully is not fatal. */
|
|
|
|
CloseHandle(hfSrc);
|
|
}
|
|
else
|
|
tr = TR_SRC_OPEN_FAILED;
|
|
}
|
|
else
|
|
tr = TR_FILE_CHANGED;
|
|
}
|
|
else
|
|
tr = TR_UNAVAILABLE_VOLUME;
|
|
|
|
#ifdef DEBUG
|
|
|
|
if (tr == TR_SUCCESS)
|
|
TRACE_OUT((TEXT("CopyFileByName(): %s\\%s copied to %s\\%s."),
|
|
pcrnSrc->pcszFolder,
|
|
pcrnSrc->priParent->pcszName,
|
|
prnDest->pcszFolder,
|
|
prnDest->priParent->pcszName));
|
|
|
|
else
|
|
TRACE_OUT((TEXT("CopyFileByName(): Failed to copy %s\\%s to %s\\%s."),
|
|
pcrnSrc->pcszFolder,
|
|
pcrnSrc->priParent->pcszName,
|
|
prnDest->pcszFolder,
|
|
prnDest->priParent->pcszName));
|
|
|
|
#endif
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** DetermineCopyScale()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE ULONG DetermineCopyScale(PCRECNODE pcrnSrc)
|
|
{
|
|
DWORD dwcbSrcFileLen;
|
|
PCRECNODE pcrn;
|
|
ULONG ulScale = 0;
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcrnSrc, CRECNODE));
|
|
|
|
/*
|
|
* RAIDRAID: (16257) If anyone tries to copy more than 4 Gb of files, this
|
|
* scaling calculation is broken.
|
|
*/
|
|
|
|
ASSERT(! pcrnSrc->fsCurrent.dwcbHighLength);
|
|
dwcbSrcFileLen = pcrnSrc->fsCurrent.dwcbLowLength;
|
|
|
|
for (pcrn = pcrnSrc->priParent->prnFirst; pcrn; pcrn = pcrn->prnNext)
|
|
{
|
|
if (pcrn != pcrnSrc)
|
|
{
|
|
if (IsCopyDestination(pcrn))
|
|
{
|
|
ASSERT(ulScale < ULONG_MAX - dwcbSrcFileLen);
|
|
ulScale += dwcbSrcFileLen;
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE_OUT((TEXT("DetermineCopyScale(): Scale for %s is %lu."),
|
|
pcrnSrc->priParent->pcszName,
|
|
ulScale));
|
|
|
|
return(ulScale);
|
|
}
|
|
|
|
|
|
/*
|
|
** IsCopyDestination()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL IsCopyDestination(PCRECNODE pcrn)
|
|
{
|
|
BOOL bDest = FALSE;
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcrn, CRECNODE));
|
|
|
|
switch (pcrn->priParent->riaction)
|
|
{
|
|
case RIA_COPY:
|
|
switch (pcrn->rnaction)
|
|
{
|
|
case RNA_COPY_TO_ME:
|
|
bDest = TRUE;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case RIA_MERGE:
|
|
switch (pcrn->rnaction)
|
|
{
|
|
case RNA_COPY_TO_ME:
|
|
case RNA_MERGE_ME:
|
|
bDest = TRUE;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ERROR_OUT((TEXT("IsCopyDestination(): Bad RECITEM action %d."),
|
|
pcrn->priParent->riaction));
|
|
break;
|
|
}
|
|
|
|
return(bDest);
|
|
}
|
|
|
|
|
|
/*
|
|
** SetDestinationTimeStamps()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL SetDestinationTimeStamps(PCRECNODE pcrnSrc)
|
|
{
|
|
BOOL bResult = TRUE;
|
|
PCRECNODE pcrn;
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcrnSrc, CRECNODE));
|
|
|
|
for (pcrn = pcrnSrc->priParent->prnFirst;
|
|
pcrn;
|
|
pcrn = pcrn->prnNext)
|
|
{
|
|
if (pcrn->rnaction == RNA_COPY_TO_ME)
|
|
{
|
|
TCHAR rgchPath[MAX_PATH_LEN];
|
|
HANDLE hfDest;
|
|
|
|
ComposePath(rgchPath, pcrn->pcszFolder, pcrn->priParent->pcszName);
|
|
ASSERT(lstrlen(rgchPath) < ARRAYSIZE(rgchPath));
|
|
|
|
hfDest = CreateFile(rgchPath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (hfDest != INVALID_HANDLE_VALUE)
|
|
{
|
|
if (! SetFileTime(hfDest, NULL, NULL, &(pcrnSrc->fsCurrent.ftMod)))
|
|
bResult = FALSE;
|
|
|
|
if (! CloseHandle(hfDest))
|
|
bResult = FALSE;
|
|
}
|
|
else
|
|
bResult = FALSE;
|
|
|
|
if (bResult)
|
|
TRACE_OUT((TEXT("SetDestinationTimeStamps(): Set last modification time stamp of %s to match last modification time stamp of %s\\%s."),
|
|
rgchPath,
|
|
pcrnSrc->pcszFolder,
|
|
pcrnSrc->priParent->pcszName));
|
|
else
|
|
WARNING_OUT((TEXT("SetDestinationTimeStamps(): Failed to set last modification time stamp of %s to match last modification time stamp of %s\\%s."),
|
|
rgchPath,
|
|
pcrnSrc->pcszFolder,
|
|
pcrnSrc->priParent->pcszName));
|
|
}
|
|
}
|
|
|
|
return(bResult);
|
|
}
|
|
|
|
|
|
/*
|
|
** DeleteFolderProc()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL DeleteFolderProc(LPCTSTR pcszFolder, PCWIN32_FIND_DATA pcwfd,
|
|
PVOID ptr)
|
|
{
|
|
ASSERT(IsCanonicalPath(pcszFolder));
|
|
ASSERT(IS_VALID_READ_PTR(pcwfd, CWIN32_FIND_DATA));
|
|
ASSERT(IS_VALID_WRITE_PTR(ptr, TWINRESULT));
|
|
|
|
if (IS_ATTR_DIR(pcwfd->dwFileAttributes))
|
|
{
|
|
TCHAR rgchPath[MAX_PATH_LEN];
|
|
|
|
ComposePath(rgchPath, pcszFolder, pcwfd->cFileName);
|
|
ASSERT(lstrlen(rgchPath) < ARRAYSIZE(rgchPath));
|
|
|
|
if (RemoveDirectory(rgchPath))
|
|
{
|
|
WARNING_OUT((TEXT("DeleteFolderProc(): Removed folder %s."),
|
|
rgchPath));
|
|
|
|
NotifyShell(rgchPath, NSE_DELETE_FOLDER);
|
|
}
|
|
else
|
|
{
|
|
WARNING_OUT((TEXT("DeleteFolderProc(): Failed to remove folder %s."),
|
|
rgchPath));
|
|
|
|
*(PTWINRESULT)ptr = TR_DEST_WRITE_FAILED;
|
|
}
|
|
}
|
|
else
|
|
TRACE_OUT((TEXT("DeleteFolderProc(): Skipping file %s\\%s."),
|
|
pcszFolder,
|
|
pcwfd->cFileName));
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
/*
|
|
** CopyBufferIsOk()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL CopyBufferIsOk(void)
|
|
{
|
|
/* Are the module copy buffer variables in a correct state? */
|
|
|
|
return((! MucbCopyBufLen &&
|
|
! MpbyteCopyBuf) ||
|
|
(MucbCopyBufLen > 0 &&
|
|
IS_VALID_WRITE_BUFFER_PTR(MpbyteCopyBuf, BYTE, MucbCopyBufLen)));
|
|
}
|
|
|
|
|
|
/*
|
|
** VerifyRECITEMAndSrcRECNODE()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL VerifyRECITEMAndSrcRECNODE(PCRECNODE pcrnSrc)
|
|
{
|
|
/* Do the RECITEM and source RECNODE actions match? */
|
|
|
|
return((pcrnSrc->priParent->riaction == RIA_COPY &&
|
|
pcrnSrc->rnaction == RNA_COPY_FROM_ME) ||
|
|
(pcrnSrc->priParent->riaction == RIA_MERGE &&
|
|
pcrnSrc->rnaction == RNA_MERGE_ME));
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/****************************** Public Functions *****************************/
|
|
|
|
|
|
/*
|
|
** BeginCopy()
|
|
**
|
|
** Increments copy buffer lock count.
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: void
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE void BeginCopy(void)
|
|
{
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
ASSERT(MulcCopyBufLock < ULONG_MAX);
|
|
MulcCopyBufLock++;
|
|
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
** EndCopy()
|
|
**
|
|
** Decrements copy buffer lock count.
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: void
|
|
**
|
|
** Side Effects: Frees copy buffer if lock count goes to 0.
|
|
*/
|
|
PUBLIC_CODE void EndCopy(void)
|
|
{
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
/* Is the copy buffer still locked? */
|
|
|
|
if (! --MulcCopyBufLock)
|
|
DestroyCopyBuffer();
|
|
|
|
ASSERT(CopyBufferIsOk());
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
** CopyHandler()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE TWINRESULT CopyHandler(PRECNODE prnSrc, RECSTATUSPROC rsp,
|
|
LPARAM lpCallbackData, DWORD dwFlags,
|
|
HWND hwndOwner, HWND hwndProgressFeedback)
|
|
{
|
|
TWINRESULT tr;
|
|
RECSTATUSUPDATE rsu;
|
|
|
|
/* lpCallbackData may be any value. */
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(prnSrc, CRECNODE));
|
|
ASSERT(! rsp ||
|
|
IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
|
|
ASSERT(FLAGS_ARE_VALID(dwFlags, ALL_RI_FLAGS));
|
|
ASSERT(IS_FLAG_CLEAR(dwFlags, RI_FL_ALLOW_UI) ||
|
|
IS_VALID_HANDLE(hwndOwner, WND));
|
|
ASSERT(IS_FLAG_CLEAR(dwFlags, RI_FL_FEEDBACK_WINDOW_VALID) ||
|
|
IS_VALID_HANDLE(hwndProgressFeedback, WND));
|
|
|
|
ASSERT(VerifyRECITEMAndSrcRECNODE(prnSrc));
|
|
|
|
/* 0% complete. */
|
|
|
|
rsu.ulScale = 1;
|
|
rsu.ulProgress = 0;
|
|
|
|
if (NotifyReconciliationStatus(rsp, RS_BEGIN_COPY, (LPARAM)&rsu,
|
|
lpCallbackData))
|
|
{
|
|
tr = CreateDestinationFolders(prnSrc);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
TCHAR rgchPath[MAX_PATH_LEN];
|
|
CLSID clsidReconciler;
|
|
HRESULT hr;
|
|
|
|
ComposePath(rgchPath, prnSrc->pcszFolder, prnSrc->priParent->pcszName);
|
|
ASSERT(lstrlen(rgchPath) < ARRAYSIZE(rgchPath));
|
|
|
|
if (SUCCEEDED(GetCopyHandlerClassID(rgchPath, &clsidReconciler)))
|
|
{
|
|
hr = OLECopy(prnSrc, &clsidReconciler, rsp, lpCallbackData,
|
|
dwFlags, hwndOwner, hwndProgressFeedback);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (hr != S_FALSE)
|
|
{
|
|
/*
|
|
* Set the destination files' time stamps to the source
|
|
* file's time stamp to assist clients that don't maintain
|
|
* a persistent briefcase database, like MPR. Failure to
|
|
* set the time stamps is not fatal.
|
|
*/
|
|
|
|
ASSERT(hr == REC_S_IDIDTHEUPDATES);
|
|
TRACE_OUT((TEXT("CopyHandler(): OLECopy() on %s returned %s."),
|
|
rgchPath,
|
|
GetHRESULTString(hr)));
|
|
|
|
if (! SetDestinationTimeStamps(prnSrc))
|
|
WARNING_OUT((TEXT("CopyHandler(): SetDestinationTimeStamps() failed. Not all destination files have been marked with source file's time stamp.")));
|
|
|
|
tr = TR_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
WARNING_OUT((TEXT("CopyHandler(): OLECopy() on %s returned %s. Resorting to internal copy routine."),
|
|
rgchPath,
|
|
GetHRESULTString(hr)));
|
|
|
|
/*
|
|
* Update the source RECNODE's file stamp in case it was
|
|
* changed by the reconciler.
|
|
*/
|
|
|
|
MyGetFileStampByHPATH(((PCOBJECTTWIN)(prnSrc->hObjectTwin))->hpath,
|
|
GetString(((PCOBJECTTWIN)(prnSrc->hObjectTwin))->ptfParent->hsName),
|
|
&(prnSrc->fsCurrent));
|
|
|
|
tr = SimpleCopy(prnSrc, rsp, lpCallbackData);
|
|
}
|
|
}
|
|
else
|
|
tr = TranslateHRESULTToTWINRESULT(hr);
|
|
}
|
|
else
|
|
tr = SimpleCopy(prnSrc, rsp, lpCallbackData);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
/* 100% complete. */
|
|
|
|
rsu.ulScale = 1;
|
|
rsu.ulProgress = 1;
|
|
|
|
/* Don't allow abort here. */
|
|
|
|
NotifyReconciliationStatus(rsp, RS_END_COPY, (LPARAM)&rsu,
|
|
lpCallbackData);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
tr = TR_ABORT;
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** NotifyReconciliationStatus()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE BOOL NotifyReconciliationStatus(RECSTATUSPROC rsp, UINT uMsg, LPARAM lp,
|
|
LPARAM lpCallbackData)
|
|
{
|
|
BOOL bContinue;
|
|
|
|
/* lp may be any value. */
|
|
/* lpCallbackData may be any value. */
|
|
|
|
ASSERT(! rsp ||
|
|
IS_VALID_CODE_PTR(rsp, RECSTATUSROC));
|
|
ASSERT(IsValidRecStatusProcMsg(uMsg));
|
|
|
|
if (rsp)
|
|
{
|
|
TRACE_OUT((TEXT("NotifyReconciliationStatus(): Calling RECSTATUSPROC with message %s, ulProgress %lu, ulScale %lu, callback data %#lx."),
|
|
GetRECSTATUSPROCMSGString(uMsg),
|
|
((PCRECSTATUSUPDATE)lp)->ulProgress,
|
|
((PCRECSTATUSUPDATE)lp)->ulScale,
|
|
lpCallbackData));
|
|
|
|
bContinue = (*rsp)(uMsg, lp, lpCallbackData);
|
|
}
|
|
else
|
|
{
|
|
TRACE_OUT((TEXT("NotifyReconciliationStatus(): Not calling NULL RECSTATUSPROC with message %s, ulProgress %lu, ulScale %lu, callback data %#lx."),
|
|
GetRECSTATUSPROCMSGString(uMsg),
|
|
((PCRECSTATUSUPDATE)lp)->ulProgress,
|
|
((PCRECSTATUSUPDATE)lp)->ulScale,
|
|
lpCallbackData));
|
|
|
|
bContinue = TRUE;
|
|
}
|
|
|
|
if (! bContinue)
|
|
WARNING_OUT((TEXT("NotifyReconciliationStatus(): Client callback aborted reconciliation.")));
|
|
|
|
return(bContinue);
|
|
}
|
|
|
|
|
|
/*
|
|
** CreateFolders()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE TWINRESULT CreateFolders(LPCTSTR pcszPath, HPATH hpath)
|
|
{
|
|
TWINRESULT tr;
|
|
|
|
ASSERT(IsCanonicalPath(pcszPath));
|
|
ASSERT(IS_VALID_HANDLE(hpath, PATH));
|
|
|
|
if (MyIsPathOnVolume(pcszPath, hpath))
|
|
{
|
|
TCHAR rgchPath[MAX_PATH_LEN];
|
|
LPTSTR pszRootEnd;
|
|
LPTSTR pszHackSlash;
|
|
|
|
/* Create working copy of path. */
|
|
|
|
ASSERT(lstrlen(pcszPath) < ARRAYSIZE(rgchPath));
|
|
lstrcpy(rgchPath, pcszPath);
|
|
|
|
pszRootEnd = FindEndOfRootSpec(rgchPath, hpath);
|
|
|
|
/*
|
|
* Hack off the path at each successive slash, and check to see if that
|
|
* folder needs to be created.
|
|
*/
|
|
|
|
tr = TR_SUCCESS;
|
|
|
|
pszHackSlash = pszRootEnd;
|
|
|
|
while (*pszHackSlash)
|
|
{
|
|
TCHAR chReplaced;
|
|
|
|
while (*pszHackSlash && *pszHackSlash != TEXT('\\'))
|
|
pszHackSlash = CharNext(pszHackSlash);
|
|
|
|
/* Replace the slash with a null terminator to set the current folder. */
|
|
|
|
chReplaced = *pszHackSlash;
|
|
*pszHackSlash = TEXT('\0');
|
|
|
|
/* Does the folder exist? */
|
|
|
|
if (! PathExists(rgchPath))
|
|
{
|
|
/* No. Try to create it. */
|
|
|
|
TCHAR szAnsiPath[MAX_PATH];
|
|
MakeAnsiPath(rgchPath, szAnsiPath);
|
|
|
|
if (CreateDirectory(szAnsiPath, NULL))
|
|
{
|
|
WARNING_OUT((TEXT("CreateFolders(): Created folder %s."),
|
|
rgchPath));
|
|
|
|
NotifyShell(rgchPath, NSE_CREATE_FOLDER);
|
|
}
|
|
else
|
|
{
|
|
WARNING_OUT((TEXT("CreateFolders(): Failed to create folder %s."),
|
|
rgchPath));
|
|
|
|
tr = TR_DEST_OPEN_FAILED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*pszHackSlash = chReplaced;
|
|
|
|
if (chReplaced)
|
|
pszHackSlash++;
|
|
}
|
|
}
|
|
else
|
|
tr = TR_UNAVAILABLE_VOLUME;
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** DestroySubtree()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE TWINRESULT DestroySubtree(LPCTSTR pcszPath, HPATH hpath)
|
|
{
|
|
TWINRESULT tr;
|
|
|
|
ASSERT(IsCanonicalPath(pcszPath));
|
|
ASSERT(IS_VALID_HANDLE(hpath, PATH));
|
|
|
|
if (MyIsPathOnVolume(pcszPath, hpath))
|
|
{
|
|
tr = ExpandSubtree(hpath, &DeleteFolderProc, &tr);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
if (RemoveDirectory(pcszPath))
|
|
{
|
|
WARNING_OUT((TEXT("DestroySubtree(): Subtree %s removed successfully."),
|
|
pcszPath));
|
|
|
|
NotifyShell(pcszPath, NSE_DELETE_FOLDER);
|
|
}
|
|
else
|
|
{
|
|
if (PathExists(pcszPath))
|
|
{
|
|
/* Still there. */
|
|
|
|
WARNING_OUT((TEXT("DestroySubtree(): Failed to remove subtree root %s."),
|
|
pcszPath));
|
|
|
|
tr = TR_DEST_WRITE_FAILED;
|
|
}
|
|
else
|
|
/* Already gone. */
|
|
tr = TR_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
tr = TR_UNAVAILABLE_VOLUME;
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
/*
|
|
** IsValidRecStatusProcMsg()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE BOOL IsValidRecStatusProcMsg(UINT uMsg)
|
|
{
|
|
BOOL bResult;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case RS_BEGIN_COPY:
|
|
case RS_DELTA_COPY:
|
|
case RS_END_COPY:
|
|
case RS_BEGIN_MERGE:
|
|
case RS_DELTA_MERGE:
|
|
case RS_END_MERGE:
|
|
case RS_BEGIN_DELETE:
|
|
case RS_DELTA_DELETE:
|
|
case RS_END_DELETE:
|
|
bResult = TRUE;
|
|
break;
|
|
|
|
default:
|
|
bResult = FALSE;
|
|
ERROR_OUT((TEXT("IsValidRecStatusProcMsg(): Invalid RecStatusProc() message %u."),
|
|
uMsg));
|
|
break;
|
|
}
|
|
|
|
return(bResult);
|
|
}
|
|
|
|
#endif
|
|
|