Fix language getting reset to system default on saving preferences
[amule.git] / src / UploadQueue.cpp
blob93f58cfc83a52c5550778a14709e95094a6c1038
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
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
9 // respective authors.
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.
20 //
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 "UploadQueue.h" // Interface declarations
28 #include <protocol/Protocols.h>
29 #include <protocol/ed2k/Client2Client/TCP.h>
30 #include <common/Macros.h>
31 #include <common/Constants.h>
33 #include <cmath>
35 #include "Types.h" // Do_not_auto_remove (win32)
37 #ifdef __WXMSW__
38 #include <winsock.h> // Do_not_auto_remove
39 #else
40 #include <sys/types.h> // Do_not_auto_remove
41 #include <netinet/in.h> // Do_not_auto_remove
42 #include <arpa/inet.h> // Do_not_auto_remove
43 #endif
45 #include "ServerConnect.h" // Needed for CServerConnect
46 #include "KnownFile.h" // Needed for CKnownFile
47 #include "Packet.h" // Needed for CPacket
48 #include "ClientTCPSocket.h" // Needed for CClientTCPSocket
49 #include "SharedFileList.h" // Needed for CSharedFileList
50 #include "updownclient.h" // Needed for CUpDownClient
51 #include "amule.h" // Needed for theApp
52 #include "Preferences.h"
53 #include "ClientList.h"
54 #include "Statistics.h" // Needed for theStats
55 #include "Logger.h"
56 #include <common/Format.h>
57 #include "UploadBandwidthThrottler.h"
58 #include "GuiEvents.h" // Needed for Notify_*
61 //TODO rewrite the whole networkcode, use overlapped sockets
63 CUploadQueue::CUploadQueue()
65 m_nLastStartUpload = 0;
66 m_lastSort = 0;
67 lastupslotHighID = true;
68 m_allowKicking = true;
69 m_allUploadingKnownFile = new CKnownFile;
73 CUpDownClient* CUploadQueue::SortGetBestClient(bool sortonly)
75 CUpDownClient* newclient = NULL;
76 // Track if we purged any clients from the queue, as to only send one notify in total
77 bool purged = false;
78 uint32 tick = GetTickCount();
79 m_lastSort = tick;
80 CClientPtrList::iterator it = m_waitinglist.begin();
81 for (; it != m_waitinglist.end(); ) {
82 CClientPtrList::iterator it2 = it++;
83 CUpDownClient* cur_client = *it2;
85 // clear dead clients
86 if (tick - cur_client->GetLastUpRequest() > MAX_PURGEQUEUETIME
87 || !theApp->sharedfiles->GetFileByID(cur_client->GetUploadFileID())) {
88 purged = true;
89 cur_client->ClearWaitStartTime();
90 RemoveFromWaitingQueue(it2);
91 if (!cur_client->GetSocket()) {
92 if (cur_client->Disconnected(wxT("AddUpNextClient - purged"))) {
93 cur_client->Safe_Delete();
94 cur_client = NULL;
97 continue;
100 if (cur_client->IsBanned() || IsSuspended(cur_client->GetUploadFileID())) { // Banned client or suspended upload ?
101 cur_client->ClearScore();
102 continue;
104 // finished clearing
106 // Calculate score of current client
107 uint32 cur_score = cur_client->CalculateScore();
108 // Check if it's better than that of a previous one, and move it up then.
109 CClientPtrList::iterator it1 = it2;
110 while (it1 != m_waitinglist.begin()) {
111 it1--;
112 if (cur_score > (*it1)->GetScore()) {
113 // swap them
114 *it2 = *it1;
115 *it1 = cur_client;
116 it2--;
117 } else {
118 // no need to check further since list is already sorted
119 break;
124 // Second Pass:
125 // - calculate queue rank
126 // - find best high id client
127 // - mark all better low id clients as enabled for upload
128 uint16 rank = 1;
129 for (it = m_waitinglist.begin(); it != m_waitinglist.end(); ) {
130 CClientPtrList::iterator it2 = it++;
131 CUpDownClient* cur_client = *it2;
132 cur_client->SetUploadQueueWaitingPosition(rank++);
133 if (newclient) {
134 // There's a better high id client
135 cur_client->m_bAddNextConnect = false;
136 } else {
137 if (cur_client->HasLowID() && !cur_client->IsConnected()) {
138 // No better high id client, so start upload to this one once it connects
139 cur_client->m_bAddNextConnect = true;
140 } else {
141 // We found a high id client (or a currently connected low id client)
142 newclient = cur_client;
143 cur_client->m_bAddNextConnect = false;
144 if (!sortonly) {
145 RemoveFromWaitingQueue(it2);
146 rank--;
147 lastupslotHighID = true; // VQB LowID alternate
153 // Update the count on GUI if any clients were purged
154 if (purged || (newclient && !sortonly)) {
155 Notify_ShowQueueCount(m_waitinglist.size());
158 #ifdef __DEBUG__
159 AddDebugLogLineN(logLocalClient, CFormat(wxT("Current UL queue (%d):")) % (rank - 1));
160 for (it = m_waitinglist.begin(); it != m_waitinglist.end(); it++) {
161 CUpDownClient* c = *it;
162 AddDebugLogLineN(logLocalClient, CFormat(wxT("%4d %7d %s %5d %s"))
163 % c->GetUploadQueueWaitingPosition()
164 % c->GetScore()
165 % (c->HasLowID() ? (c->IsConnected() ? wxT("LoCon") : wxT("LowId")) : wxT("High "))
166 % c->ECID()
167 % c->GetUserName()
170 #endif // __DEBUG__
172 return newclient;
176 void CUploadQueue::AddUpNextClient(CUpDownClient* directadd)
178 CUpDownClient* newclient = NULL;
179 // select next client or use given client
180 if (!directadd) {
181 newclient = SortGetBestClient(false);
182 if (!newclient) {
183 return;
185 } else {
186 // Check if requested file is suspended or not shared (maybe deleted recently)
188 if (IsSuspended(directadd->GetUploadFileID())
189 || !theApp->sharedfiles->GetFileByID(directadd->GetUploadFileID())) {
190 return;
191 } else {
192 newclient = directadd;
196 if (IsDownloading(newclient)) {
197 return;
199 // tell the client that we are now ready to upload
200 if (!newclient->IsConnected()) {
201 newclient->SetUploadState(US_CONNECTING);
202 if (!newclient->TryToConnect(true)) {
203 return;
205 } else {
206 CPacket* packet = new CPacket(OP_ACCEPTUPLOADREQ, 0, OP_EDONKEYPROT);
207 theStats::AddUpOverheadFileRequest(packet->GetPacketSize());
208 AddDebugLogLineN( logLocalClient, wxT("Local Client: OP_ACCEPTUPLOADREQ to ") + newclient->GetFullIP() );
209 newclient->SendPacket(packet,true);
210 newclient->SetUploadState(US_UPLOADING);
212 newclient->SetUpStartTime();
213 newclient->ResetSessionUp();
215 theApp->uploadBandwidthThrottler->AddToStandardList(m_uploadinglist.size(), newclient->GetSocket());
216 m_uploadinglist.push_back(newclient);
217 m_allUploadingKnownFile->AddUploadingClient(newclient);
218 theStats::AddUploadingClient();
220 // Statistic
221 CKnownFile* reqfile = (CKnownFile*) newclient->GetUploadFile();
222 if (reqfile) {
223 reqfile->statistic.AddAccepted();
226 Notify_SharedCtrlRefreshClient(newclient, AVAILABLE_SOURCE);
229 void CUploadQueue::Process()
231 // Check if someone's waiting, if there is a slot for him,
232 // or if we should try to free a slot for him
233 uint32 tick = GetTickCount();
234 // Nobody waiting or upload started recently
235 // (Actually instead of "empty" it should check for "no HighID clients queued",
236 // but the cost for that outweights the benefit. As it is, a slot will be freed
237 // even if it can't be taken because all of the queue is LowID. But just one,
238 // and the kicked client will instantly get it back if he has HighID.)
239 if (m_waitinglist.empty() || tick - m_nLastStartUpload < 1000) {
240 m_allowKicking = false;
241 // Already a slot free, try to fill it
242 } else if (m_uploadinglist.size() < GetMaxSlots()) {
243 m_allowKicking = false;
244 m_nLastStartUpload = tick;
245 AddUpNextClient();
246 // All slots taken, try to free one
247 } else {
248 m_allowKicking = true;
251 // The loop that feeds the upload slots with data.
252 CClientPtrList::iterator it = m_uploadinglist.begin();
253 while (it != m_uploadinglist.end()) {
254 // Get the client. Note! Also updates pos as a side effect.
255 CUpDownClient* cur_client = *it++;
257 // It seems chatting or friend slots can get stuck at times in upload.. This needs looked into..
258 if (!cur_client->GetSocket()) {
259 RemoveFromUploadQueue(cur_client);
260 if(cur_client->Disconnected(_T("CUploadQueue::Process"))){
261 cur_client->Safe_Delete();
263 } else {
264 cur_client->SendBlockData();
268 // Save used bandwidth for speed calculations
269 uint64 sentBytes = theApp->uploadBandwidthThrottler->GetNumberOfSentBytesSinceLastCallAndReset();
270 (void)theApp->uploadBandwidthThrottler->GetNumberOfSentBytesOverheadSinceLastCallAndReset();
272 // Update statistics
273 if (sentBytes) {
274 theStats::AddSentBytes(sentBytes);
277 // Periodically resort queue if it doesn't happen anyway
278 if ((sint32) (tick - m_lastSort) > MIN2MS(2)) {
279 SortGetBestClient(true);
284 uint16 CUploadQueue::GetMaxSlots() const
286 uint16 nMaxSlots = 0;
287 float kBpsUpPerClient = (float)thePrefs::GetSlotAllocation();
288 if (thePrefs::GetMaxUpload() == UNLIMITED) {
289 float kBpsUp = theStats::GetUploadRate() / 1024.0f;
290 nMaxSlots = (uint16)(kBpsUp / kBpsUpPerClient) + 2;
291 } else {
292 if (thePrefs::GetMaxUpload() >= 10) {
293 nMaxSlots = (uint16)floor((float)thePrefs::GetMaxUpload() / kBpsUpPerClient + 0.5);
294 // floor(x + 0.5) is a way of doing round(x) that works with gcc < 3 ...
295 if (nMaxSlots < MIN_UP_CLIENTS_ALLOWED) {
296 nMaxSlots=MIN_UP_CLIENTS_ALLOWED;
298 } else {
299 nMaxSlots = MIN_UP_CLIENTS_ALLOWED;
302 if (nMaxSlots > MAX_UP_CLIENTS_ALLOWED) {
303 nMaxSlots = MAX_UP_CLIENTS_ALLOWED;
305 return nMaxSlots;
309 CUploadQueue::~CUploadQueue()
311 wxASSERT(m_waitinglist.empty());
312 wxASSERT(m_uploadinglist.empty());
313 delete m_allUploadingKnownFile;
317 bool CUploadQueue::IsOnUploadQueue(const CUpDownClient* client) const
319 return std::find(m_waitinglist.begin(), m_waitinglist.end(), client)
320 != m_waitinglist.end();
324 bool CUploadQueue::IsDownloading(CUpDownClient* client) const
326 return std::find(m_uploadinglist.begin(), m_uploadinglist.end(), client)
327 != m_uploadinglist.end();
331 CUpDownClient* CUploadQueue::GetWaitingClientByIP_UDP(uint32 dwIP, uint16 nUDPPort, bool bIgnorePortOnUniqueIP, bool* pbMultipleIPs)
333 CUpDownClient* pMatchingIPClient = NULL;
335 int cMatches = 0;
337 CClientPtrList::iterator it = m_waitinglist.begin();
338 for (; it != m_waitinglist.end(); ++it) {
339 CUpDownClient* cur_client = *it;
341 if ((dwIP == cur_client->GetIP()) && (nUDPPort == cur_client->GetUDPPort())) {
342 return cur_client;
343 } else if ((dwIP == cur_client->GetIP()) && bIgnorePortOnUniqueIP) {
344 pMatchingIPClient = cur_client;
345 cMatches++;
349 if (pbMultipleIPs) {
350 *pbMultipleIPs = cMatches > 1;
353 if (pMatchingIPClient && cMatches == 1) {
354 return pMatchingIPClient;
355 } else {
356 return NULL;
361 void CUploadQueue::AddClientToQueue(CUpDownClient* client)
363 if (theApp->serverconnect->IsConnected() && theApp->serverconnect->IsLowID() && !theApp->serverconnect->IsLocalServer(client->GetServerIP(),client->GetServerPort()) && client->GetDownloadState() == DS_NONE && !client->IsFriend() && theStats::GetWaitingUserCount() > 50) {
364 // Well, all that issues finish in the same: don't allow to add to the queue
365 return;
368 if ( client->IsBanned() ) {
369 return;
372 client->AddAskedCount();
373 client->SetLastUpRequest();
375 // Find all clients with the same user-hash
376 CClientList::SourceList found = theApp->clientlist->GetClientsByHash( client->GetUserHash() );
378 CClientList::SourceList::iterator it = found.begin();
379 while (it != found.end()) {
380 CUpDownClient* cur_client = *it++;
382 if ( IsOnUploadQueue( cur_client ) ) {
383 if ( cur_client == client ) {
384 // This is where LowID clients get their upload slot assigned.
385 // They can't be contacted if they reach top of the queue, so they are just marked for uploading.
386 // When they reconnect next time AddClientToQueue() is called, and they get their slot
387 // through the connection they initiated.
388 // Since at that time no slot is free they get assigned an extra slot,
389 // so then the number of slots exceeds the configured number by one.
390 // To prevent a further increase no more LowID clients get a slot, until
391 // - a HighID client has got one (which happens only after two clients
392 // have been kicked so a slot is free again)
393 // - or there is a free slot, which means there is no HighID client on queue
394 if (client->m_bAddNextConnect) {
395 uint16 maxSlots = GetMaxSlots();
396 if (lastupslotHighID) {
397 maxSlots++;
399 if (m_uploadinglist.size() < maxSlots) {
400 client->m_bAddNextConnect = false;
401 RemoveFromWaitingQueue(client);
402 AddUpNextClient(client);
403 lastupslotHighID = false; // LowID alternate
404 return;
408 client->SendRankingInfo();
409 Notify_SharedCtrlRefreshClient(client, AVAILABLE_SOURCE);
410 return;
411 } else {
412 // Hash-clash, remove unidentified clients (possibly both)
414 if ( !cur_client->IsIdentified() ) {
415 // Cur_client isn't identifed, remove it
416 theApp->clientlist->AddTrackClient( cur_client );
418 RemoveFromWaitingQueue( cur_client );
419 if ( !cur_client->GetSocket() ) {
420 if (cur_client->Disconnected( wxT("AddClientToQueue - same userhash") ) ) {
421 cur_client->Safe_Delete();
426 if ( !client->IsIdentified() ) {
427 // New client isn't identified, remove it
428 theApp->clientlist->AddTrackClient( client );
430 if ( !client->GetSocket() ) {
431 if ( client->Disconnected( wxT("AddClientToQueue - same userhash") ) ) {
432 client->Safe_Delete();
436 return;
442 // Count the number of clients with the same IP-address
443 found = theApp->clientlist->GetClientsByIP( client->GetIP() );
445 int ipCount = 0;
446 for ( it = found.begin(); it != found.end(); it++ ) {
447 if ( ( *it == client ) || IsOnUploadQueue( *it ) ) {
448 ipCount++;
452 // We do not accept more than 3 clients from the same IP
453 if ( ipCount > 3 ) {
454 return;
455 } else if ( theApp->clientlist->GetClientsFromIP(client->GetIP()) >= 3 ) {
456 return;
459 // statistic values
460 CKnownFile* reqfile = (CKnownFile*) client->GetUploadFile();
461 if (reqfile) {
462 reqfile->statistic.AddRequest();
465 if (client->IsDownloading()) {
466 // he's already downloading and wants probably only another file
467 CPacket* packet = new CPacket(OP_ACCEPTUPLOADREQ, 0, OP_EDONKEYPROT);
468 theStats::AddUpOverheadFileRequest(packet->GetPacketSize());
469 AddDebugLogLineN( logLocalClient, wxT("Local Client: OP_ACCEPTUPLOADREQ to ") + client->GetFullIP() );
470 client->SendPacket(packet,true);
471 return;
474 // TODO find better ways to cap the list
475 if (m_waitinglist.size() >= (thePrefs::GetQueueSize())) {
476 return;
479 uint32 tick = GetTickCount();
480 client->ClearWaitStartTime();
481 // if possible start upload right away
482 if (m_waitinglist.empty() && tick - m_nLastStartUpload >= 1000 && m_uploadinglist.size() < GetMaxSlots()) {
483 AddUpNextClient(client);
484 m_nLastStartUpload = tick;
485 } else {
486 // add to waiting queue
487 m_waitinglist.push_back(client);
488 // and sort it to update queue ranks
489 SortGetBestClient(true);
490 theStats::AddWaitingClient();
491 client->ClearAskedCount();
492 client->SetUploadState(US_ONUPLOADQUEUE);
493 client->SendRankingInfo();
494 //Notify_QlistAddClient(client);
499 bool CUploadQueue::RemoveFromUploadQueue(CUpDownClient* client)
501 // Keep track of this client
502 theApp->clientlist->AddTrackClient(client);
504 CClientPtrList::iterator it = std::find(m_uploadinglist.begin(),
505 m_uploadinglist.end(), client);
507 if (it != m_uploadinglist.end()) {
508 m_uploadinglist.erase(it);
509 m_allUploadingKnownFile->RemoveUploadingClient(client);
510 theStats::RemoveUploadingClient();
511 if( client->GetTransferredUp() ) {
512 theStats::AddSuccessfulUpload();
513 theStats::AddUploadTime(client->GetUpStartTimeDelay() / 1000);
514 } else {
515 theStats::AddFailedUpload();
517 client->SetUploadState(US_NONE);
518 client->ClearUploadBlockRequests();
519 return true;
522 return false;
526 bool CUploadQueue::CheckForTimeOver(CUpDownClient* client)
528 // Don't kick anybody if there's no need to
529 if (!m_allowKicking) {
530 return false;
532 // First, check if it is a VIP slot (friend or Release-Prio).
533 if (client->GetFriendSlot()) {
534 return false; // never drop the friend
536 // Release-Prio and nobody on queue for it?
537 if (client->GetUploadFile()->GetUpPriority() == PR_POWERSHARE) {
538 // Keep it unless half of the UL slots are occupied with friends or Release uploads.
539 uint16 vips = 0;
540 for (CClientPtrList::iterator it = m_uploadinglist.begin(); it != m_uploadinglist.end(); ++it) {
541 CUpDownClient* cur_client = *it;
542 if (cur_client->GetFriendSlot() || cur_client->GetUploadFile()->GetUpPriority() == PR_POWERSHARE) {
543 vips++;
546 // allow if VIP uploads occupy at most half of the possible upload slots
547 if (vips <= GetMaxSlots() / 2) {
548 return false;
550 // Otherwise normal rules apply.
553 // Ordinary slots
554 // "Transfer full chunks": drop client after 10 MB upload, or after an hour.
555 // (so average UL speed should at least be 2.84 kB/s)
556 // We don't track what he is downloading, but if it's all from one chunk he gets it.
557 if (client->GetUpStartTimeDelay() > 3600000 // time: 1h
558 || client->GetSessionUp() > 10485760) { // data: 10MB
559 m_allowKicking = false; // kick max one client per cycle
560 return true;
563 return false;
568 * This function removes a file indicated by filehash from suspended_uploads_list.
570 void CUploadQueue::ResumeUpload( const CMD4Hash& filehash )
572 suspendedUploadsSet.erase(filehash);
573 AddLogLineN(CFormat( _("Resuming uploads of file: %s" ) )
574 % filehash.Encode() );
578 * This function stops upload of a file indicated by filehash.
580 * a) teminate == false:
581 * File is suspended while a download completes. Then it is resumed after completion,
582 * so it makes sense to keep the client. Such files are kept in suspendedUploadsSet.
583 * b) teminate == true:
584 * File is deleted. Then the client is not added to the waiting list.
585 * Waiting clients are swept out with next run of AddUpNextClient,
586 * because their file is not shared anymore.
588 uint16 CUploadQueue::SuspendUpload(const CMD4Hash& filehash, bool terminate)
590 AddLogLineN(CFormat( _("Suspending upload of file: %s" ) )
591 % filehash.Encode() );
592 uint16 removed = 0;
594 //Append the filehash to the list.
595 if (!terminate) {
596 suspendedUploadsSet.insert(filehash);
599 CClientPtrList::iterator it = m_uploadinglist.begin();
600 while (it != m_uploadinglist.end()) {
601 CUpDownClient *potential = *it++;
602 //check if the client is uploading the file we need to suspend
603 if (potential->GetUploadFileID() == filehash) {
604 // remove the unlucky client from the upload queue
605 RemoveFromUploadQueue(potential);
606 // if suspend isn't permanent add it to the waiting queue
607 if (terminate) {
608 potential->SetUploadState(US_NONE);
609 } else {
610 m_waitinglist.push_back(potential);
611 theStats::AddWaitingClient();
612 potential->SetUploadState(US_ONUPLOADQUEUE);
613 potential->SendRankingInfo();
614 Notify_SharedCtrlRefreshClient(potential, AVAILABLE_SOURCE);
616 removed++;
619 if (removed) {
620 Notify_ShowQueueCount(m_waitinglist.size());
622 return removed;
625 bool CUploadQueue::RemoveFromWaitingQueue(CUpDownClient* client)
627 CClientPtrList::iterator it = m_waitinglist.begin();
629 uint16 rank = 1;
630 while (it != m_waitinglist.end()) {
631 CClientPtrList::iterator it1 = it++;
632 if (*it1 == client) {
633 RemoveFromWaitingQueue(it1);
634 Notify_ShowQueueCount(m_waitinglist.size());
635 // update ranks of remaining queue
636 while (it != m_waitinglist.end()) {
637 (*it)->SetUploadQueueWaitingPosition(rank++);
638 it++;
640 return true;
642 rank++;
644 return false;
648 void CUploadQueue::RemoveFromWaitingQueue(CClientPtrList::iterator pos)
650 CUpDownClient* todelete = *pos;
651 m_waitinglist.erase(pos);
652 theStats::RemoveWaitingClient();
653 if( todelete->IsBanned() ) {
654 todelete->UnBan();
656 //Notify_QlistRemoveClient(todelete);
657 todelete->SetUploadState(US_NONE);
658 todelete->ClearScore();
659 todelete->SetUploadQueueWaitingPosition(0);
662 // File_checked_for_headers