2933 lines
70 KiB
C++
2933 lines
70 KiB
C++
/************************************************************************
|
|
* *
|
|
* MASTKEY.CPP *
|
|
* *
|
|
* Copyright (C) Microsoft Corporation 1993-1994. *
|
|
* All Rights reserved. *
|
|
* *
|
|
*************************************************************************
|
|
|
|
This module is used to create a .GID file, and to add a global index if
|
|
the user doesn't agree to its creation the first time. A .GID file is
|
|
similar to a .HLP file. It contains the following:
|
|
keyword btree
|
|
keyword data file
|
|
keyword btree leaf map
|
|
title btree
|
|
directory of indexed help files and original .CNT file
|
|
Contents Tab string btree (displayed to the user)
|
|
Contents context-string btree (where to jump to)
|
|
File of expansion/contraction flags and image types for Contents Tab
|
|
|
|
Note that the keys and records in these btrees are different from
|
|
the ones in a regular help file.
|
|
|
|
****************************************************************************/
|
|
|
|
extern "C" { // Assume C declarations for C++
|
|
#include "help.h"
|
|
}
|
|
|
|
#include "inc\whclass.h"
|
|
#pragma hdrstop
|
|
|
|
extern "C" { // Assume C declarations for C++
|
|
#include <io.h>
|
|
#include <dos.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <direct.h>
|
|
#include "inc\fspriv.h"
|
|
// #include "inc\btpriv.h"
|
|
}
|
|
|
|
#include "inc\table.h" // CTable class
|
|
#include "inc\hdlgsrch.h"
|
|
#include "inc\anim.h" // Animate class
|
|
#include "inc\input.h" // CInput class
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
// Names of help file keyword btree, keyword data file, and title btree.
|
|
|
|
#ifndef NO_PRAGMAS
|
|
#pragma data_seg(".text", "CODE")
|
|
#endif
|
|
const char txtTEMP[] = "temp";
|
|
static const char txtAnimateClassName[] = "Help_Setup";
|
|
|
|
/*
|
|
* We have to hard-code these strings because international no longer looks
|
|
* at the .rc file, and therefore won't know that translating these also
|
|
* means translating the .CNT files.
|
|
*/
|
|
|
|
static const char txtBase[] = ":base";
|
|
static const char txtTitle[] = ":title";
|
|
static const char txtIndex[] = ":index";
|
|
static const char txtTab[] = ":tab";
|
|
static const char txtLink[] = ":link";
|
|
static const char txtNoFind[] = ":nofind";
|
|
static const char txtInclude[] = ":include";
|
|
static const char txtMASTERKEYMAP[] = "|KWMAP";
|
|
#ifndef NO_PRAGMAS
|
|
#pragma data_seg()
|
|
#endif
|
|
|
|
const int MAX_CNT_LINE = 1024;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Master index stuff
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Names of master keyword btree, leaf map, title btree,
|
|
// and helpfile directory.
|
|
|
|
// REVIEW: if these don't change, then use the ones from global.c
|
|
|
|
static int iFile;
|
|
static PSTR pszCntBaseFile, pszCntTitle;
|
|
static int curReadPos;
|
|
static CTable* ptblExist;
|
|
|
|
#ifdef _PRIVATE
|
|
static int cIndexes;
|
|
#endif
|
|
|
|
INLINE static void STDCALL ContentsCmdLine(PSTR pszLine, HBT hbtCntText, BOOL* pfSeenBase, BOOL* pfSeenTitle, HFS hfsDst, PCSTR pszMasterFile, BOOL* pfValidIndexes, int curInput);
|
|
INLINE static BOOL STDCALL InitHelpFile(int iFile, HFS* phfsHelp, HBT* phbtKey, HF* phfDataSrc, HBT hbtFilesDst);
|
|
INLINE static KEY STDCALL KeyLeastInSubtree(QBTHR qbthr, DWORD bk, int icbLevel);
|
|
|
|
static BOOL STDCALL AddContentsFile(PSTR pszMasterFile, HFS hfsDst, BOOL* pfValidIndexes);
|
|
static BOOL STDCALL AddIndexes(HFS hfsDst, PCSTR pszMasterFile, HBT hbtFilesDst);
|
|
static int STDCALL CompareSz(PCSTR psz, LPCSTR pszSub);
|
|
static HBT STDCALL CreateFilesBt(PCSTR pszMasterFile, HFS hfsDst);
|
|
static FM STDCALL DoesFileExist(PCSTR pszFileName, DIR dir);
|
|
static BOOL STDCALL FindEqCharacter(PCSTR pszLine);
|
|
static QBTHR STDCALL HbtInitFill(PCSTR sz, BTREE_PARAMS* qbtp);
|
|
static void FASTCALL InitBtreeStruct(BTREE_PARAMS* pbt, PCSTR pszFormat);
|
|
static LONG STDCALL LcbReadHfSeq(HF hf, LPVOID qb, LONG lcb);
|
|
static VOID FASTCALL PointFromStroke(int xStroke, int yStroke, POINT* lppt);
|
|
static RC STDCALL RcOffsetPosFast(HBT hbt, BTPOS* pbtpos);
|
|
static void STDCALL ReportBtreeError(RC rc, PCSTR pszFile);
|
|
|
|
RC STDCALL RcFillHbt(HBT hbt, KEY key, void* qvRec);
|
|
RC STDCALL RcFiniFillHbt(HBT hbt);
|
|
QBTHR STDCALL HbtInitFill(PCSTR sz, BTREE_PARAMS* qbtp);
|
|
RC STDCALL RcGrowCache(QBTHR qbthr);
|
|
|
|
LRESULT CALLBACK StatusWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
|
BOOL CALLBACK NotifyWinHelp(HWND hwnd, LPARAM lParam);
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: CreateGidFile
|
|
|
|
PURPOSE: Creates a .GID file. This file includes the .CNT file, and
|
|
if the .CNT file contains more then one help file, this will
|
|
optionally add a cross-file keyword index based on the 'K'
|
|
keywords.
|
|
|
|
PARAMETERS:
|
|
pszMasterFile -- points to the .CNT file.
|
|
|
|
RETURNS: Locally allocated buffer containing .GID file name, which
|
|
may be in a different location then the .CNT file
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
25-Nov-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
PSTR STDCALL CreateGidFile(PSTR pszMasterFile, BOOL fEmpty)
|
|
{
|
|
HF hf = 0;
|
|
HFS hfsDst;
|
|
RC rc;
|
|
BOOL fValidIndexes = 0;
|
|
PSTR pszNewGid;
|
|
char szTmpFile[MAX_PATH];
|
|
char szMasterCopy[MAX_PATH];
|
|
|
|
#ifdef _PRIVATE
|
|
CTimeReport report("Gid creation time:");
|
|
#endif
|
|
|
|
if (ptblExist) {
|
|
delete ptblExist;
|
|
ptblExist = NULL;
|
|
}
|
|
|
|
if (!*pszMasterFile) {
|
|
ASSERT(fmCreating);
|
|
lstrcpy(szMasterCopy, fmCreating);
|
|
pszMasterFile = szMasterCopy;
|
|
}
|
|
|
|
ASSERT(*pszMasterFile);
|
|
/*
|
|
* If we had a full-text search group file, we must delete it whenever
|
|
* the .GID file changes, since we assume that help files have changed,
|
|
* thus invalidating any indexes.
|
|
*/
|
|
|
|
lstrcpy(szTmpFile, pszMasterFile);
|
|
ChangeExtension(szTmpFile, txtGrpExtension);
|
|
FM fm = FmNewExistSzDir(szTmpFile,
|
|
DIR_PATH | DIR_CUR_HELP | DIR_CURRENT);
|
|
if (fm) {
|
|
DeleteFile(fm);
|
|
RemoveFM(&fm);
|
|
}
|
|
|
|
/*
|
|
* This isn't really a for loop. We use it simply so that we can break
|
|
* out in case of an error condition. We need to do this instead of using
|
|
* goto's so that the compiler can cleanup the CInput and Animate classes.
|
|
*/
|
|
|
|
for(;;) {
|
|
|
|
CWaitCursor waitcur;
|
|
|
|
ASSERT(!hbtTabDialogs);
|
|
|
|
// We want to be in the same drive and directory as the .CNT file
|
|
|
|
strcpy(szTmpFile, pszMasterFile);
|
|
|
|
if (hwndParent) {
|
|
char szBuf[MAX_PATH + 100];
|
|
wsprintf(szBuf, GetStringResource(sidIndexing), szTmpFile);
|
|
SendStringToParent(szBuf);
|
|
}
|
|
|
|
ChangeDirectory(szTmpFile);
|
|
|
|
ChangeExtension(szTmpFile, txtTmpExtension);
|
|
// Sometimes this will be the name of a help file
|
|
ChangeExtension(pszMasterFile, txtCntExtension);
|
|
|
|
/*
|
|
* If the temporary file exists, then another instance of WinHelp
|
|
* is creating it (could be on a different machine with the .gid file
|
|
* being created on the server. If an animation window is up, then we
|
|
* simply wait for it to go away. If there's no animation window then
|
|
* either a) we're doing a silent build or b) the .gid is being
|
|
* created by a different machine. In this case we put up our
|
|
* animation window and wait until the temporary file goes away.
|
|
*/
|
|
|
|
if (FExistFm(szTmpFile)) {
|
|
|
|
SomebodyElse:
|
|
WIN32_FIND_DATA fd;
|
|
HFILE hfile;
|
|
if ((hfile = _lopen(szTmpFile, OF_WRITE)) != HFILE_ERROR) {
|
|
_lclose(hfile);
|
|
goto ForgetIt; // If it's writeable, then WinHelp doesn't
|
|
// have it open
|
|
}
|
|
|
|
HANDLE hfind = FindFirstFile(szTmpFile, &fd);
|
|
if (hfind != INVALID_HANDLE_VALUE) {
|
|
SYSTEMTIME st;
|
|
FILETIME ft, ftLocal;
|
|
FindClose(hfind);
|
|
GetSystemTime(&st);
|
|
SystemTimeToFileTime(&st, &ft);
|
|
FileTimeToLocalFileTime(&fd.ftLastWriteTime, &ftLocal);
|
|
|
|
|
|
// If the file is older then roughly 10 minutes, then assume
|
|
// some instance of WinHelp crashed or was killed.
|
|
|
|
#ifdef _DEBUG
|
|
int diff = ft.dwHighDateTime - ftLocal.dwHighDateTime;
|
|
#endif
|
|
if (ft.dwHighDateTime - ftLocal.dwHighDateTime > 5)
|
|
goto ForgetIt;
|
|
}
|
|
|
|
// Is it currently being created?
|
|
|
|
HWND hwndAni;
|
|
if ((hwndAni = FindWindow(txtAnimateClassName, NULL))) {
|
|
int count = 0;
|
|
SetForegroundWindow(hwndAni); // bring it in front
|
|
do {
|
|
count;
|
|
Sleep(1000);
|
|
FlushMessageQueue(WM_USER);
|
|
|
|
// Loop for up to 20 minutes
|
|
|
|
} while (FindWindow(txtAnimateClassName, NULL) && count <
|
|
20 * 60 * 1000);
|
|
}
|
|
else {
|
|
// Silent creation, or creation on another machine
|
|
if (!StartAnimation(sidHelpStatus)) {
|
|
rc = rcOutOfMemory;
|
|
break;
|
|
}
|
|
#ifdef _DEBUG
|
|
char szMsg[300];
|
|
wsprintf(szMsg, "Waiting on %s", szTmpFile);
|
|
DBWIN(szMsg);
|
|
#endif
|
|
do {
|
|
Sleep(250);
|
|
NextAnimation();
|
|
} while (FExistFm(szTmpFile));
|
|
StopAnimation();
|
|
}
|
|
ChangeExtension(szTmpFile, txtGidExtension);
|
|
if (!FExistFm(szTmpFile)) // no GID? then start over
|
|
return CreateGidFile(pszMasterFile, fEmpty);
|
|
pszNewGid = LocalStrDup(szTmpFile);
|
|
return pszNewGid;
|
|
}
|
|
|
|
ForgetIt:
|
|
|
|
/*
|
|
* The master file we are creating is temporary until it is
|
|
* completely built, so if it already exists, we must delete it now.
|
|
* Theoretically, it won't exist.
|
|
*/
|
|
|
|
if (GetFileAttributes(szTmpFile) != (DWORD) -1)
|
|
DeleteFile(szTmpFile);
|
|
|
|
// Create a file system within the file
|
|
|
|
if (!(hfsDst = HfsCreateFileSysFm((FM) szTmpFile, NULL))) {
|
|
if (rcIOError == rcOutOfMemory) {
|
|
rc = rcOutOfMemory;
|
|
break;
|
|
}
|
|
else {
|
|
|
|
/*
|
|
* If we don't have write permission, then create the .GID
|
|
* file in the windows\help directory.
|
|
*/
|
|
|
|
char szHelpDir[MAX_PATH];
|
|
ConvertToWindowsHelp(szTmpFile, szHelpDir);
|
|
lstrcpy(szTmpFile, szHelpDir); // because szTmpFile is used later on
|
|
|
|
if (FExistFm(szTmpFile))
|
|
goto SomebodyElse; // someone else is building this
|
|
|
|
DBWIN(szHelpDir);
|
|
|
|
if (!(hfsDst = HfsCreateFileSysFm((FM) szTmpFile, NULL))) {
|
|
if (rcIOError == rcOutOfMemory)
|
|
rc = rcOutOfMemory;
|
|
else
|
|
rc = rcNoPermission;
|
|
break; // drop out into error handler
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!StartAnimation(sidHelpStatus)) {
|
|
rc = rcOutOfMemory;
|
|
break;
|
|
}
|
|
NextAnimation();
|
|
ZeroMemory(&cntFlags, sizeof(cntFlags));
|
|
if (fEmpty)
|
|
cntFlags.flags = GID_NO_CNT;
|
|
cntFlags.version = GID_VERSION;
|
|
|
|
if (pFileInfo) // REVIEW: only free if its too small
|
|
FreeGh(pFileInfo);
|
|
pFileInfo = (GID_FILE_INFO*) GhAlloc(LMEM_FIXED | LMEM_ZEROINIT,
|
|
sizeof(GID_FILE_INFO) * MAX_FILES);
|
|
if (!pFileInfo) {
|
|
lcClearFree(&pbTree);
|
|
rc = rcOutOfMemory; // indicates error has already already reported
|
|
break;
|
|
}
|
|
|
|
if (!fEmpty) {
|
|
if (!AddContentsFile(pszMasterFile, hfsDst, &fValidIndexes)) {
|
|
rc = rcFailure; // indicates error has already already reported
|
|
break;;
|
|
}
|
|
}
|
|
if (hbtTabDialogs) {
|
|
RcCloseBtreeHbt(hbtTabDialogs);
|
|
hbtTabDialogs = NULL;
|
|
}
|
|
|
|
if (cntFlags.cCntItems > 1)
|
|
cntFlags.flags |= GID_CONTENTS;
|
|
|
|
HBT hbtFiles = CreateFilesBt(pszMasterFile, hfsDst);
|
|
if (!hbtFiles)
|
|
break; // break out into error handler
|
|
|
|
if (AddIndexes(hfsDst, pszMasterFile, hbtFiles))
|
|
cntFlags.flags |= GID_GINDEX;
|
|
|
|
RcCloseBtreeHbt(hbtFiles);
|
|
|
|
// Save off flags and Contents Tab tree array
|
|
|
|
hf = HfCreateFileHfs(hfsDst, txtFlags, fFSOpenReadWrite);
|
|
if (!hf) {
|
|
if (RcGetFSError() == rcOutOfMemory)
|
|
rc = rcOutOfMemory;
|
|
else
|
|
rc = rcNoPermission;
|
|
break; // drop out into error handler
|
|
}
|
|
|
|
#ifdef DBCS
|
|
if (fDBCS) {
|
|
char szLexLib[20];
|
|
|
|
wsprintf(szLexLib, "ftlx%04lx.dll", lcid ?
|
|
lcid : GetUserDefaultLCID());
|
|
|
|
HINSTANCE hLexLib = LoadLibrary(szLexLib); // attempt to load non-English word break
|
|
#ifdef _PRIVATE
|
|
{
|
|
char szBuf[200];
|
|
wsprintf(szBuf, "LoadLibray of \042%s\042 %s.\r\n",
|
|
szLexLib, hLexLib ?
|
|
GetStringResource(sidSuccess) : GetStringResource(sidFail));
|
|
SendStringToParent(szBuf);
|
|
}
|
|
#endif
|
|
if (hLexLib) {
|
|
cntFlags.flags |= GID_FTS;
|
|
FreeLibrary(hLexLib);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
cntFlags.flags |= GID_FTS;
|
|
|
|
if (pTblFiles->CountStrings() < 3) {
|
|
if (curHelpFileVersion == wVersion3_0)
|
|
cntFlags.flags &= ~GID_FTS; // no help for 3.0 files
|
|
}
|
|
|
|
if (LcbWriteHf(hf, &cntFlags, sizeof(cntFlags)) != sizeof(cntFlags)) {
|
|
rc = rcNoPermission;
|
|
break; // drop out into error handling code
|
|
}
|
|
{
|
|
CGMem mem(sizeof(POS_RECT) * MAX_POSITIONS);
|
|
|
|
// REVIEW: can we add our current values?
|
|
|
|
if (LcbWriteHf(hf, mem.GetPtr(), sizeof(POS_RECT) * MAX_POSITIONS) !=
|
|
sizeof(POS_RECT) * MAX_POSITIONS) {
|
|
rc = rcNoPermission;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cntFlags.cCntItems > 1) {
|
|
NextAnimation();
|
|
if (LcbWriteHf(hf, pbTree, cntFlags.cCntItems) !=
|
|
(LONG) cntFlags.cCntItems) {
|
|
rc = rcNoPermission;
|
|
break;
|
|
}
|
|
}
|
|
if (RcCloseHf(hf) != rcSuccess) {
|
|
hf = 0; // Don't try to close it again
|
|
rc = rcNoPermission;
|
|
break;
|
|
}
|
|
hf = 0;
|
|
if (pFileInfo) {
|
|
|
|
// Write file type and time stamp information
|
|
|
|
hf = HfCreateFileHfs(hfsDst, txtFileInfo, fFSOpenReadWrite);
|
|
|
|
if (hf) {
|
|
if (LcbWriteHf(hf, pFileInfo, sizeof(GID_FILE_INFO) * MAX_FILES) !=
|
|
sizeof(GID_FILE_INFO) * MAX_FILES) {
|
|
rc = rcNoPermission;
|
|
break;
|
|
}
|
|
if (RcCloseHf(hf) != rcSuccess) {
|
|
hf = 0; // Don't try to close it again
|
|
rc = rcNoPermission;
|
|
break;
|
|
}
|
|
hf = 0;
|
|
}
|
|
}
|
|
|
|
if (RcCloseHfs(hfsDst) != rcSuccess) {
|
|
hfsDst = 0; // Don't try to close it again
|
|
rc = rcNoPermission;
|
|
break;
|
|
}
|
|
|
|
{
|
|
char szGidFile[MAX_PATH];
|
|
strcpy(szGidFile, szTmpFile);
|
|
ChangeExtension(szGidFile, txtGidExtension);
|
|
|
|
// Change file attributes so that we can delete it
|
|
|
|
SetFileAttributes(szGidFile, FILE_ATTRIBUTE_NORMAL);
|
|
DeleteFile(szGidFile); // remove any existing .GID file
|
|
if (!MoveFile(szTmpFile, szGidFile)) {
|
|
|
|
/*
|
|
* If we can't rename, then it may be because WinHelp
|
|
* is already running. Close all instances of help
|
|
* and then try again.
|
|
*/
|
|
|
|
EnumWindows((WNDENUMPROC) NotifyWinHelp, WM_DESTROY);
|
|
Sleep(500);
|
|
DeleteFile(szGidFile); // remove any existing .GID file
|
|
if (!MoveFile(szTmpFile, szGidFile)) {
|
|
BOOL fCopy = CopyFile(szTmpFile, szGidFile, FALSE);
|
|
DeleteFile(szTmpFile);
|
|
if (!fCopy) {
|
|
StopAnimation();
|
|
Error(wERRS_CANT_RENAME, wERRA_DIE);
|
|
}
|
|
}
|
|
}
|
|
SetFileAttributes(szGidFile, FILE_ATTRIBUTE_HIDDEN);
|
|
|
|
pszNewGid = LocalStrDup(szGidFile);
|
|
}
|
|
#ifdef _PRIVATE
|
|
{
|
|
char szMsg[256];
|
|
wsprintf(szMsg, "Cnt items: %s\r\n", FormatNumber(cntFlags.cCntItems));
|
|
SendStringToParent(szMsg);
|
|
wsprintf(szMsg, "Keywords: %s\r\n", FormatNumber(cIndexes));
|
|
SendStringToParent(szMsg);
|
|
}
|
|
#endif
|
|
StopAnimation();
|
|
return pszNewGid;
|
|
}
|
|
|
|
StopAnimation();
|
|
|
|
// We only get here if there are error conditions
|
|
|
|
if (hbtTabDialogs) {
|
|
RcCloseBtreeHbt(hbtTabDialogs);
|
|
hbtTabDialogs = NULL;
|
|
}
|
|
if (hf)
|
|
RcCloseHf(hf);
|
|
if (hfsDst)
|
|
RcCloseHfs(hfsDst);
|
|
DeleteFile(szTmpFile);
|
|
|
|
if (rc == rcOutOfMemory)
|
|
OOM();
|
|
else if (rc != rcFailure) // rcFailure has already been reported
|
|
ErrorVarArgs(wERRS_GIND_CABT_WRITE, wERRA_DIE, szTmpFile);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: AddContentsFile
|
|
|
|
PURPOSE: Process the .CNT file, adding entries to the hfsDst GID file.
|
|
|
|
PARAMETERS:
|
|
pszMasterFile
|
|
hfsDst
|
|
|
|
RETURNS: TRUE if successful. pTblFiles will be non-null if there
|
|
were :Index or :Link files specified.
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
25-Nov-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
const int CNT_ANIMATECOUNT = 60; // Lines to process before animation frame
|
|
|
|
const UINT INCR_ITEMS = 8000;
|
|
|
|
const int MAX_NEST_INPUT = 2;
|
|
|
|
typedef struct {
|
|
CInput* pinput;
|
|
PSTR pszBase;
|
|
} NEST_INPUT;
|
|
static NEST_INPUT ainput[MAX_NEST_INPUT + 1];
|
|
|
|
BOOL STDCALL AddContentsFile(PSTR pszMasterFile, HFS hfsDst, BOOL* pfValidIndexes)
|
|
{
|
|
BOOL fSeenBase = FALSE;
|
|
BOOL fSeenTitle = FALSE;
|
|
int curInput = 0;
|
|
CTable tblMissingFiles;
|
|
CTable tblExistingFiles;
|
|
PSTR apszBooks[16];
|
|
|
|
ZeroMemory(apszBooks, sizeof(apszBooks));
|
|
ZeroMemory(ainput, sizeof(ainput));
|
|
|
|
ainput[curInput].pinput = new CInput(pszMasterFile); // open the contents file
|
|
if (!ainput[curInput].pinput->fInitialized) {
|
|
ErrorVarArgs(wERRS_GIND_OPEN, wERRA_RETURN, pszMasterFile);
|
|
return FALSE;
|
|
}
|
|
|
|
BTREE_PARAMS btpString;
|
|
btpString.hfs = hfsDst;
|
|
|
|
// key is KT_LONG, record is FMT_SZ
|
|
|
|
InitBtreeStruct(&btpString, "Lz");
|
|
|
|
HBT hbtCntText = HbtCreateBtreeSz(txtCntText, &btpString);
|
|
if (!hbtCntText) {
|
|
ReportBtreeError(RcGetBtreeError(), pszMasterFile);
|
|
return FALSE;
|
|
}
|
|
|
|
HBT hbtCntJump = HbtCreateBtreeSz(txtCntJump, &btpString);
|
|
if (!hbtCntJump) {
|
|
ReportBtreeError(RcGetBtreeError(), pszMasterFile);
|
|
RcCloseBtreeHbt(hbtCntText);
|
|
return FALSE;
|
|
}
|
|
|
|
int cMaxItems = INCR_ITEMS;
|
|
|
|
if (pbTree)
|
|
FreeGh(pbTree);
|
|
pbTree = (PBYTE) GhAlloc(GMEM_FIXED, cMaxItems);
|
|
if (!pbTree) {
|
|
OOM();
|
|
return FALSE;
|
|
}
|
|
|
|
char szLine[MAX_CNT_LINE];
|
|
int image, LastBook = 0;
|
|
int SaveImage = 0;
|
|
|
|
if (pTblFiles) {
|
|
delete pTblFiles;
|
|
pTblFiles = NULL;
|
|
}
|
|
|
|
int cLines = 0;
|
|
KEY cCntItems = 1; // so we don't have a key value of zero
|
|
|
|
iFile = 0;
|
|
|
|
#ifdef _DEBUG
|
|
int ii = 0; // so we can break on a certain iteration
|
|
#endif
|
|
|
|
for (;;) {
|
|
#ifdef _DEBUG
|
|
ii++;
|
|
if (ii > 3800)
|
|
lcHeapCheck();
|
|
#endif
|
|
|
|
if (curInput < 0) // set when processing FakeLine
|
|
break;
|
|
|
|
if (!ainput[curInput].pinput->getline(szLine)) {
|
|
delete ainput[curInput].pinput;
|
|
if (ainput[curInput].pszBase)
|
|
lcClearFree(&ainput[curInput].pszBase);
|
|
|
|
if (curInput == 0) {
|
|
if (pszCntBaseFile && !pszCntTitle) {
|
|
|
|
/*
|
|
* If we got a :Base directive, but no title, then
|
|
* we will set up a default :Index entry using the
|
|
* filename as the title.
|
|
*/
|
|
|
|
strcpy(szLine, (LPCSTR)txtIndex);
|
|
strcat(szLine, " ");
|
|
strcat(szLine, pszCntBaseFile);
|
|
strcat(szLine, "=");
|
|
strcat(szLine, pszCntBaseFile);
|
|
lcClearFree(&pszCntBaseFile);
|
|
curInput--;
|
|
goto FakeLine;
|
|
}
|
|
if (pszCntBaseFile)
|
|
lcClearFree(&pszCntBaseFile);
|
|
if (pszCntTitle)
|
|
lcClearFree(&pszCntTitle);
|
|
|
|
break; // we're all done.
|
|
}
|
|
else {
|
|
curInput--;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
FakeLine:
|
|
if (++cLines >= CNT_ANIMATECOUNT) {
|
|
NextAnimation();
|
|
cLines = 0;
|
|
}
|
|
|
|
// REVIEW: how about no warnings and let the help compiler do the
|
|
// testing and complaining?
|
|
|
|
if (isdigit((BYTE) szLine[0]) &&
|
|
atoi(szLine) > MAX_LEVELS || szLine[0] == '0') {
|
|
continue; // context string not specified
|
|
}
|
|
|
|
if (szLine[0] == ':') {
|
|
int cb;
|
|
PSTR psz = StrRChrDBCS(szLine, ';');
|
|
if (psz && (psz == szLine || psz[-1] != '\\'))
|
|
*psz = '\0'; // remove any comment
|
|
|
|
if ((cb = CompareSz(szLine, (LPCSTR) txtInclude))) {
|
|
|
|
/*
|
|
* Silently ignore failures and nesting too deep. We're
|
|
* going to let the compiler do syntax checking of the .CNT
|
|
* file and it should be responsible for the nest check.
|
|
*/
|
|
|
|
if (curInput >= MAX_NEST_INPUT)
|
|
continue;
|
|
|
|
FM fm = DoesFileExist(FirstNonSpace(szLine + cb),
|
|
DIR_SILENT_INI | DIR_PATH | DIR_CURRENT | DIR_SILENT_REG);
|
|
if (fm) {
|
|
ainput[++curInput].pinput = new CInput(PszFromGh(fm));
|
|
DisposeFm(fm);
|
|
if (!ainput[curInput].pinput->fInitialized) {
|
|
--curInput;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ContentsCmdLine(szLine, hbtCntText, &fSeenBase, &fSeenTitle,
|
|
hfsDst, pszMasterFile, pfValidIndexes, curInput);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Ignore everything else if we have overflowed
|
|
|
|
/*
|
|
* If the line doesn't start with a digit, or if it contains an
|
|
* '=' character, then it is a topic.
|
|
*/
|
|
|
|
if (!isdigit((BYTE) szLine[0]) || FindEqCharacter(szLine)) {
|
|
|
|
// *** TOPIC LINE ************************************************
|
|
|
|
// Ignore everything after a semi-colon
|
|
|
|
PSTR psz = StrChrDBCS(szLine, ';');
|
|
if (psz) {
|
|
while (psz && psz > szLine && psz[-1] == '\\') {
|
|
strcpy(psz - 1, psz); // remove the backslash
|
|
psz = StrChrDBCS(psz, ';');
|
|
}
|
|
if (psz)
|
|
*psz = '\0';
|
|
}
|
|
|
|
/*
|
|
* It would be more efficient to check if we have two spaces,
|
|
* and if so, just shift over to the beginning of two spaces,
|
|
* but by doing so we add more code overhead for an unlikely
|
|
* case -- i.e., the HCW Contents editor should prevent lines
|
|
* like this from ever occurring.
|
|
*/
|
|
|
|
if (szLine[0] == ' ')
|
|
strcpy(szLine, FirstNonSpace(szLine));
|
|
|
|
if (!szLine[0])
|
|
continue; // ignore blank lines
|
|
|
|
if (!isdigit((BYTE) szLine[0])) {
|
|
image = LastBook + 1; // set to the same as the previous book
|
|
}
|
|
else {
|
|
image = szLine[0] - '0';
|
|
strcpy(szLine, FirstNonSpace(szLine + 1));
|
|
if (image > LastBook + 1)
|
|
image = LastBook + 1;
|
|
}
|
|
|
|
if (!(psz = StrChrDBCS(szLine, '=')))
|
|
continue; // context string not specified
|
|
|
|
while (psz && psz > szLine && psz[-1] == '\\') {
|
|
strcpy(psz - 1, psz); // remove the backslash
|
|
psz = StrChrDBCS(psz, '=');
|
|
}
|
|
if (!psz)
|
|
continue;
|
|
|
|
// Remove any space between '=' and context string
|
|
|
|
else if (psz[1] == ' ')
|
|
strcpy(psz + 1, FirstNonSpace(psz + 2));
|
|
|
|
PSTR pszFile = StrChrDBCS(psz, FILESEPARATOR);
|
|
|
|
/*
|
|
* If this is a nested .CNT file with its own :BASE command,
|
|
* then we need to add that :Base filename to every topic that
|
|
* doesn't specify one.
|
|
*/
|
|
|
|
if (!pszFile && ainput[curInput].pszBase) {
|
|
strcat(psz + 1, "@");
|
|
strcat(psz + 1, ainput[curInput].pszBase);
|
|
pszFile = StrChrDBCS(psz + 1, FILESEPARATOR);
|
|
}
|
|
|
|
// If file is specified, and no windows separator, and no
|
|
// extension, then add the .HLP extension.
|
|
|
|
if (pszFile && !StrChrDBCS(pszFile, WINDOWSEPARATOR) &&
|
|
!StrChrDBCS(pszFile, '.'))
|
|
ChangeExtension(pszFile, txtHlpExtension);
|
|
|
|
if (!pszFile)
|
|
pszFile = StrChrDBCS(psz, WINDOWSEPARATOR);
|
|
char chSeparator;
|
|
if (pszFile) {
|
|
|
|
// Remove spaces between end of context string and beginning
|
|
// of window or filename specification.
|
|
|
|
PSTR pszTmp = pszFile - 1;
|
|
while (*pszTmp == ' ')
|
|
pszTmp--;
|
|
if (pszTmp < pszFile - 1) {
|
|
strcpy(pszTmp + 1, pszFile);
|
|
pszFile = pszTmp + 1;
|
|
}
|
|
chSeparator = *pszFile;
|
|
*pszFile = '\0';
|
|
}
|
|
|
|
if (psz[1] != chMACRO && !FValidContextSz(psz + 1)) {
|
|
char szMsg[512];
|
|
GetStringResource2(wERRS_INVALID_CTX, szMsg);
|
|
strcat(szMsg, szLine);
|
|
AuthorMsg(szMsg, hwndAnimate);
|
|
continue; // context string invalid
|
|
}
|
|
|
|
// Find out if the file exists. Don't include this line if
|
|
// it doesn't exist.
|
|
|
|
if (pszFile) {
|
|
if (chSeparator == FILESEPARATOR) {
|
|
PSTR pszTmp = pszFile + 1;
|
|
PSTR pszSep = StrChrDBCS(pszTmp, WINDOWSEPARATOR);
|
|
if (pszSep)
|
|
*pszSep = '\0';
|
|
|
|
if (tblMissingFiles.IsStringInTable(pszTmp))
|
|
continue; // The help file doesn't exist
|
|
else if (!tblExistingFiles.IsStringInTable(pszTmp)) {
|
|
FM fm = DoesFileExist(pszTmp,
|
|
DIR_INI | DIR_PATH | DIR_CURRENT | DIR_SILENT_REG);
|
|
if (fm) {
|
|
tblExistingFiles.AddString(pszTmp);
|
|
DisposeFm(fm);
|
|
}
|
|
else {
|
|
tblMissingFiles.AddString(pszTmp);
|
|
continue;
|
|
}
|
|
}
|
|
if (pszSep)
|
|
*pszSep = WINDOWSEPARATOR;
|
|
}
|
|
*pszFile = chSeparator;
|
|
}
|
|
|
|
// If we had a book pending, and this topic is a subset of that
|
|
// book, then save the book now.
|
|
|
|
if (apszBooks[SaveImage]) {
|
|
if (image >= SaveImage) {
|
|
SaveBooks:
|
|
// We have a topic. Write out any books above it.
|
|
|
|
int i;
|
|
|
|
// REVIEW: can there ever be a zero-level book?
|
|
|
|
ASSERT(!apszBooks[0]);
|
|
ASSERT(SaveImage < sizeof(apszBooks) / sizeof(PSTR));
|
|
for (i = 1; i <= SaveImage; i++) {
|
|
if (apszBooks[i]) {
|
|
pbTree[cCntItems] = (BYTE) ((i << 4) | IMAGE_CLOSED_FOLDER);
|
|
RcInsertHbt(hbtCntText, (KEY) (LPVOID) &cCntItems,
|
|
apszBooks[i]);
|
|
cCntItems++;
|
|
if (cCntItems >= cMaxItems) {
|
|
cMaxItems += INCR_ITEMS;
|
|
pbTree = (PBYTE) PtrFromGh(GhResize(pbTree, 0, cMaxItems));
|
|
}
|
|
lcClearFree(&apszBooks[i]);
|
|
}
|
|
}
|
|
SaveImage = 0; // we've flushed all of our saves
|
|
}
|
|
else {
|
|
|
|
/*
|
|
* Remove books at a higher level then this topic
|
|
* since they didn't have any topics associated with
|
|
* them.
|
|
*/
|
|
|
|
while (SaveImage > image) {
|
|
if (apszBooks[SaveImage]) {
|
|
lcClearFree(&apszBooks[SaveImage]);
|
|
}
|
|
SaveImage--;
|
|
}
|
|
goto SaveBooks;
|
|
}
|
|
}
|
|
|
|
pbTree[cCntItems] = (BYTE) ((image << 4) | IMAGE_TOPIC);
|
|
|
|
*psz = '\0'; // remove the '=';
|
|
|
|
PSTR pszContext = psz + 1;
|
|
|
|
// Remove trailing spaces
|
|
|
|
psz = szLine + strlen(szLine) - 1;
|
|
while (*psz == ' ')
|
|
*psz-- = '\0';
|
|
|
|
RcInsertHbt(hbtCntJump, (KEY) (LPVOID) &cCntItems, pszContext);
|
|
RcInsertHbt(hbtCntText, (KEY) (LPVOID) &cCntItems, szLine);
|
|
cCntItems++;
|
|
}
|
|
else {
|
|
|
|
// *** BOOK LINE ************************************************************
|
|
|
|
// Remove all escaped characters
|
|
|
|
PSTR psz;
|
|
while ((psz = strstr(szLine, "\\=")) || (psz = strstr(szLine, "\\;")))
|
|
strcpy(psz, psz + 1);
|
|
|
|
if (!isdigit((BYTE) szLine[0])) {
|
|
image = 1;
|
|
}
|
|
else {
|
|
image = szLine[0] - '0';
|
|
}
|
|
if (image > 15) // out of range
|
|
continue;
|
|
if (image < 1)
|
|
image = 1;
|
|
|
|
if (image > LastBook + 1) // Don't skip levels
|
|
image = LastBook + 1;
|
|
|
|
/*
|
|
* We don't write out any books here, because we don't know
|
|
* yet if they contain any topics. We can assume that any books
|
|
* higher in level then our current book had no topics and should
|
|
* be removed.
|
|
*/
|
|
|
|
if (apszBooks[image])
|
|
FreeLh(apszBooks[image]);
|
|
ASSERT(image < sizeof(apszBooks) / sizeof(PSTR));
|
|
apszBooks[image] = LocalStrDup(FirstNonSpace(szLine + 1));
|
|
ASSERT(SaveImage < sizeof(apszBooks) / sizeof(PSTR));
|
|
while (SaveImage > image) {
|
|
if (apszBooks[SaveImage]) {
|
|
lcClearFree(&apszBooks[SaveImage]);
|
|
}
|
|
SaveImage--;
|
|
}
|
|
|
|
SaveImage = image;
|
|
LastBook = image;
|
|
}
|
|
|
|
if (cCntItems >= cMaxItems) {
|
|
cMaxItems += INCR_ITEMS;
|
|
pbTree = (PBYTE) PtrFromGh(GhResize(pbTree, 0, cMaxItems));
|
|
}
|
|
}
|
|
RcCloseBtreeHbt(hbtCntText);
|
|
RcCloseBtreeHbt(hbtCntJump);
|
|
cntFlags.cCntItems = (cCntItems > 1) ? cCntItems : 0 ;
|
|
|
|
|
|
/*
|
|
* Free unused memory in the tree array. Ultimately, we're going to free
|
|
* this, but not until after we have added the global index, so we try
|
|
* to free up some memory here.
|
|
*/
|
|
|
|
pbTree = (PBYTE) PtrFromGh(GhResize(pbTree, 0, cntFlags.cCntItems));
|
|
|
|
{
|
|
// Create a BTREE for window positions, but don't actually save
|
|
// anything at this point.
|
|
|
|
BTREE_PARAMS btpWinPos;
|
|
btpWinPos.hfs = hfsDst;
|
|
|
|
// key is string with 10-byte record (sizeof(POS_RECT))
|
|
|
|
InitBtreeStruct(&btpWinPos, "za");
|
|
|
|
HBT hbt = HbtCreateBtreeSz(txtWinPos, &btpWinPos);
|
|
if (!hbt) {
|
|
ReportBtreeError(RcGetBtreeError(), pszMasterFile);
|
|
RcCloseBtreeHbt(hbt);
|
|
return FALSE;
|
|
}
|
|
RcCloseBtreeHbt(hbt);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: ContentsCmdLine
|
|
|
|
PURPOSE: Process a command line in the contents file
|
|
|
|
PARAMETERS:
|
|
pszLine
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
:Title -- contains the help title to display in the dialog box.
|
|
This is added to "|CntText" under CNT_TITLE key
|
|
|
|
:Base -- contains the base help file -- this will be used in
|
|
the absence of an interfile jump. If not specified,
|
|
the contents base name plus a .HLP extension will be
|
|
used. This is added to "|CntJump" under CNT_BASE key
|
|
|
|
:Index -- contains an index tab name followed by an '=' followed
|
|
by a help file name. If none are specified, the word
|
|
"Index" is used in conjunction with the Base help file.
|
|
|
|
:Tab -- Contains tab text followed by an '=' followed by a
|
|
routine name and a dll name. Used to extend the Tab control.
|
|
|
|
There can be multiple :Index commands. If there are multiple :Title
|
|
or :Base commands, the first one entered takes precedence.
|
|
|
|
MODIFICATION DATES:
|
|
17-Aug-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
INLINE static void STDCALL ContentsCmdLine(PSTR pszLine,
|
|
HBT hbtCntText, BOOL* pfSeenBase, BOOL* pfSeenTitle,
|
|
HFS hfsDst, PCSTR pszMasterFile, BOOL* pfValidIndexes, int curInput)
|
|
{
|
|
int cb;
|
|
PSTR psz;
|
|
char szMsg[512];
|
|
KEY key;
|
|
|
|
/*
|
|
* Note that only the first occurence of a :title and :base
|
|
* specification is allowed. This is so you can use a .CNT for a single
|
|
* file and also merge it in unchanged with a master .CNT file.
|
|
*/
|
|
|
|
if ((cb = CompareSz(pszLine, (LPCSTR)txtTitle))) {
|
|
if (!*pfSeenTitle) {
|
|
key = CNT_TITLE;
|
|
pszCntTitle = lcStrDup(FirstNonSpace(pszLine + cb));
|
|
RcInsertHbt(hbtCntText, (KEY) &key, pszCntTitle);
|
|
*pfSeenTitle = TRUE;
|
|
|
|
/*
|
|
* Once we have a base and a title, then turn this into an
|
|
* Index.
|
|
*/
|
|
|
|
if (*pfSeenBase) {
|
|
ConvertToFakeIndex:
|
|
strcpy(pszLine, (LPCSTR)txtIndex);
|
|
strcat(pszLine, " ");
|
|
strcat(pszLine, pszCntTitle);
|
|
strcat(pszLine, "=");
|
|
strcat(pszLine, pszCntBaseFile);
|
|
goto FakeIndex;
|
|
}
|
|
}
|
|
}
|
|
else if ((cb = CompareSz(pszLine, (LPCSTR)txtBase))) {
|
|
if (!*pfSeenBase) {
|
|
key = CNT_BASE;
|
|
if (!StrChrDBCS(pszLine + cb, '.'))
|
|
ChangeExtension(pszLine + cb, txtHlpExtension);
|
|
pszCntBaseFile = lcStrDup(FirstNonSpace(pszLine + cb));
|
|
RcInsertHbt(hbtCntText, (KEY) (LPVOID) &key, pszCntBaseFile);
|
|
PSTR psz = StrChrDBCS(pszCntBaseFile, '>');;
|
|
if (psz)
|
|
*psz = '\0';
|
|
*pfSeenBase = TRUE;
|
|
if (*pfSeenTitle)
|
|
goto ConvertToFakeIndex;
|
|
}
|
|
else if (curInput > 0 && !ainput[curInput].pszBase) {
|
|
if (!StrChrDBCS(pszLine + cb, '.'))
|
|
ChangeExtension(pszLine + cb, txtHlpExtension);
|
|
ainput[curInput].pszBase = lcStrDup(FirstNonSpace(pszLine + cb));
|
|
}
|
|
}
|
|
else if ((cb = CompareSz(pszLine, (LPCSTR)txtIndex))) {
|
|
FakeIndex:
|
|
if (!pTblFiles)
|
|
pTblFiles = new CTable();
|
|
|
|
psz = StrChrDBCS(pszLine, '=');
|
|
if (!psz) {
|
|
MissingEqual:
|
|
return;
|
|
}
|
|
while (psz && psz > pszLine && psz[-1] == '\\') {
|
|
strcpy(psz - 1, psz); // remove the backslash
|
|
psz = StrChrDBCS(psz, '=');
|
|
}
|
|
|
|
PSTR pszExt = StrChrDBCS(psz, '.');
|
|
if (!pszExt)
|
|
ChangeExtension(psz, txtHlpExtension);
|
|
|
|
pszExt = FirstNonSpace(psz + 1);
|
|
|
|
do
|
|
psz--;
|
|
while (*psz == ' ');
|
|
psz[1] = '\0';
|
|
|
|
CStr cszTitle(FirstNonSpace(pszLine + cb));
|
|
|
|
FM fm = DoesFileExist(pszExt,
|
|
DIR_INI | DIR_PATH | DIR_CURRENT | DIR_SILENT_REG);
|
|
|
|
if (fm) {
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hfd;
|
|
if (!pTblFiles->IsSecondaryStringInTable(fm)) {
|
|
|
|
pFileInfo[iFile].filetype = CHFLAG_INDEX;
|
|
|
|
if ((hfd = FindFirstFile(fm, &fd)) == INVALID_HANDLE_VALUE)
|
|
pFileInfo[iFile].filetype |= CHFLAG_MISSING;
|
|
else {
|
|
AdjustForTimeZoneBias(&fd.ftLastWriteTime.dwLowDateTime);
|
|
pFileInfo[iFile].timestamp = fd.ftLastWriteTime.dwLowDateTime;
|
|
FindClose(hfd);
|
|
}
|
|
|
|
pTblFiles->AddString(cszTitle.psz, fm);
|
|
(*pfValidIndexes)++;
|
|
iFile++;
|
|
}
|
|
DisposeFm(fm);
|
|
}
|
|
else {
|
|
|
|
// REVIEW: what if .CNT and .GID are in different directories?
|
|
|
|
if (!hwndParent) {
|
|
wsprintf(szMsg, GetStringResource(wERRS_INDEX_NOT_FOUND),
|
|
AnsiUpper(pszExt), (LPSTR) pszMasterFile);
|
|
SendStringToParent(szMsg);
|
|
}
|
|
|
|
if (!pTblFiles->IsSecondaryStringInTable(pszExt)) {
|
|
// REVIEW: does pszExt point to the correct name?
|
|
|
|
// add the filename, but indicate it is missing
|
|
|
|
pFileInfo[iFile].filetype |=
|
|
(CHFLAG_INDEX | CHFLAG_MISSING);
|
|
|
|
pTblFiles->AddString(cszTitle.psz, pszExt);
|
|
iFile++;
|
|
}
|
|
}
|
|
}
|
|
else if ((cb = CompareSz(pszLine, (LPCSTR)txtLink))) {
|
|
if (!pTblFiles)
|
|
pTblFiles = new CTable();
|
|
psz = StrChrDBCS(pszLine, ' ');
|
|
if (!psz) {
|
|
return;
|
|
}
|
|
psz = FirstNonSpace(psz + 1);
|
|
|
|
PSTR pszExt = StrChrDBCS(psz, '.');
|
|
if (!pszExt)
|
|
ChangeExtension(psz, txtHlpExtension);
|
|
|
|
FM fm = DoesFileExist(psz,
|
|
DIR_SILENT_INI | DIR_PATH | DIR_CURRENT | DIR_SILENT_REG);
|
|
|
|
if (fm)
|
|
strcpy(pszLine, PszFromGh(fm));
|
|
else
|
|
strcpy(pszLine, psz);
|
|
|
|
if (!pTblFiles->IsSecondaryStringInTable(pszLine)) {
|
|
pFileInfo[iFile].filetype = CHFLAG_LINK;
|
|
pTblFiles->AddString(txtZeroLength, pszLine);
|
|
iFile++;
|
|
}
|
|
DisposeFm(fm);
|
|
}
|
|
else if ((cb = CompareSz(pszLine, (LPCSTR)txtTab))) {
|
|
if (!hbtTabDialogs) {
|
|
BTREE_PARAMS btpString;
|
|
btpString.hfs = hfsDst;
|
|
|
|
// key is KT_LONG, record is FMT_SZ
|
|
|
|
InitBtreeStruct(&btpString, "Lz");
|
|
|
|
hbtTabDialogs = HbtCreateBtreeSz(txtTabDlgs, &btpString);
|
|
if (!hbtTabDialogs) {
|
|
ReportBtreeError(RcGetBtreeError(), pszMasterFile);
|
|
return;
|
|
}
|
|
}
|
|
// Remove all spaces before and after the '='
|
|
|
|
psz = StrChrDBCS(pszLine, '=');
|
|
if (!psz)
|
|
goto MissingEqual;
|
|
while (psz && psz > pszLine && psz[-1] == '\\') {
|
|
strcpy(psz - 1, psz); // remove the backslash
|
|
psz = StrChrDBCS(psz, '=');
|
|
}
|
|
|
|
if (psz[1] == ' ')
|
|
strcpy(psz + 1, FirstNonSpace(psz + 1));
|
|
PSTR pszTmp = psz - 1;
|
|
if (*pszTmp == ' ') {
|
|
while (*pszTmp == ' ' && pszTmp > pszLine)
|
|
pszTmp--;
|
|
strcpy(pszTmp + 1, psz);
|
|
}
|
|
cntFlags.cTabs++;
|
|
RcInsertHbt(hbtTabDialogs, (KEY) (LPVOID) &cntFlags.cTabs,
|
|
FirstNonSpace(pszLine + cb));
|
|
}
|
|
else if ((cb = CompareSz(pszLine, (LPCSTR)txtNoFind))) {
|
|
cntFlags.flags &= ~GID_FTS;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: InitHelpFile
|
|
|
|
PURPOSE: Initialize a help file that will be added to the .GID file
|
|
|
|
PARAMETERS:
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
29-Nov-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
INLINE static BOOL STDCALL InitHelpFile(
|
|
int iFile,
|
|
HFS* phfsSrc,
|
|
HBT* phbtKeySrc,
|
|
HF* phfDataSrc,
|
|
HBT hbtFilesDst)
|
|
{
|
|
BOOL fResult;
|
|
|
|
CFM fmHelp(pTblFiles->GetPointer(iFile), DIR_INI | DIR_PATH);
|
|
if (!fmHelp.fm) {
|
|
pFileInfo[iFile / 2 - 1].filetype |= CHFLAG_MISSING;
|
|
fResult = FALSE;
|
|
goto SaveName;
|
|
}
|
|
|
|
/*
|
|
* From here to SaveName, anything that fails will result in us just
|
|
* tossing the help file. Reason is that if the help file is corrupted
|
|
* and we can't open one of the internal files, then we'd end up trying
|
|
* to generate the .GID file everytime we opened up help. So, we just
|
|
* silently ignore the file.
|
|
*/
|
|
|
|
if (!(*phfsSrc = HfsOpenFm(fmHelp.fm, fFSOpenReadOnly)))
|
|
return FALSE;
|
|
|
|
*phbtKeySrc = HbtOpenBtreeSz(txtKEYWORDBTREE, *phfsSrc, fFSOpenReadOnly);
|
|
*phfDataSrc = HfOpenHfs(*phfsSrc, txtKWDATA, fFSOpenReadOnly);
|
|
|
|
if (!*phfDataSrc || !*phbtKeySrc) {
|
|
RcCloseBtreeHbt(*phbtKeySrc);
|
|
RcCloseHfs(*phfsSrc);
|
|
fResult = FALSE;
|
|
}
|
|
else
|
|
fResult = TRUE;
|
|
|
|
SaveName:
|
|
HELPFILE_DIRECTORY_ENTRY hfde;
|
|
|
|
// Create a title=filename string
|
|
|
|
strcpy(hfde.szFileName, pTblFiles->GetPointer(iFile - 1));
|
|
strcat(hfde.szFileName, "=");
|
|
strcat(hfde.szFileName, pTblFiles->GetPointer(iFile));
|
|
|
|
// Save this info in the GID file
|
|
|
|
KEY key = (KEY) (iFile / 2);
|
|
RcInsertHbt(hbtFilesDst, (KEY) (LPVOID) &key, &hfde);
|
|
return fResult;
|
|
}
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: AddIndexes
|
|
|
|
PURPOSE:
|
|
|
|
PARAMETERS:
|
|
hfsDst -- GID file
|
|
pszMasterFile -- temporary filename we are writing to
|
|
|
|
RETURNS: TRUE on success
|
|
|
|
COMMENTS:
|
|
for each helpfile
|
|
for each keyword in helpfile
|
|
for each hit for this keyword
|
|
generate a master address for the hit
|
|
if it's new, add to master title btree
|
|
look up the keyword in master keyword btree
|
|
if exists, update with new hits
|
|
otherwise, add new hits
|
|
|
|
The temporary btree hbtT allows us to tell whether a topic already
|
|
has been referenced. The first time it's referenced we add it to the
|
|
title btree
|
|
|
|
MODIFICATION DATES:
|
|
25-Nov-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
const int INDEX_ANIMATECOUNT = 50; // rep's before next animation frame
|
|
|
|
#define KEY_BLOCKDEFAULT 4096
|
|
|
|
static BOOL STDCALL AddIndexes(HFS hfsDst, PCSTR pszMasterFile, HBT hbtFilesDst)
|
|
{
|
|
int cIndexFiles = 0;
|
|
int i;
|
|
LCID lcidSystem = GetUserDefaultLCID();
|
|
LCID lcidSave = lcid;
|
|
|
|
#ifdef _PRIVATE
|
|
cIndexes = 0;
|
|
#endif
|
|
|
|
// If no files have been defined, then add the current help file
|
|
|
|
if (!pTblFiles || pTblFiles->CountStrings() < 2) {
|
|
ASSERT(fmCreating);
|
|
|
|
if (!pTblFiles)
|
|
pTblFiles = new CTable();
|
|
|
|
// Save the filename
|
|
|
|
pFileInfo[0].filetype = CHFLAG_INDEX;
|
|
// Note that we don't care about the title
|
|
pTblFiles->AddString(txtZeroLength, PszFromGh(fmCreating));
|
|
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hfd;
|
|
|
|
if ((hfd = FindFirstFile(fmCreating, &fd)) == INVALID_HANDLE_VALUE)
|
|
pFileInfo[0].filetype |= CHFLAG_MISSING;
|
|
else {
|
|
AdjustForTimeZoneBias(&fd.ftLastWriteTime.dwLowDateTime);
|
|
pFileInfo[0].timestamp = fd.ftLastWriteTime.dwLowDateTime;
|
|
FindClose(hfd);
|
|
}
|
|
}
|
|
|
|
// Find out how many files there are. We count missing ones, since
|
|
// they might show up later.
|
|
|
|
for (i = 2; i <= pTblFiles->CountStrings(); i += 2) {
|
|
if (pFileInfo[i / 2 - 1].filetype == CHFLAG_INDEX) {
|
|
if (++cIndexFiles > 1)
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0 // We now always sort keywords and stuff them in the .gid file
|
|
|
|
// If there's only one file, then we may not need to create a global
|
|
// index
|
|
|
|
if (cIndexFiles < 2) {
|
|
HFS hfsSrc;
|
|
HBT hbtKeySrc;
|
|
KT kt = KT_NLSI;
|
|
|
|
#ifdef _DEBUG
|
|
PSTR pszFile = pTblFiles->GetPointer(2);
|
|
#endif
|
|
CFM fmHelp(pTblFiles->GetPointer(2), DIR_INI | DIR_PATH);
|
|
if (!fmHelp.fm)
|
|
goto NoGidIndex;
|
|
|
|
if (!(hfsSrc = HfsOpenFm(fmHelp.fm, fFSOpenReadOnly)))
|
|
goto NoGidIndex;
|
|
|
|
hbtKeySrc = HbtOpenBtreeSz(txtKEYWORDBTREE, hfsSrc, fFSOpenReadOnly);
|
|
if (hbtKeySrc) {
|
|
QBTHR qbthr = (QBTHR) PtrFromGh(hbtKeySrc);
|
|
kt = (KT) qbthr->bth.rgchFormat[0];
|
|
RcCloseBtreeHbt(hbtKeySrc);
|
|
}
|
|
else
|
|
cntFlags.flags |= GID_NO_INDEX;
|
|
RcCloseHfs(hfsSrc);
|
|
|
|
/*
|
|
* If the kt values are SZ, and the current locale is anthing other
|
|
* then English, then we will assume sorting is incorrect, and we will
|
|
* force NLS sorting. Only a NLS-sorted help file will not be resorted
|
|
* on a non-US system.
|
|
*/
|
|
|
|
switch (kt) {
|
|
case KT_SZ:
|
|
case KT_SZI:
|
|
goto ForceGidIndex;
|
|
|
|
case KT_NLSI:
|
|
case KT_NLS:
|
|
goto NoGidIndex;
|
|
|
|
#ifdef _DEBUG
|
|
case KT_LONG:
|
|
case '1': case '2': case '3': case '4': case '5': // assume null term
|
|
case '6': case '7': case '8': case '9': case 'a':
|
|
case 'b': case 'c': case 'd': case 'e': case 'f':
|
|
case KT_SZDEL: // assume keys have been expanded for delta codeds
|
|
case KT_SZDELMIN:
|
|
case KT_SZMIN:
|
|
ASSERT(!"Invalid KT value");
|
|
|
|
// fall through
|
|
#endif
|
|
|
|
default:
|
|
goto ForceGidIndex;
|
|
}
|
|
|
|
// Add all, in case there is only one :Index, but one or more
|
|
// :Link files.
|
|
|
|
NoGidIndex:
|
|
for (i = 2; i <= pTblFiles->CountStrings(); i += 2) {
|
|
HELPFILE_DIRECTORY_ENTRY hfde;
|
|
strcpy(hfde.szFileName, pTblFiles->GetPointer(i - 1));
|
|
strcat(hfde.szFileName, "=");
|
|
strcat(hfde.szFileName, pTblFiles->GetPointer(i));
|
|
|
|
// Save this info in the GID file
|
|
|
|
KEY key = (KEY) i / 2;
|
|
ENSURE(RcInsertHbt(hbtFilesDst, (KEY) (LPVOID) &key, &hfde),
|
|
rcSuccess);
|
|
}
|
|
return FALSE; // no global index
|
|
}
|
|
|
|
ForceGidIndex:
|
|
|
|
#endif // #if 0
|
|
|
|
// REVIEW: allocated size doesn't make sense -- looks like help compiler
|
|
// uses a block twice this size. We should increase this and see what
|
|
// happens. Might end up faster if we use a 2 or 4 K block instead of 1K.
|
|
|
|
CGMem memKey(KEY_BLOCKDEFAULT / 2);
|
|
if (!memKey.hmem) {
|
|
OOM();
|
|
return FALSE;
|
|
}
|
|
|
|
CGMem memKr(sizeof(MASTER_RECKW));
|
|
if (!memKr.hmem) {
|
|
OOM();
|
|
return FALSE;
|
|
}
|
|
|
|
// allocate ample blocks for helpfile hits (ADDRs)
|
|
|
|
CLMem memAddr(KEY_BLOCKDEFAULT);
|
|
if (!memAddr.pBuf) {
|
|
OOM();
|
|
return FALSE;
|
|
}
|
|
|
|
// Create the keyword btree.
|
|
BTREE_PARAMS btpKeyword;
|
|
btpKeyword.hfs = hfsDst;
|
|
|
|
// key is KT_SZI, record is FMT_DWORD_PREFIX
|
|
|
|
InitBtreeStruct(&btpKeyword, "i!");
|
|
btpKeyword.cbBlock = KEY_BLOCKDEFAULT;
|
|
lcidSave = lcid;
|
|
lcid = lcidSystem;
|
|
|
|
if (lcid)
|
|
btpKeyword.rgchFormat[0] = KT_NLSI; // use NLS sorting
|
|
|
|
HBT hbtKeyDst = HbtInitFill(txtKEYWORDBTREE, &btpKeyword);
|
|
if (!hbtKeyDst) {
|
|
ReportBtreeError(RcGetBtreeError(), pszMasterFile);
|
|
lcid = lcidSave;
|
|
return FALSE;
|
|
}
|
|
|
|
LPSTR pszKey = (LPSTR) memKey.GetPtr();
|
|
MASTER_RECKW* pmkr = (MASTER_RECKW*) memKr.GetPtr();
|
|
ADDR* paddr = (ADDR *) memAddr.pBuf;
|
|
int cAnimate = 0;
|
|
|
|
NextAnimation();
|
|
|
|
// for each helpfile, add keys/titles to master index
|
|
|
|
BOOL fIndexFound = FALSE;
|
|
CTable tblKeywords;
|
|
|
|
for (i = 2; i <= pTblFiles->CountStrings(); i += 2) {
|
|
HFS hfsSrc;
|
|
HBT hbtKeySrc;
|
|
HF hfDataSrc;
|
|
RECKW kwrec;
|
|
MASTER_TITLE_RECORD* pmtr;
|
|
BTPOS btpos;
|
|
DWORD iaddr;
|
|
|
|
pmtr = (MASTER_TITLE_RECORD*) LhAlloc(LMEM_FIXED,
|
|
sizeof(MASTER_TITLE_RECORD));
|
|
|
|
// open source helpfile
|
|
|
|
#ifdef _DEBUG
|
|
PSTR pszFile = pTblFiles->GetPointer(i);
|
|
#endif
|
|
|
|
// This is a missing file or a link file
|
|
|
|
if (pFileInfo[i / 2 - 1].filetype & (CHFLAG_LINK | CHFLAG_MISSING)) {
|
|
HELPFILE_DIRECTORY_ENTRY hfde;
|
|
strcpy(hfde.szFileName, pTblFiles->GetPointer(i - 1));
|
|
strcat(hfde.szFileName, "=");
|
|
strcat(hfde.szFileName, pTblFiles->GetPointer(i));
|
|
|
|
// Save this info in the GID file
|
|
|
|
KEY key = (KEY) (i / 2);
|
|
ENSURE(RcInsertHbt(hbtFilesDst, (KEY) (LPVOID) &key, &hfde),
|
|
rcSuccess);
|
|
continue;
|
|
}
|
|
|
|
if (!InitHelpFile(i, &hfsSrc, &hbtKeySrc, &hfDataSrc, hbtFilesDst))
|
|
|
|
// this helpfile isn't suitable for indexing: skip it
|
|
|
|
continue;
|
|
|
|
fIndexFound = TRUE;
|
|
|
|
// for each keyword in the help file
|
|
|
|
RC rc = RcFirstHbt(hbtKeySrc, (KEY) pszKey, &kwrec, &btpos);
|
|
#ifndef _X86_
|
|
// quick hack
|
|
{
|
|
UNALIGNED long* ulTemp = (QL)((QB)&kwrec+2);
|
|
kwrec.lOffset = *ulTemp ;
|
|
}
|
|
#endif
|
|
QRWFO qrwfo = (QRWFO) PtrFromGh(hfDataSrc);
|
|
|
|
curReadPos = -1; // for a seek the first time
|
|
while (rc == rcSuccess) {
|
|
int cbKey = strlen(pszKey);
|
|
|
|
/*
|
|
* Avoid the temptation to remove the following line. While
|
|
* its unnecessary for most help files, version 3.0 files will
|
|
* fail if you remove this.
|
|
*/
|
|
|
|
qrwfo->lifCurrent = kwrec.lOffset;
|
|
|
|
if (kwrec.iCount * sizeof(ADDR) > (UINT) memAddr.cbCur) {
|
|
memAddr.ReAlloc(kwrec.iCount * sizeof(ADDR) + 1024);
|
|
paddr = (ADDR *) memAddr.pBuf;
|
|
}
|
|
|
|
// read new hits (ADDRs) into paddr
|
|
|
|
if (LcbReadHfSeq(hfDataSrc, paddr, kwrec.iCount * sizeof(ADDR))
|
|
!= (LONG) kwrec.iCount * (LONG) sizeof(ADDR)) {
|
|
rc = RcGetFSError();
|
|
// REVIEW: report what the problem was
|
|
ASSERT(FALSE);
|
|
goto Egress3;
|
|
}
|
|
|
|
// add these hits
|
|
|
|
#ifdef _PRIVATE
|
|
cIndexes++;
|
|
#endif
|
|
|
|
// Make certain we don't overflow our block size
|
|
|
|
if (kwrec.iCount > KEY_BLOCKDEFAULT / sizeof(MASTER_TITLE_RECORD) / 8) {
|
|
|
|
/*
|
|
* 800 is a futz value -- even though we theoretically
|
|
* should be able to handle much larger, there is currently
|
|
* a problem with large blocks causing an failure.
|
|
*/
|
|
|
|
int cMax = min(800, (KEY_BLOCKDEFAULT / sizeof(MASTER_TITLE_RECORD) / 2) -
|
|
((cbKey / sizeof(MASTER_TITLE_RECORD)) + 1) - 1);
|
|
|
|
if (kwrec.iCount > cMax) {
|
|
#ifdef _DEBUG
|
|
char szBuf[256];
|
|
wsprintf(szBuf, "Truncating %u titles from the keyword \042%s\042\r\n",
|
|
kwrec.iCount - cMax, pszKey);
|
|
SendStringToParent(szBuf);
|
|
#endif
|
|
kwrec.iCount = cMax;
|
|
}
|
|
}
|
|
|
|
pmkr->cb = 0;
|
|
|
|
for (iaddr = 0; iaddr < (DWORD) kwrec.iCount; iaddr++) {
|
|
pmkr->mtr[iaddr].idHelpFile = i;
|
|
pmkr->mtr[iaddr].addr = paddr[iaddr];
|
|
ASSERT(iaddr < 800); // we tend to die over 800 entries
|
|
pmkr->cb += sizeof(MASTER_TITLE_RECORD);
|
|
}
|
|
|
|
// add the hits to the master keyword btree
|
|
|
|
int cbData = cbKey + 1 + pmkr->cb + sizeof(DWORD);
|
|
|
|
if (tblKeywords.endpos >= tblKeywords.maxpos)
|
|
tblKeywords.IncreaseTableBuffer();
|
|
|
|
if ((tblKeywords.ppszTable[tblKeywords.endpos] =
|
|
tblKeywords.TableMalloc(cbData)) == NULL) {
|
|
OOM();
|
|
goto Egress2;
|
|
}
|
|
void* pDst = tblKeywords.ppszTable[tblKeywords.endpos++];
|
|
|
|
strcpy((PSTR) pDst, pszKey);
|
|
CopyMemory((PSTR) pDst + cbKey + 1,
|
|
pmkr, sizeof(DWORD) + pmkr->cb);
|
|
|
|
DWORD bkOld = btpos.bk;
|
|
|
|
rc = RcOffsetPosFast(hbtKeySrc, &btpos);
|
|
if (rc != rcSuccess)
|
|
break;
|
|
|
|
if (bkOld != btpos.bk)
|
|
curReadPos = -1; // force a new seek in LcbReadHfSeq
|
|
|
|
rc = RcLookupByPos(hbtKeySrc, &btpos, (KEY) pszKey, &kwrec);
|
|
|
|
// lOffset is there, just need to get it
|
|
#ifndef _X86_
|
|
// quick hack
|
|
{
|
|
UNALIGNED long* ulTemp = (QL) ((QB) &kwrec + 2);
|
|
kwrec.lOffset = *ulTemp ;
|
|
}
|
|
#endif
|
|
if (++cAnimate > INDEX_ANIMATECOUNT) {
|
|
NextAnimation();
|
|
cAnimate = 0;
|
|
}
|
|
}
|
|
|
|
// deal with rc here: if rcNoExists, that's OK
|
|
|
|
if (rc == rcNoExists)
|
|
rc = rcSuccess;
|
|
|
|
ENSURE(RcCloseBtreeHbt(hbtKeySrc), rcSuccess);
|
|
ENSURE(RcCloseHf(hfDataSrc), rcSuccess);
|
|
ENSURE(RcCloseHfs(hfsSrc), rcSuccess);
|
|
}
|
|
|
|
NextAnimation();
|
|
|
|
tblKeywords.SetSorting(lcid, kwlcid.fsCompareI, kwlcid.fsCompare);
|
|
tblKeywords.SortTablei();
|
|
|
|
NextAnimation();
|
|
|
|
int pos;
|
|
for (pos = 1; pos <= tblKeywords.CountStrings(); pos++) {
|
|
#ifdef _DEBUG
|
|
PSTR pszKeyword = tblKeywords.GetPointer(pos);
|
|
#endif
|
|
UNALIGNED MASTER_RECKW* pmkrTmp;
|
|
|
|
/*
|
|
* If we have a duplicate keyword, then we combine all the title
|
|
* addresses together and save it out as a single record.
|
|
*/
|
|
|
|
if (pos < tblKeywords.CountStrings() &&
|
|
#if 0
|
|
strcmp(tblKeywords.GetPointer(pos),
|
|
tblKeywords.GetPointer(pos + 1)) == 0) {
|
|
#else
|
|
CompareStringA(lcid, kwlcid.fsCompareI | NORM_IGNORECASE,
|
|
tblKeywords.GetPointer(pos), -1,
|
|
tblKeywords.GetPointer(pos + 1), - 1) == 2) {
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
PSTR psznextKeyword = tblKeywords.GetPointer(pos + 1);
|
|
#endif
|
|
pmkrTmp = (MASTER_RECKW*)
|
|
(tblKeywords.GetPointer(pos) +
|
|
strlen(tblKeywords.GetPointer(pos)) + 1);
|
|
CopyMemory(pmkr->mtr, pmkrTmp->mtr, pmkrTmp->cb);
|
|
pmkr->cb = pmkrTmp->cb;
|
|
do {
|
|
pos++;
|
|
pmkrTmp = (MASTER_RECKW*)
|
|
(tblKeywords.GetPointer(pos) +
|
|
strlen(tblKeywords.GetPointer(pos)) + 1);
|
|
|
|
// truncate if too many hits
|
|
|
|
if (pmkr->cb + pmkrTmp->cb < KEY_BLOCKDEFAULT) {
|
|
CopyMemory(((PBYTE) pmkr->mtr) + pmkr->cb, ((PBYTE) pmkrTmp->mtr),
|
|
pmkrTmp->cb);
|
|
pmkr->cb += pmkrTmp->cb;
|
|
}
|
|
} while (pos < tblKeywords.CountStrings() &&
|
|
#if 0
|
|
strcmp(tblKeywords.GetPointer(pos),
|
|
tblKeywords.GetPointer(pos + 1)) == 0);
|
|
#else
|
|
CompareStringA(lcid, kwlcid.fsCompareI | NORM_IGNORECASE,
|
|
tblKeywords.GetPointer(pos), -1,
|
|
tblKeywords.GetPointer(pos + 1), - 1) == 2);
|
|
#endif
|
|
|
|
Ensure(RcFillHbt(hbtKeyDst,
|
|
(KEY) tblKeywords.GetPointer(pos), pmkr),
|
|
rcSuccess);
|
|
}
|
|
else {
|
|
pmkrTmp = (MASTER_RECKW*)
|
|
(tblKeywords.GetPointer(pos) +
|
|
strlen(tblKeywords.GetPointer(pos)) + 1);
|
|
|
|
Ensure(RcFillHbt(hbtKeyDst,
|
|
(KEY) tblKeywords.GetPointer(pos), pmkrTmp),
|
|
rcSuccess);
|
|
}
|
|
if (++cAnimate > INDEX_ANIMATECOUNT * 10) {
|
|
NextAnimation();
|
|
cAnimate = 0;
|
|
}
|
|
}
|
|
if (RcFiniFillHbt(hbtKeyDst) != rcSuccess)
|
|
goto Egress2;
|
|
|
|
ENSURE(RcCreateBTMapHfs(hfsDst, hbtKeyDst, txtMASTERKEYMAP), rcSuccess);
|
|
|
|
if (pTblFiles->CountStrings() > 1 && fIndexFound) {
|
|
cntFlags.flags |= GID_GINDEX;
|
|
cntFlags.fUseGlobalIndex = TRUE;
|
|
}
|
|
else
|
|
cntFlags.flags |= GID_NO_INDEX;
|
|
cntFlags.lcid = lcid;
|
|
|
|
// REVIEW: we need to check the return values, since closing will
|
|
// also flush the file and could cause an error.
|
|
|
|
RcCloseBtreeHbt(hbtKeyDst);
|
|
lcid = lcidSave;
|
|
return TRUE;
|
|
|
|
Egress3: // can't read: abandon temp btree
|
|
Egress2: // can't create temp btree:
|
|
// clean up after master init and helpfile init
|
|
|
|
RcCloseBtreeHbt(hbtKeyDst);
|
|
|
|
lcid = lcidSave;
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: ReportBtreeError
|
|
|
|
PURPOSE: Report some of the error conditions that we might run into
|
|
|
|
PARAMETERS:
|
|
rc
|
|
pszFile
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
25-Nov-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
static void STDCALL ReportBtreeError(RC rc, PCSTR pszFile)
|
|
{
|
|
if (rc == rcOutOfMemory)
|
|
OOM();
|
|
else if (rc == rcBadArg)
|
|
Error(wERRS_INTERNAL_GIND, wERRA_RETURN);
|
|
else if (rc == rcNoPermission)
|
|
ErrorVarArgs(wERRS_CANT_WRITE, wERRA_RETURN, pszFile);
|
|
else
|
|
ErrorVarArgs(wERRS_GIND_CABT_WRITE, wERRA_RETURN, pszFile);
|
|
}
|
|
|
|
const int VPAD = 62;
|
|
const int HPAD = 30;
|
|
|
|
BOOL STDCALL Animate::CreateStatusWindow(HWND hwndCaller, int idTitle)
|
|
{
|
|
WNDCLASS wc;
|
|
SIZE sSize;
|
|
|
|
if (fHiddenSetup)
|
|
return TRUE;
|
|
|
|
ZeroMemory(&wc, sizeof(wc));
|
|
|
|
// Register Main window class
|
|
|
|
wc.hInstance = hInsNow;
|
|
wc.style = CS_BYTEALIGNWINDOW | CS_CLASSDC;
|
|
wc.lpfnWndProc = StatusWndProc;
|
|
wc.lpszClassName = (LPCSTR) txtAnimateClassName;
|
|
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
|
|
wc.hIcon = hIconDefault;
|
|
|
|
if (!RegisterClass(&wc))
|
|
return FALSE;
|
|
|
|
WRECT rc;
|
|
if (hwndCaller != ahwnd[MAIN_HWND].hwndParent)
|
|
GetWindowWRect(hwndCaller, &rc);
|
|
else
|
|
GetWindowWRect(GetDesktopWindow(), &rc);
|
|
|
|
HDC hdc = GetDC(hwndCaller);
|
|
PSTR psz = GetStringResource(idTitle);
|
|
GetTextExtentPoint32(hdc, psz, lstrlen(psz), (LPSIZE)&sSize);
|
|
int width = sSize.cx;
|
|
if (width < CX_BOOK)
|
|
width = CX_BOOK;
|
|
if (!fIsThisNewShell4)
|
|
width += HPAD;
|
|
ReleaseDC(hwndCaller, hdc);
|
|
|
|
hwndAnimate = CreateWindowEx(WS_EX_WINDOWEDGE,
|
|
(LPCSTR) txtAnimateClassName, psz, WS_POPUP | WS_BORDER | WS_CAPTION,
|
|
rc.left + rc.cx / 2 - width / 2,
|
|
rc.top + rc.cy / 2 - CY_BOOK / 2 + HPAD,
|
|
width + GetSystemMetrics(SM_CXBORDER) * 2 + 2,
|
|
CY_BOOK + GetSystemMetrics(SM_CYBORDER) * 2 + VPAD +
|
|
GetSystemMetrics(SM_CYCAPTION),
|
|
(fAniOwner || IsWindowVisible(hwndCaller)) ? hwndCaller : NULL,
|
|
NULL, hInsNow, NULL);
|
|
ASSERT(hwndAnimate);
|
|
SetPosition((width - CX_BOOK) / 2, VPAD / 4);
|
|
|
|
if (!hwndAnimate) {
|
|
UnregisterClass((LPCSTR)txtAnimateClassName, hInsNow);
|
|
return FALSE;
|
|
}
|
|
|
|
fShown = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CALLBACK StatusWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
}
|
|
|
|
///////////////////////////// ANIMATE CLASS ///////////////////////////////
|
|
|
|
Animate::Animate(HINSTANCE hinstCaller)
|
|
{
|
|
hdcBmp = NULL;
|
|
hwndAnimate = NULL;
|
|
hinst = hinstCaller;
|
|
originalTime = GetTickCount();
|
|
if (fHiddenSetup) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Animate::~Animate(void)
|
|
{
|
|
if (hdcBmp != NULL)
|
|
DeleteDC(hdcBmp);
|
|
|
|
if (hwndAnimate) {
|
|
DestroyWindow(hwndAnimate);
|
|
UnregisterClass((LPCSTR) txtAnimateClassName, hInsNow);
|
|
hwndAnimate = NULL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: AnimFrame
|
|
|
|
PURPOSE: Displays one frame of the "build index" animation
|
|
in the specified device context.
|
|
|
|
PARAMETERS:
|
|
hdc
|
|
x
|
|
y
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
04-Nov-1993 [niklasb]
|
|
|
|
***************************************************************************/
|
|
|
|
void PASCAL Animate::NextFrame(void)
|
|
{
|
|
if (fHiddenSetup)
|
|
return;
|
|
|
|
DWORD curTickCount = GetTickCount();
|
|
if (curTickCount - oldTickCount < ANIMATE_INCREMENTS)
|
|
return;
|
|
oldTickCount = curTickCount;
|
|
|
|
// Delay showing the window for one second. If we get done before then,
|
|
// then there's no need to have gone to all the trouble.
|
|
|
|
if (!fShown) {
|
|
if (curTickCount - originalTime < 1000)
|
|
return;
|
|
|
|
HBITMAP hbmBooks = LoadBitmap(hinst, MAKEINTRESOURCE(IDBMP_BOOK));
|
|
HBITMAP hbmPens = LoadBitmap(hinst, MAKEINTRESOURCE(IDBMP_PENS));
|
|
HDC hdcTemp = CreateCompatibleDC(NULL);
|
|
hdcBmp = CreateCompatibleDC(NULL);
|
|
|
|
HBITMAP hbmpOldBook;
|
|
if (hdcBmp) {
|
|
hbmpOldBook = (HBITMAP)SelectObject(hdcBmp, hbmBooks);
|
|
hbmTemp = CreateCompatibleBitmap(hdcBmp, CX_DRAWAREA, CY_DRAWAREA);
|
|
}
|
|
else
|
|
hbmTemp = NULL;
|
|
|
|
if (!hbmBooks || !hbmPens || !hbmTemp || !hdcBmp || !hdcTemp) {
|
|
if (hbmpOldBook)
|
|
SelectObject(hdcBmp, hbmpOldBook);
|
|
SafeDeleteObject(hbmpOldBook);
|
|
SafeDeleteObject(hdcBmp);
|
|
SafeDeleteObject(hbmBooks);
|
|
SafeDeleteObject(hbmPens);
|
|
SafeDeleteObject(hdcTemp);
|
|
fHiddenSetup = TRUE;
|
|
return;
|
|
}
|
|
|
|
himl = CreateCompatibleBitmap(hdcBmp, CX_DRAWAREA * C_FRAMES, CY_DRAWAREA);
|
|
|
|
HBITMAP hbmpOldTemp = (HBITMAP) SelectObject(hdcTemp, hbmTemp);
|
|
HBITMAP hbmpOldBmp = (HBITMAP) SelectObject(hdcBmp, hbmBooks);
|
|
|
|
// Create the frames in which the pen scribbles on the open book.
|
|
|
|
iFrame = 0;
|
|
for (int y = 0; y < C_VERT_STROKES; y++) {
|
|
for (int x = 0; x < C_HORZ_STROKES; x++) {
|
|
|
|
// Show the book on a white background.
|
|
|
|
PatBlt(hdcTemp, 0, 0, CX_DRAWAREA, CY_DRAWAREA, WHITENESS);
|
|
SelectObject(hdcBmp, hbmBooks);
|
|
|
|
BitBlt(hdcTemp, X_BOOK, Y_BOOK, CX_BOOK, CY_BOOK, hdcBmp,
|
|
(C_BOOKS - 1) * CX_BOOK, 0, SRCCOPY);
|
|
|
|
// Add in the scribbled "text".
|
|
|
|
POINT pt;
|
|
for (int yDraw = 0; yDraw < y; yDraw++) {
|
|
for (int xDraw = 0; xDraw < C_HORZ_STROKES; xDraw++) {
|
|
PointFromStroke(xDraw, yDraw, &pt);
|
|
SetPixel(hdcTemp, pt.x, pt.y + CY_PEN - 1,
|
|
(xDraw & 1) ? clrPenA : clrPenB);
|
|
}
|
|
}
|
|
for (int xDraw = 0; xDraw <= x; xDraw++) {
|
|
PointFromStroke(xDraw, y, &pt);
|
|
SetPixel(hdcTemp, pt.x, pt.y + CY_PEN - 1,
|
|
(xDraw & 1) ? clrPenA : clrPenB);
|
|
}
|
|
|
|
// Add in the pen using the SRCAND operation.
|
|
|
|
SelectObject(hdcBmp, hbmPens);
|
|
BitBlt(hdcTemp, pt.x, pt.y, CX_PEN, CY_PEN, hdcBmp,
|
|
(iFrame & 1) ? CX_PEN : (iFrame & 2) * CX_PEN, 0, SRCAND);
|
|
|
|
SelectObject(hdcBmp, himl);
|
|
BitBlt(hdcBmp, iFrame * CX_DRAWAREA, 0, CX_DRAWAREA, CY_DRAWAREA,
|
|
hdcTemp, 0, 0, SRCCOPY);
|
|
|
|
iFrame++;
|
|
}
|
|
}
|
|
|
|
// Blast a white background into the temporary bitmap, and
|
|
// select the books bitmap.
|
|
|
|
PatBlt(hdcTemp, 0, 0, CX_DRAWAREA, CY_DRAWAREA, WHITENESS);
|
|
|
|
// Add the frames for the page turning (from the books bitmap).
|
|
|
|
for (int iBook = 0; iBook < C_BOOKS; iBook++) {
|
|
SelectObject(hdcBmp, hbmBooks);
|
|
BitBlt(hdcTemp, X_BOOK, Y_BOOK, CX_BOOK, CY_BOOK,
|
|
hdcBmp, iBook * CX_BOOK, 0, SRCCOPY);
|
|
SelectObject(hdcBmp, himl);
|
|
BitBlt(hdcBmp, iFrame * CX_DRAWAREA, 0, CX_DRAWAREA, CY_DRAWAREA,
|
|
hdcTemp, 0, 0, SRCCOPY);
|
|
iFrame++;
|
|
}
|
|
|
|
SelectObject(hdcTemp, hbmpOldTemp);
|
|
|
|
iFrame = 0;
|
|
|
|
if (hbmpOldBook)
|
|
SelectObject(hdcBmp, hbmpOldBook);
|
|
SafeDeleteObject(hbmpOldBook);
|
|
SafeDeleteObject(hbmBooks);
|
|
SafeDeleteObject(hbmPens);
|
|
SafeDeleteObject(hdcTemp);
|
|
|
|
fShown = TRUE;
|
|
ShowWindow(hwndAnimate, SW_NORMAL);
|
|
}
|
|
|
|
ASSERT(IsValidWindow(hwndAnimate));
|
|
HDC hdc = GetDC(hwndAnimate);
|
|
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcBmp, himl);
|
|
BitBlt(hdc, xPos, yPos, CX_DRAWAREA, CY_DRAWAREA, hdcBmp,
|
|
iFrame * CX_DRAWAREA, 0, SRCCOPY);
|
|
SelectObject(hdcBmp, hbmpOld);
|
|
|
|
ReleaseDC(hwndAnimate, hdc);
|
|
{
|
|
/*
|
|
* 19-Feb-1995 [ralphw]
|
|
* Don't process any of our internal messages, or will end up
|
|
* processing a WinHelp API call that will come through before we have
|
|
* had a chance to fully initialize.
|
|
*/
|
|
FlushMessageQueue(WM_USER);
|
|
}
|
|
|
|
// Next time draw the next frame.
|
|
|
|
if (++iFrame > C_FRAMES) {
|
|
iFrame = 0;
|
|
}
|
|
}
|
|
|
|
static VOID FASTCALL PointFromStroke(int xStroke, int yStroke, POINT* lppt)
|
|
{
|
|
int cx = (C_HORZ_STROKES / 2) - xStroke;
|
|
|
|
lppt->x = X_PEN + xStroke * CX_STROKE;
|
|
lppt->y = Y_PEN + yStroke * CY_STROKE + cx * cx / 10;
|
|
}
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: CompareSz
|
|
|
|
PURPOSE: Compare a sub-string from the specified STRINGTABLE resource
|
|
with the first part of a main string.
|
|
|
|
PARAMETERS:
|
|
psz
|
|
id
|
|
|
|
RETURNS: 0 if no match, else the length of the matching string.
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
17-Aug-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
static int STDCALL CompareSz(PCSTR psz, PCSTR pszSub)
|
|
{
|
|
int cb;
|
|
|
|
if (_strnicmp(psz, pszSub, cb = strlen(pszSub)) == 0)
|
|
return cb;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void FASTCALL InitBtreeStruct(BTREE_PARAMS* pbt, PCSTR pszFormat)
|
|
{
|
|
pbt->cbBlock = CBBTREEBLOCKDEFAULT;
|
|
pbt->bFlags = fFSOpenReadWrite;
|
|
strcpy(pbt->rgchFormat, pszFormat);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: CreateFilesBt
|
|
|
|
PURPOSE: Create |FILES btree, and add .CNT name and time/date stamp
|
|
|
|
PARAMETERS:
|
|
pszMasterFile
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
26-Dec-1993 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
static HBT STDCALL CreateFilesBt(PCSTR pszMasterFile, HFS hfsDst)
|
|
{
|
|
// Create files btree
|
|
|
|
BTREE_PARAMS btpFiles;
|
|
btpFiles.hfs = hfsDst;
|
|
|
|
// key is KT_LONG, record is '4', FMT_SZ
|
|
|
|
InitBtreeStruct(&btpFiles, "L4z");
|
|
btpFiles.cbBlock = 512;
|
|
|
|
|
|
HBT hbtFilesDst = HbtCreateBtreeSz(txtFNAMES, &btpFiles);
|
|
if (!hbtFilesDst) {
|
|
ReportBtreeError(RcGetBtreeError(), pszMasterFile);
|
|
return NULL;
|
|
}
|
|
|
|
// Copy the name of the file
|
|
|
|
{
|
|
HELPFILE_DIRECTORY_ENTRY hfde;
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hfd;
|
|
|
|
strcpy(hfde.szFileName, pszMasterFile);
|
|
|
|
// Get the time stamp of the .CNT file.
|
|
|
|
if ((hfd = FindFirstFile(hfde.szFileName, &fd)) == INVALID_HANDLE_VALUE) {
|
|
// This happens if there is no .CNT file
|
|
hfde.TimeStamp = 0;
|
|
}
|
|
else {
|
|
AdjustForTimeZoneBias(&fd.ftLastWriteTime.dwLowDateTime);
|
|
hfde.TimeStamp = fd.ftLastWriteTime.dwLowDateTime;
|
|
FindClose(hfd);
|
|
}
|
|
|
|
KEY key = CNT_FILE;
|
|
if (RcInsertHbt(hbtFilesDst, (KEY) (LPVOID) &key, &hfde) != rcSuccess) {
|
|
ReportBtreeError(RcGetBtreeError(), pszMasterFile);
|
|
RcCloseBtreeHbt(hbtFilesDst);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return hbtFilesDst;
|
|
}
|
|
|
|
const int INPUT_BUF_SIZE = 2048;
|
|
|
|
CInput::CInput(LPCSTR pszFileName)
|
|
{
|
|
if ((hfile = _lopen(pszFileName,
|
|
OF_READ | OF_SHARE_DENY_WRITE)) == HFILE_ERROR) {
|
|
fInitialized = FALSE;
|
|
return;
|
|
}
|
|
fInitialized = TRUE;
|
|
pbuf = (PBYTE) LhAlloc(LMEM_FIXED, INPUT_BUF_SIZE);
|
|
ASSERT(pbuf);
|
|
|
|
// Position current buffer at end to force a read
|
|
|
|
pCurBuf = pEndBuf = pbuf + INPUT_BUF_SIZE;
|
|
}
|
|
|
|
CInput::~CInput(void)
|
|
{
|
|
_lclose(hfile);
|
|
FreeLh((HLOCAL) pbuf);
|
|
}
|
|
|
|
BOOL PASCAL CInput::getline(PSTR pszDst)
|
|
{
|
|
PSTR pszOrgBuf = pszDst;
|
|
int i;
|
|
|
|
for (i = 0;;) {
|
|
if (pCurBuf >= pEndBuf) {
|
|
if (!ReadNextBuffer())
|
|
return FALSE;
|
|
}
|
|
switch (*pszDst = *pCurBuf++) {
|
|
case '\n':
|
|
if (pszDst > pszOrgBuf) {
|
|
while (pszDst[-1] == ' ') // remove trailing spaces
|
|
pszDst--;
|
|
}
|
|
*pszDst = '\0';
|
|
return TRUE;
|
|
|
|
case '\r':
|
|
break; // ignore it
|
|
|
|
case '\t':
|
|
*pszDst++ = ' ';
|
|
if (++i >= MAX_CNT_LINE)
|
|
return FALSE; // bad line, possible binary file
|
|
break;
|
|
|
|
default:
|
|
pszDst++;
|
|
if (++i >= MAX_CNT_LINE)
|
|
return FALSE; // bad line, possible binary file
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL CInput::ReadNextBuffer(void)
|
|
{
|
|
UINT cbRead;
|
|
|
|
if ((cbRead = _lread(hfile, pbuf, INPUT_BUF_SIZE)) <= 0)
|
|
return FALSE;
|
|
pCurBuf = pbuf;
|
|
pEndBuf = pbuf + cbRead;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: ChangeDirectory
|
|
|
|
PURPOSE: Change drive and directory
|
|
|
|
PARAMETERS:
|
|
pszFile
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
REVIEW: what happens with a network location?
|
|
|
|
MODIFICATION DATES:
|
|
16-Feb-1994 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
void STDCALL ChangeDirectory(PCSTR pszFile)
|
|
{
|
|
char szPath[MAX_PATH];
|
|
strcpy(szPath, pszFile);
|
|
PSTR psz = StrRChrDBCS(szPath, '\\');
|
|
if (!psz)
|
|
psz = StrRChrDBCS(szPath, '/');
|
|
if (!psz)
|
|
return;
|
|
else
|
|
*psz = '\0';
|
|
|
|
// REVIEW: Is this really necessary? SetCurrentDirectory should
|
|
// take care of it..
|
|
|
|
if (szPath[1] == ':')
|
|
_chdrive(tolower(szPath[0]) - ('a' - 1));
|
|
|
|
// REVIEW: does this change the drive?
|
|
|
|
SetCurrentDirectory(szPath);
|
|
|
|
}
|
|
|
|
CGMem::~CGMem(void)
|
|
{
|
|
if (hmem)
|
|
FreeGh(hmem);
|
|
}
|
|
|
|
CLMem::~CLMem(void)
|
|
{
|
|
if (pBuf)
|
|
FreeGh(pBuf);
|
|
}
|
|
|
|
static Animate* panimate;
|
|
|
|
extern "C" BOOL STDCALL StartAnimation(int idTitle)
|
|
{
|
|
ASSERT(!panimate);
|
|
panimate = new Animate(hInsNow);
|
|
if (!panimate->CreateStatusWindow(ahwnd[iCurWindow].hwndParent, idTitle)) {
|
|
delete panimate;
|
|
panimate = NULL;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
extern "C" void STDCALL NextAnimation(void)
|
|
{
|
|
if (panimate)
|
|
panimate->NextFrame();
|
|
}
|
|
|
|
extern "C" void STDCALL StopAnimation(void)
|
|
{
|
|
if (panimate)
|
|
delete panimate;
|
|
panimate = NULL;
|
|
}
|
|
|
|
static BOOL STDCALL FindEqCharacter(PCSTR pszLine)
|
|
{
|
|
PSTR psz = StrChrDBCS(pszLine, '=');
|
|
while (psz && psz > pszLine && psz[-1] == '\\')
|
|
psz = StrChrDBCS(psz + 1, '=');
|
|
|
|
return (BOOL) psz;
|
|
}
|
|
|
|
extern "C" const char txtDocClass[];
|
|
|
|
BOOL CALLBACK NotifyWinHelp(HWND hwnd, LPARAM lParam)
|
|
{
|
|
char szClass[256];
|
|
|
|
if (GetClassName(hwnd, szClass, sizeof(szClass)) && hwnd !=
|
|
ahwnd[MAIN_HWND].hwndParent) {
|
|
if ( lstrcmpi(szClass, MS_WINHELP) == 0 ||
|
|
lstrcmpi(szClass, MS_POPUPHELP) == 0 ||
|
|
lstrcmpi(szClass, MS_TCARDHELP) == 0 ||
|
|
lstrcmpi(szClass, txtDocClass) == 0)
|
|
::SendMessage(hwnd, (UINT) lParam, 0, 0);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
QBTHR STDCALL HbtInitFill(PCSTR sz, BTREE_PARAMS* qbtp)
|
|
{
|
|
// Get a btree handle
|
|
|
|
QBTHR qbthr = (QBTHR) HbtCreateBtreeSz(sz, qbtp);
|
|
|
|
// make a one-block cache
|
|
|
|
qbthr->pCache = (PBYTE) lcCalloc(CbCacheBlock(qbthr));
|
|
PCACHE pcache = (PCACHE) qbthr->pCache;
|
|
|
|
qbthr->bth.cLevels = 1;
|
|
pcache->bk = (BK) BkAlloc(qbthr);
|
|
qbthr->bth.bkFirst =
|
|
qbthr->bth.bkLast = (BK) pcache->bk;
|
|
pcache->bFlags = CACHE_DIRTY | CACHE_VALID;
|
|
pcache->db.cbSlack = qbthr->bth.cbBlock - cbDISK_BLOCK + 1
|
|
- 2 * sizeof(BK);
|
|
pcache->db.cKeys = 0;
|
|
#ifdef _X86_
|
|
SetBkPrev(pcache, bkNil);
|
|
#else
|
|
{
|
|
BK bkTmp = bkNil;
|
|
SetBkPrev(qbthr,pcache, bkTmp);
|
|
}
|
|
#endif
|
|
|
|
return qbthr;
|
|
}
|
|
|
|
RC STDCALL RcFillHbt(HBT hbt, KEY key, void* qvRec)
|
|
{
|
|
QBTHR qbthr = (QBTHR) hbt;
|
|
ASSERT(key);
|
|
ASSERT(qvRec);
|
|
|
|
PCACHE pcache = (PCACHE) qbthr->pCache;
|
|
|
|
int cbRec = CbSizeRec(qvRec, qbthr);
|
|
int cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
|
|
if (cbRec + cbKey > pcache->db.cbSlack) {
|
|
|
|
// key and rec don't fit in this block: write it out
|
|
|
|
#ifdef _X86_
|
|
SetBkNext(pcache, BkAlloc(qbthr));
|
|
#else
|
|
{
|
|
BK bkTmp = (BK) BkAlloc(qbthr);
|
|
SetBkNext(qbthr, pcache, bkTmp);
|
|
}
|
|
#endif
|
|
RC rc = RcWriteBlock(pcache, qbthr);
|
|
if (rc != rcSuccess) {
|
|
lcFree(qbthr->pCache);
|
|
RcAbandonHf(qbthr->hf);
|
|
lcFree(qbthr);
|
|
return rcBtreeError;
|
|
}
|
|
|
|
// recycle the block
|
|
|
|
#ifdef _X86_
|
|
SetBkPrev(pcache, pcache->bk);
|
|
pcache->bk = BkNext(pcache);
|
|
#else
|
|
SetBkPrev(qbthr, pcache, pcache->bk);
|
|
pcache->bk = BkNext(qbthr,pcache);
|
|
#endif
|
|
pcache->bFlags = CACHE_DIRTY | CACHE_VALID;
|
|
pcache->db.cbSlack = qbthr->bth.cbBlock - cbDISK_BLOCK + 1
|
|
- 2 * sizeof(BK);
|
|
pcache->db.cKeys = 0;
|
|
}
|
|
|
|
// add key and rec to the current block;
|
|
|
|
PBYTE pb = ((PBYTE) &pcache->db) + qbthr->bth.cbBlock - pcache->db.cbSlack;
|
|
memmove(pb, (void*) key, cbKey);
|
|
memmove(pb + cbKey, qvRec, cbRec);
|
|
pcache->db.cKeys++;
|
|
pcache->db.cbSlack -= (cbKey + cbRec);
|
|
qbthr->bth.lcEntries++;
|
|
|
|
return rcBtreeError = rcSuccess;
|
|
}
|
|
|
|
RC STDCALL RcFiniFillHbt(HBT hbt)
|
|
{
|
|
DWORD bkThisMin, bkThisMost, bkThisCur;
|
|
DWORD bkTopMin, bkTopMost;
|
|
PCACHE pcacheThis, pcacheTop;
|
|
int cbKey;
|
|
KEY key;
|
|
PBYTE qbDst;
|
|
RC rc;
|
|
QBTHR qbthr = (QBTHR) hbt;
|
|
|
|
pcacheThis = QCacheBlock(qbthr, 0);
|
|
|
|
#ifdef _X86_
|
|
SetBkNext(pcacheThis, bkNil);
|
|
#else
|
|
{
|
|
BK bkTmp = bkNil;
|
|
SetBkNext(qbthr,pcacheThis, bkTmp);
|
|
}
|
|
#endif
|
|
|
|
bkThisMin = qbthr->bth.bkFirst;
|
|
bkThisMost = pcacheThis->bk;
|
|
qbthr->bth.bkLast = (BK) pcacheThis->bk;
|
|
|
|
if (bkThisMin == bkThisMost) { // only one leaf
|
|
qbthr->bth.bkRoot = (BK) bkThisMin;
|
|
goto normal_return;
|
|
}
|
|
|
|
if (rcSuccess != RcGrowCache(qbthr))
|
|
goto error_return;
|
|
|
|
pcacheTop = QCacheBlock(qbthr, 0);
|
|
pcacheTop->bk = (BK) BkAlloc(qbthr);
|
|
bkTopMin = bkTopMost = pcacheTop->bk;
|
|
pcacheTop->bFlags = CACHE_DIRTY | CACHE_VALID;
|
|
pcacheTop->db.cbSlack = qbthr->bth.cbBlock - cbDISK_BLOCK + 1
|
|
- sizeof(BK);
|
|
pcacheTop->db.cKeys = 0;
|
|
|
|
// Get first key from each leaf node and build a layer of internal nodes.
|
|
|
|
// add bk of first leaf to the node
|
|
|
|
qbDst = pcacheTop->db.rgbBlock;
|
|
*(BK *) qbDst = (BK) bkThisMin;
|
|
qbDst += sizeof(BK);
|
|
|
|
for (bkThisCur = bkThisMin + 1; bkThisCur <= bkThisMost; ++bkThisCur) {
|
|
pcacheThis = QFromBk(bkThisCur, 1, qbthr);
|
|
|
|
key = (KEY) (pcacheThis->db.rgbBlock + 2 * sizeof(BK));
|
|
cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
|
|
if (cbKey + (int) sizeof(BK) > pcacheTop->db.cbSlack) {
|
|
|
|
// key and bk don't fit in this block: write it out
|
|
|
|
rc = RcWriteBlock(pcacheTop, qbthr);
|
|
|
|
// recycle the block
|
|
|
|
pcacheTop->bk = (BK) BkAlloc(qbthr);
|
|
bkTopMost = pcacheTop->bk;
|
|
pcacheTop->db.cbSlack = qbthr->bth.cbBlock - cbDISK_BLOCK + 1
|
|
- sizeof(BK); // (bk added below)
|
|
pcacheTop->db.cKeys = 0;
|
|
qbDst = pcacheTop->db.rgbBlock;
|
|
}
|
|
else {
|
|
pcacheTop->db.cbSlack -= cbKey + sizeof(BK);
|
|
memmove(qbDst, (PBYTE) key, cbKey);
|
|
qbDst += cbKey;
|
|
pcacheTop->db.cKeys++;
|
|
}
|
|
|
|
*(UNALIGNED BK *) qbDst = (BK) bkThisCur;
|
|
qbDst += sizeof(BK);
|
|
}
|
|
|
|
// Keep adding layers of internal nodes until we have a root.
|
|
|
|
while (bkTopMost > bkTopMin) {
|
|
bkThisMin = bkTopMin;
|
|
bkThisMost = bkTopMost;
|
|
bkTopMin = bkTopMost = (BK) BkAlloc(qbthr);
|
|
|
|
rc = RcGrowCache(qbthr);
|
|
if (rc != rcSuccess)
|
|
goto error_return;
|
|
|
|
pcacheTop = QCacheBlock(qbthr, 0);
|
|
pcacheTop->bk = (BK) bkTopMin;
|
|
pcacheTop->bFlags = CACHE_DIRTY | CACHE_VALID;
|
|
pcacheTop->db.cbSlack = qbthr->bth.cbBlock - cbDISK_BLOCK + 1
|
|
- sizeof(BK);
|
|
pcacheTop->db.cKeys = 0;
|
|
|
|
// add bk of first node of this level to current node of top level;
|
|
|
|
qbDst = pcacheTop->db.rgbBlock;
|
|
*(BK *) qbDst = (BK) bkThisMin;
|
|
qbDst += sizeof(BK);
|
|
|
|
// for ( each internal node in this level after first )
|
|
|
|
for (bkThisCur = bkThisMin + 1; bkThisCur <= bkThisMost; ++bkThisCur) {
|
|
key = KeyLeastInSubtree(qbthr, bkThisCur, 1);
|
|
|
|
cbKey = CbSizeKey(key, qbthr, FALSE);
|
|
|
|
if (cbKey + (int) sizeof(BK) > pcacheTop->db.cbSlack) {
|
|
|
|
// key and bk don't fit in this block: write it out
|
|
|
|
rc = RcWriteBlock(pcacheTop, qbthr);
|
|
|
|
// recycle the block
|
|
|
|
pcacheTop->bk = (BK) BkAlloc(qbthr);
|
|
bkTopMost = pcacheTop->bk;
|
|
pcacheTop->db.cbSlack = qbthr->bth.cbBlock - cbDISK_BLOCK + 1
|
|
- sizeof(BK); // (bk added below)
|
|
pcacheTop->db.cKeys = 0;
|
|
qbDst = pcacheTop->db.rgbBlock;
|
|
}
|
|
else {
|
|
pcacheTop->db.cbSlack -= cbKey + sizeof(BK);
|
|
memmove(qbDst, (PBYTE) key, cbKey);
|
|
qbDst += cbKey;
|
|
pcacheTop->db.cKeys++;
|
|
}
|
|
|
|
*(UNALIGNED BK *) qbDst = (BK) bkThisCur;
|
|
qbDst += sizeof(BK);
|
|
}
|
|
}
|
|
|
|
ASSERT(bkTopMin == bkTopMost);
|
|
|
|
qbthr->bth.bkRoot = (BK) bkTopMin;
|
|
qbthr->bth.bkEOF = (BK) bkTopMin + 1;
|
|
|
|
normal_return:
|
|
return rcBtreeError;
|
|
|
|
error_return:
|
|
rc = rcBtreeError;
|
|
RcAbandonHbt(qbthr);
|
|
return rcBtreeError = rc;
|
|
}
|
|
|
|
RC STDCALL RcGrowCache(QBTHR qbthr)
|
|
{
|
|
int cbcb = CbCacheBlock(qbthr);
|
|
|
|
qbthr->bth.cLevels++;
|
|
|
|
PBYTE pb = (PBYTE) lcCalloc(cbcb * qbthr->bth.cLevels);
|
|
|
|
CopyMemory(pb + cbcb, qbthr->pCache, cbcb * (qbthr->bth.cLevels - 1));
|
|
|
|
lcFree(qbthr->pCache);
|
|
qbthr->pCache = pb;
|
|
|
|
return rcBtreeError = rcSuccess;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
- Function: KeyLeastInSubtree( qbthr, bk, icbLevel )
|
|
-
|
|
* Purpose: Return the least key in the subtree speced by bk and
|
|
* icbLevel.
|
|
*
|
|
* ASSUMES
|
|
* args IN: qbthr -
|
|
* bk - bk at root of subtree
|
|
* icbLevel - level of subtree root
|
|
*
|
|
* PROMISES
|
|
* returns: key - the smallest key in the subtree
|
|
* args OUT: qbthr->ghCache, ->pCache - contents of cache may change
|
|
* globals OUT: rcBtreeError?
|
|
*
|
|
\***************************************************************************/
|
|
|
|
INLINE static KEY STDCALL KeyLeastInSubtree(QBTHR qbthr, DWORD bk,
|
|
int icbLevel)
|
|
{
|
|
PCACHE pcache;
|
|
int icbMost = qbthr->bth.cLevels - 1;
|
|
|
|
while (icbLevel < icbMost) {
|
|
pcache = QFromBk(bk, icbLevel, qbthr);
|
|
bk = *(BK *) pcache->db.rgbBlock;
|
|
++icbLevel;
|
|
}
|
|
|
|
pcache = QFromBk(bk, icbLevel, qbthr);
|
|
return (KEY) (pcache->db.rgbBlock + 2 * sizeof(BK));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: LcbReadHfSeq
|
|
|
|
PURPOSE: Similar to LcbReadHf, but assumes sequential reading
|
|
|
|
PARAMETERS:
|
|
hf
|
|
qb
|
|
lcb
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
29-May-1995 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
static LONG STDCALL LcbReadHfSeq(HF hf, LPVOID qb, LONG lcb)
|
|
{
|
|
QRWFO qrwfo = (QRWFO) PtrFromGh(hf);
|
|
LONG lcbTotalRead;
|
|
FID fid;
|
|
LONG lifOffset;
|
|
|
|
if (qrwfo->lifCurrent + lcb > qrwfo->lcbFile) {
|
|
lcb = qrwfo->lcbFile - qrwfo->lifCurrent;
|
|
if (lcb <= 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
QFSHR qfshr = (QFSHR) PtrFromGh(qrwfo->hfs);
|
|
|
|
ASSERT(qfshr->fid >= 0);
|
|
|
|
fid = qfshr->fid;
|
|
lifOffset = qrwfo->lifBase;
|
|
|
|
#ifdef _X86_
|
|
if (curReadPos != (int) (lifOffset + sizeof(FH) + qrwfo->lifCurrent)) {
|
|
curReadPos = LSeekFid(fid, lifOffset + sizeof(FH) + qrwfo->lifCurrent, SEEK_SET);
|
|
}
|
|
#ifdef _DEBUG
|
|
int curCheck = _llseek(fid, 0, 1);
|
|
ASSERT(curCheck == curReadPos);
|
|
#endif
|
|
|
|
#else
|
|
{
|
|
LONG lcbSizeofFH;
|
|
lcbSizeofFH = LcbStructSizeSDFF(ISdffFileIdHfs(qrwfo->hfs), SE_FH);
|
|
|
|
if (curReadPos != lifOffset + lcbSizeofFH + qrwfo->lifCurrent)
|
|
curReadPos = LSeekFid(fid, lifOffset + lcbSizeofFH + qrwfo->lifCurrent, SEEK_SET);
|
|
}
|
|
#endif
|
|
|
|
// read the data
|
|
|
|
lcbTotalRead = _lread(fid, qb, lcb);
|
|
|
|
// update file pointer
|
|
|
|
if (lcbTotalRead >= 0) {
|
|
qrwfo->lifCurrent += lcbTotalRead;
|
|
curReadPos += lcbTotalRead;
|
|
}
|
|
|
|
return lcbTotalRead;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: RcOffsetPosFast
|
|
|
|
PURPOSE: Version of RcOffsetPos optimized for reading keywords
|
|
|
|
PARAMETERS:
|
|
hbt
|
|
pbtpos
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
29-May-1995 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
static RC STDCALL RcOffsetPosFast(HBT hbt, BTPOS* pbtpos)
|
|
{
|
|
int c;
|
|
LONG lcKey, lcDelta = 0;
|
|
QCB qcb;
|
|
QB qb;
|
|
QBTHR qbthr;
|
|
|
|
ASSERT(FValidPos(pbtpos));
|
|
DWORD bk = pbtpos->bk;
|
|
|
|
qbthr = (QBTHR) PtrFromGh(hbt);
|
|
|
|
if (qbthr->bth.cLevels <= 0)
|
|
return rcBtreeError = rcNoExists;
|
|
|
|
ASSERT(qbthr->pCache);
|
|
|
|
if ((qcb = QFromBk(bk, qbthr->bth.cLevels - 1, qbthr))
|
|
== NULL) {
|
|
return rcBtreeError;
|
|
}
|
|
|
|
lcKey = pbtpos->cKey + 1;
|
|
|
|
ASSERT(lcKey >= 0);
|
|
|
|
// chase next to find the right block
|
|
|
|
while (lcKey >= qcb->db.cKeys) {
|
|
lcKey -= qcb->db.cKeys;
|
|
#ifdef _X86_
|
|
bk = BkNext(qcb);
|
|
#else
|
|
bk = BkNext(qbthr, qcb);
|
|
#endif
|
|
if (bk == bkNil) {
|
|
bk = qcb->bk;
|
|
lcDelta = lcKey + 1;
|
|
lcKey = qcb->db.cKeys - 1;
|
|
break;
|
|
}
|
|
if ((qcb = QFromBk(bk, qbthr->bth.cLevels - 1, qbthr)) == NULL)
|
|
return rcBtreeError;
|
|
}
|
|
|
|
if (bk == pbtpos->bk && lcKey >= pbtpos->cKey) {
|
|
c = pbtpos->cKey;
|
|
qb = qcb->db.rgbBlock + pbtpos->iKey;
|
|
}
|
|
else {
|
|
c = 0;
|
|
qb = qcb->db.rgbBlock + 2 * sizeof(BK);
|
|
}
|
|
|
|
while (c < lcKey) {
|
|
qb += CbSizeKey((KEY) qb, qbthr, TRUE);
|
|
qb += CbSizeRec(qb, qbthr);
|
|
c++;
|
|
}
|
|
|
|
pbtpos->bk = (BK) bk;
|
|
pbtpos->iKey = qb - (PBYTE) qcb->db.rgbBlock;
|
|
pbtpos->cKey = c;
|
|
|
|
return rcBtreeError = lcDelta ? rcNoExists : rcSuccess;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
FUNCTION: DoesFileExist
|
|
|
|
PURPOSE: Find a file, keeping track if we have looked for it before
|
|
|
|
PARAMETERS:
|
|
pszFileName
|
|
dir
|
|
|
|
RETURNS:
|
|
|
|
COMMENTS:
|
|
|
|
MODIFICATION DATES:
|
|
29-May-1995 [ralphw]
|
|
|
|
***************************************************************************/
|
|
|
|
static FM STDCALL DoesFileExist(PCSTR pszFileName, DIR dir)
|
|
{
|
|
int pos;
|
|
FM fm;
|
|
|
|
if (ptblExist && (pos = ptblExist->IsPrimaryStringInTable(pszFileName)) > 1) {
|
|
fm = ptblExist->GetPointer(pos + 1);
|
|
return *fm ? FmCopyFm(fm) : NULL;
|
|
}
|
|
|
|
fm = FmNewExistSzDir(pszFileName, dir);
|
|
|
|
if (!ptblExist)
|
|
ptblExist = new CTable;
|
|
|
|
ptblExist->AddString(pszFileName, fm ? fm : "");
|
|
|
|
return fm;
|
|
}
|