// . the url being reuqested
// . removes &code= facebook cruft
bool HttpRequest::getCurrentUrl ( SafeBuf &cu ) {
	// makre sure we got enough room
	if ( ! cu.reserve ( m_hostLen + 64 + m_plen + 1 + 1 ) ) return false;
	// need a "Host: "
	char *host = m_host;
	if ( ! host ) host = APPSUBDOMAIN;
	cu.safePrintf("http");
	if ( m_isSSL ) cu.pushChar('s');
	cu.safePrintf("://%s",host);
	char *path = m_path;
	long  plen = m_plen;
	if ( ! path ) {
		path = "/";
		plen = 1;
	}
	// . scan path and change \0 back to = or &
	// . similar logic in HttpServer.cpp for logging!
	char *dst = cu.getBuf();
	char *src = path;
	char *srcEnd = path + plen;
	char dd = '=';
	for ( ; src < srcEnd ; src++ , dst++ ) {
		*dst = *src;
		if ( *src ) continue;
		*dst = dd;
		if ( dd == '=' ) dd = '&';
		else             dd = '=';
	}
	*dst = '\0';
	// cut it off at facebook's &code=
	char *buf = cu.getBufStart();
	char *code = strstr( buf,"&code=");
	// fix for eventguru.com/blog.html?code=
	if ( ! code ) code = strstr(buf,"?code=");
	// hack that off if there
	if ( code ) {
		*code = '\0';
		dst = code;
	}
	// update length
	cu.setLength( dst - cu.getBufStart() );
	return true;
}
bool HttpRequest::getCurrentUrlPath ( SafeBuf &cup ) {
	// makre sure we got enough room
	if ( ! cup.reserve ( m_plen + 1 + 1 ) ) return false;
	char *path = m_path;
	long  plen = m_plen;
	if ( ! path ) {
		path = "/";
		plen = 1;
	}
	// . scan path and change \0 back to = or &
	// . similar logic in HttpServer.cpp for logging!
	char *dst = cup.getBuf();
	char *start = dst;
	char *src = path;
	char *srcEnd = path + plen;
	// stop if we hit '?'
	for ( ; src < srcEnd && *src != '?' ; src++ , dst++ ) {
		*dst = *src;
	}
	cup.incrementLength(dst - start);
	*dst = '\0';
	return true;
}
//
// . ENTRY POINT FOR IMPORTING TITLEDB RECS FROM ANOTHER CLUSTER
// . when user clicks 'begin' in import page we come here..
// . so when that parm changes in Parms.cpp we sense that and call
//   beginImport(CollectionRec *cr)
// . or on startup we call resumeImports to check each coll for 
//   an import in progress.
// . search for files named titledb*.dat
// . if none found just return
// . when msg7 inject competes it calls this
// . call this from sleep wrapper in Process.cpp
// . returns false if would block (outstanding injects), true otherwise
// . sets g_errno on error
bool ImportState::importLoop ( ) {

	CollectionRec *cr = g_collectiondb.getRec ( m_collnum );

	if ( ! cr || g_hostdb.m_hostId != 0 ) { 
		// if coll was deleted!
		log("import: collnum %li deleted while importing into",
		    (long)m_collnum);
		//if ( m_numOut > m_numIn ) return true;
		// delete the entire import state i guess
		// what happens if we have a msg7 reply come back in?
		// it should see the collrec is NULL and just fail.
		mdelete ( this, sizeof(ImportState) , "impstate");
		delete (this);
		return true;
	}

 INJECTLOOP:

	// stop if waiting on outstanding injects
	long long out = m_numOut - m_numIn;
	if ( out >= cr->m_numImportInjects ) {
		g_errno = 0;
		return false;
	}
	

	if ( ! cr->m_importEnabled ) {
		// wait for all to return
		if ( out > 0 ) return false;
		// then delete it
		log("import: collnum %li import loop disabled",
		    (long)m_collnum);
		mdelete ( this, sizeof(ImportState) , "impstate");
		delete (this);
		return true;
	}




	// scan each titledb file scanning titledb0001.dat first,
	// titledb0003.dat second etc.

	//long long offset = -1;
	// . when offset is too big for current m_bigFile file then
	//   we go to the next and set offset to 0.
	// . sets m_bf and m_fileOffset
	if ( ! setCurrentTitleFileAndOffset ( ) ) {//cr  , -1 );
		log("import: import: no files to read");
		//goto INJECTLOOP;
		return true;
	}



	// this is -1 if none remain!
	if ( m_fileOffset == -1 ) {
		log("import: import fileoffset is -1. done.");
		return true;
	}

	long long saved = m_fileOffset;

	//Msg7 *msg7;
	//GigablastRequest *gr;
	//SafeBuf *sbuf = NULL;

	long need = 12;
	long dataSize = -1;
	//XmlDoc xd;
	key_t tkey;
	bool status;
	SafeBuf tmp;
	SafeBuf *sbuf = &tmp;
	long long docId;
	long shardNum;
	long key;
	Multicast *mcast;
	char *req;
	long reqSize;

	if ( m_fileOffset >= m_bfFileSize ) {
		log("inject: import: done processing file %li %s",
		    m_bfFileId,m_bf.getFilename());
		goto nextFile;
	}
	
	// read in title rec key and data size
	status = m_bf.read ( &tkey, sizeof(key_t) , m_fileOffset );
	
	//if ( n != 12 ) goto nextFile;
	if ( g_errno ) {
		log("inject: import: reading file error: %s. advancing "
		    "to next file",mstrerror(g_errno));
		goto nextFile;
	}

	m_fileOffset += 12;

	// if negative key, skip
	if ( (tkey.n0 & 0x01) == 0 ) {
		goto INJECTLOOP;
	}

	// if non-negative then read in size
	status = m_bf.read ( &dataSize , 4 , m_fileOffset );
	if ( g_errno ) {
		log("main: failed to read in title rec "
		    "file. %s. Skipping file %s",
		    mstrerror(g_errno),m_bf.getFilename());
		goto nextFile;
	}
	m_fileOffset += 4;
	need += 4;
	need += dataSize;
	need += 4; // collnum, first 4 bytes
	if ( dataSize < 0 || dataSize > 500000000 ) {
		log("main: could not scan in titledb rec of "
		    "corrupt dataSize of %li. BAILING ENTIRE "
		    "SCAN of file %s",dataSize,m_bf.getFilename());
		goto nextFile;
	}

	//gr = &msg7->m_gr;

	//XmlDoc *xd = getAvailXmlDoc();
	//msg7 = getAvailMsg7();
	mcast = getAvailMulticast();

	// if none, must have to wait for some to come back to us
	if ( ! mcast ) {
		// restore file offset
		//m_fileOffset = saved;
		// no, must have been a oom or something
		log("import: import no mcast available");
		return true;//false;
	}
	
	// this is for holding a compressed titlerec
	//sbuf = &mcast->m_sbuf;//&gr->m_sbuf;

	// point to start of buf
	sbuf->reset();

	// ensure we have enough room
	sbuf->reserve ( need );

	// collnum first 4 bytes
	sbuf->pushLong( (long)m_collnum );

	// store title key
	sbuf->safeMemcpy ( &tkey , sizeof(key_t) );

	// then datasize if any. neg rec will have -1 datasize
	if ( dataSize >= 0 ) 
		sbuf->pushLong ( dataSize );

	// then read data rec itself into it, compressed titlerec part
	if ( dataSize > 0 ) {
		// read in the titlerec after the key/datasize
		status = m_bf.read ( sbuf->getBuf() ,
				     dataSize ,
				     m_fileOffset );
		if ( g_errno ) { // n != dataSize ) {
			log("main: failed to read in title rec "
			    "file. %s. Skipping file %s",
			    mstrerror(g_errno),m_bf.getFilename());
			// essentially free up this msg7 now
			//msg7->m_inUse = false;
			//msg7->reset();
			goto nextFile;
		}
		// advance
		m_fileOffset += dataSize;
		// it's good, count it
		sbuf->m_length += dataSize;
	}

	// set xmldoc from the title rec
	//xd->set ( sbuf.getBufStart() );
	//xd->m_masterState = NULL;
	//xd->m_masterCallback ( titledbInjectLoop );

	// we use this so we know where the doc we are injecting
	// was in the foregien titledb file. so we can update our bookmark
	// code.
	mcast->m_hackFileOff = saved;//m_fileOffset;
	mcast->m_hackFileId  = m_bfFileId;

	//
	// inject a title rec buf this time, we are doing an import
	// FROM A TITLEDB FILE!!!
	//
	//gr->m_titleRecBuf = &sbuf;

	// break it down into gw
	// xd.set2 ( sbuf.getBufStart() ,
	// 	  sbuf.length() , // max size
	// 	  cr->m_coll, // use our coll
	// 	  NULL , // pbuf for page parser
	// 	  1 , // niceness
	// 	  NULL ); //sreq );

	// // note it
	// log("import: importing %s",xd.m_firstUrl.getUrl());

	// now we can set gr for the injection
	// TODO: inject the whole "sbuf" so we get sitenuminlinks etc
	// all exactly the same...
	// gr->m_url = xd.getFirstUrl()->getUrl();
	// gr->m_queryToScrape = NULL;
	// gr->m_contentDelim = 0;
	// gr->m_contentTypeStr = g_contentTypeStrings [xd.m_contentType];
	// gr->m_contentFile = NULL;
	// gr->m_content = xd.ptr_utf8Content;
	// gr->m_diffbotReply = NULL;
	// gr->m_injectLinks = false;
	// gr->m_spiderLinks = true;
	// gr->m_shortReply = false;
	// gr->m_newOnly = false;
	// gr->m_deleteUrl = false;
	// gr->m_recycle = true; // recycle content? or sitelinks?
	// gr->m_dedup = false;
	// gr->m_hasMime = false;
	// gr->m_doConsistencyTesting = false;
	// gr->m_getSections = false;
	// gr->m_gotSections = false;
	// gr->m_charset = xd.m_charset;
	// gr->m_hopCount = xd.m_hopCount;


	//
	// point to next doc in the titledb file
	//
	//m_fileOffset += need;

	// get docid from key
	docId = g_titledb.getDocIdFromKey ( &tkey );

	// get shard that holds the titlerec for it
	shardNum = g_hostdb.getShardNumFromDocId ( docId );

	// for selecting which host in the shard receives it
	key = (long)docId;


	m_numOut++;

	// then index it. master callback will be called
	//if ( ! xd->index() ) return false;

	// TODO: make this forward the request to an appropriate host!!
	// . gr->m_sbuf is set to the titlerec so this should handle that
	//   and use XmlDoc::set4() or whatever
	// if ( msg7->injectTitleRec ( msg7 , // state
	// 			    gotMsg7ReplyWrapper , // callback
	// 			    cr )) {
	// 	// it didn't block somehow...
	// 	msg7->m_inUse = false;
	// 	msg7->gotMsg7Reply();
	// }


	req = sbuf->getBufStart();
	reqSize = sbuf->length();

	if ( reqSize != need ) { char *xx=NULL;*xx=0 ; }

	// do not free it, let multicast free it after sending it
	sbuf->detachBuf();


	if ( ! mcast->send ( req ,
			     reqSize ,
			     0x07 ,
			     true , // ownmsg?
			     shardNum,
			     false, // send to whole shard?
			     key , // for selecting host in shard
			     mcast , // state
			     NULL , // state2
			     gotMulticastReplyWrapper ,
			     999999 ) ) { // total timeout in seconds
		log("import: import mcast had error: %s",mstrerror(g_errno));
		m_numIn++;
	}

	goto INJECTLOOP;

 nextFile:
	// invalidate this flag
	//m_offIsValid = false;
	// . and call this function. we add one to m_bfFileId so we
	//   do not re-get the file we just injected.
	// . sets m_bf and m_fileOffset
	// . returns false if nothing to read
	if ( ! setCurrentTitleFileAndOffset ( ) ) { //cr , m_bfFileId+1 );
		log("import: import: no files left to read");
		//goto INJECTLOOP;
		return true;
	}

	// if it returns NULL we are done!
	log("main: titledb injection loop completed. waiting for "
	    "outstanding injects to return.");
		
	if ( m_numOut > m_numIn )
		return false;

	log("main: all injects have returned. DONE.");

	// dummy return
	return true;
}
bool sendPageThesaurus( TcpSocket *s, HttpRequest *r ) {
	SafeBuf p;
	char getBuf[64]; // holds extra values for GET method
	char formBuf[256]; // holds extra values for forms
	snprintf(getBuf, 64, "c=%s", 
		 r->getString("c", 0, ""));
	snprintf(formBuf, 256, 
		 "<input type=hidden name=\"c\" value=\"%s\">",
		 //"<input type=hidden name=\"pwd\" value=\"%s\">",
		 r->getString("c", 0, ""));
	g_pages.printAdminTop( &p, s, r);
	
	if (r->getLong("cancel", 0) != 0) {
		g_thesaurus.cancelRebuild();
		p.safePrintf("<br><br>\n");
		p.safePrintf(
		  "<center><b><font color=#ff0000>"
		  "rebuild canceled"
		  "</font></b></center>");
	}

	if (r->getLong("rebuild", 0) != 0) {
		bool full = r->getLong("full", 0);
		p.safePrintf("<br><br>\n");
		if (g_thesaurus.rebuild(0, full)) {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "error starting rebuild, check log for details"
			  "</font></b></center>");
		} else {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "rebuild started"
			  "</font></b></center>");
		}
	}
	
	if (r->getLong("rebuildaff", 0) != 0) {
		bool full = r->getLong("full", 0);
		p.safePrintf("<br><br>\n");
		if (g_thesaurus.rebuildAffinity(0, full)) {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "error starting rebuild, check log for details"
			  "</font></b></center>");
		} else {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "rebuild started"
			  "</font></b></center>");
		}
	}

	if (r->getLong("distribute", 0) != 0) {
		char cmd[1024];
		p.safePrintf("<br><br>\n");
		if (g_thesaurus.m_affinityState) {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "cannot distribute during rebuild"
			  "</font></b></center>");
		} else {
			for ( long i = 0; i < g_hostdb.getNumHosts() ; i++ ) {
				Host *h = g_hostdb.getHost(i);
				snprintf(cmd, 512,
					"rcp -r "
					"./dict/thesaurus.* "
					"%s:%s/dict/ &",
					iptoa(h->m_ip),
					h->m_dir);
				log(LOG_INFO, "admin: %s", cmd);
				system( cmd );
			}
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "data distributed"
			  "</font></b></center>");
		}	
	}

	if (r->getLong("reload", 0) != 0) {
		p.safePrintf("<br><br>\n");
		if (r->getLong("cast", 0) != 0) {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "reload command broadcast"
			  "</font></b></center>");
		} else if (g_thesaurus.init()) {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "thesaurus data reloaded"
			  "</font></b></center>");
		} else {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "error reloading thesaurus data"
			  "</font></b></center>");
		}
	}

	long manualAddLen = 0;
	char *manualAdd = NULL;
	SafeBuf manualAddBuf;
	if ((manualAdd = r->getString("manualadd", &manualAddLen))) {
		trimWhite(manualAdd);
		manualAddLen = gbstrlen(manualAdd);
		File manualFile;
		manualFile.set(g_hostdb.m_dir, "dict/thesaurus-manual.txt");
		if (manualFile.open(O_WRONLY | O_CREAT | O_TRUNC) &&
			(manualFile.write(manualAdd, manualAddLen, 0) ==
			 manualAddLen)) {
			char newl = '\n'; // for write()
			if (manualAdd[manualAddLen-1] != '\n')
				manualFile.write(&newl, 1, manualAddLen);
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "updated manual add file sucessfully"
			  "</font></b></center>");
		} else {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "error writing manual add file"
			  "</font></b></center>");
		}
	} else {
		char ff[PATH_MAX];
		snprintf(ff, PATH_MAX, "%sdict/thesaurus-manual.txt",
			g_hostdb.m_dir);
		if (manualAddBuf.fillFromFile(ff)) {
			if (*(manualAddBuf.getBuf()-1) != '\n')
				manualAddBuf.pushChar('\n');
			manualAdd = manualAddBuf.getBufStart();
			manualAddLen = manualAddBuf.length();
		}
	}

	long affinityAddLen = 0;
	char *affinityAdd = NULL;
	SafeBuf affinityAddBuf;
	if ((affinityAdd = r->getString("affinityadd", &affinityAddLen))) {
		trimWhite(affinityAdd);
		affinityAddLen = gbstrlen(affinityAdd);
		File affinityFile;
		affinityFile.set(g_hostdb.m_dir, 
			"dict/thesaurus-affinity.txt");
		if (affinityFile.open(O_WRONLY | O_CREAT | O_TRUNC) &&
			(affinityFile.write(affinityAdd, affinityAddLen, 0) ==
			 affinityAddLen)) {
			char newl = '\n'; // for write()
			if (affinityAdd[affinityAddLen-1] != '\n')
				affinityFile.write(&newl, 1, affinityAddLen);
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "updated affinity add file sucessfully"
			  "</font></b></center>");
		} else {
			p.safePrintf(
			  "<center><b><font color=#ff0000>"
			  "error writing affinity add file"
			  "</font></b></center>");
		}
	} else {
		char ff[PATH_MAX];
		snprintf(ff, PATH_MAX, "%sdict/thesaurus-affinity.txt",
			g_hostdb.m_dir);
		if (affinityAddBuf.fillFromFile(ff)) {
			if (*(affinityAddBuf.getBuf()-1) != '\n')
				affinityAddBuf.pushChar('\n');
			affinityAdd = affinityAddBuf.getBufStart();
			affinityAddLen = affinityAddBuf.length();
		}
	}
	

	char *syn = r->getString("synonym");
	long len = 0;
	if (syn) len = gbstrlen(syn);

	if (len) {
		SynonymInfo info;
		bool r = g_thesaurus.getAllInfo(syn, &info, len, SYNBIT_ALL);
		p.safePrintf("<br><br>\n");
		p.safePrintf ( 
		  "<table cellpadding=4 width=100%% bgcolor=#%s border=1>"
		  "<tr>"
		  "<td colspan=2 bgcolor=#%s>"
		  "<center><b>Synonym List (%ld)</b></center>"
		  "</td>"
		  "</tr>\n",
		  LIGHT_BLUE, DARK_BLUE, info.m_numSyns);
		if (r) {
			p.safePrintf("<tr>"
			  "<td align=right><tt>%s</tt></td>"
			  "<td align=left>"
			  "<tt>1.000/%08lX (1.000/%08lX)</tt>"
			  "</td>"
			  "</tr>\n", syn, MAX_AFFINITY, MAX_AFFINITY);
			for (long i = 0; i < info.m_numSyns; i++) {
				// get the reverse affinity as well
				long aff = g_thesaurus.getAffinity(
					info.m_syn[i], syn,
					info.m_len[i], len);
				p.safePrintf( 
				  "<tr>"
				  "<td width=40%% align=right>"
				  "<tt>");
				p.safeMemcpy(info.m_syn[i], info.m_len[i]);
				p.safePrintf("</tt>"
				  "</td>"
				  "<td width=60%% align=left>"
				  "<tt>");
				if (info.m_affinity[i] >= 0) {
					p.safePrintf("%0.3f/%08lX ",
				  	  (float)info.m_affinity[i] 
					  	/ MAX_AFFINITY,
					  info.m_affinity[i]);
				} else {
					p.safePrintf("u ");
				}
				if (aff >= 0) {
					p.safePrintf("(%0.3f/%08lX) ",
					  (float)aff / MAX_AFFINITY, 
					  aff);
				} else {
					p.safePrintf("(u) ");
				}
				p.safePrintf("(%ld) (%ld) (%ld) (%ld) "
					     "(%lld) (%lld)",
				  (long)info.m_type[i], (long)info.m_sort[i],
				  info.m_firstId[i], info.m_lastId[i],
				  info.m_leftSynHash[i], 
				  info.m_rightSynHash[i]);
				for (int j = info.m_firstId[i]; 
					j <= info.m_lastId[i];
					j++) {
					p.safePrintf(" (%lld)",
						info.m_termId[j]);
				}
				p.safePrintf(
				  "</tt>"
				  "</td>"
				  "</tr>\n");
			}
			p.safePrintf("</table>");
		} else {
			p.safePrintf("<tr>"
			  "<td align=center><font color=#FF0000>"
			  "synonym not found: %s"
			  "</font></td>"
			  "</tr>\n",
			  syn);
		}
	}

	p.safePrintf ( "<br><br>\n" );

	p.safePrintf ( 
		  "<table cellpadding=4 width=100%% bgcolor=#%s border=1>"
		  "<tr>"
		  "<td colspan=2 bgcolor=#%s>"
		  "<center><b>Thesaurus Controls"
		  "</b></center></td>"
		  "</tr>\n",
		  LIGHT_BLUE, DARK_BLUE);
	
	p.safePrintf (
		  "<tr>"
		  "<td width=37%%><b>rebuild all data</b><br>"
		  "<font size=1>"
		  "rebuilds synonyms and then begins the rebuild process for "
		  "affinity data; this should only be run on one host, as the "
		  "data is copied when the process is finished; full rebuild "
		  "does not use existing affinity data"
		  "</font>"
		  "</td>"
		  "<td width=12%% bgcolor=#0000ff>"
		  "<center><b><a href=\"/master/thesaurus?rebuild=1&%s\">"
		  "rebuild all data</a> <a href=\"/master/thesaurus?"
		  "rebuild=1&full=1&%s\">(full)</a></b></center>"
		  "</td>"
		  "</tr>\n", getBuf, getBuf);

	p.safePrintf (
		  "<tr>"
		  "<td width=37%%><b>distribute data</b><br>"
		  "<font size=1>"
		  "distributes all thesaurus data to all hosts, this is "
		  "normally done automatically but if there was a problem "
		  "with the copy, this lets you do it manually"
		  "</font>"
		  "</td>"
		  "<td width=12%% bgcolor=#0000ff>"
		  "<center><b><a href=\"/master/thesaurus?distribute=1&%s\">"
		  "distribute data</a></b></center>"
		  "</td>"
		  "</tr>\n", getBuf);

	p.safePrintf (
		  "<tr>"
		  "<td width=37%%><b>reload data</b><br>"
		  "<font size=1>"
		  "reloads the synonyms and affinity table on this host only"
		  "</font>"
		  "</td>"
		  "<td width=12%% bgcolor=#0000ff>"
		  "<center><b>"
		  "<a href=\"/master/thesaurus?reload=1&cast=0&%s\">"
		  "reload data</a></b></center>"
		  "</td>"
		  "</tr>\n", getBuf);

	p.safePrintf (
		  "<tr>"
		  "<td width=37%%><b>reload data (all hosts)</b><br>"
		  "<font size=1>"
		  "reloads the synonyms and affinity table on all hosts"
		  "</font>"
		  "</td>"
		  "<td width=12%% bgcolor=#0000ff>"
		  "<center><b>"
		  "<a href=\"/master/thesaurus?reload=1&cast=1&%s\">"
		  "reload data (all hosts)</a></b></center>"
		  "</td>"
		  "</tr>\n", getBuf);

	p.safePrintf (
		  "<tr>"
		  "<td width=37%%><b>list synonyms</b><br>"
		  "<font size=1>"
		  "enter a word here to list all synonym entries and their "
		  "affinities"
		  "</font>"
		  "</td>"
		  "<td width=12%%>"
		  "<form action=\"/master/thesaurus>\">"
		  "<input type=text name=synonym size=20>"
		  "<input type=submit value=Submit>"
		  "%s"
		  "</form></td>"
		  "</tr>\n", formBuf);
		
	p.safePrintf (
		  "<tr>"
		  "<td colspan=2 bgcolor=#%s>"
		  "<center><b>Affinity Controls"
		  "</b></center></td>"
		  "</tr>\n",
		  DARK_BLUE);

	p.safePrintf (
		  "<tr>"
		  "<td width=37%%><b>cancel running rebuild</b><br>"
		  "<font size=1>"
		  "cancels the rebuild and throws all intermediate data away"
		  "</font>"
		  "</td>"
		  "<td width=12%% bgcolor=#0000ff>"
		  "<center><b><a href=\"/master/thesaurus?cancel=1&%s\">"
		  "cancel running rebuild</a></b></center>"
		  "</td>"
		  "</tr>\n", getBuf);
	
	p.safePrintf (
		  "<tr>"
		  "<td width=37%%><b>rebuild affinity only</b><br>"
		  "<font size=1>"
		  "begins the rebuild process for affinity data, has no "
		  "effect if a rebuild is already in progress; full rebuild "
		  "does not reuse existing affinity data"
		  "</font>"
		  "</td>"
		  "<td width=12%% bgcolor=#0000ff>"
		  "<center><b><a href=\"/master/thesaurus?rebuildaff=1&%s\">"
		  "rebuild affinity</a> <a href=\"/master/thesaurus?"
		  "rebuildaff=1&full=1&%s\">(full)</a></b></center>"
		  "</td>"
		  "</tr>\n", getBuf, getBuf);
	
	p.safePrintf (
		  "<tr>"
		  "<td colspan=2 bgcolor=#%s>"
		  "<center><b>Manual File Controls"
		  "</b></td>"
		  "</tr>\n",
		  DARK_BLUE);

	p.safePrintf (
		  "<tr>"
		  "<td align=center colspan=2>");
	
	p.safePrintf(
		  "<b>manually added pairs</b><br>\n"
		  "<font size=1>place word pairs here that should be linked "
		  "as synonyms, one pair per line, seperated by a pipe '|' "
		  "character, optionally followed by another pipe and a type "
		  "designation; any badly formatted lines will be silently "
		  "ignored</font><br>\n"
		  "<form action=\"/master/thesaurus\" method=post>"
		  "<textarea name=\"manualadd\" rows=20 cols=80>");

	if (manualAdd && manualAddLen) {
		p.htmlEncode(manualAdd, manualAddLen, true);
	}
	
	p.safePrintf (
		  "</textarea><br>"
		  "<input type=submit value=Submit>"
		  "<input type=reset value=Reset>"
		  "%s"
		  "</form></td>"
		  "</tr>\n",
		  formBuf);

	
	p.safePrintf (
		  "<tr>"
		  "<td align=center colspan=2>"
		  "<b>affinity value overrides</b><br>\n"
		  "<font size=1>place word/phrase pairs here that should have "
		  "there affinity values overridden, format is "
		  "\"word1|word2|value\", where value is a floating point, "
		  "integer (either decimal or hex), or the word \"max\"; "
		  "any badly formatted lines will be silently ignored; note "
		  "that these pairs will only work if the thesaurus otherwise "
		  "has an entry for them, so add them to the manual add file "
		  "above if need be</font><br>\n"
		  "<form action=\"/master/thesaurus\" method=post>"
		  "<textarea name=\"affinityadd\" rows=20 cols=80>");

	if (affinityAdd && affinityAddLen) {
		p.htmlEncode(affinityAdd, affinityAddLen, true);
	}
	
	p.safePrintf (
		  "</textarea><br>"
		  "<input type=submit value=Submit>"
		  "<input type=reset value=Reset>"
		  "%s"
		  "</form></td>"
		  "</tr>\n", 
		  formBuf);


	p.safePrintf ( "</table>\n" );
	p.safePrintf ( "<br><br>\n" );

	p.safePrintf (
		  "<table cellpadding=4 width=100%% bgcolor=#%s border=1>"
		  "<tr>"
		  "<td colspan=2 bgcolor=#%s>"
		  "<center><b>Affinity Builder Status"
		  "</b></td>"
		  "</tr>\n",
		  LIGHT_BLUE, DARK_BLUE);

	long long a, b, c, d, e, f, g, h, i, j, k;
	StateAffinity *aff = g_thesaurus.m_affinityState;
	if (!aff) {
		p.safePrintf (
		  "<tr><td colspan=2>"
		  "<center><b>Not running</b></center>"
		  "</td></tr>\n");
		a = b = c = d = e = f = g = h = i = j = k = 0;
	} else {
		a = aff->m_oldTable->getNumSlotsUsed();
		b = aff->m_oldTable->getNumSlotsUsed() - aff->m_n;
		c = aff->m_n;
		d = (gettimeofdayInMilliseconds() - aff->m_time) / 1000;
		if (!d || !(c / d)) { 
			e = 0;
		} else {
			e = b / (c / d);
		}
		f = aff->m_sent;
		g = aff->m_recv;
		h = aff->m_errors;
		i = aff->m_old;
		j = aff->m_cache;
		k = aff->m_hitsTable.getNumSlotsUsed();
	}
	p.safePrintf (
		  "<tr><td><b># of total pairs</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b># of pairs remaining</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b># of pairs processed</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b>elapsed time in seconds</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b>estimated remaining time in seconds</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b># of requests sent</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b># of requests received</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b># of request errors</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b># of old values reused</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b># of cache hits</b></td>"
		  "<td>%lli</td></tr>\n"
		  "<tr><td><b>cache size</b></td>"
		  "<td>%lli</td></tr>\n",
		  a, b, c, d, e, f, g, h, i, j, k);
	p.safePrintf ( "</table>\n" );

	return g_httpServer.sendDynamicPage ( s, p.getBufStart(), p.length() );
}