//this file is part of eMule //Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net ) // //This program is free software; you can redistribute it and/or //modify it under the terms of the GNU General Public License //as published by the Free Software Foundation; either //version 2 of the License, or (at your option) any later version. // //This program is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU General Public License for more details. // //You should have received a copy of the GNU General Public License //along with this program; if not, write to the Free Software //Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include "stdafx.h" #include "emule.h" #include "SearchList.h" #include "JigleSearch.h" #include "soapH.h" #include "soapJigleService-1.0.nsmap" #include "OtherFunctions.h" #include "emuledlg.h" #include "SearchDlg.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif // eMule has to *ensure* that the "gzip compression" is compiled. Do NOT change this!! #if !defined(_DEBUG_SOAP) && (!defined(WITH_GZIP) || !defined(WITH_ZLIB)) #error "You have to define WITH_GZIP and WITH_ZLIB to complile this module for usage in the eMule project!" #endif // MOD Note: Do NOT change this part #define MAX_JIGLE_RESULTS 100 // MOD Note: end #define TYPECODE_AUDIO 0x0001 // Identifies audio files. #define TYPECODE_VIDEO 0x0002 // Identifies video files. #define TYPECODE_IMAGE 0x0004 // Identifies image files. #define TYPECODE_PROGRAM 0x0008 // Identifies program files. #define TYPECODE_DOCUMENT 0x0010 // Identifies document files. #define TYPECODE_COLLECTION 0x0020 // Identifies file collections (deprecated?). #define MATCH_KEYWORDS 0x0100 // Match keywords instead of fulltext when searching. #define SORT_ATTR_SIZE 0x0200 // Sort search results by file size. #define SORT_ATTR_RELEVANCE 0x0400 // Sort search results by relevance. #define SORT_ASCENDING 0x0800 // Sort search results in ascending order. #define GET_FILENAMES 0x1000 // Include each files filename list in the search results. #define GET_SERVERS 0x2000 // Include each files server list in the search results. #define GET_SR_RELEASEID 0x4000 // Include the ShareReactor release ID of files in the search results. bool CSearchDlg::DoNewJigleSearch(SSearchParams* pParams) { // to search the Jigle database we have to specify either // *) a file type (TYPECODE_xxx) // --or-- // *) a file extension if (GetResString(IDS_SEARCH_ANY) == pParams->strFileType && pParams->strExtension.IsEmpty()){ AfxMessageBox( GetResString(IDS_ERR_JIGLENEEDSTYPE) ); GetDlgItem(IDC_EDITSEARCHEXTENSION)->SetFocus(); return false; } if (!m_pJigleThread){ m_pJigleThread = new CJigleSOAPThread; if (!m_pJigleThread->CreateThread()){ delete m_pJigleThread; m_pJigleThread = NULL; return false; } } JIGLE_SEARCH_REQUEST* pReq = new JIGLE_SEARCH_REQUEST; // Set Jigle search parameters pReq->strPhrase = pParams->strExpression; pReq->strExt = pParams->strExtension; pReq->iAvail = pParams->uAvailability <= 2 ? 3 : pParams->uAvailability; pReq->llMinSize = (pParams->ulMinSize == 0) ? 1I64 : pParams->ulMinSize; pReq->llMaxSize = (pParams->ulMaxSize == 0) ? -1I64 : pParams->ulMaxSize; pReq->iOffset = 0; //pReq->iTotal = 10; // this will put more load on the server than querying for 100 results! pReq->iTotal = MAX_JIGLE_RESULTS; pReq->iOptions = GET_FILENAMES | (pParams->bMatchKeywords ? MATCH_KEYWORDS : 0); //TODO: use 'GetE2DKFileTypeSearchTerm' CString strResultType = pParams->strFileType; if (GetResString(IDS_SEARCH_AUDIO) == strResultType) { pReq->iOptions |= TYPECODE_AUDIO; } else if (GetResString(IDS_SEARCH_VIDEO) == strResultType) { pReq->iOptions |= TYPECODE_VIDEO; } else if (GetResString(IDS_SEARCH_PRG) == strResultType) { strResultType = GetResString(IDS_SEARCH_ANY); pReq->iOptions |= TYPECODE_PROGRAM; } else if (GetResString(IDS_SEARCH_PICS) == strResultType) { pReq->iOptions |= TYPECODE_IMAGE; } else if (GetResString(IDS_SEARCH_CDIMG) == strResultType) { pReq->iOptions |= TYPECODE_PROGRAM; } else if (GetResString(IDS_SEARCH_ARC) == strResultType) { pReq->iOptions |= TYPECODE_PROGRAM; } else { //TODO: Support "Doc" types ASSERT( GetResString(IDS_SEARCH_ANY) == strResultType ); ASSERT( !pReq->strExt.IsEmpty() ); } m_nSearchID++; pParams->dwSearchID = m_nSearchID; theApp.searchlist->NewSearch(&searchlistctrl, strResultType, m_nSearchID); canceld = false; globsearch = false; CreateNewTab(pParams); pReq->hWnd = AfxGetMainWnd()->GetSafeHwnd(); // 'iTotalSearchResults' is used as an extra 'safety' counter for the nr. of search results, to avoid the worst case // of querying the server in an endless loop because of false 'offset' and 'total' fields in the search results. pReq->iTotalSearchResults = 0; pReq->nSearchID = m_nSearchID; m_pJigleThread->PostThreadMessage(WM_JIGLE_SEARCH_REQUEST, 0, (LPARAM)pReq); return true; } static int __cdecl Compare_api__Filename(const void* p1, const void* p2) { const api__Filename* pFile1 = *(const api__Filename**)p1; const api__Filename* pFile2 = *(const api__Filename**)p2; return pFile2->a - pFile1->a; } static void Free(api__searchResponse* res) { if (res) { if (res->_r) { if (res->_r->m) { const api__File* pFileHash = res->_r->m->__ptr; int iFileHashs = res->_r->m->__size; while (iFileHashs--) { if (pFileHash->n) { delete pFileHash->n->__ptr; delete pFileHash->n; } if (pFileHash->v) delete pFileHash->v; pFileHash++; } delete res->_r->m->__ptr; delete res->_r->m; } delete res->_r; } } } LRESULT CSearchDlg::ProcessJigleSearchResponse(WPARAM wParam, LPARAM lParam) { JIGLE_SEARCH_RESPONSE* pRes = (JIGLE_SEARCH_RESPONSE*)lParam; struct soap* soap = pRes->soap; bool bMore = false; if (pRes->iResult) { Debug("Jigle search response: Error\n"); CString strError; if (soap->error) { //Examples for gSOAP errors // //Connecting to non existing host (e.g. http://xyz.com/soap) //---------------------------------------------------------- //SOAP fault: SOAP-ENV:Client //"Host not found" //Detail: TCP get host by name failed int tcp_connect() // //Connecting to a non existing directory on the server (e.g. http://jigle.com/soap404) //------------------------------------------------------------------------------------ //SOAP fault: SOAP-ENV:Server //"HTTP error" //Detail: HTTP/1.0 404 Not Found const char **s = soap_faultdetail(soap); if (!*soap_faultcode(soap)) soap_set_fault(soap); if (!*soap_faultstring(soap)) *soap_faultstring(soap) = ""; strError.Format(_T("Failed to search the jigle database!\r\n\r\n") _T("SOAP fault: %hs\r\n\"%hs\""), *soap_faultcode(soap), *soap_faultstring(soap)); if (s && *s) { strError += _T("\r\nDetail: "); strError += *s; } if (*soap_faultcode(soap) && stricmp(*soap_faultcode(soap), "SOAP-ENV:Server")==0) strError += _T("\r\n\r\nVisit http://jigle.com/soap for more details about the error."); } AfxMessageBox(strError, MB_ICONERROR); Free(pRes->res); } else if (pRes->res && pRes->res->_r) { if (pRes->res->_r->m) { Debug("Jigle search response\n offset=%u total=%u matches=%u\n", pRes->res->_r->o, pRes->res->_r->t, pRes->res->_r->m->__size); int iResultsAdded = 0; for (int iFileHash = 0; iFileHash < pRes->res->_r->m->__size; iFileHash++) { const api__File* pFileHash = pRes->res->_r->m->__ptr + iFileHash; uchar aucFileHash[16]; if (!canceld && strmd4(pFileHash->h, aucFileHash)) { //Debug(" size=%9u hash=%s\n", (ULONG)pFileHash->size, pFileHash->hash); if (pFileHash->n) { // sort filenames by availability CTypedPtrArray aFileNames; aFileNames.SetSize(pFileHash->n->__size); for (int iFileName = 0; iFileName < aFileNames.GetSize(); iFileName++) aFileNames[iFileName] = pFileHash->n->__ptr + iFileName; qsort(aFileNames.GetData(), aFileNames.GetSize(), sizeof(aFileNames[0]), Compare_api__Filename); bool bAddedFile = false; for (int iFileName = 0; iFileName < aFileNames.GetSize(); iFileName++) { const api__Filename* pFileName = aFileNames[iFileName]; //Debug(" avail=%5u name=%s\n", pFileName->avail, pFileName->text); CSearchFile* pSearchFile = new CSearchFile(m_nSearchID, aucFileHash, pFileHash->s, pFileName->t, pFileHash->t, pFileName->a); if (theApp.searchlist->AddToList(pSearchFile)) bAddedFile = true; } if (bAddedFile) // if we added at least one file, we count this hash+size as a search result iResultsAdded++; } } if (pFileHash->n) { delete pFileHash->n->__ptr; delete pFileHash->n; } } pRes->pReq->iTotalSearchResults += iResultsAdded; Debug(" iResultsAdded=%u iTotalSearchResults=%u\n", iResultsAdded, pRes->pReq->iTotalSearchResults); if ( !canceld && pRes->pReq->iTotalSearchResults < MAX_JIGLE_RESULTS && pRes->res->_r->o + pRes->res->_r->m->__size < pRes->res->_r->t) { pRes->pReq->iOffset = pRes->res->_r->o + pRes->res->_r->m->__size; pRes->pReq->iTotal = MAX_JIGLE_RESULTS - pRes->pReq->iTotalSearchResults; // because we are post-filtering the search results, it's more efficient to query the server for at // least 10-20 entries instead of only 1-3 (which may all be dropped because of local filtering)! const iMinSearchReqEntries = 20; if (pRes->pReq->iTotal < iMinSearchReqEntries) pRes->pReq->iTotal = iMinSearchReqEntries; bMore = true; } delete pRes->res->_r->m->__ptr; delete pRes->res->_r->m; } else Debug("Jigle search response: Success: \n"); delete pRes->res->_r; } else { Debug("Jigle search response: Not found\n"); } soap_set_recv_logfile(soap, NULL); soap_set_sent_logfile(soap, NULL); soap_set_test_logfile(soap, NULL); soap_end(pRes->soap); // remove all temporary and deserialized data JIGLE_SEARCH_REQUEST* pReq = pRes->pReq; delete pRes->res; delete pRes->soap; delete pRes; if (!bMore || (m_pJigleThread && !m_pJigleThread->PostThreadMessage(WM_JIGLE_SEARCH_REQUEST, 0, (LPARAM)pReq))) { if (m_pJigleThread) { m_pJigleThread->PostThreadMessage(WM_QUIT, 0, 0); m_pJigleThread = NULL; } delete pReq; GetDlgItem(IDC_STARTS)->EnableWindow(true); CWnd* pWndFocus = GetFocus(); GetDlgItem(IDC_CANCELS)->EnableWindow(false); if (pWndFocus == GetDlgItem(IDC_CANCELS)) GetDlgItem(IDC_SEARCHNAME)->SetFocus(); } return 0; } /////////////////////////////////////////////////////////////////////////////// // CJigleSOAPThread IMPLEMENT_DYNAMIC(CJigleSOAPThread, CWinThread) CJigleSOAPThread::CJigleSOAPThread() { } BOOL CJigleSOAPThread::InitInstance() { BOOL bTerminate = FALSE; while (!bTerminate) { UINT uNumEvents = 0; DWORD dwEventIdx; dwEventIdx = MsgWaitForMultipleObjects(uNumEvents, NULL, FALSE, INFINITE, QS_SENDMESSAGE | QS_POSTMESSAGE | QS_TIMER); if (dwEventIdx == (DWORD)-1) ASSERT(0); else if (dwEventIdx == WAIT_TIMEOUT) ASSERT(0); else if (dwEventIdx == WAIT_OBJECT_0 + uNumEvents) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_JIGLE_SEARCH_REQUEST) { struct soap* soap = new struct soap; soap_init2(soap, SOAP_IO_KEEPALIVE, SOAP_IO_KEEPALIVE); #ifndef _DEBUG_SOAP soap_set_recv_logfile(soap, NULL); soap_set_sent_logfile(soap, NULL); soap_set_test_logfile(soap, NULL); #endif JIGLE_SEARCH_REQUEST* pReq = (JIGLE_SEARCH_REQUEST*)msg.lParam; // MOD Note: A Jigle search expression must follow those rules - Do NOT change this! // - The client must announce gzip functionality to the Jigle server with the appropriate HTTP header. (see. 'WITH_GZIP') // - The max. nr. of results to be returned for one search request must be specified as <=100. // - The search expression must either contain one of the TYPECODE_xxx flags or an filename extension. // - Be gentle to the Jigle server! // MOD Note: end ASSERT( pReq->iTotal <= 100 ); ASSERT( !pReq->strExt.IsEmpty() || (pReq->iOptions & (TYPECODE_AUDIO | TYPECODE_VIDEO | TYPECODE_IMAGE | TYPECODE_PROGRAM | TYPECODE_DOCUMENT)) ); api__searchResponse* r = new api__searchResponse; r->_r = NULL; Debug("Jigle search request\n offset=%u total=%u\n", pReq->iOffset, pReq->iTotal); int iResult = soap_call_api__search(soap, "http://jigle.com/soap", "", const_cast((const char*)pReq->strPhrase), const_cast((const char*)pReq->strExt), pReq->iAvail, pReq->llMinSize, pReq->llMaxSize, pReq->iOffset, pReq->iTotal, pReq->iOptions, r); if (iResult) { JIGLE_SEARCH_RESPONSE* pRes = new JIGLE_SEARCH_RESPONSE; pRes->iResult = iResult; pRes->soap = soap; pRes->res = r; pRes->pReq = pReq; ::PostMessage(pReq->hWnd, WM_JIGLE_SEARCH_RESPONSE, iResult, (LPARAM)pRes); } else if (r->_r) { JIGLE_SEARCH_RESPONSE* pRes = new JIGLE_SEARCH_RESPONSE; pRes->iResult = iResult; pRes->soap = soap; pRes->res = r; pRes->pReq = pReq; ::PostMessage(pReq->hWnd, WM_JIGLE_SEARCH_RESPONSE, iResult, (LPARAM)pRes); } else { // Not found delete r; JIGLE_SEARCH_RESPONSE* pRes = new JIGLE_SEARCH_RESPONSE; pRes->iResult = iResult; pRes->soap = soap; pRes->res = NULL; pRes->pReq = pReq; ::PostMessage(pReq->hWnd, WM_JIGLE_SEARCH_RESPONSE, iResult, (LPARAM)pRes); } } else if (msg.message == WM_QUIT) { bTerminate = TRUE; break; } else ASSERT(0); } } else ASSERT(0); } return FALSE; }