2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net )
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #include "SearchList.h" // Interface declarations.
28 #include <protocol/Protocols.h>
29 #include <protocol/kad/Constants.h>
30 #include <tags/ClientTags.h>
31 #include <tags/FileTags.h>
33 #include "updownclient.h" // Needed for CUpDownClient
34 #include "MemFile.h" // Needed for CMemFile
35 #include "amule.h" // Needed for theApp
36 #include "ServerConnect.h" // Needed for theApp->serverconnect
37 #include "Server.h" // Needed for CServer
38 #include "ServerList.h" // Needed for theApp->serverlist
39 #include "Statistics.h" // Needed for theStats
40 #include "ObservableQueue.h"// Needed for CQueueObserver
41 #include <common/Format.h>
42 #include "Logger.h" // Needed for AddLogLineM/...
43 #include "Packet.h" // Needed for CPacket
44 #include "GuiEvents.h" // Needed for Notify_*
48 #include "amuleDlg.h" // Needed for CamuleDlg
49 #include "SearchDlg.h" // Needed for CSearchDlg
52 #include "kademlia/kademlia/Kademlia.h"
53 #include "kademlia/kademlia/Search.h"
55 #include "SearchExpr.h"
56 #include "Scanner.h.in"
60 extern int yyerror(const char* errstr
);
61 extern int yyerror(wxString errstr
);
63 static wxString s_strCurKadKeyword
;
65 static CSearchExpr _SearchExpr
;
67 wxArrayString _astrParserErrors
;
70 // Helper function for lexer.
71 void ParsedSearchExpression(const CSearchExpr
* pexpr
)
77 for (unsigned int i
= 0; i
< pexpr
->m_aExpr
.GetCount(); i
++) {
78 const wxString
& str
= pexpr
->m_aExpr
[i
];
79 if (str
== SEARCHOPTOK_AND
) {
81 } else if (str
== SEARCHOPTOK_OR
) {
83 } else if (str
== SEARCHOPTOK_NOT
) {
88 // this limit (+ the additional operators which will be added later) has to match the limit in 'CreateSearchExpressionTree'
89 // +1 Type (Audio, Video)
94 // +1 Complete sources
103 if (iOpAnd
+ iOpOr
+ iOpNot
> 10) {
104 yyerror(wxT("Search expression is too complex"));
107 _SearchExpr
.m_aExpr
.Empty();
109 // optimize search expression, if no OR nor NOT specified
110 if (iOpAnd
> 0 && iOpOr
== 0 && iOpNot
== 0) {
111 // figure out if we can use a better keyword than the one the user selected
112 // for example most user will search like this "The oxymoronaccelerator 2", which would ask the node which indexes "the"
113 // This causes higher traffic for such nodes and makes them a viable target to attackers, while the kad result should be
114 // the same or even better if we ask the node which indexes the rare keyword "oxymoronaccelerator", so we try to rearrange
115 // keywords and generally assume that the longer keywords are rarer
116 if (/*thePrefs::GetRearrangeKadSearchKeywords() &&*/ !s_strCurKadKeyword
.IsEmpty()) {
117 for (unsigned int i
= 0; i
< pexpr
->m_aExpr
.GetCount(); i
++) {
118 if (pexpr
->m_aExpr
[i
] != SEARCHOPTOK_AND
) {
119 if (pexpr
->m_aExpr
[i
] != s_strCurKadKeyword
120 && pexpr
->m_aExpr
[i
].find_first_of(Kademlia::CSearchManager::GetInvalidKeywordChars()) == wxString::npos
121 && pexpr
->m_aExpr
[i
].Find('"') != 0 // no quoted expressions as keyword
122 && pexpr
->m_aExpr
[i
].length() >= 3
123 && s_strCurKadKeyword
.length() < pexpr
->m_aExpr
[i
].length())
125 s_strCurKadKeyword
= pexpr
->m_aExpr
[i
];
130 wxString strAndTerms
;
131 for (unsigned int i
= 0; i
< pexpr
->m_aExpr
.GetCount(); i
++) {
132 if (pexpr
->m_aExpr
[i
] != SEARCHOPTOK_AND
) {
133 // Minor optimization: Because we added the Kad keyword to the boolean search expression,
134 // we remove it here (and only here) again because we know that the entire search expression
135 // does only contain (implicit) ANDed strings.
136 if (pexpr
->m_aExpr
[i
] != s_strCurKadKeyword
) {
137 if (!strAndTerms
.IsEmpty()) {
140 strAndTerms
+= pexpr
->m_aExpr
[i
];
144 wxASSERT( _SearchExpr
.m_aExpr
.GetCount() == 0);
145 _SearchExpr
.m_aExpr
.Add(strAndTerms
);
147 if (pexpr
->m_aExpr
.GetCount() != 1 || pexpr
->m_aExpr
[0] != s_strCurKadKeyword
)
148 _SearchExpr
.Add(pexpr
);
153 //! Helper class for packet creation
154 class CSearchExprTarget
157 CSearchExprTarget(CMemFile
* pData
, EUtf8Str eStrEncode
, bool supports64bit
, bool& using64bit
)
159 m_eStrEncode(eStrEncode
),
160 m_supports64bit(supports64bit
),
161 m_using64bit(using64bit
)
163 m_using64bit
= false;
166 void WriteBooleanAND()
168 m_data
->WriteUInt8(0); // boolean operator parameter type
169 m_data
->WriteUInt8(0x00); // "AND"
172 void WriteBooleanOR()
174 m_data
->WriteUInt8(0); // boolean operator parameter type
175 m_data
->WriteUInt8(0x01); // "OR"
178 void WriteBooleanNOT()
180 m_data
->WriteUInt8(0); // boolean operator parameter type
181 m_data
->WriteUInt8(0x02); // "NOT"
184 void WriteMetaDataSearchParam(const wxString
& rstrValue
)
186 m_data
->WriteUInt8(1); // string parameter type
187 m_data
->WriteString(rstrValue
, m_eStrEncode
); // string value
190 void WriteMetaDataSearchParam(uint8 uMetaTagID
, const wxString
& rstrValue
)
192 m_data
->WriteUInt8(2); // string parameter type
193 m_data
->WriteString(rstrValue
, m_eStrEncode
); // string value
194 m_data
->WriteUInt16(sizeof(uint8
)); // meta tag ID length
195 m_data
->WriteUInt8(uMetaTagID
); // meta tag ID name
198 void WriteMetaDataSearchParamASCII(uint8 uMetaTagID
, const wxString
& rstrValue
)
200 m_data
->WriteUInt8(2); // string parameter type
201 m_data
->WriteString(rstrValue
, utf8strNone
); // string value
202 m_data
->WriteUInt16(sizeof(uint8
)); // meta tag ID length
203 m_data
->WriteUInt8(uMetaTagID
); // meta tag ID name
206 void WriteMetaDataSearchParam(const wxString
& pszMetaTagID
, const wxString
& rstrValue
)
208 m_data
->WriteUInt8(2); // string parameter type
209 m_data
->WriteString(rstrValue
, m_eStrEncode
); // string value
210 m_data
->WriteString(pszMetaTagID
); // meta tag ID
213 void WriteMetaDataSearchParam(uint8_t uMetaTagID
, uint8_t uOperator
, uint64_t value
)
215 bool largeValue
= value
> wxULL(0xFFFFFFFF);
216 if (largeValue
&& m_supports64bit
) {
218 m_data
->WriteUInt8(8); // numeric parameter type (int64)
219 m_data
->WriteUInt64(value
); // numeric value
224 m_data
->WriteUInt8(3); // numeric parameter type (int32)
225 m_data
->WriteUInt32(value
); // numeric value
227 m_data
->WriteUInt8(uOperator
); // comparison operator
228 m_data
->WriteUInt16(sizeof(uint8
)); // meta tag ID length
229 m_data
->WriteUInt8(uMetaTagID
); // meta tag ID name
232 void WriteMetaDataSearchParam(const wxString
& pszMetaTagID
, uint8_t uOperator
, uint64_t value
)
234 bool largeValue
= value
> wxULL(0xFFFFFFFF);
235 if (largeValue
&& m_supports64bit
) {
237 m_data
->WriteUInt8(8); // numeric parameter type (int64)
238 m_data
->WriteUInt64(value
); // numeric value
243 m_data
->WriteUInt8(3); // numeric parameter type (int32)
244 m_data
->WriteUInt32(value
); // numeric value
246 m_data
->WriteUInt8(uOperator
); // comparison operator
247 m_data
->WriteString(pszMetaTagID
); // meta tag ID
252 EUtf8Str m_eStrEncode
;
253 bool m_supports64bit
;
260 ///////////////////////////////////////////////////////////
263 BEGIN_EVENT_TABLE(CSearchList
, wxEvtHandler
)
264 EVT_MULE_TIMER(wxID_ANY
, CSearchList::OnGlobalSearchTimer
)
268 CSearchList::CSearchList()
269 : m_searchTimer(this, 0 /* Timer-id doesn't matter. */ ),
270 m_searchType(LocalSearch
),
271 m_searchInProgress(false),
273 m_searchPacket(NULL
),
274 m_64bitSearchPacket(false),
275 m_KadSearchFinished(true)
279 CSearchList::~CSearchList()
283 while (!m_results
.empty()) {
284 RemoveResults(m_results
.begin()->first
);
289 void CSearchList::RemoveResults(long searchID
)
291 // A non-existant search id will just be ignored
292 Kademlia::CSearchManager::StopSearch(searchID
, true);
294 ResultMap::iterator it
= m_results
.find(searchID
);
295 if ( it
!= m_results
.end() ) {
296 CSearchResultList
& list
= it
->second
;
298 for (size_t i
= 0; i
< list
.size(); ++i
) {
302 m_results
.erase( it
);
307 wxString
CSearchList::StartNewSearch(uint32
* searchID
, SearchType type
, CSearchParams
& params
)
309 // Check that we can actually perform the specified desired search.
310 if ((type
== KadSearch
) && !Kademlia::CKademlia::IsRunning()) {
311 return _("Kad search can't be done if Kad is not running");
312 } else if ((type
!= KadSearch
) && !theApp
->IsConnectedED2K()) {
313 return _("eD2k search can't be done if eD2k is not connected");
316 if (params
.typeText
!= ED2KFTSTR_PROGRAM
) {
317 if (params
.typeText
.CmpNoCase(wxT("Any"))) {
318 m_resultType
= params
.typeText
;
320 m_resultType
.Clear();
323 // No check is to be made on returned results if the
324 // type is 'Programs', since this returns multiple types.
325 m_resultType
.Clear();
328 if (type
== KadSearch
) {
329 Kademlia::WordList words
;
330 Kademlia::CSearchManager::GetWords(params
.searchString
, &words
);
331 if (!words
.empty()) {
332 params
.strKeyword
= words
.front();
334 return _("No keyword for Kad search - aborting");
338 bool supports64bit
= type
== KadSearch
? true : theApp
->serverconnect
->GetCurrentServer() != NULL
&& (theApp
->serverconnect
->GetCurrentServer()->GetTCPFlags() & SRV_TCPFLG_LARGEFILES
);
339 bool packetUsing64bit
;
341 // This MemFile is automatically free'd
342 CMemFilePtr data
= CreateSearchData(params
, type
, supports64bit
, packetUsing64bit
);
344 if (data
.get() == NULL
) {
345 wxASSERT(_astrParserErrors
.GetCount());
348 for (unsigned int i
= 0; i
< _astrParserErrors
.GetCount(); ++i
) {
349 error
+= _astrParserErrors
[i
] + wxT("\n");
356 if (type
== KadSearch
) {
358 if (*searchID
== 0xffffffff) {
359 Kademlia::CSearchManager::StopSearch(0xffffffff, false);
362 // searchstring will get tokenized there
363 // The tab must be created with the Kad search ID, so searchID is updated.
364 Kademlia::CSearch
* search
= Kademlia::CSearchManager::PrepareFindKeywords(params
.strKeyword
, data
->GetLength(), data
->GetRawBuffer(), *searchID
);
366 *searchID
= search
->GetSearchID();
367 m_currentSearch
= *searchID
;
368 m_KadSearchFinished
= false;
369 } catch (const wxString
& what
) {
371 return _("Unexpected error while attempting Kad search: ") + what
;
374 // This is an ed2k search, local or global
375 m_currentSearch
= *(searchID
);
376 m_searchInProgress
= true;
378 CPacket
* searchPacket
= new CPacket(*data
.get(), OP_EDONKEYPROT
, OP_SEARCHREQUEST
);
380 theStats::AddUpOverheadServer(searchPacket
->GetPacketSize());
381 theApp
->serverconnect
->SendPacket(searchPacket
, (type
== LocalSearch
));
383 if (type
== GlobalSearch
) {
384 delete m_searchPacket
;
385 m_searchPacket
= searchPacket
;
386 m_64bitSearchPacket
= packetUsing64bit
;
387 m_searchPacket
->SetOpCode(OP_GLOBSEARCHREQ
); // will be changed later when actually sending the packet!!
391 return wxEmptyString
;
395 void CSearchList::LocalSearchEnd()
397 if (m_searchType
== GlobalSearch
) {
398 wxCHECK_RET(m_searchPacket
, wxT("Global search, but no packet"));
400 // Ensure that every global search starts over.
401 theApp
->serverlist
->RemoveObserver(&m_serverQueue
);
402 m_searchTimer
.Start(750);
404 m_searchInProgress
= false;
405 Notify_SearchLocalEnd();
410 uint32
CSearchList::GetSearchProgress() const
412 if (m_searchType
== KadSearch
) {
413 // We cannot measure the progress of Kad searches.
414 // But we can tell when they are over.
415 return m_KadSearchFinished
? 0xfffe : 0;
417 if (m_searchInProgress
== false) { // true only for ED2K search
418 // No search, no progress ;)
422 switch (m_searchType
) {
427 return 100 - (m_serverQueue
.GetRemaining() * 100)
428 / theApp
->serverlist
->GetServerCount();
437 void CSearchList::OnGlobalSearchTimer(CTimerEvent
& WXUNUSED(evt
))
439 // Ensure that the server-queue contains the current servers.
440 if (m_searchPacket
== NULL
) {
441 // This was a pending event, handled after 'Stop' was pressed.
443 } else if (!m_serverQueue
.IsActive()) {
444 theApp
->serverlist
->AddObserver(&m_serverQueue
);
447 // UDP requests must not be sent to this server.
448 const CServer
* localServer
= theApp
->serverconnect
->GetCurrentServer();
450 uint32 localIP
= localServer
->GetIP();
451 uint16 localPort
= localServer
->GetPort();
452 while (m_serverQueue
.GetRemaining()) {
453 CServer
* server
= m_serverQueue
.GetNext();
455 // Compare against the currently connected server.
456 if ((server
->GetPort() == localPort
) && (server
->GetIP() == localIP
)) {
457 // We've already requested from the local server.
460 if (server
->SupportsLargeFilesUDP() && (server
->GetUDPFlags() & SRV_UDPFLG_EXT_GETFILES
)) {
462 uint32_t tagCount
= 1;
463 data
.WriteUInt32(tagCount
);
464 CTagVarInt
flags(CT_SERVER_UDPSEARCH_FLAGS
, SRVCAP_UDP_NEWTAGS_LARGEFILES
);
465 flags
.WriteNewEd2kTag(&data
);
466 CPacket
*extSearchPacket
= new CPacket(OP_GLOBSEARCHREQ3
, m_searchPacket
->GetPacketSize() + (uint32_t)data
.GetLength(), OP_EDONKEYPROT
);
467 extSearchPacket
->CopyToDataBuffer(0, data
.GetRawBuffer(), data
.GetLength());
468 extSearchPacket
->CopyToDataBuffer(data
.GetLength(), m_searchPacket
->GetDataBuffer(), m_searchPacket
->GetPacketSize());
469 theStats::AddUpOverheadServer(extSearchPacket
->GetPacketSize());
470 theApp
->serverconnect
->SendUDPPacket(extSearchPacket
, server
, true);
471 AddDebugLogLineN(logServerUDP
, wxT("Sending OP_GLOBSEARCHREQ3 to server ") + Uint32_16toStringIP_Port(server
->GetIP(), server
->GetPort()));
472 } else if (server
->GetUDPFlags() & SRV_UDPFLG_EXT_GETFILES
) {
473 if (!m_64bitSearchPacket
|| server
->SupportsLargeFilesUDP()) {
474 m_searchPacket
->SetOpCode(OP_GLOBSEARCHREQ2
);
475 AddDebugLogLineN(logServerUDP
, wxT("Sending OP_GLOBSEARCHREQ2 to server ") + Uint32_16toStringIP_Port(server
->GetIP(), server
->GetPort()));
476 theStats::AddUpOverheadServer(m_searchPacket
->GetPacketSize());
477 theApp
->serverconnect
->SendUDPPacket(m_searchPacket
, server
, false);
479 AddDebugLogLineN(logServerUDP
, wxT("Skipped UDP search on server ") + Uint32_16toStringIP_Port(server
->GetIP(), server
->GetPort()) + wxT(": No large file support"));
482 if (!m_64bitSearchPacket
|| server
->SupportsLargeFilesUDP()) {
483 m_searchPacket
->SetOpCode(OP_GLOBSEARCHREQ
);
484 AddDebugLogLineN(logServerUDP
, wxT("Sending OP_GLOBSEARCHREQ to server ") + Uint32_16toStringIP_Port(server
->GetIP(), server
->GetPort()));
485 theStats::AddUpOverheadServer(m_searchPacket
->GetPacketSize());
486 theApp
->serverconnect
->SendUDPPacket(m_searchPacket
, server
, false);
488 AddDebugLogLineN(logServerUDP
, wxT("Skipped UDP search on server ") + Uint32_16toStringIP_Port(server
->GetIP(), server
->GetPort()) + wxT(": No large file support"));
491 CoreNotify_Search_Update_Progress(GetSearchProgress());
496 // No more servers left to ask.
501 void CSearchList::ProcessSharedFileList(const byte
* in_packet
, uint32 size
,
502 CUpDownClient
* sender
, bool *moreResultsAvailable
, const wxString
& directory
)
504 wxCHECK_RET(sender
, wxT("No sender in search-results from client."));
506 long searchID
= reinterpret_cast<wxUIntPtr
>(sender
);
509 if (!theApp
->amuledlg
->m_searchwnd
->CheckTabNameExists(sender
->GetUserName())) {
510 theApp
->amuledlg
->m_searchwnd
->CreateNewTab(sender
->GetUserName() + wxT(" (0)"), searchID
);
514 const CMemFile
packet(in_packet
, size
);
515 uint32 results
= packet
.ReadUInt32();
516 bool unicoded
= (sender
->GetUnicodeSupport() != utf8strNone
);
517 for (unsigned int i
= 0; i
!= results
; ++i
){
518 CSearchFile
* toadd
= new CSearchFile(packet
, unicoded
, searchID
, 0, 0, directory
);
519 toadd
->SetClientID(sender
->GetUserIDHybrid());
520 toadd
->SetClientPort(sender
->GetUserPort());
521 AddToList(toadd
, true);
524 if (moreResultsAvailable
)
525 *moreResultsAvailable
= false;
527 int iAddData
= (int)(packet
.GetLength() - packet
.GetPosition());
529 uint8 ucMore
= packet
.ReadUInt8();
530 if (ucMore
== 0x00 || ucMore
== 0x01){
531 if (moreResultsAvailable
) {
532 *moreResultsAvailable
= (ucMore
== 1);
539 void CSearchList::ProcessSearchAnswer(const uint8_t* in_packet
, uint32_t size
, bool optUTF8
, uint32_t serverIP
, uint16_t serverPort
)
541 CMemFile
packet(in_packet
, size
);
543 uint32_t results
= packet
.ReadUInt32();
544 for (; results
> 0; --results
) {
545 AddToList(new CSearchFile(packet
, optUTF8
, m_currentSearch
, serverIP
, serverPort
), false);
550 void CSearchList::ProcessUDPSearchAnswer(const CMemFile
& packet
, bool optUTF8
, uint32_t serverIP
, uint16_t serverPort
)
552 AddToList(new CSearchFile(packet
, optUTF8
, m_currentSearch
, serverIP
, serverPort
), false);
556 bool CSearchList::AddToList(CSearchFile
* toadd
, bool clientResponse
)
558 const uint64 fileSize
= toadd
->GetFileSize();
559 // If filesize is 0, or file is too large for the network, drop it
560 if ((fileSize
== 0) || (fileSize
> MAX_FILE_SIZE
)) {
561 AddDebugLogLineN(logSearch
,
562 CFormat(wxT("Dropped result with filesize %u: %s"))
564 % toadd
->GetFileName());
570 // If the result was not the type the user wanted, drop it.
571 if ((clientResponse
== false) && !m_resultType
.IsEmpty()) {
572 if (GetFileTypeByName(toadd
->GetFileName()) != m_resultType
) {
573 AddDebugLogLineN(logSearch
,
574 CFormat( wxT("Dropped result type %s != %s, file %s") )
575 % GetFileTypeByName(toadd
->GetFileName())
577 % toadd
->GetFileName());
585 // Get, or implictly create, the map of results for this search
586 CSearchResultList
& results
= m_results
[toadd
->GetSearchID()];
588 for (size_t i
= 0; i
< results
.size(); ++i
) {
589 CSearchFile
* item
= results
.at(i
);
591 if ((toadd
->GetFileHash() == item
->GetFileHash()) && (toadd
->GetFileSize() == item
->GetFileSize())) {
592 AddDebugLogLineN(logSearch
, CFormat(wxT("Received duplicate results for '%s' : %s")) % item
->GetFileName() % item
->GetFileHash().Encode());
593 // Add the child, possibly updating the parents filename.
594 item
->AddChild(toadd
);
595 Notify_Search_Update_Sources(item
);
600 AddDebugLogLineN(logSearch
,
601 CFormat(wxT("Added new result '%s' : %s"))
602 % toadd
->GetFileName() % toadd
->GetFileHash().Encode());
604 // New unique result, simply add and display.
605 results
.push_back(toadd
);
606 Notify_Search_Add_Result(toadd
);
612 const CSearchResultList
& CSearchList::GetSearchResults(long searchID
) const
614 ResultMap::const_iterator it
= m_results
.find(searchID
);
615 if (it
!= m_results
.end()) {
619 // TODO: Should we assert in this case?
620 static CSearchResultList list
;
625 void CSearchList::AddFileToDownloadByHash(const CMD4Hash
& hash
, uint8 cat
)
627 ResultMap::iterator it
= m_results
.begin();
628 for ( ; it
!= m_results
.end(); ++it
) {
629 CSearchResultList
& list
= it
->second
;
631 for ( unsigned int i
= 0; i
< list
.size(); ++i
) {
632 if ( list
[i
]->GetFileHash() == hash
) {
633 CoreNotify_Search_Add_Download( list
[i
], cat
);
642 void CSearchList::StopSearch(bool globalOnly
)
644 if (m_searchType
== GlobalSearch
) {
645 m_currentSearch
= -1;
646 delete m_searchPacket
;
647 m_searchPacket
= NULL
;
648 m_searchInProgress
= false;
650 // Order is crucial here: on wx_MSW an additional event can be generated during the stop.
651 // So the packet has to be deleted first, so that OnGlobalSearchTimer() returns immediately
652 // without calling StopGlobalSearch() again.
653 m_searchTimer
.Stop();
655 CoreNotify_Search_Update_Progress(0xffff);
656 } else if (m_searchType
== KadSearch
&& !globalOnly
) {
657 Kademlia::CSearchManager::StopSearch(m_currentSearch
, false);
658 m_currentSearch
= -1;
663 CSearchList::CMemFilePtr
CSearchList::CreateSearchData(CSearchParams
& params
, SearchType type
, bool supports64bit
, bool& packetUsing64bit
)
665 // Count the number of used parameters
666 unsigned int parametercount
= 0;
667 if ( !params
.typeText
.IsEmpty() ) ++parametercount
;
668 if ( params
.minSize
> 0 ) ++parametercount
;
669 if ( params
.maxSize
> 0 ) ++parametercount
;
670 if ( params
.availability
> 0 ) ++parametercount
;
671 if ( !params
.extension
.IsEmpty() ) ++parametercount
;
673 wxString typeText
= params
.typeText
;
674 if (typeText
== ED2KFTSTR_ARCHIVE
){
675 // eDonkeyHybrid 0.48 uses type "Pro" for archives files
676 // www.filedonkey.com uses type "Pro" for archives files
677 typeText
= ED2KFTSTR_PROGRAM
;
678 } else if (typeText
== ED2KFTSTR_CDIMAGE
){
679 // eDonkeyHybrid 0.48 uses *no* type for iso/nrg/cue/img files
680 // www.filedonkey.com uses type "Pro" for CD-image files
681 typeText
= ED2KFTSTR_PROGRAM
;
684 // Must write parametercount - 1 parameter headers
685 CMemFilePtr
data(new CMemFile(100));
687 _astrParserErrors
.Empty();
688 _SearchExpr
.m_aExpr
.Empty();
690 s_strCurKadKeyword
.Clear();
691 if (type
== KadSearch
) {
692 wxASSERT( !params
.strKeyword
.IsEmpty() );
693 s_strCurKadKeyword
= params
.strKeyword
;
696 LexInit(params
.searchString
);
697 int iParseResult
= yyparse();
700 if (_astrParserErrors
.GetCount() > 0) {
701 for (unsigned int i
=0; i
< _astrParserErrors
.GetCount(); ++i
) {
702 AddLogLineNS(CFormat(wxT("Error %u: %s\n")) % i
% _astrParserErrors
[i
]);
705 return CMemFilePtr(nullptr);
708 if (iParseResult
!= 0) {
709 _astrParserErrors
.Add(CFormat(wxT("Undefined error %i on search expression")) % iParseResult
);
711 return CMemFilePtr(nullptr);
714 if (type
== KadSearch
&& s_strCurKadKeyword
!= params
.strKeyword
) {
715 AddDebugLogLineN(logSearch
, CFormat(wxT("Keyword was rearranged, using '%s' instead of '%s'")) % s_strCurKadKeyword
% params
.strKeyword
);
716 params
.strKeyword
= s_strCurKadKeyword
;
719 parametercount
+= _SearchExpr
.m_aExpr
.GetCount();
721 /* Leave the unicode comment there, please... */
722 CSearchExprTarget
target(data
.get(), true /*I assume everyone is unicoded */ ? utf8strRaw
: utf8strNone
, supports64bit
, packetUsing64bit
);
724 unsigned int iParameterCount
= 0;
725 if (_SearchExpr
.m_aExpr
.GetCount() <= 1) {
726 // lugdunummaster requested that searchs without OR or NOT operators,
727 // and hence with no more expressions than the string itself, be sent
728 // using a series of ANDed terms, intersecting the ANDs on the terms
729 // (but prepending them) instead of putting the boolean tree at the start
730 // like other searches. This type of search is supposed to take less load
731 // on servers. Go figure.
733 // input: "a" AND min=1 AND max=2
734 // instead of: AND AND "a" min=1 max=2
735 // we use: AND "a" AND min=1 max=2
737 if (_SearchExpr
.m_aExpr
.GetCount() > 0) {
738 if (++iParameterCount
< parametercount
) {
739 target
.WriteBooleanAND();
741 target
.WriteMetaDataSearchParam(_SearchExpr
.m_aExpr
[0]);
744 if (!typeText
.IsEmpty()) {
745 if (++iParameterCount
< parametercount
) {
746 target
.WriteBooleanAND();
748 // Type is always ascii string
749 target
.WriteMetaDataSearchParamASCII(FT_FILETYPE
, typeText
);
752 if (params
.minSize
> 0) {
753 if (++iParameterCount
< parametercount
) {
754 target
.WriteBooleanAND();
756 target
.WriteMetaDataSearchParam(FT_FILESIZE
, ED2K_SEARCH_OP_GREATER
, params
.minSize
);
759 if (params
.maxSize
> 0){
760 if (++iParameterCount
< parametercount
) {
761 target
.WriteBooleanAND();
763 target
.WriteMetaDataSearchParam(FT_FILESIZE
, ED2K_SEARCH_OP_LESS
, params
.maxSize
);
766 if (params
.availability
> 0){
767 if (++iParameterCount
< parametercount
) {
768 target
.WriteBooleanAND();
770 target
.WriteMetaDataSearchParam(FT_SOURCES
, ED2K_SEARCH_OP_GREATER
, params
.availability
);
773 if (!params
.extension
.IsEmpty()){
774 if (++iParameterCount
< parametercount
) {
775 target
.WriteBooleanAND();
777 target
.WriteMetaDataSearchParam(FT_FILEFORMAT
, params
.extension
);
780 //#warning TODO - I keep this here, ready if we ever allow such searches...
783 if (++iParameterCount
< parametercount
) {
784 target
.WriteBooleanAND();
786 target
.WriteMetaDataSearchParam(FT_COMPLETE_SOURCES
, ED2K_SEARCH_OP_GREATER
, complete
);
790 if (++iParameterCount
< parametercount
) {
791 target
.WriteBooleanAND();
793 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_BITRATE
: FT_ED2K_MEDIA_BITRATE
, ED2K_SEARCH_OP_GREATER
, minBitrate
);
797 if (++iParameterCount
< parametercount
) {
798 target
.WriteBooleanAND();
800 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_LENGTH
: FT_ED2K_MEDIA_LENGTH
, ED2K_SEARCH_OP_GREATER
, minLength
);
803 if (!codec
.IsEmpty()){
804 if (++iParameterCount
< parametercount
) {
805 target
.WriteBooleanAND();
807 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_CODEC
: FT_ED2K_MEDIA_CODEC
, codec
);
810 if (!title
.IsEmpty()){
811 if (++iParameterCount
< parametercount
) {
812 target
.WriteBooleanAND();
814 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_TITLE
: FT_ED2K_MEDIA_TITLE
, title
);
817 if (!album
.IsEmpty()){
818 if (++iParameterCount
< parametercount
) {
819 target
.WriteBooleanAND();
821 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_ALBUM
: FT_ED2K_MEDIA_ALBUM
, album
);
824 if (!artist
.IsEmpty()){
825 if (++iParameterCount
< parametercount
) {
826 target
.WriteBooleanAND();
828 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_ARTIST
: FT_ED2K_MEDIA_ARTIST
, artist
);
832 // If this assert fails... we're seriously fucked up
834 wxASSERT( iParameterCount
== parametercount
);
837 if (!params
.extension
.IsEmpty()) {
838 if (++iParameterCount
< parametercount
) {
839 target
.WriteBooleanAND();
843 if (params
.availability
> 0) {
844 if (++iParameterCount
< parametercount
) {
845 target
.WriteBooleanAND();
849 if (params
.maxSize
> 0){
850 if (++iParameterCount
< parametercount
) {
851 target
.WriteBooleanAND();
855 if (params
.minSize
> 0) {
856 if (++iParameterCount
< parametercount
) {
857 target
.WriteBooleanAND();
861 if (!typeText
.IsEmpty()){
862 if (++iParameterCount
< parametercount
) {
863 target
.WriteBooleanAND();
867 //#warning TODO - same as above...
870 if (++iParameterCount
< parametercount
) {
871 target
.WriteBooleanAND();
876 if (++iParameterCount
< parametercount
) {
877 target
.WriteBooleanAND();
882 if (++iParameterCount
< parametercount
) {
883 target
.WriteBooleanAND();
887 if (!codec
.IsEmpty()){
888 if (++iParameterCount
< parametercount
) {
889 target
.WriteBooleanAND();
893 if (!title
.IsEmpty()){
894 if (++iParameterCount
< parametercount
) {
895 target
.WriteBooleanAND();
899 if (!album
.IsEmpty()) {
900 if (++iParameterCount
< parametercount
) {
901 target
.WriteBooleanAND();
905 if (!artist
.IsEmpty()) {
906 if (++iParameterCount
< parametercount
) {
907 target
.WriteBooleanAND();
912 // As above, if this fails, we're seriously fucked up.
913 wxASSERT( iParameterCount
+ _SearchExpr
.m_aExpr
.GetCount() == parametercount
);
915 for (unsigned int j
= 0; j
< _SearchExpr
.m_aExpr
.GetCount(); ++j
) {
916 if (_SearchExpr
.m_aExpr
[j
] == SEARCHOPTOK_AND
) {
917 target
.WriteBooleanAND();
918 } else if (_SearchExpr
.m_aExpr
[j
] == SEARCHOPTOK_OR
) {
919 target
.WriteBooleanOR();
920 } else if (_SearchExpr
.m_aExpr
[j
] == SEARCHOPTOK_NOT
) {
921 target
.WriteBooleanNOT();
923 target
.WriteMetaDataSearchParam(_SearchExpr
.m_aExpr
[j
]);
927 if (!params
.typeText
.IsEmpty()) {
928 // Type is always ASCII string
929 target
.WriteMetaDataSearchParamASCII(FT_FILETYPE
, params
.typeText
);
932 if (params
.minSize
> 0) {
933 target
.WriteMetaDataSearchParam(FT_FILESIZE
, ED2K_SEARCH_OP_GREATER
, params
.minSize
);
936 if (params
.maxSize
> 0) {
937 target
.WriteMetaDataSearchParam(FT_FILESIZE
, ED2K_SEARCH_OP_LESS
, params
.maxSize
);
940 if (params
.availability
> 0) {
941 target
.WriteMetaDataSearchParam(FT_SOURCES
, ED2K_SEARCH_OP_GREATER
, params
.availability
);
944 if (!params
.extension
.IsEmpty()) {
945 target
.WriteMetaDataSearchParam(FT_FILEFORMAT
, params
.extension
);
948 //#warning TODO - third and last warning of the same series.
951 target
.WriteMetaDataSearchParam(FT_COMPLETE_SOURCES
, ED2K_SEARCH_OP_GREATER
, pParams
->uComplete
);
954 if (minBitrate
> 0) {
955 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_BITRATE
: FT_ED2K_MEDIA_BITRATE
, ED2K_SEARCH_OP_GREATER
, minBitrate
);
959 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_LENGTH
: FT_ED2K_MEDIA_LENGTH
, ED2K_SEARCH_OP_GREATER
, minLength
);
962 if (!codec
.IsEmpty()) {
963 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_CODEC
: FT_ED2K_MEDIA_CODEC
, codec
);
966 if (!title
.IsEmpty()) {
967 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_TITLE
: FT_ED2K_MEDIA_TITLE
, title
);
970 if (!album
.IsEmpty()) {
971 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_ALBUM
: FT_ED2K_MEDIA_ALBUM
, album
);
974 if (!artist
.IsEmpty()) {
975 target
.WriteMetaDataSearchParam(type
== KadSearch
? TAG_MEDIA_ARTIST
: FT_ED2K_MEDIA_ARTIST
, artist
);
981 // Packet ready to go.
986 void CSearchList::KademliaSearchKeyword(uint32_t searchID
, const Kademlia::CUInt128
*fileID
,
987 const wxString
& name
, uint64_t size
, const wxString
& type
, uint32_t kadPublishInfo
, const TagPtrList
& taglist
)
989 EUtf8Str eStrEncode
= utf8strRaw
;
993 fileID
->ToByteArray(fileid
);
994 temp
.WriteHash(CMD4Hash(fileid
));
996 temp
.WriteUInt32(0); // client IP
997 temp
.WriteUInt16(0); // client port
1000 unsigned int uFilePosTagCount
= temp
.GetPosition();
1001 uint32 tagcount
= 0;
1002 temp
.WriteUInt32(tagcount
); // dummy tag count, will be filled later
1005 CTagString
tagName(FT_FILENAME
, name
);
1006 tagName
.WriteTagToFile(&temp
, eStrEncode
);
1009 CTagInt64
tagSize(FT_FILESIZE
, size
);
1010 tagSize
.WriteTagToFile(&temp
, eStrEncode
);
1013 if (!type
.IsEmpty()) {
1014 CTagString
tagType(FT_FILETYPE
, type
);
1015 tagType
.WriteTagToFile(&temp
, eStrEncode
);
1019 // Misc tags (bitrate, etc)
1020 for (TagPtrList::const_iterator it
= taglist
.begin(); it
!= taglist
.end(); ++it
) {
1021 (*it
)->WriteTagToFile(&temp
,eStrEncode
);
1025 temp
.Seek(uFilePosTagCount
, wxFromStart
);
1026 temp
.WriteUInt32(tagcount
);
1028 temp
.Seek(0, wxFromStart
);
1030 CSearchFile
*tempFile
= new CSearchFile(temp
, (eStrEncode
== utf8strRaw
), searchID
, 0, 0, wxEmptyString
, true);
1031 tempFile
->SetKadPublishInfo(kadPublishInfo
);
1033 AddToList(tempFile
);
1036 void CSearchList::UpdateSearchFileByHash(const CMD4Hash
& hash
)
1038 for (ResultMap::iterator it
= m_results
.begin(); it
!= m_results
.end(); ++it
) {
1039 CSearchResultList
& results
= it
->second
;
1040 for (size_t i
= 0; i
< results
.size(); ++i
) {
1041 CSearchFile
* item
= results
.at(i
);
1043 if (hash
== item
->GetFileHash()) {
1044 // This covers only parent items,
1045 // child items have to be updated separately.
1046 Notify_Search_Update_Sources(item
);
1052 // File_checked_for_headers