mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
624 lines
17 KiB
624 lines
17 KiB
#include "shellprv.h"
|
|
#pragma hdrstop
|
|
|
|
#include "lnktrack.h"
|
|
|
|
#include "resolve.h"
|
|
|
|
|
|
|
|
// compute a weighted score for a given find
|
|
|
|
int ScoreFindData(RESOLVE_SEARCH_DATA *prs, LPCTSTR pszDir, const WIN32_FIND_DATA *pfd)
|
|
{
|
|
int iScore = 0;
|
|
BOOL bSameName, bSameCreateDate, bSameWriteTime, bSameExt, bHasCreateDate;
|
|
|
|
bSameName = lstrcmpi(prs->pfd->cFileName, pfd->cFileName) == 0;
|
|
|
|
bSameExt = lstrcmpi(PathFindExtension(prs->pfd->cFileName), PathFindExtension(pfd->cFileName)) == 0;
|
|
|
|
bHasCreateDate = !IsNullTime(&pfd->ftCreationTime);
|
|
|
|
bSameCreateDate = bHasCreateDate &&
|
|
(CompareFileTime(&pfd->ftCreationTime, &prs->pfd->ftCreationTime) == 0);
|
|
|
|
bSameWriteTime = !IsNullTime(&pfd->ftLastWriteTime) &&
|
|
(CompareFileTime(&pfd->ftLastWriteTime, &prs->pfd->ftLastWriteTime) == 0);
|
|
|
|
if (bSameName || bSameCreateDate)
|
|
{
|
|
if (bSameName)
|
|
iScore += bHasCreateDate ? 16 : 32;
|
|
|
|
if (bSameCreateDate)
|
|
{
|
|
iScore += 32;
|
|
|
|
if (bSameExt)
|
|
iScore += 8;
|
|
}
|
|
|
|
if (bSameWriteTime)
|
|
iScore += 8;
|
|
|
|
if (pfd->nFileSizeLow == prs->pfd->nFileSizeLow)
|
|
iScore += 4;
|
|
|
|
// if it is in the same folder as the original give it a slight bonus
|
|
if (lstrcmpi(pszDir, prs->pszSearchOrigin) == 0)
|
|
iScore += 2;
|
|
}
|
|
else
|
|
{
|
|
// doesn't have create date, apply different rules
|
|
|
|
if (bSameExt)
|
|
iScore += 8;
|
|
|
|
if (bSameWriteTime)
|
|
iScore += 8;
|
|
|
|
if (pfd->nFileSizeLow == prs->pfd->nFileSizeLow)
|
|
iScore += 4;
|
|
}
|
|
|
|
return iScore;
|
|
}
|
|
|
|
BOOL BeenThereDoneThat(LPCTSTR pszOriginal, LPCTSTR pszPath)
|
|
{
|
|
return PathCommonPrefix(pszOriginal, pszPath, NULL) == lstrlen(pszPath);
|
|
}
|
|
|
|
typedef struct _PATH_NODE {
|
|
struct _PATH_NODE *pNext;
|
|
int cDepth; // levels from top of search
|
|
// I choose to store this rather than use two lists for
|
|
// simplicity; even though using two lists would result
|
|
// in slightly less memory.
|
|
TCHAR szPath[1];
|
|
} PATH_NODE;
|
|
|
|
PATH_NODE *AllocPathNode(LPCTSTR pszStr)
|
|
{
|
|
PATH_NODE *p = LocalAlloc(LPTR, lstrlen(pszStr)*SIZEOF(TCHAR) + sizeof(PATH_NODE));
|
|
if (p)
|
|
lstrcpy(p->szPath, pszStr);
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
// iterative link search code (breadth first search)
|
|
//
|
|
// in:
|
|
// prs resolve search state
|
|
//
|
|
// pszFolder root of the search, we start here then resurse on
|
|
// all sub folders
|
|
//
|
|
// cLevels -1 means do all subdirs
|
|
// 0 means do this directory only
|
|
// 1 means do this directory and immediate children
|
|
|
|
BOOL SearchInFolder(RESOLVE_SEARCH_DATA *prs,
|
|
LPCTSTR pszFolder,
|
|
int cLevels,
|
|
int iStopScore,
|
|
const BOOL *pfTerminate)
|
|
{
|
|
PATH_NODE *pFree, *pFirst, *pLast; // list in FIFO order
|
|
|
|
// initial list of the one folder we want to look in
|
|
|
|
pLast = pFirst = AllocPathNode(pszFolder);
|
|
|
|
while (pFirst && prs->bContinue)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
HANDLE hfind;
|
|
|
|
DebugMsg(DM_TRACE, TEXT("SearchInFolder: %s"), pFirst->szPath);
|
|
|
|
PathCombine(szPath, pFirst->szPath, c_szStarDotStar);
|
|
|
|
hfind = FindFirstFile(szPath, &prs->fd);
|
|
if (hfind != INVALID_HANDLE_VALUE)
|
|
{
|
|
do {
|
|
if (prs->fd.cFileName[0] != TEXT('.'))
|
|
{
|
|
DWORD dwFind = prs->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
if (!(dwFind ^ prs->dwMatch) && !PathIsLink(prs->fd.cFileName))
|
|
{
|
|
// both are files or folders, see how it scores
|
|
int iScore = ScoreFindData(prs, pFirst->szPath, &prs->fd);
|
|
|
|
if (iScore > prs->iScore)
|
|
{
|
|
prs->fdFound = prs->fd;
|
|
|
|
DebugMsg(DM_TRACE, TEXT("Better match found %s, %d"), prs->fd.cFileName, iScore);
|
|
|
|
// store the score and fully qualified path
|
|
prs->iScore = iScore;
|
|
PathCombine(prs->fdFound.cFileName, pFirst->szPath, prs->fd.cFileName);
|
|
}
|
|
}
|
|
|
|
if (prs->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
PathCombine(szPath, pFirst->szPath, prs->fd.cFileName);
|
|
|
|
if (!BeenThereDoneThat(prs->pszSearchOrigin, szPath) && !IsFileInBitBucket(szPath))
|
|
{
|
|
if (cLevels == -1 || pFirst->cDepth + 1 <= cLevels)
|
|
{
|
|
PATH_NODE *p = AllocPathNode(szPath);
|
|
if (p)
|
|
{
|
|
p->cDepth = pFirst->cDepth + 1;
|
|
pLast->pNext = p;
|
|
pLast = p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_TRACK
|
|
if (g_fNewTrack)
|
|
{
|
|
if( FAILED( TimeoutExpired( prs->dwTimeLimit ))
|
|
||
|
|
*pfTerminate
|
|
)
|
|
{
|
|
prs->bContinue = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
|
|
if (GetTickCount() >= prs->dwTimeLimit)
|
|
prs->bContinue = FALSE;
|
|
|
|
#ifdef ENABLE_TRACK
|
|
}
|
|
#endif
|
|
} while (prs->bContinue && FindNextFile(hfind, &prs->fd));
|
|
FindClose(hfind);
|
|
}
|
|
|
|
// remove the element we just searched from the list
|
|
// leaving other elements we may have added
|
|
|
|
Assert(pFirst && pLast);
|
|
|
|
pFree = pFirst;
|
|
|
|
pFirst = pFirst->pNext;
|
|
|
|
Assert(pFirst || pFree == pLast);
|
|
|
|
LocalFree(pFree);
|
|
|
|
if (prs->iScore >= iStopScore)
|
|
{
|
|
prs->bContinue = FALSE;
|
|
DebugMsg(DM_TRACE, TEXT("search terminated match found %d"), prs->iScore);
|
|
}
|
|
}
|
|
|
|
// if we were canceled make sure we clean up
|
|
while (pFirst)
|
|
{
|
|
pFree = pFirst;
|
|
pFirst = pFirst->pNext;
|
|
LocalFree(pFree);
|
|
}
|
|
|
|
return prs->bContinue;
|
|
}
|
|
|
|
// result in prs->fdFound.cFileName
|
|
|
|
VOID
|
|
DoDownLevelSearch(RESOLVE_SEARCH_DATA *prs,
|
|
TCHAR szFolderPath[],
|
|
int iStopScore,
|
|
const BOOL *pfTerminate)
|
|
{
|
|
int cUp = LNKTRACK_HINTED_UPLEVELS;
|
|
LPITEMIDLIST pidl;
|
|
|
|
//
|
|
// search up from old location
|
|
//
|
|
|
|
lstrcpy(szFolderPath, prs->pszSearchOriginFirst);
|
|
prs->pszSearchOrigin = prs->pszSearchOriginFirst;
|
|
|
|
while (cUp-- != 0 && SearchInFolder(prs, szFolderPath, LNKTRACK_HINTED_DOWNLEVELS, iStopScore, pfTerminate))
|
|
{
|
|
if (PathIsRoot(szFolderPath) || !PathRemoveFileSpec(szFolderPath))
|
|
break;
|
|
}
|
|
|
|
if (prs->bContinue)
|
|
{
|
|
//
|
|
// search down from desktop
|
|
//
|
|
HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl);
|
|
if (hr == NOERROR)
|
|
{
|
|
if (SHGetPathFromIDList(pidl, szFolderPath))
|
|
{
|
|
prs->pszSearchOrigin = szFolderPath;
|
|
SearchInFolder(prs, szFolderPath, LNKTRACK_DESKTOP_DOWNLEVELS, iStopScore, pfTerminate);
|
|
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
if (prs->bContinue)
|
|
{
|
|
//
|
|
// search down from root of fixed drives
|
|
//
|
|
TCHAR atch[4];
|
|
|
|
lstrcpy(atch, TEXT("C:\\"));
|
|
prs->pszSearchOrigin = atch;
|
|
|
|
for (; prs->bContinue && atch[0] <= TEXT('Z'); atch[0]++)
|
|
{
|
|
lstrcpy(szFolderPath, atch);
|
|
if (GetDriveType(atch) == DRIVE_FIXED)
|
|
{
|
|
SearchInFolder(prs, szFolderPath, LNKTRACK_ROOT_DOWNLEVELS, iStopScore, pfTerminate);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (prs->bContinue)
|
|
{
|
|
//
|
|
// resume search of last volume (should do an exclude list)
|
|
//
|
|
|
|
lstrcpy(szFolderPath, prs->pszSearchOriginFirst);
|
|
prs->pszSearchOrigin = prs->pszSearchOriginFirst;
|
|
|
|
while (SearchInFolder(prs, szFolderPath, -1, iStopScore, pfTerminate))
|
|
{
|
|
if (PathIsRoot(szFolderPath) || !PathRemoveFileSpec(szFolderPath))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD CALLBACK LinkFindThreadProc(LPVOID pv)
|
|
{
|
|
RESOLVE_SEARCH_DATA *prs = pv;
|
|
TCHAR szPath[MAX_PATH];
|
|
HRESULT hr;
|
|
BOOL fTerminate = FALSE;
|
|
|
|
DoDownLevelSearch(prs, szPath, MIN_NO_UI_SCORE, &fTerminate);
|
|
|
|
if (prs->hDlg)
|
|
PostMessage(prs->hDlg, WM_COMMAND, IDOK, 0);
|
|
|
|
return prs->iScore;
|
|
}
|
|
|
|
void LinkFindInit(HWND hDlg, RESOLVE_SEARCH_DATA *prs)
|
|
{
|
|
DWORD idThread;
|
|
TCHAR szFmt[128];
|
|
TCHAR szTemp[MAX_PATH + ARRAYSIZE(szFmt)];
|
|
|
|
GetDlgItemText(hDlg, IDD_NAME, szFmt, ARRAYSIZE(szFmt));
|
|
wsprintf(szTemp, szFmt, prs->pfd->cFileName);
|
|
SetDlgItemText(hDlg, IDD_NAME, szTemp);
|
|
|
|
prs->hDlg = hDlg;
|
|
|
|
prs->hThread = CreateThread(NULL, 0, LinkFindThreadProc, prs, 0, &idThread);
|
|
if (!prs->hThread)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Failed to create search thread"));
|
|
EndDialog(hDlg, IDCANCEL);
|
|
}
|
|
else
|
|
{
|
|
HWND hwndAni = GetDlgItem(hDlg, IDD_STATUS);
|
|
|
|
Animate_Open(hwndAni, MAKEINTRESOURCE(IDA_SEARCH)); // open the resource
|
|
Animate_Play(hwndAni, 0, -1, -1); // play from start to finish and repeat
|
|
}
|
|
}
|
|
|
|
int CALLBACK LinkFindDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
RESOLVE_SEARCH_DATA *prs = (RESOLVE_SEARCH_DATA *)GetWindowLong(hDlg, DWL_USER);
|
|
|
|
switch (wMsg) {
|
|
case WM_INITDIALOG:
|
|
SetWindowLong(hDlg, DWL_USER, lParam);
|
|
LinkFindInit(hDlg, (RESOLVE_SEARCH_DATA *)lParam);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
|
|
|
|
case IDD_BROWSE:
|
|
prs->hDlg = NULL; // don't let the thread close us
|
|
prs->bContinue = FALSE; // cancel thread
|
|
|
|
Animate_Stop(GetDlgItem(hDlg, IDD_STATUS));
|
|
|
|
if (GetFileNameFromBrowse(hDlg, prs->fd.cFileName, ARRAYSIZE(prs->fd.cFileName), prs->pszSearchOriginFirst, prs->pfd->cFileName, NULL, NULL))
|
|
{
|
|
HANDLE hfind = FindFirstFile(prs->fd.cFileName, &prs->fdFound);
|
|
Assert(hfind != INVALID_HANDLE_VALUE);
|
|
FindClose(hfind);
|
|
lstrcpy(prs->fdFound.cFileName, prs->fd.cFileName);
|
|
|
|
prs->iScore = MIN_NO_UI_SCORE;
|
|
wParam = IDOK;
|
|
}
|
|
else
|
|
{
|
|
wParam = IDCANCEL;
|
|
}
|
|
// Fall through...
|
|
|
|
case IDCANCEL:
|
|
prs->bContinue = FALSE; // tell searching thread to stop
|
|
// Fall through...
|
|
|
|
case IDOK:
|
|
// thread posts this to us
|
|
|
|
Assert(prs->hThread);
|
|
|
|
// We will attempt to wait up to 5 seconds for the thread to terminate
|
|
if (WaitForSingleObject(prs->hThread, 5000) == WAIT_TIMEOUT)
|
|
{
|
|
// BUGBUG: if this timed out we potentially leaked the list
|
|
// of paths that we are searching (PATH_NODE list)
|
|
|
|
TerminateThread(prs->hThread, (DWORD)-1); // Blow it away!
|
|
}
|
|
|
|
CloseHandle(prs->hThread);
|
|
|
|
EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
DWORD
|
|
GetTimeOut(DWORD uFlags)
|
|
{
|
|
DWORD dwTimeOut = HIWORD(uFlags);
|
|
if (dwTimeOut == 0)
|
|
dwTimeOut = NOUI_SEARCH_TIMEOUT;
|
|
else
|
|
if (dwTimeOut == 0xFFFF)
|
|
{
|
|
TCHAR tszTimeOut[10];
|
|
LONG cbTimeOut = SIZEOF(tszTimeOut);
|
|
|
|
tszTimeOut[0] = 0;
|
|
|
|
if (ERROR_SUCCESS == RegQueryValue(HKEY_LOCAL_MACHINE,
|
|
TEXT("Software\\Microsoft\\Tracking\\TimeOut"),
|
|
tszTimeOut,
|
|
&cbTimeOut))
|
|
dwTimeOut = _fatoi(tszTimeOut);
|
|
else
|
|
dwTimeOut = NOUI_SEARCH_TIMEOUT;
|
|
}
|
|
return(dwTimeOut);
|
|
}
|
|
|
|
// in:
|
|
// hwnd NULL implies NOUI
|
|
// uFlags IShellLink::Resolve flags parameter
|
|
// pszFolder place to look
|
|
//
|
|
//
|
|
// in/out:
|
|
// pfd in: thing we are looking for on input (cFileName unqualified path)
|
|
// out: if return is TRUE filled in with the new find info
|
|
// returns:
|
|
// IDOK found something
|
|
// IDNO didn't find it
|
|
// IDCANCEL user canceled the operation
|
|
//
|
|
|
|
int FindInFolder(HWND hwnd, UINT uFlags, LPCTSTR pszPath, WIN32_FIND_DATA *pfd)
|
|
{
|
|
RESOLVE_SEARCH_DATA rs;
|
|
TCHAR szSearchStart[MAX_PATH];
|
|
|
|
lstrcpy(szSearchStart, pszPath);
|
|
PathRemoveFileSpec(szSearchStart);
|
|
|
|
while (!PathIsDirectory(szSearchStart))
|
|
{
|
|
if (PathIsRoot(szSearchStart) || !PathRemoveFileSpec(szSearchStart))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("root path does not exists %s"), szSearchStart);
|
|
return IDNO;
|
|
}
|
|
}
|
|
|
|
rs.iScore = 0; // nothing found yet
|
|
rs.bContinue = TRUE;
|
|
rs.pszSearchOriginFirst = szSearchStart;
|
|
rs.pszSearchOrigin = szSearchStart;
|
|
|
|
rs.dwTimeLimit = GetTickCount() + UI_SEARCH_TIMEOUT;
|
|
|
|
rs.dwMatch = pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; // must match bits
|
|
rs.pfd = pfd;
|
|
rs.hDlg = NULL;
|
|
|
|
if (uFlags & SLR_NO_UI)
|
|
{
|
|
rs.dwTimeLimit = GetTickCount() + GetTimeOut(uFlags);
|
|
|
|
LinkFindThreadProc(&rs);
|
|
}
|
|
else
|
|
{
|
|
if (DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_LINK_SEARCH), hwnd, LinkFindDlgProc, (LPARAM)&rs) != IDOK)
|
|
{
|
|
return IDCANCEL; // cancel stuff below
|
|
}
|
|
}
|
|
|
|
if (rs.iScore < MIN_NO_UI_SCORE)
|
|
{
|
|
if (rs.iScore == 0 || (uFlags & SLR_NO_UI))
|
|
return IDNO;
|
|
|
|
// BUGBUG: strip extensions
|
|
if (ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_LINKCHANGED), MAKEINTRESOURCE(IDS_LINKERROR),
|
|
MB_YESNO | MB_ICONEXCLAMATION, PathFindFileName(pszPath), rs.fdFound.cFileName) != IDYES)
|
|
return IDCANCEL;
|
|
}
|
|
|
|
*pfd = rs.fdFound;
|
|
return IDOK;
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: TimeoutExpired
|
|
//
|
|
// Synopsis: Return MK_E_EXCEEDEDDEADLINE if the deadline has
|
|
// passed. Note that the deadline is an absolute tick
|
|
// count, as specified by GetTickCount().
|
|
// A deadline of 0, however, indicates infinity, and
|
|
// can never be expired.
|
|
//
|
|
// History: 17-Nov-95 MikeHill Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
#ifdef ENABLE_TRACK
|
|
|
|
HRESULT
|
|
TimeoutExpired( DWORD dwTickCountDeadline )
|
|
{
|
|
// If there is time left on the deadline, then we haven't expired.
|
|
|
|
return( DeltaTickCount( dwTickCountDeadline ) ? S_OK : MK_E_EXCEEDEDDEADLINE );
|
|
|
|
} // TimeoutExpired
|
|
|
|
#endif
|
|
|
|
//+--------------------------------------------------------------------
|
|
//
|
|
// Function: DeltaTickCount
|
|
//
|
|
// Purpose: Calculate the number of ticks from the current time
|
|
// to a given deadline. If the given deadline is 0,
|
|
// (indicating infinity), this routine will return 0xFFFFFFFF.
|
|
//
|
|
// Inputs: [DWORD] -- End tick count
|
|
//
|
|
// Output: [DWORD] -- Number of ticks until the end tick count,
|
|
// or zero if current time is past the end.
|
|
//
|
|
// Notes: This routine handles wraps in
|
|
// GetTickCount() by assuming that if the deadline is
|
|
// more than 2**31 ticks below the current time, then
|
|
// it actually represents a time in the future (after
|
|
// GetTickCount() wraps). This definition/use of
|
|
// dwTickCountDeadline is described in the OLE2.0
|
|
// design spec, section 7.2 "IMoniker interface and
|
|
// OLE IMoniker Implementations".
|
|
//
|
|
// History: 5-Nov-95 MikeHill Modified from CTimeout::Expired().
|
|
// 17-Nov-95 MikeHill Changed to match OLE 2.0 design spec.
|
|
//
|
|
//+--------------------------------------------------------------------
|
|
|
|
#ifdef ENABLE_TRACK
|
|
|
|
#define TICK_COUNT_WRAP_MARGIN 0x80000000 // 2**31
|
|
|
|
DWORD DeltaTickCount( DWORD dwTickCountDeadline )
|
|
{
|
|
|
|
DWORD dwNow;
|
|
|
|
// Is there a deadline?
|
|
|
|
if( dwTickCountDeadline == 0 )
|
|
{
|
|
return INFINITE; // There is no deadline.
|
|
}
|
|
|
|
// Are we approaching or at the deadline?
|
|
|
|
dwNow = GetTickCount();
|
|
|
|
if( dwNow <= dwTickCountDeadline )
|
|
{
|
|
return( dwTickCountDeadline - dwNow );
|
|
}
|
|
|
|
|
|
// Otherwise, the current tick count is greater than the deadline tick count.
|
|
|
|
else
|
|
{
|
|
// But if the deadline is more than 2**31 below
|
|
// the current time, then it actually represents a time
|
|
// in the future (after GetTickCount() wraps).
|
|
|
|
if( ( dwNow > TICK_COUNT_WRAP_MARGIN )
|
|
&&
|
|
( dwTickCountDeadline < (dwNow - TICK_COUNT_WRAP_MARGIN) )
|
|
)
|
|
{
|
|
// The delta tick count is the time from now to the wrap,
|
|
// plus the time after the wrap.
|
|
|
|
return ( (INFINITE - dwNow) + 1 + dwTickCountDeadline );
|
|
|
|
}
|
|
|
|
// Otherwise, the current time is greater than the deadline,
|
|
// and there's no way a wrap can explain it.
|
|
|
|
else
|
|
{
|
|
return( 0 ); // Timeout is Expired
|
|
}
|
|
}
|
|
|
|
} // DeltaTickCount()
|
|
|
|
#endif
|
|
|