Upstream tarball 10020
[amule.git] / src / PartFile.cpp
blobb8f4aacf602cd667f3bbe3ac6e53772efda22e96
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 <wx/wx.h>
28 #include "PartFile.h" // Interface declarations.
30 #ifdef HAVE_CONFIG_H
31 #include "config.h" // Needed for VERSION
32 #endif
34 #include <protocol/kad/Constants.h>
35 #include <protocol/ed2k/Client2Client/TCP.h>
36 #include <protocol/Protocols.h>
37 #include <common/DataFileVersion.h>
38 #include <common/Constants.h>
39 #include <tags/FileTags.h>
41 #include <wx/utils.h>
42 #include <wx/tokenzr.h> // Needed for wxStringTokenizer
44 #include "KnownFileList.h" // Needed for CKnownFileList
45 #include "CanceledFileList.h"
46 #include "UploadQueue.h" // Needed for CFileHash
47 #include "IPFilter.h" // Needed for CIPFilter
48 #include "Server.h" // Needed for CServer
49 #include "ServerConnect.h" // Needed for CServerConnect
50 #include "updownclient.h" // Needed for CUpDownClient
51 #include "MemFile.h" // Needed for CMemFile
52 #include "Preferences.h" // Needed for CPreferences
53 #include "DownloadQueue.h" // Needed for CDownloadQueue
54 #include "amule.h" // Needed for theApp
55 #include "ED2KLink.h" // Needed for CED2KLink
56 #include "Packet.h" // Needed for CTag
57 #include "SearchList.h" // Needed for CSearchFile
58 #include "ClientList.h" // Needed for clientlist
59 #include "Statistics.h" // Needed for theStats
60 #include "Logger.h"
61 #include <common/Format.h> // Needed for CFormat
62 #include <common/FileFunctions.h> // Needed for GetLastModificationTime
63 #include "ThreadTasks.h" // Needed for CHashingTask/CCompletionTask/CAllocateFileTask
64 #include "GuiEvents.h" // Needed for Notify_*
65 #include "DataToText.h" // Needed for OriginToText()
66 #include "PlatformSpecific.h" // Needed for CreateSparseFile()
67 #include "FileArea.h" // Needed for CFileArea
68 #include "ScopedPtr.h" // Needed for CScopedArray
69 #include "CorruptionBlackBox.h"
71 #include "kademlia/kademlia/Kademlia.h"
72 #include "kademlia/kademlia/Search.h"
75 SFileRating::SFileRating(const wxString &u, const wxString &f, sint16 r, const wxString &c)
77 UserName(u),
78 FileName(f),
79 Rating(r),
80 Comment(c)
85 SFileRating::SFileRating(const SFileRating &fr)
87 UserName(fr.UserName),
88 FileName(fr.FileName),
89 Rating(fr.Rating),
90 Comment(fr.Comment)
95 SFileRating::SFileRating(const CUpDownClient &client)
97 UserName(client.GetUserName()),
98 FileName(client.GetClientFilename()),
99 Rating(client.GetFileRating()),
100 Comment(client.GetFileComment())
105 SFileRating::~SFileRating()
110 class PartFileBufferedData
112 public:
113 CFileArea area; // File area to be written
114 uint64 start; // This is the start offset of the data
115 uint64 end; // This is the end offset of the data
116 Requested_Block_Struct *block; // This is the requested block that this data relates to
118 PartFileBufferedData(CFileAutoClose& file, byte * data, uint64 _start, uint64 _end, Requested_Block_Struct *_block)
119 : start(_start), end(_end), block(_block)
121 area.StartWriteAt(file, start, end-start+1);
122 memcpy(area.GetBuffer(), data, end-start+1);
127 typedef std::list<Chunk> ChunkList;
130 #ifndef CLIENT_GUI
132 CPartFile::CPartFile()
134 Init();
137 CPartFile::CPartFile(CSearchFile* searchresult)
139 Init();
141 m_abyFileHash = searchresult->GetFileHash();
142 SetFileName(searchresult->GetFileName());
143 SetFileSize(searchresult->GetFileSize());
145 for (unsigned int i = 0; i < searchresult->m_taglist.size(); ++i){
146 const CTag& pTag = searchresult->m_taglist[i];
148 bool bTagAdded = false;
149 if (pTag.GetNameID() == 0 && !pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
150 static const struct {
151 wxString pszName;
152 uint8 nType;
153 } _aMetaTags[] =
155 { wxT(FT_ED2K_MEDIA_ARTIST), 2 },
156 { wxT(FT_ED2K_MEDIA_ALBUM), 2 },
157 { wxT(FT_ED2K_MEDIA_TITLE), 2 },
158 { wxT(FT_ED2K_MEDIA_LENGTH), 2 },
159 { wxT(FT_ED2K_MEDIA_BITRATE), 3 },
160 { wxT(FT_ED2K_MEDIA_CODEC), 2 }
163 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
164 if ( pTag.GetType() == _aMetaTags[t].nType &&
165 (pTag.GetName() == _aMetaTags[t].pszName)) {
166 // skip string tags with empty string values
167 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
168 break;
171 // skip "length" tags with "0: 0" values
172 if (pTag.GetName() == wxT(FT_ED2K_MEDIA_LENGTH)) {
173 if (pTag.GetStr().IsSameAs(wxT("0: 0")) ||
174 pTag.GetStr().IsSameAs(wxT("0:0"))) {
175 break;
179 // skip "bitrate" tags with '0' values
180 if ((pTag.GetName() == wxT(FT_ED2K_MEDIA_BITRATE)) && !pTag.GetInt()) {
181 break;
184 AddDebugLogLineM( false, logPartFile,
185 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
186 pTag.GetFullInfo() );
187 m_taglist.push_back(pTag);
188 bTagAdded = true;
189 break;
192 } else if (pTag.GetNameID() != 0 && pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
193 static const struct {
194 uint8 nID;
195 uint8 nType;
196 } _aMetaTags[] =
198 { FT_FILETYPE, 2 },
199 { FT_FILEFORMAT, 2 }
201 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
202 if (pTag.GetType() == _aMetaTags[t].nType && pTag.GetNameID() == _aMetaTags[t].nID) {
203 // skip string tags with empty string values
204 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
205 break;
208 AddDebugLogLineM( false, logPartFile,
209 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
210 pTag.GetFullInfo() );
211 m_taglist.push_back(pTag);
212 bTagAdded = true;
213 break;
218 if (!bTagAdded) {
219 AddDebugLogLineM( false, logPartFile,
220 wxT("CPartFile::CPartFile(CSearchFile*): ignored tag ") +
221 pTag.GetFullInfo() );
225 CreatePartFile();
229 CPartFile::CPartFile(const CED2KFileLink* fileLink)
231 Init();
233 SetFileName(CPath(fileLink->GetName()));
234 SetFileSize(fileLink->GetSize());
235 m_abyFileHash = fileLink->GetHashKey();
237 CreatePartFile();
239 if (fileLink->m_hashset) {
240 if (!LoadHashsetFromFile(fileLink->m_hashset, true)) {
241 AddDebugLogLineM(true, logPartFile, wxT("eD2K link contained invalid hashset: ") + fileLink->GetLink());
247 CPartFile::~CPartFile()
249 // if it's not opened, it was completed or deleted
250 if (m_hpartfile.IsOpened()) {
251 FlushBuffer();
252 m_hpartfile.Close();
253 // Update met file (with current directory entry)
254 SavePartFile();
257 DeleteContents(m_BufferedData_list);
258 delete m_CorruptionBlackBox;
260 wxASSERT(m_SrcList.empty());
261 wxASSERT(m_A4AFsrclist.empty());
264 void CPartFile::CreatePartFile()
266 // use lowest free partfilenumber for free file (InterCeptor)
267 int i = 0;
268 do {
269 ++i;
270 m_partmetfilename = CPath(wxString::Format(wxT("%03i.part.met"), i));
271 m_fullname = thePrefs::GetTempDir().JoinPaths(m_partmetfilename);
272 } while (m_fullname.FileExists());
274 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
276 wxString strPartName = m_partmetfilename.RemoveExt().GetRaw();
277 m_taglist.push_back(CTagString(FT_PARTFILENAME, strPartName ));
279 m_gaplist.Init(GetFileSize(), true); // Init empty
281 m_PartPath = m_fullname.RemoveExt();
282 bool fileCreated;
283 if (thePrefs::GetAllocFullFile()) {
284 fileCreated = m_hpartfile.Create(m_PartPath, true);
285 m_hpartfile.Close();
286 } else {
287 fileCreated = PlatformSpecific::CreateSparseFile(m_PartPath, GetFileSize());
289 if (!fileCreated) {
290 AddLogLineM(false,_("ERROR: Failed to create partfile)"));
291 SetPartFileStatus(PS_ERROR);
294 SetFilePath(thePrefs::GetTempDir());
296 if (thePrefs::GetAllocFullFile()) {
297 SetPartFileStatus(PS_ALLOCATING);
298 CThreadScheduler::AddTask(new CAllocateFileTask(this, thePrefs::AddNewFilesPaused()));
299 } else {
300 AllocationFinished();
303 m_hashsetneeded = (GetED2KPartHashCount() > 0);
305 SavePartFile(true);
306 SetActive(theApp->IsConnected());
310 uint8 CPartFile::LoadPartFile(const CPath& in_directory, const CPath& filename, bool from_backup, bool getsizeonly)
312 bool isnewstyle = false;
313 uint8 version,partmettype=PMT_UNKNOWN;
315 std::map<uint16, Gap_Struct*> gap_map; // Slugfiller
316 transferred = 0;
318 m_partmetfilename = filename;
319 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
320 m_filePath = in_directory;
321 m_fullname = m_filePath.JoinPaths(m_partmetfilename);
322 m_PartPath = m_fullname.RemoveExt();
324 // readfile data form part.met file
325 CPath curMetFilename = m_fullname;
326 if (from_backup) {
327 curMetFilename = curMetFilename.AppendExt(PARTMET_BAK_EXT);
328 AddLogLineM(false, CFormat( _("Trying to load backup of met-file from %s") )
329 % curMetFilename );
332 try {
333 CFile metFile(curMetFilename, CFile::read);
334 if (!metFile.IsOpened()) {
335 AddLogLineM(false, CFormat( _("ERROR: Failed to open part.met file: %s ==> %s") )
336 % curMetFilename
337 % GetFileName() );
339 return false;
340 } else if (metFile.GetLength() == 0) {
341 AddLogLineM(false, CFormat( _("ERROR: part.met file is 0 size: %s ==> %s") )
342 % m_partmetfilename
343 % GetFileName() );
345 return false;
348 version = metFile.ReadUInt8();
349 if (version != PARTFILE_VERSION && version != PARTFILE_SPLITTEDVERSION && version != PARTFILE_VERSION_LARGEFILE){
350 metFile.Close();
351 //if (version == 83) return ImportShareazaTempFile(...)
352 AddLogLineM(false, CFormat( _("ERROR: Invalid part.met file version: %s ==> %s") )
353 % m_partmetfilename
354 % GetFileName() );
355 return false;
358 isnewstyle = (version == PARTFILE_SPLITTEDVERSION);
359 partmettype = isnewstyle ? PMT_SPLITTED : PMT_DEFAULTOLD;
361 if (version == PARTFILE_VERSION) {// Do we still need this check ?
362 uint8 test[4]; // It will fail for certain files.
363 metFile.Seek(24, wxFromStart);
364 metFile.Read(test,4);
366 metFile.Seek(1, wxFromStart);
367 if (test[0]==0 && test[1]==0 && test[2]==2 && test[3]==1) {
368 isnewstyle=true; // edonkeys so called "old part style"
369 partmettype=PMT_NEWOLD;
373 if (isnewstyle) {
374 uint32 temp = metFile.ReadUInt32();
376 if (temp==0) { // 0.48 partmets - different again
377 LoadHashsetFromFile(&metFile, false);
378 } else {
379 metFile.Seek(2, wxFromStart);
380 LoadDateFromFile(&metFile);
381 m_abyFileHash = metFile.ReadHash();
384 } else {
385 LoadDateFromFile(&metFile);
386 LoadHashsetFromFile(&metFile, false);
389 uint32 tagcount = metFile.ReadUInt32();
391 for (uint32 j = 0; j < tagcount; ++j) {
392 CTag newtag(metFile,true);
393 if ( !getsizeonly ||
394 (getsizeonly &&
395 (newtag.GetNameID() == FT_FILESIZE ||
396 newtag.GetNameID() == FT_FILENAME))) {
397 switch(newtag.GetNameID()) {
398 case FT_FILENAME: {
399 if (!GetFileName().IsOk()) {
400 // If it's not empty, we already loaded the unicoded one
401 SetFileName(CPath(newtag.GetStr()));
403 break;
405 case FT_LASTSEENCOMPLETE: {
406 lastseencomplete = newtag.GetInt();
407 break;
409 case FT_FILESIZE: {
410 SetFileSize(newtag.GetInt());
411 break;
413 case FT_TRANSFERRED: {
414 transferred = newtag.GetInt();
415 break;
417 case FT_FILETYPE:{
418 //#warning needs setfiletype string
419 //SetFileType(newtag.GetStr());
420 break;
422 case FT_CATEGORY: {
423 m_category = newtag.GetInt();
424 if (m_category > theApp->glob_prefs->GetCatCount() - 1 ) {
425 m_category = 0;
427 break;
429 case FT_OLDDLPRIORITY:
430 case FT_DLPRIORITY: {
431 if (!isnewstyle){
432 m_iDownPriority = newtag.GetInt();
433 if( m_iDownPriority == PR_AUTO ){
434 m_iDownPriority = PR_HIGH;
435 SetAutoDownPriority(true);
437 else{
438 if ( m_iDownPriority != PR_LOW &&
439 m_iDownPriority != PR_NORMAL &&
440 m_iDownPriority != PR_HIGH)
441 m_iDownPriority = PR_NORMAL;
442 SetAutoDownPriority(false);
445 break;
447 case FT_STATUS: {
448 m_paused = (newtag.GetInt() == 1);
449 m_stopped = m_paused;
450 break;
452 case FT_OLDULPRIORITY:
453 case FT_ULPRIORITY: {
454 if (!isnewstyle){
455 SetUpPriority(newtag.GetInt(), false);
456 if( GetUpPriority() == PR_AUTO ){
457 SetUpPriority(PR_HIGH, false);
458 SetAutoUpPriority(true);
459 } else {
460 SetAutoUpPriority(false);
463 break;
465 case FT_KADLASTPUBLISHSRC:{
466 SetLastPublishTimeKadSrc(newtag.GetInt(), 0);
467 if(GetLastPublishTimeKadSrc() > (uint32)time(NULL)+KADEMLIAREPUBLISHTIMES) {
468 //There may be a posibility of an older client that saved a random number here.. This will check for that..
469 SetLastPublishTimeKadSrc(0,0);
471 break;
473 case FT_KADLASTPUBLISHNOTES:{
474 SetLastPublishTimeKadNotes(newtag.GetInt());
475 break;
477 // old tags: as long as they are not needed, take the chance to purge them
478 case FT_PERMISSIONS:
479 case FT_KADLASTPUBLISHKEY:
480 break;
481 case FT_DL_ACTIVE_TIME:
482 if (newtag.IsInt()) {
483 m_nDlActiveTime = newtag.GetInt();
485 break;
486 case FT_CORRUPTEDPARTS: {
487 wxASSERT(m_corrupted_list.empty());
488 wxString strCorruptedParts(newtag.GetStr());
489 wxStringTokenizer tokenizer(strCorruptedParts, wxT(","));
490 while ( tokenizer.HasMoreTokens() ) {
491 wxString token = tokenizer.GetNextToken();
492 unsigned long uPart;
493 if (token.ToULong(&uPart)) {
494 if (uPart < GetPartCount() && !IsCorruptedPart(uPart)) {
495 m_corrupted_list.push_back(uPart);
499 break;
501 case FT_AICH_HASH:{
502 CAICHHash hash;
503 bool hashSizeOk =
504 hash.DecodeBase32(newtag.GetStr()) == CAICHHash::GetHashSize();
505 wxASSERT(hashSizeOk);
506 if (hashSizeOk) {
507 m_pAICHHashSet->SetMasterHash(hash, AICH_VERIFIED);
509 break;
511 case FT_ATTRANSFERRED:{
512 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (uint64)newtag.GetInt());
513 break;
515 case FT_ATTRANSFERREDHI:{
516 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (((uint64)newtag.GetInt()) << 32));
517 break;
519 case FT_ATREQUESTED:{
520 statistic.SetAllTimeRequests(newtag.GetInt());
521 break;
523 case FT_ATACCEPTED:{
524 statistic.SetAllTimeAccepts(newtag.GetInt());
525 break;
527 default: {
528 // Start Changes by Slugfiller for better exception handling
530 wxCharBuffer tag_ansi_name = newtag.GetName().ToAscii();
531 char gap_mark = tag_ansi_name ? tag_ansi_name[0u] : 0;
532 if ( newtag.IsInt() && (newtag.GetName().Length() > 1) &&
533 ((gap_mark == FT_GAPSTART) ||
534 (gap_mark == FT_GAPEND))) {
535 Gap_Struct *gap = NULL;
536 unsigned long int gapkey;
537 if (newtag.GetName().Mid(1).ToULong(&gapkey)) {
538 if ( gap_map.find( gapkey ) == gap_map.end() ) {
539 gap = new Gap_Struct;
540 gap_map[gapkey] = gap;
541 gap->start = (uint64)-1;
542 gap->end = (uint64)-1;
543 } else {
544 gap = gap_map[ gapkey ];
546 if (gap_mark == FT_GAPSTART) {
547 gap->start = newtag.GetInt();
549 if (gap_mark == FT_GAPEND) {
550 gap->end = newtag.GetInt()-1;
552 } else {
553 AddDebugLogLineN(logPartFile, wxT("Wrong gap map key while reading met file!"));
554 wxFAIL;
556 // End Changes by Slugfiller for better exception handling
557 } else {
558 m_taglist.push_back(newtag);
562 } else {
563 // Nothing. Else, nothing.
567 // load the hashsets from the hybridstylepartmet
568 if (isnewstyle && !getsizeonly && (metFile.GetPosition()<metFile.GetLength()) ) {
569 metFile.Seek(1, wxFromCurrent);
571 uint16 parts=GetPartCount(); // assuming we will get all hashsets
573 for (uint16 i = 0; i < parts && (metFile.GetPosition()+16<metFile.GetLength()); ++i){
574 CMD4Hash cur_hash = metFile.ReadHash();
575 m_hashlist.push_back(cur_hash);
578 CMD4Hash checkhash;
579 if (!m_hashlist.empty()) {
580 CreateHashFromHashlist(m_hashlist, &checkhash);
582 bool flag=false;
583 if (m_abyFileHash == checkhash) {
584 flag=true;
585 } else {
586 m_hashlist.clear();
587 flag=false;
590 } catch (const CInvalidPacket& e) {
591 AddLogLineM(true, CFormat(wxT("Error: %s (%s) is corrupt (bad tags: %s), unable to load file."))
592 % m_partmetfilename
593 % GetFileName()
594 % e.what());
595 return false;
596 } catch (const CIOFailureException& e) {
597 AddDebugLogLineM(true, logPartFile, CFormat( wxT("IO failure while loading '%s': %s") )
598 % m_partmetfilename
599 % e.what() );
600 return false;
601 } catch (const CEOFException& WXUNUSED(e)) {
602 AddLogLineM(true, CFormat( _("ERROR: %s (%s) is corrupt (wrong tagcount), unable to load file.") )
603 % m_partmetfilename
604 % GetFileName() );
605 AddLogLineM(true, _("Trying to recover file info..."));
607 // Safe file is that who have
608 // - FileSize
609 if (GetFileSize()) {
610 // We have filesize, try other needed info
612 // Do we need to check gaps? I think not,
613 // because they are checked below. Worst
614 // scenario will only mark file as 0 bytes downloaded.
616 // -Filename
617 if (!GetFileName().IsOk()) {
618 // Not critical, let's put a random filename.
619 AddLogLineM(true, _(
620 "Recovering no-named file - will try to recover it as RecoveredFile.dat"));
621 SetFileName(CPath(wxT("RecoveredFile.dat")));
624 AddLogLineM(true,
625 _("Recovered all available file info :D - Trying to use it..."));
626 } else {
627 AddLogLineM(true, _("Unable to recover file info :("));
628 return false;
632 if (getsizeonly) {
633 return partmettype;
635 // Init Gaplist
636 m_gaplist.Init(GetFileSize(), false); // Init full, then add gaps
637 // Now to flush the map into the list (Slugfiller)
638 std::map<uint16, Gap_Struct*>::iterator it = gap_map.begin();
639 for ( ; it != gap_map.end(); ++it ) {
640 Gap_Struct* gap = it->second;
641 // SLUGFILLER: SafeHash - revised code, and extra safety
642 if ( (gap->start != (uint64)-1) &&
643 (gap->end != (uint64)-1) &&
644 gap->start <= gap->end &&
645 gap->start < GetFileSize()) {
646 if (gap->end >= GetFileSize()) {
647 gap->end = GetFileSize()-1; // Clipping
649 m_gaplist.AddGap(gap->start, gap->end); // All tags accounted for, use safe adding
651 delete gap;
652 // SLUGFILLER: SafeHash
655 //check if this is a backup
656 if ( m_fullname.GetExt().MakeLower() == wxT("backup" )) {
657 m_fullname = m_fullname.RemoveExt();
660 // open permanent handle
661 if ( !m_hpartfile.Open(m_PartPath, CFile::read_write)) {
662 AddLogLineM(false, CFormat( _("Failed to open %s (%s)") )
663 % m_fullname
664 % GetFileName() );
665 return false;
668 SetPartFileStatus(PS_EMPTY);
670 try {
671 // SLUGFILLER: SafeHash - final safety, make sure any missing part of the file is gap
672 if (m_hpartfile.GetLength() < GetFileSize())
673 AddGap(m_hpartfile.GetLength(), GetFileSize()-1);
674 // Goes both ways - Partfile should never be too large
675 if (m_hpartfile.GetLength() > GetFileSize()) {
676 AddDebugLogLineM( true, logPartFile, CFormat( wxT("Partfile \"%s\" is too large! Truncating %llu bytes.") ) % GetFileName() % (m_hpartfile.GetLength() - GetFileSize()));
677 m_hpartfile.SetLength(GetFileSize());
679 // SLUGFILLER: SafeHash
680 } catch (const CIOFailureException& e) {
681 AddDebugLogLineM( true, logPartFile, CFormat( wxT("Error while accessing partfile \"%s\": %s") ) % GetFileName() % e.what());
682 SetPartFileStatus(PS_ERROR);
685 // now close the file again until needed
686 m_hpartfile.Release(true);
688 // check hashcount, file status etc
689 if (GetHashCount() != GetED2KPartHashCount()){
690 m_hashsetneeded = true;
691 return true;
692 } else {
693 m_hashsetneeded = false;
694 for (size_t i = 0; i < m_hashlist.size(); ++i) {
695 if (IsComplete(i)) {
696 SetPartFileStatus(PS_READY);
701 if (m_gaplist.IsComplete()) { // is this file complete already?
702 CompleteFile(false);
703 return true;
706 if (!isnewstyle) { // not for importing
707 const time_t file_date = CPath::GetModificationTime(m_PartPath);
708 if (m_lastDateChanged != file_date) {
709 // It's pointless to rehash an empty file, since the case
710 // where a user has zero'd a file is handled above ...
711 if (m_hpartfile.GetLength()) {
712 AddLogLineM(false, CFormat( _("WARNING: %s might be corrupted (%i)") )
713 % m_PartPath
714 % (m_lastDateChanged - file_date) );
715 // rehash
716 SetPartFileStatus(PS_WAITINGFORHASH);
718 CPath partFileName = m_partmetfilename.RemoveExt();
719 CThreadScheduler::AddTask(new CHashingTask(m_filePath, partFileName, this));
724 UpdateCompletedInfos();
725 if (completedsize > transferred) {
726 m_iGainDueToCompression = completedsize - transferred;
727 } else if (completedsize != transferred) {
728 m_iLostDueToCorruption = transferred - completedsize;
731 return true;
735 bool CPartFile::SavePartFile(bool Initial)
737 switch (status) {
738 case PS_WAITINGFORHASH:
739 case PS_HASHING:
740 case PS_COMPLETE:
741 return false;
744 /* Don't write anything to disk if less than 100 KB of free space is left. */
745 sint64 free = CPath::GetFreeSpaceAt(GetFilePath());
746 if ((free != wxInvalidOffset) && (free < (100 * 1024))) {
747 return false;
750 CFile file;
751 try {
752 if (!m_PartPath.FileExists()) {
753 throw wxString(wxT(".part file not found"));
756 uint32 lsc = lastseencomplete;
758 if (!Initial) {
759 CPath::BackupFile(m_fullname, wxT(".backup"));
760 CPath::RemoveFile(m_fullname);
763 file.Open(m_fullname, CFile::write);
764 if (!file.IsOpened()) {
765 throw wxString(wxT("Failed to open part.met file"));
768 // version
769 file.WriteUInt8(IsLargeFile() ? PARTFILE_VERSION_LARGEFILE : PARTFILE_VERSION);
771 file.WriteUInt32(CPath::GetModificationTime(m_PartPath));
772 // hash
773 file.WriteHash(m_abyFileHash);
774 uint16 parts = m_hashlist.size();
775 file.WriteUInt16(parts);
776 for (int x = 0; x < parts; ++x) {
777 file.WriteHash(m_hashlist[x]);
779 // tags
780 #define FIXED_TAGS 15
781 uint32 tagcount = m_taglist.size() + FIXED_TAGS + (m_gaplist.size()*2);
782 if (!m_corrupted_list.empty()) {
783 ++tagcount;
786 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
787 ++tagcount;
790 if (GetLastPublishTimeKadSrc()){
791 ++tagcount;
794 if (GetLastPublishTimeKadNotes()){
795 ++tagcount;
798 if (GetDlActiveTime()){
799 ++tagcount;
802 file.WriteUInt32(tagcount);
804 //#warning Kry - Where are lost by coruption and gained by compression?
806 // 0 (unicoded part file name)
807 // We write it with BOM to keep eMule compatibility. Note that the 'printable' filename is saved,
808 // as presently the filename does not represent an actual file.
809 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file, utf8strOptBOM );
810 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file ); // 1
812 CTagIntSized( FT_FILESIZE, GetFileSize(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file );// 2
813 CTagIntSized( FT_TRANSFERRED, transferred, IsLargeFile() ? 64 : 32).WriteTagToFile( &file ); // 3
814 CTagInt32( FT_STATUS, (m_paused?1:0)).WriteTagToFile( &file ); // 4
816 if ( IsAutoDownPriority() ) {
817 CTagInt32( FT_DLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 5
818 CTagInt32( FT_OLDDLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 6
819 } else {
820 CTagInt32( FT_DLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 5
821 CTagInt32( FT_OLDDLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 6
824 CTagInt32( FT_LASTSEENCOMPLETE, lsc ).WriteTagToFile( &file ); // 7
826 if ( IsAutoUpPriority() ) {
827 CTagInt32( FT_ULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 8
828 CTagInt32( FT_OLDULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 9
829 } else {
830 CTagInt32( FT_ULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 8
831 CTagInt32( FT_OLDULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 9
834 CTagInt32(FT_CATEGORY, m_category).WriteTagToFile( &file ); // 10
835 CTagInt32(FT_ATTRANSFERRED, statistic.GetAllTimeTransferred() & 0xFFFFFFFF).WriteTagToFile( &file );// 11
836 CTagInt32(FT_ATTRANSFERREDHI, statistic.GetAllTimeTransferred() >>32).WriteTagToFile( &file );// 12
837 CTagInt32(FT_ATREQUESTED, statistic.GetAllTimeRequests()).WriteTagToFile( &file ); // 13
838 CTagInt32(FT_ATACCEPTED, statistic.GetAllTimeAccepts()).WriteTagToFile( &file ); // 14
840 // currupt part infos
841 if (!m_corrupted_list.empty()) {
842 wxString strCorruptedParts;
843 std::list<uint16>::iterator it = m_corrupted_list.begin();
844 for (; it != m_corrupted_list.end(); ++it) {
845 uint16 uCorruptedPart = *it;
846 if (!strCorruptedParts.IsEmpty()) {
847 strCorruptedParts += wxT(",");
849 strCorruptedParts += wxString::Format(wxT("%u"), (unsigned)uCorruptedPart);
851 wxASSERT( !strCorruptedParts.IsEmpty() );
853 CTagString( FT_CORRUPTEDPARTS, strCorruptedParts ).WriteTagToFile( &file); // 11?
856 //AICH Filehash
857 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
858 CTagString aichtag(FT_AICH_HASH, m_pAICHHashSet->GetMasterHash().GetString() );
859 aichtag.WriteTagToFile(&file); // 12?
862 if (GetLastPublishTimeKadSrc()){
863 CTagInt32(FT_KADLASTPUBLISHSRC, GetLastPublishTimeKadSrc()).WriteTagToFile(&file); // 15?
866 if (GetLastPublishTimeKadNotes()){
867 CTagInt32(FT_KADLASTPUBLISHNOTES, GetLastPublishTimeKadNotes()).WriteTagToFile(&file); // 16?
870 if (GetDlActiveTime()){
871 CTagInt32(FT_DL_ACTIVE_TIME, GetDlActiveTime()).WriteTagToFile(&file); // 17
874 for (uint32 j = 0; j < (uint32)m_taglist.size();++j) {
875 m_taglist[j].WriteTagToFile(&file);
878 // gaps
879 unsigned i_pos = 0;
880 for (CGapList::const_iterator it = m_gaplist.begin(); it != m_gaplist.end(); ++it) {
881 wxString tagName = wxString::Format(wxT(" %u"), i_pos);
883 // gap start = first missing byte but gap ends = first non-missing byte
884 // in edonkey but I think its easier to user the real limits
885 tagName[0] = FT_GAPSTART;
886 CTagIntSized(tagName, it.start() , IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
888 tagName[0] = FT_GAPEND;
889 CTagIntSized(tagName, it.end() + 1, IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
891 ++i_pos;
893 } catch (const wxString& error) {
894 AddLogLineNS(CFormat( _("ERROR while saving partfile: %s (%s ==> %s)") )
895 % error
896 % m_partmetfilename
897 % GetFileName() );
899 return false;
900 } catch (const CIOFailureException& e) {
901 AddLogLineCS(_("IO failure while saving partfile: ") + e.what());
903 return false;
906 file.Close();
908 if (!Initial) {
909 CPath::RemoveFile(m_fullname.AppendExt(wxT(".backup")));
912 sint64 metLength = m_fullname.GetFileSize();
913 if (metLength == wxInvalidOffset) {
914 theApp->ShowAlert( CFormat( _("Could not retrieve length of '%s' - using %s file.") )
915 % m_fullname
916 % PARTMET_BAK_EXT,
917 _("Message"), wxOK);
919 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
920 } else if (metLength == 0) {
921 // Don't backup if it's 0 size but raise a warning!!!
922 theApp->ShowAlert( CFormat( _("'%s' is 0 size somehow - using %s file.") )
923 % m_fullname
924 % PARTMET_BAK_EXT,
925 _("Message"), wxOK);
927 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
928 } else {
929 // no error, just backup
930 CPath::BackupFile(m_fullname, PARTMET_BAK_EXT);
933 return true;
937 void CPartFile::SaveSourceSeeds()
939 #define MAX_SAVED_SOURCES 10
941 // Kry - Sources seeds
942 // Based on a Feature request, this saves the last MAX_SAVED_SOURCES
943 // sources of the file, giving a 'seed' for the next run.
944 // We save the last sources because:
945 // 1 - They could be the hardest to get
946 // 2 - They will more probably be available
947 // However, if we have downloading sources, they have preference because
948 // we probably have more credits on them.
949 // Anyway, source exchange will get us the rest of the sources
950 // This feature is currently used only on rare files (< 20 sources)
953 if (GetSourceCount()>20) {
954 return;
957 CClientPtrList source_seeds;
958 int n_sources = 0;
960 CClientPtrList::iterator it = m_downloadingSourcesList.begin();
961 for( ; it != m_downloadingSourcesList.end() && n_sources < MAX_SAVED_SOURCES; ++it) {
962 CUpDownClient *cur_src = *it;
963 if (!cur_src->HasLowID()) {
964 source_seeds.push_back(cur_src);
965 ++n_sources;
969 if (n_sources < MAX_SAVED_SOURCES) {
970 // Not enough downloading sources to fill the list, going to sources list
971 if (GetSourceCount() > 0) {
972 SourceSet::reverse_iterator rit = m_SrcList.rbegin();
973 for ( ; ((rit != m_SrcList.rend()) && (n_sources<MAX_SAVED_SOURCES)); ++rit) {
974 CUpDownClient* cur_src = *rit;
975 if (!cur_src->HasLowID()) {
976 source_seeds.push_back(cur_src);
977 ++n_sources;
983 // Write the file
984 if (!n_sources) {
985 return;
988 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
990 CFile file;
991 file.Create(seedsPath, true);
992 if (!file.IsOpened()) {
993 AddLogLineM(false, CFormat( _("Failed to save part.met.seeds file for %s") )
994 % m_fullname);
995 return;
998 try {
999 file.WriteUInt8(0); // v3, to avoid v2 clients choking on it.
1000 file.WriteUInt8(source_seeds.size());
1002 CClientPtrList::iterator it2 = source_seeds.begin();
1003 for (; it2 != source_seeds.end(); ++it2) {
1004 CUpDownClient* cur_src = *it2;
1005 file.WriteUInt32(cur_src->GetUserIDHybrid());
1006 file.WriteUInt16(cur_src->GetUserPort());
1007 file.WriteHash(cur_src->GetUserHash());
1008 // CryptSettings - See SourceExchange V4
1009 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
1010 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
1011 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
1012 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
1013 file.WriteUInt8(byCryptOptions);
1016 /* v2: Added to keep track of too old seeds */
1017 file.WriteUInt32(wxDateTime::Now().GetTicks());
1019 AddLogLineM(false, CFormat( wxPLURAL("Saved %i source seed for partfile: %s (%s)", "Saved %i source seeds for partfile: %s (%s)", n_sources) )
1020 % n_sources
1021 % m_fullname
1022 % GetFileName());
1023 } catch (const CIOFailureException& e) {
1024 AddDebugLogLineM(true, logPartFile, CFormat( wxT("Error saving partfile's seeds file (%s - %s): %s") )
1025 % m_partmetfilename
1026 % GetFileName()
1027 % e.what() );
1029 n_sources = 0;
1030 file.Close();
1031 CPath::RemoveFile(seedsPath);
1035 void CPartFile::LoadSourceSeeds()
1037 CMemFile sources_data;
1039 bool valid_sources = false;
1041 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
1042 if (!seedsPath.FileExists()) {
1043 return;
1046 CFile file(seedsPath, CFile::read);
1047 if (!file.IsOpened()) {
1048 AddLogLineM(false, CFormat( _("Partfile %s (%s) has no seeds file") )
1049 % m_partmetfilename
1050 % GetFileName() );
1051 return;
1055 try {
1056 if (file.GetLength() <= 1) {
1057 AddLogLineM(false, CFormat( _("Partfile %s (%s) has a void seeds file") )
1058 % m_partmetfilename
1059 % GetFileName() );
1060 return;
1063 uint8 src_count = file.ReadUInt8();
1065 bool bUseSX2Format = (src_count == 0);
1067 if (bUseSX2Format) {
1068 // v3 sources seeds
1069 src_count = file.ReadUInt8();
1072 sources_data.WriteUInt16(src_count);
1074 for (int i = 0; i< src_count; ++i) {
1075 uint32 dwID = file.ReadUInt32();
1076 uint16 nPort = file.ReadUInt16();
1078 sources_data.WriteUInt32(bUseSX2Format ? dwID : wxUINT32_SWAP_ALWAYS(dwID));
1079 sources_data.WriteUInt16(nPort);
1080 sources_data.WriteUInt32(0);
1081 sources_data.WriteUInt16(0);
1083 if (bUseSX2Format) {
1084 sources_data.WriteHash(file.ReadHash());
1085 sources_data.WriteUInt8(file.ReadUInt8());
1090 if (!file.Eof()) {
1092 // v2: Added to keep track of too old seeds
1093 time_t time = (time_t)file.ReadUInt32();
1095 // Time frame is 2 hours. More than enough to compile
1096 // your new aMule version!.
1097 if ((time + MIN2S(120)) >= wxDateTime::Now().GetTicks()) {
1098 valid_sources = true;
1101 } else {
1102 // v1 has no time data. We can safely use
1103 // the sources, next time will be saved.
1104 valid_sources = true;
1107 if (valid_sources) {
1108 sources_data.Seek(0);
1109 AddClientSources(&sources_data, SF_SOURCE_SEEDS, bUseSX2Format ? 4 : 1, bUseSX2Format);
1112 } catch (const CSafeIOException& e) {
1113 AddLogLineM(false, CFormat( _("Error reading partfile's seeds file (%s - %s): %s") )
1114 % m_partmetfilename
1115 % GetFileName()
1116 % e.what() );
1119 file.Close();
1122 void CPartFile::PartFileHashFinished(CKnownFile* result)
1124 m_lastDateChanged = result->m_lastDateChanged;
1125 bool errorfound = false;
1126 if (GetED2KPartHashCount() == 0){
1127 if (IsComplete(0, GetFileSize()-1)){
1128 if (result->GetFileHash() != GetFileHash()){
1129 AddLogLineM(false,
1130 CFormat(wxPLURAL(
1131 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1132 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1137 % GetFileName()
1138 % result->GetFileHash().Encode()
1139 % GetFileHash().Encode() );
1140 AddGap(0, GetFileSize()-1);
1141 errorfound = true;
1145 else{
1146 for (size_t i = 0; i < m_hashlist.size(); ++i){
1147 // Kry - trel_ar's completed parts check on rehashing.
1148 // Very nice feature, if a file is completed but .part.met don't believe it,
1149 // update it.
1151 uint64 partStart = i * PARTSIZE;
1152 uint64 partEnd = partStart + GetPartSize(i) - 1;
1153 if (!( i < result->GetHashCount() && (result->GetPartHash(i) == GetPartHash(i)))){
1154 if (IsComplete(i)) {
1155 CMD4Hash wronghash;
1156 if ( i < result->GetHashCount() )
1157 wronghash = result->GetPartHash(i);
1159 AddLogLineM(false,
1160 CFormat(wxPLURAL(
1161 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1162 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1163 GetED2KPartHashCount())
1165 % ( i + 1 )
1166 % GetED2KPartHashCount()
1167 % GetFileName()
1168 % wronghash.Encode()
1169 % GetPartHash(i).Encode() );
1171 AddGap(i);
1172 errorfound = true;
1174 } else {
1175 if (!IsComplete(i)){
1176 AddLogLineM(false, CFormat( _("Found completed part (%i) in %s") )
1177 % ( i + 1 )
1178 % GetFileName() );
1180 FillGap(i);
1181 RemoveBlockFromList(partStart, partEnd);
1187 if ( !errorfound &&
1188 result->GetAICHHashset()->GetStatus() == AICH_HASHSETCOMPLETE &&
1189 status == PS_COMPLETING) {
1190 delete m_pAICHHashSet;
1191 m_pAICHHashSet = result->GetAICHHashset();
1192 result->SetAICHHashset(NULL);
1193 m_pAICHHashSet->SetOwner(this);
1195 else if (status == PS_COMPLETING) {
1196 AddDebugLogLineM(false, logPartFile,
1197 CFormat(wxT("Failed to store new AICH Hashset for completed file: %s"))
1198 % GetFileName());
1202 delete result;
1203 if (!errorfound){
1204 if (status == PS_COMPLETING){
1205 CompleteFile(true);
1206 return;
1208 else {
1209 AddLogLineM(false, CFormat( _("Finished rehashing %s") ) % GetFileName());
1212 else{
1213 SetStatus(PS_READY);
1214 SavePartFile();
1215 return;
1217 SetStatus(PS_READY);
1218 SavePartFile();
1219 theApp->sharedfiles->SafeAddKFile(this);
1222 void CPartFile::AddGap(uint64 start, uint64 end)
1224 m_gaplist.AddGap(start, end);
1225 UpdateDisplayedInfo();
1228 void CPartFile::AddGap(uint16 part)
1230 m_gaplist.AddGap(part);
1231 UpdateDisplayedInfo();
1234 bool CPartFile::IsAlreadyRequested(uint64 start, uint64 end)
1236 std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
1237 for (; it != m_requestedblocks_list.end(); ++it) {
1238 Requested_Block_Struct* cur_block = *it;
1240 if ((start <= cur_block->EndOffset) && (end >= cur_block->StartOffset)) {
1241 return true;
1244 return false;
1247 bool CPartFile::GetNextEmptyBlockInPart(uint16 partNumber, Requested_Block_Struct *result)
1249 // Find start of this part
1250 uint64 partStart = (PARTSIZE * partNumber);
1251 uint64 start = partStart;
1253 // What is the end limit of this block, i.e. can't go outside part (or filesize)
1254 uint64 partEnd = partStart + GetPartSize(partNumber) - 1;
1255 // Loop until find a suitable gap and return true, or no more gaps and return false
1256 CGapList::const_iterator it = m_gaplist.begin();
1257 while (true) {
1258 bool noGap = true;
1259 uint64 gapStart, end;
1261 // Find the first gap from the start position
1262 for (; it != m_gaplist.end(); ++it) {
1263 gapStart = it.start();
1264 end = it.end();
1266 // Want gaps that overlap start<->partEnd
1267 if (gapStart <= partEnd && end >= start) {
1268 noGap = false;
1269 break;
1270 } else if (gapStart > partEnd) {
1271 break;
1275 // If no gaps after start, exit
1276 if (noGap) {
1277 return false;
1279 // Update start position if gap starts after current pos
1280 if (start < gapStart) {
1281 start = gapStart;
1283 // Find end, keeping within the max block size and the part limit
1284 uint64 blockLimit = partStart + (BLOCKSIZE * (((start - partStart) / BLOCKSIZE) + 1)) - 1;
1285 if (end > blockLimit) {
1286 end = blockLimit;
1288 if (end > partEnd) {
1289 end = partEnd;
1291 // If this gap has not already been requested, we have found a valid entry
1292 if (!IsAlreadyRequested(start, end)) {
1293 // Was this block to be returned
1294 if (result != NULL) {
1295 result->StartOffset = start;
1296 result->EndOffset = end;
1297 md4cpy(result->FileID, GetFileHash().GetHash());
1298 result->transferred = 0;
1300 return true;
1301 } else {
1302 // Reposition to end of that gap
1303 start = end + 1;
1305 // If tried all gaps then break out of the loop
1306 if (end == partEnd) {
1307 break;
1310 // No suitable gap found
1311 return false;
1315 void CPartFile::FillGap(uint64 start, uint64 end)
1317 m_gaplist.FillGap(start, end);
1318 UpdateCompletedInfos();
1319 UpdateDisplayedInfo();
1322 void CPartFile::FillGap(uint16 part)
1324 m_gaplist.FillGap(part);
1325 UpdateCompletedInfos();
1326 UpdateDisplayedInfo();
1330 void CPartFile::UpdateCompletedInfos()
1332 uint64 allgaps = m_gaplist.GetGapSize();
1334 percentcompleted = (1.0 - (double)allgaps/GetFileSize()) * 100.0;
1335 completedsize = GetFileSize() - allgaps;
1339 void CPartFile::WritePartStatus(CMemFile* file)
1341 uint16 parts = GetED2KPartCount();
1342 file->WriteUInt16(parts);
1343 uint16 done = 0;
1344 while (done != parts){
1345 uint8 towrite = 0;
1346 for (uint32 i = 0;i != 8;++i) {
1347 if (IsComplete(done)) {
1348 towrite |= (1<<i);
1350 ++done;
1351 if (done == parts) {
1352 break;
1355 file->WriteUInt8(towrite);
1359 void CPartFile::WriteCompleteSourcesCount(CMemFile* file)
1361 file->WriteUInt16(m_nCompleteSourcesCount);
1364 uint32 CPartFile::Process(uint32 reducedownload/*in percent*/,uint8 m_icounter)
1366 uint16 old_trans;
1367 uint32 dwCurTick = ::GetTickCount();
1369 // If buffer size exceeds limit, or if not written within time limit, flush data
1370 if ( (m_nTotalBufferData > thePrefs::GetFileBufferSize()) ||
1371 (dwCurTick > (m_nLastBufferFlushTime + BUFFER_TIME_LIMIT))) {
1372 // Avoid flushing while copying preview file
1373 if (!m_bPreviewing) {
1374 FlushBuffer();
1379 // check if we want new sources from server --> MOVED for 16.40 version
1380 old_trans=transferingsrc;
1381 transferingsrc = 0;
1382 kBpsDown = 0.0;
1384 if (m_icounter < 10) {
1385 // Update only downloading sources.
1386 CClientPtrList::iterator it = m_downloadingSourcesList.begin();
1387 for( ; it != m_downloadingSourcesList.end(); ) {
1388 CUpDownClient *cur_src = *it++;
1389 if(cur_src->GetDownloadState() == DS_DOWNLOADING) {
1390 ++transferingsrc;
1391 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1394 } else {
1395 // Update all sources (including downloading sources)
1396 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
1397 CUpDownClient* cur_src = *it++;
1398 switch (cur_src->GetDownloadState()) {
1399 case DS_DOWNLOADING: {
1400 ++transferingsrc;
1401 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1402 break;
1404 case DS_BANNED: {
1405 break;
1407 case DS_ERROR: {
1408 break;
1410 case DS_LOWTOLOWIP: {
1411 if (cur_src->HasLowID() && !theApp->CanDoCallback(cur_src)) {
1412 // If we are almost maxed on sources,
1413 // slowly remove these client to see
1414 // if we can find a better source.
1415 if (((dwCurTick - lastpurgetime) > 30000) &&
1416 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8))) {
1417 RemoveSource(cur_src);
1418 lastpurgetime = dwCurTick;
1419 break;
1421 } else {
1422 cur_src->SetDownloadState(DS_ONQUEUE);
1425 break;
1427 case DS_NONEEDEDPARTS: {
1428 // we try to purge noneeded source, even without reaching the limit
1429 if((dwCurTick - lastpurgetime) > 40000) {
1430 if(!cur_src->SwapToAnotherFile(false , false, false , NULL)) {
1431 //however we only delete them if reaching the limit
1432 if (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) {
1433 RemoveSource(cur_src);
1434 lastpurgetime = dwCurTick;
1435 break; //Johnny-B - nothing more to do here (good eye!)
1437 } else {
1438 lastpurgetime = dwCurTick;
1439 break;
1442 // doubled reasktime for no needed parts - save connections and traffic
1443 if ( !((!cur_src->GetLastAskedTime()) ||
1444 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME*2)) {
1445 break;
1447 // Recheck this client to see if still NNP..
1448 // Set to DS_NONE so that we force a TCP reask next time..
1449 cur_src->SetDownloadState(DS_NONE);
1451 break;
1453 case DS_ONQUEUE: {
1454 if( cur_src->IsRemoteQueueFull()) {
1455 if( ((dwCurTick - lastpurgetime) > 60000) &&
1456 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) ) {
1457 RemoveSource( cur_src );
1458 lastpurgetime = dwCurTick;
1459 break; //Johnny-B - nothing more to do here (good eye!)
1463 // Give up to 1 min for UDP to respond..
1464 // If we are within on min on TCP, do not try..
1465 if ( theApp->IsConnected() &&
1466 ( (!cur_src->GetLastAskedTime()) ||
1467 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME-20000)) {
1468 cur_src->UDPReaskForDownload();
1471 // No break here, since the next case takes care of asking for downloads.
1473 case DS_CONNECTING:
1474 case DS_TOOMANYCONNS:
1475 case DS_NONE:
1476 case DS_WAITCALLBACK:
1477 case DS_WAITCALLBACKKAD: {
1478 if ( theApp->IsConnected() &&
1479 ( (!cur_src->GetLastAskedTime()) ||
1480 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME)) {
1481 if (!cur_src->AskForDownload()) {
1482 // I left this break here just as a reminder
1483 // just in case re rearange things..
1484 break;
1487 break;
1492 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
1493 if (IsA4AFAuto() && ((!m_LastNoNeededCheck) || (dwCurTick - m_LastNoNeededCheck > 900000))) {
1494 m_LastNoNeededCheck = dwCurTick;
1495 for ( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
1496 CUpDownClient *cur_source = *it++;
1497 uint8 download_state=cur_source->GetDownloadState();
1498 if( download_state != DS_DOWNLOADING
1499 && cur_source->GetRequestFile()
1500 && ((!cur_source->GetRequestFile()->IsA4AFAuto()) || download_state == DS_NONEEDEDPARTS))
1502 cur_source->SwapToAnotherFile(false, false, false, this);
1506 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
1508 // swap No needed partfiles if possible
1510 if (((old_trans==0) && (transferingsrc>0)) || ((old_trans>0) && (transferingsrc==0))) {
1511 SetPartFileStatus(status);
1514 // Kad source search
1515 if( GetMaxSourcePerFileUDP() > GetSourceCount()){
1516 //Once we can handle lowID users in Kad, we remove the second IsConnected
1517 if (theApp->downloadqueue->DoKademliaFileRequest() && (Kademlia::CKademlia::GetTotalFile() < KADEMLIATOTALFILE) && (dwCurTick > m_LastSearchTimeKad) && Kademlia::CKademlia::IsConnected() && theApp->IsConnected() && !IsStopped()){
1518 //Kademlia
1519 theApp->downloadqueue->SetLastKademliaFileRequest();
1521 if (GetKadFileSearchID()) {
1522 /* This will never happen anyway. We're talking a
1523 1h timespan and searches are at max 45secs */
1524 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1527 Kademlia::CUInt128 kadFileID(GetFileHash().GetHash());
1528 Kademlia::CSearch* pSearch = Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FILE, true, kadFileID);
1529 AddDebugLogLineM(false, logKadSearch, CFormat(wxT("Preparing a Kad Search for '%s'")) % GetFileName());
1530 if (pSearch) {
1531 AddDebugLogLineM(false, logKadSearch, CFormat(wxT("Kad lookup started for '%s'")) % GetFileName());
1532 if(m_TotalSearchesKad < 7) {
1533 m_TotalSearchesKad++;
1535 m_LastSearchTimeKad = dwCurTick + (KADEMLIAREASKTIME*m_TotalSearchesKad);
1536 SetKadFileSearchID(pSearch->GetSearchID());
1539 } else {
1540 if(GetKadFileSearchID()) {
1541 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
1545 // check if we want new sources from server
1546 if ( !m_localSrcReqQueued &&
1547 ( (!m_lastsearchtime) ||
1548 (dwCurTick - m_lastsearchtime) > SERVERREASKTIME) &&
1549 theApp->IsConnectedED2K() &&
1550 thePrefs::GetMaxSourcePerFileSoft() > GetSourceCount() &&
1551 !m_stopped ) {
1552 m_localSrcReqQueued = true;
1553 theApp->downloadqueue->SendLocalSrcRequest(this);
1556 // calculate datarate, set limit etc.
1559 ++m_count;
1561 // Kry - does the 3 / 30 difference produce too much flickering or CPU?
1562 if (m_count >= 30) {
1563 m_count = 0;
1564 UpdateAutoDownPriority();
1565 UpdateDisplayedInfo();
1566 if(m_bPercentUpdated == false) {
1567 UpdateCompletedInfos();
1569 m_bPercentUpdated = false;
1570 if (thePrefs::ShowCatTabInfos()) {
1571 Notify_ShowUpdateCatTabTitles();
1575 // release file handle if unused for some time
1576 m_hpartfile.Release();
1578 return (uint32)(kBpsDown*1024.0);
1581 bool CPartFile::CanAddSource(uint32 userid, uint16 port, uint32 serverip, uint16 serverport, uint8* pdebug_lowiddropped, bool ed2kID)
1584 //The incoming ID could have the userid in the Hybrid format..
1585 uint32 hybridID = 0;
1586 if( ed2kID ) {
1587 if (IsLowID(userid)) {
1588 hybridID = userid;
1589 } else {
1590 hybridID = wxUINT32_SWAP_ALWAYS(userid);
1592 } else {
1593 hybridID = userid;
1594 if (!IsLowID(userid)) {
1595 userid = wxUINT32_SWAP_ALWAYS(userid);
1599 // MOD Note: Do not change this part - Merkur
1600 if (theApp->IsConnectedED2K()) {
1601 if(::IsLowID(theApp->GetED2KID())) {
1602 if(theApp->GetED2KID() == userid && theApp->serverconnect->GetCurrentServer()->GetIP() == serverip && theApp->serverconnect->GetCurrentServer()->GetPort() == serverport ) {
1603 return false;
1605 if(theApp->GetPublicIP() == userid) {
1606 return false;
1608 } else {
1609 if(theApp->GetED2KID() == userid && thePrefs::GetPort() == port) {
1610 return false;
1615 if (Kademlia::CKademlia::IsConnected()) {
1616 if(!Kademlia::CKademlia::IsFirewalled()) {
1617 if(Kademlia::CKademlia::GetIPAddress() == hybridID && thePrefs::GetPort() == port) {
1618 return false;
1623 //This allows *.*.*.0 clients to not be removed if Ed2kID == false
1624 if ( IsLowID(hybridID) && theApp->IsFirewalled()) {
1625 if (pdebug_lowiddropped) {
1626 (*pdebug_lowiddropped)++;
1628 return false;
1630 // MOD Note - end
1631 return true;
1634 void CPartFile::AddSources(CMemFile& sources,uint32 serverip, uint16 serverport, unsigned origin, bool bWithObfuscationAndHash)
1636 uint8 count = sources.ReadUInt8();
1637 uint8 debug_lowiddropped = 0;
1638 uint8 debug_possiblesources = 0;
1639 CMD4Hash achUserHash;
1641 if (m_stopped) {
1642 // since we may received multiple search source UDP results we have to "consume" all data of that packet
1643 AddDebugLogLineM(false, logPartFile, wxT("Trying to add sources for a stopped file"));
1644 sources.Seek(count*(4+2), wxFromCurrent);
1645 return;
1648 for (int i = 0;i != count;++i) {
1649 uint32 userid = sources.ReadUInt32();
1650 uint16 port = sources.ReadUInt16();
1652 uint8 byCryptOptions = 0;
1653 if (bWithObfuscationAndHash){
1654 byCryptOptions = sources.ReadUInt8();
1655 if ((byCryptOptions & 0x80) > 0) {
1656 achUserHash = sources.ReadHash();
1659 if ((thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x01/*supported*/) > 0 && (byCryptOptions & 0x80) == 0)
1660 || (thePrefs::IsClientCryptLayerSupported() && (byCryptOptions & 0x02/*requested*/) > 0 && (byCryptOptions & 0x80) == 0)) {
1661 AddDebugLogLineM(false, logPartFile, wxString::Format(wxT("Server didn't provide UserHash for source %u, even if it was expected to (or local obfuscationsettings changed during serverconnect"), userid));
1662 } else if (!thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x02/*requested*/) == 0 && (byCryptOptions & 0x80) != 0) {
1663 AddDebugLogLineM(false, logPartFile, wxString::Format(wxT("Server provided UserHash for source %u, even if it wasn't expected to (or local obfuscationsettings changed during serverconnect"), userid));
1668 // "Filter LAN IPs" and "IPfilter" the received sources IP addresses
1669 if (!IsLowID(userid)) {
1670 // check for 0-IP, localhost and optionally for LAN addresses
1671 if ( !IsGoodIP(userid, thePrefs::FilterLanIPs()) ) {
1672 continue;
1674 if (theApp->ipfilter->IsFiltered(userid)) {
1675 continue;
1679 if (!CanAddSource(userid, port, serverip, serverport, &debug_lowiddropped)) {
1680 continue;
1683 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
1684 ++debug_possiblesources;
1685 CUpDownClient* newsource = new CUpDownClient(port,userid,serverip,serverport,this, true, true);
1687 newsource->SetSourceFrom((ESourceFrom)origin);
1688 newsource->SetConnectOptions(byCryptOptions, true, false);
1690 if ((byCryptOptions & 0x80) != 0) {
1691 newsource->SetUserHash(achUserHash);
1694 theApp->downloadqueue->CheckAndAddSource(this,newsource);
1695 } else {
1696 AddDebugLogLineM(false, logPartFile, wxT("Consuming a packet because of max sources reached"));
1697 // Since we may receive multiple search source UDP results we have to "consume" all data of that packet
1698 // This '+1' is added because 'i' counts from 0.
1699 sources.Seek((count-(i+1))*(4+2), wxFromCurrent);
1700 if (GetKadFileSearchID()) {
1701 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1703 break;
1708 void CPartFile::UpdatePartsInfo()
1710 if( !IsPartFile() ) {
1711 CKnownFile::UpdatePartsInfo();
1712 return;
1715 // Cache part count
1716 uint16 partcount = GetPartCount();
1717 bool flag = (time(NULL) - m_nCompleteSourcesTime > 0);
1719 // Ensure the frequency-list is ready
1720 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
1721 m_SrcpartFrequency.clear();
1722 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
1725 // Find number of available parts
1726 uint16 availablecounter = 0;
1727 for ( uint16 i = 0; i < partcount; ++i ) {
1728 if ( m_SrcpartFrequency[i] )
1729 ++availablecounter;
1732 if ( ( availablecounter == partcount ) && ( m_availablePartsCount < partcount ) ) {
1733 lastseencomplete = time(NULL);
1736 m_availablePartsCount = availablecounter;
1738 if ( flag ) {
1739 ArrayOfUInts16 count;
1741 count.reserve(GetSourceCount());
1743 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
1744 if ( !(*it)->GetUpPartStatus().empty() && (*it)->GetUpPartCount() == partcount ) {
1745 count.push_back((*it)->GetUpCompleteSourcesCount());
1749 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo = m_nCompleteSourcesCountHi = 0;
1751 for (uint16 i = 0; i < partcount; ++i) {
1752 if( !i ) {
1753 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1755 else if( m_nCompleteSourcesCount > m_SrcpartFrequency[i]) {
1756 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1759 count.push_back(m_nCompleteSourcesCount);
1761 int32 n = count.size();
1762 if (n > 0) {
1763 std::sort(count.begin(), count.end(), std::less<uint16>());
1765 // calculate range
1766 int32 i= n >> 1; // (n / 2)
1767 int32 j= (n * 3) >> 2; // (n * 3) / 4
1768 int32 k= (n * 7) >> 3; // (n * 7) / 8
1770 //When still a part file, adjust your guesses by 20% to what you see..
1773 if (n < 5) {
1774 //Not many sources, so just use what you see..
1775 // welcome to 'plain stupid code'
1776 // m_nCompleteSourcesCount;
1777 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1778 m_nCompleteSourcesCountHi= m_nCompleteSourcesCount;
1779 } else if (n < 20) {
1780 // For low guess and normal guess count
1781 // If we see more sources then the guessed low and normal, use what we see.
1782 // If we see less sources then the guessed low, adjust network accounts for 80%,
1783 // we account for 20% with what we see and make sure we are still above the normal.
1784 // For high guess
1785 // Adjust 80% network and 20% what we see.
1786 if ( count[i] < m_nCompleteSourcesCount ) {
1787 m_nCompleteSourcesCountLo = m_nCompleteSourcesCount;
1788 } else {
1789 m_nCompleteSourcesCountLo =
1790 (uint16)((float)(count[i]*.8) +
1791 (float)(m_nCompleteSourcesCount*.2));
1793 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1794 m_nCompleteSourcesCountHi =
1795 (uint16)((float)(count[j]*.8) +
1796 (float)(m_nCompleteSourcesCount*.2));
1797 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1798 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1800 } else {
1801 // Many sources
1802 // ------------
1803 // For low guess
1804 // Use what we see.
1805 // For normal guess
1806 // Adjust network accounts for 80%, we account for 20% with what
1807 // we see and make sure we are still above the low.
1808 // For high guess
1809 // Adjust network accounts for 80%, we account for 20% with what
1810 // we see and make sure we are still above the normal.
1812 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1813 m_nCompleteSourcesCount= (uint16)((float)(count[j]*.8)+(float)(m_nCompleteSourcesCount*.2));
1814 if( m_nCompleteSourcesCount < m_nCompleteSourcesCountLo ) {
1815 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1817 m_nCompleteSourcesCountHi= (uint16)((float)(count[k]*.8)+(float)(m_nCompleteSourcesCount*.2));
1818 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1819 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1823 m_nCompleteSourcesTime = time(NULL) + (60);
1825 UpdateDisplayedInfo();
1828 // [Maella -Enhanced Chunk Selection- (based on jicxicmic)]
1829 bool CPartFile::GetNextRequestedBlock(CUpDownClient* sender,
1830 std::vector<Requested_Block_Struct*>& toadd, uint16& count)
1833 // The purpose of this function is to return a list of blocks (~180KB) to
1834 // download. To avoid a prematurely stop of the downloading, all blocks that
1835 // are requested from the same source must be located within the same
1836 // chunk (=> part ~9MB).
1838 // The selection of the chunk to download is one of the CRITICAL parts of the
1839 // edonkey network. The selection algorithm must insure the best spreading
1840 // of files.
1842 // The selection is based on 4 criteria:
1843 // 1. Frequency of the chunk (availability), very rare chunks must be downloaded
1844 // as quickly as possible to become a new available source.
1845 // 2. Parts used for preview (first + last chunk), preview or check a
1846 // file (e.g. movie, mp3)
1847 // 3. Request state (downloading in process), try to ask each source for another
1848 // chunk. Spread the requests between all sources.
1849 // 4. Completion (shortest-to-complete), partially retrieved chunks should be
1850 // completed before starting to download other one.
1852 // The frequency criterion defines three zones: very rare (<10%), rare (<50%)
1853 // and common (>30%). Inside each zone, the criteria have a specific weight, used
1854 // to calculate the priority of chunks. The chunk(s) with the highest
1855 // priority (highest=0, lowest=0xffff) is/are selected first.
1857 // very rare (preview) rare common
1858 // 0% <---- +0 pt ----> 10% <----- +10000 pt -----> 50% <---- +20000 pt ----> 100%
1859 // 1. <------- frequency: +25*frequency pt ----------->
1860 // 2. <- preview: +1 pt --><-------------- preview: set to 10000 pt ------------->
1861 // 3. <------ request: download in progress +20000 pt ------>
1862 // 4a. <- completion: 0% +100, 25% +75 .. 100% +0 pt --><-- !req => completion --->
1863 // 4b. <--- req => !completion -->
1865 // Unrolled, the priority scale is:
1867 // 0..xxxx unrequested and requested very rare chunks
1868 // 10000..1xxxx unrequested rare chunks + unrequested preview chunks
1869 // 20000..2xxxx unrequested common chunks (priority to the most complete)
1870 // 30000..3xxxx requested rare chunks + requested preview chunks
1871 // 40000..4xxxx requested common chunks (priority to the least complete)
1873 // This algorithm usually selects first the rarest chunk(s). However, partially
1874 // complete chunk(s) that is/are close to completion may overtake the priority
1875 // (priority inversion).
1876 // For the common chuncks, the algorithm tries to spread the dowload between
1877 // the sources
1880 // Check input parameters
1881 if ( sender->GetPartStatus().empty() ) {
1882 return false;
1884 // Define and create the list of the chunks to download
1885 const uint16 partCount = GetPartCount();
1886 ChunkList chunksList;
1888 // Main loop
1889 uint16 newBlockCount = 0;
1890 while(newBlockCount != count) {
1891 // Create a request block stucture if a chunk has been previously selected
1892 if(sender->GetLastPartAsked() != 0xffff) {
1893 Requested_Block_Struct* pBlock = new Requested_Block_Struct;
1894 if(GetNextEmptyBlockInPart(sender->GetLastPartAsked(), pBlock) == true) {
1895 // Keep a track of all pending requested blocks
1896 m_requestedblocks_list.push_back(pBlock);
1897 // Update list of blocks to return
1898 toadd.push_back(pBlock);
1899 newBlockCount++;
1900 // Skip end of loop (=> CPU load)
1901 continue;
1902 } else {
1903 // All blocks for this chunk have been already requested
1904 delete pBlock;
1905 // => Try to select another chunk
1906 sender->SetLastPartAsked(0xffff);
1910 // Check if a new chunk must be selected (e.g. download starting, previous chunk complete)
1911 if(sender->GetLastPartAsked() == 0xffff) {
1912 // Quantify all chunks (create list of chunks to download)
1913 // This is done only one time and only if it is necessary (=> CPU load)
1914 if(chunksList.empty()) {
1915 // Indentify the locally missing part(s) that this source has
1916 for(uint16 i=0; i < partCount; ++i) {
1917 if(sender->IsPartAvailable(i) == true && GetNextEmptyBlockInPart(i, NULL) == true) {
1918 // Create a new entry for this chunk and add it to the list
1919 Chunk newEntry;
1920 newEntry.part = i;
1921 newEntry.frequency = m_SrcpartFrequency[i];
1922 chunksList.push_back(newEntry);
1926 // Check if any bloks(s) could be downloaded
1927 if(chunksList.empty()) {
1928 break; // Exit main loop while()
1931 // Define the bounds of the three zones (very rare, rare)
1932 // more depending on available sources
1933 uint8 modif=10;
1934 if (GetSourceCount()>800) {
1935 modif=2;
1936 } else if (GetSourceCount()>200) {
1937 modif=5;
1939 uint16 limit= modif*GetSourceCount()/ 100;
1940 if (limit==0) {
1941 limit=1;
1943 const uint16 veryRareBound = limit;
1944 const uint16 rareBound = 2*limit;
1946 // Cache Preview state (Criterion 2)
1947 FileType type = GetFiletype(GetFileName());
1948 const bool isPreviewEnable =
1949 thePrefs::GetPreviewPrio() &&
1950 (type == ftArchive || type == ftVideo);
1952 // Collect and calculate criteria for all chunks
1953 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
1954 Chunk& cur_chunk = *it;
1956 // Offsets of chunk
1957 const uint64 uStart = cur_chunk.part * PARTSIZE;
1958 const uint64 uEnd = uStart + GetPartSize(cur_chunk.part) - 1;
1959 // Criterion 2. Parts used for preview
1960 // Remark: - We need to download the first part and the last part(s).
1961 // - When the last part is very small, it's necessary to
1962 // download the two last parts.
1963 bool critPreview = false;
1964 if(isPreviewEnable == true) {
1965 if(cur_chunk.part == 0) {
1966 critPreview = true; // First chunk
1967 } else if(cur_chunk.part == partCount-1) {
1968 critPreview = true; // Last chunk
1969 } else if(cur_chunk.part == partCount-2) {
1970 // Last chunk - 1 (only if last chunk is too small)
1971 const uint32 sizeOfLastChunk = GetFileSize() - uEnd;
1972 if(sizeOfLastChunk < PARTSIZE/3) {
1973 critPreview = true; // Last chunk - 1
1978 // Criterion 3. Request state (downloading in process from other source(s))
1979 // => CPU load
1980 const bool critRequested =
1981 cur_chunk.frequency > veryRareBound &&
1982 IsAlreadyRequested(uStart, uEnd);
1984 // Criterion 4. Completion
1985 // PARTSIZE instead of GetPartSize() favours the last chunk - but that may be intentional
1986 uint32 partSize = PARTSIZE - m_gaplist.GetGapSize(cur_chunk.part);
1987 const uint16 critCompletion = (uint16)(partSize/(PARTSIZE/100)); // in [%]
1989 // Calculate priority with all criteria
1990 if(cur_chunk.frequency <= veryRareBound) {
1991 // 0..xxxx unrequested + requested very rare chunks
1992 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
1993 ((critPreview == true) ? 0 : 1) + // Criterion 2
1994 (100 - critCompletion); // Criterion 4
1995 } else if(critPreview == true) {
1996 // 10000..10100 unrequested preview chunks
1997 // 30000..30100 requested preview chunks
1998 cur_chunk.rank = ((critRequested == false) ? 10000 : 30000) + // Criterion 3
1999 (100 - critCompletion); // Criterion 4
2000 } else if(cur_chunk.frequency <= rareBound) {
2001 // 10101..1xxxx unrequested rare chunks
2002 // 30101..3xxxx requested rare chunks
2003 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
2004 ((critRequested == false) ? 10101 : 30101) + // Criterion 3
2005 (100 - critCompletion); // Criterion 4
2006 } else {
2007 // common chunk
2008 if(critRequested == false) { // Criterion 3
2009 // 20000..2xxxx unrequested common chunks
2010 cur_chunk.rank = 20000 + // Criterion 3
2011 (100 - critCompletion); // Criterion 4
2012 } else {
2013 // 40000..4xxxx requested common chunks
2014 // Remark: The weight of the completion criterion is inversed
2015 // to spead the requests over the completing chunks.
2016 // Without this, the chunk closest to completion will
2017 // received every new sources.
2018 cur_chunk.rank = 40000 + // Criterion 3
2019 (critCompletion); // Criterion 4
2025 // Select the next chunk to download
2026 if(!chunksList.empty()) {
2027 // Find and count the chunck(s) with the highest priority
2028 uint16 chunkCount = 0; // Number of found chunks with same priority
2029 uint16 rank = 0xffff; // Highest priority found
2031 // Collect and calculate criteria for all chunks
2032 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2033 const Chunk& cur_chunk = *it;
2034 if(cur_chunk.rank < rank) {
2035 chunkCount = 1;
2036 rank = cur_chunk.rank;
2037 } else if(cur_chunk.rank == rank) {
2038 ++chunkCount;
2042 // Use a random access to avoid that everybody tries to download the
2043 // same chunks at the same time (=> spread the selected chunk among clients)
2044 uint16 randomness = 1 + (int) (((float)(chunkCount-1))*rand()/(RAND_MAX+1.0));
2046 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2047 const Chunk& cur_chunk = *it;
2048 if(cur_chunk.rank == rank) {
2049 randomness--;
2050 if(randomness == 0) {
2051 // Selection process is over
2052 sender->SetLastPartAsked(cur_chunk.part);
2053 // Remark: this list might be reused up to *count times
2054 chunksList.erase(it);
2055 break; // exit loop for()
2059 } else {
2060 // There is no remaining chunk to download
2061 break; // Exit main loop while()
2065 // Return the number of the blocks
2066 count = newBlockCount;
2067 // Return
2068 return (newBlockCount > 0);
2070 // Maella end
2073 void CPartFile::RemoveBlockFromList(uint64 start,uint64 end)
2075 std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
2076 while (it != m_requestedblocks_list.end()) {
2077 std::list<Requested_Block_Struct*>::iterator it2 = it++;
2079 if ((*it2)->StartOffset <= start && (*it2)->EndOffset >= end) {
2080 m_requestedblocks_list.erase(it2);
2086 void CPartFile::RemoveAllRequestedBlocks(void)
2088 m_requestedblocks_list.clear();
2092 void CPartFile::CompleteFile(bool bIsHashingDone)
2094 if (GetKadFileSearchID()) {
2095 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
2098 theApp->downloadqueue->RemoveLocalServerRequest(this);
2100 AddDebugLogLineM( false, logPartFile, wxString( wxT("CPartFile::CompleteFile: Hash ") ) + ( bIsHashingDone ? wxT("done") : wxT("not done") ) );
2102 if (!bIsHashingDone) {
2103 SetPartFileStatus(PS_COMPLETING);
2104 kBpsDown = 0.0;
2106 CPath partFile = m_partmetfilename.RemoveExt();
2107 CThreadScheduler::AddTask(new CHashingTask(GetFilePath(), partFile, this));
2108 return;
2109 } else {
2110 StopFile();
2111 m_is_A4AF_auto=false;
2112 SetPartFileStatus(PS_COMPLETING);
2113 // guess I was wrong about not need to spaw a thread ...
2114 // It is if the temp and incoming dirs are on different
2115 // partitions/drives and the file is large...[oz]
2118 PerformFileComplete();
2122 if (thePrefs::ShowCatTabInfos()) {
2123 Notify_ShowUpdateCatTabTitles();
2125 UpdateDisplayedInfo(true);
2129 void CPartFile::CompleteFileEnded(bool errorOccured, const CPath& newname)
2131 if (errorOccured) {
2132 m_paused = true;
2133 SetPartFileStatus(PS_ERROR);
2134 AddLogLineM(true, CFormat( _("Unexpected error while completing %s. File paused") )% GetFileName() );
2135 } else {
2136 m_fullname = newname;
2138 SetFilePath(m_fullname.GetPath());
2139 SetFileName(m_fullname.GetFullName());
2140 m_lastDateChanged = CPath::GetModificationTime(m_fullname);
2142 SetPartFileStatus(PS_COMPLETE);
2143 m_paused = false;
2144 ClearPriority();
2147 // Remove from list of canceled files in case it was canceled once upon a time
2148 if (theApp->canceledfiles->Remove(GetFileHash())) {
2149 theApp->canceledfiles->Save();
2152 // Mark as known (checks if it's already known),
2153 // also updates search files
2154 theApp->knownfiles->SafeAddKFile(this);
2156 // remove the file from the suspended uploads list
2157 theApp->uploadqueue->ResumeUpload(GetFileHash());
2158 theApp->downloadqueue->RemoveFile(this);
2159 theApp->sharedfiles->SafeAddKFile(this);
2160 UpdateDisplayedInfo(true);
2162 // republish that file to the ed2k-server to update the 'FT_COMPLETE_SOURCES' counter on the server.
2163 theApp->sharedfiles->RepublishFile(this);
2165 // Ensure that completed shows the correct value
2166 completedsize = GetFileSize();
2168 // clear the blackbox to free up memory
2169 m_CorruptionBlackBox->Free();
2171 AddLogLineM(true, CFormat( _("Finished downloading: %s") ) % GetFileName() );
2174 theApp->downloadqueue->StartNextFile(this);
2178 void CPartFile::PerformFileComplete()
2180 // add this file to the suspended uploads list
2181 theApp->uploadqueue->SuspendUpload(GetFileHash());
2182 FlushBuffer();
2184 // close permanent handle
2185 if (m_hpartfile.IsOpened()) {
2186 m_hpartfile.Close();
2189 // Schedule task for completion of the file
2190 CThreadScheduler::AddTask(new CCompletionTask(this));
2194 void CPartFile::RemoveAllSources(bool bTryToSwap)
2196 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end();) {
2197 CUpDownClient* cur_src = *it++;
2198 if (bTryToSwap) {
2199 if (!cur_src->SwapToAnotherFile(true, true, true, NULL)) {
2200 RemoveSource(cur_src,true,false);
2201 // If it was not swapped, it's not on any file anymore, and should die
2203 } else {
2204 RemoveSource(cur_src,true,false);
2208 UpdatePartsInfo();
2210 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
2211 // remove all links A4AF in sources to this file
2212 if(!m_A4AFsrclist.empty()) {
2213 for( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
2214 CUpDownClient* cur_src = *it++;
2215 if ( cur_src->DeleteFileRequest( this ) ) {
2216 Notify_DownloadCtrlRemoveSource(cur_src, this);
2219 m_A4AFsrclist.clear();
2221 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
2222 UpdateFileRatingCommentAvail();
2226 void CPartFile::Delete()
2228 AddLogLineM(false, CFormat(_("Deleting file: %s")) % GetFileName());
2229 // Barry - Need to tell any connected clients to stop sending the file
2230 StopFile(true);
2231 AddDebugLogLineM(false, logPartFile, wxT("\tStopped"));
2233 uint16 removed = theApp->uploadqueue->SuspendUpload(GetFileHash());
2234 AddDebugLogLineM(false, logPartFile, CFormat(wxT("\tSuspended upload to %d clients")) % removed);
2235 theApp->sharedfiles->RemoveFile(this);
2236 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved from shared"));
2237 theApp->downloadqueue->RemoveFile(this);
2238 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved from download queue"));
2239 Notify_DownloadCtrlRemoveFile(this);
2240 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved from transferwnd"));
2241 if (theApp->canceledfiles->Add(GetFileHash())) {
2242 theApp->canceledfiles->Save();
2244 AddDebugLogLineM(false, logPartFile, wxT("\tAdded to canceled file list"));
2245 theApp->searchlist->UpdateSearchFileByHash(GetFileHash()); // Update file in the search dialog if it's still open
2247 if (m_hpartfile.IsOpened()) {
2248 m_hpartfile.Close();
2251 AddDebugLogLineM(false, logPartFile, wxT("\tClosed"));
2253 if (!CPath::RemoveFile(m_fullname)) {
2254 AddDebugLogLineM(true, logPartFile, CFormat(wxT("\tFailed to delete '%s'")) % m_fullname);
2255 } else {
2256 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .part.met"));
2259 if (!CPath::RemoveFile(m_PartPath)) {
2260 AddDebugLogLineM(true, logPartFile, CFormat(wxT("Failed to delete '%s'")) % m_PartPath);
2261 } else {
2262 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .part"));
2265 CPath BAKName = m_fullname.AppendExt(PARTMET_BAK_EXT);
2266 if (!CPath::RemoveFile(BAKName)) {
2267 AddDebugLogLineM(true, logPartFile, CFormat(wxT("Failed to delete '%s'")) % BAKName);
2268 } else {
2269 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .BAK"));
2272 CPath SEEDSName = m_fullname.AppendExt(wxT(".seeds"));
2273 if (SEEDSName.FileExists()) {
2274 if (CPath::RemoveFile(SEEDSName)) {
2275 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .seeds"));
2276 } else {
2277 AddDebugLogLineM(true, logPartFile, CFormat(wxT("Failed to delete '%s'")) % SEEDSName);
2281 AddDebugLogLineM(false, logPartFile, wxT("Done"));
2283 delete this;
2287 bool CPartFile::HashSinglePart(uint16 partnumber)
2289 if ((GetHashCount() <= partnumber) && (GetPartCount() > 1)) {
2290 AddLogLineM(true,
2291 CFormat( _("WARNING: Unable to hash downloaded part - hashset incomplete for '%s'") )
2292 % GetFileName() );
2293 m_hashsetneeded = true;
2294 return true;
2295 } else if ((GetHashCount() <= partnumber) && GetPartCount() != 1) {
2296 AddLogLineM(true, CFormat( _("ERROR: Unable to hash downloaded part - hashset incomplete (%s). This should never happen")) % GetFileName() );
2297 m_hashsetneeded = true;
2298 return true;
2299 } else {
2300 CMD4Hash hashresult;
2301 uint64 offset = PARTSIZE * partnumber;
2302 uint32 length = GetPartSize(partnumber);
2303 try {
2304 CreateHashFromFile(m_hpartfile, offset, length, &hashresult, NULL);
2305 } catch (const CIOFailureException& e) {
2306 AddLogLineM(true, CFormat( wxT("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2307 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2308 SetPartFileStatus(PS_ERROR);
2309 return false;
2310 } catch (const CEOFException& e) {
2311 AddLogLineM(true, CFormat( wxT("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2312 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2313 return false;
2316 if (GetPartCount() > 1) {
2317 if (hashresult != GetPartHash(partnumber)) {
2318 AddDebugLogLineM(false, logPartFile, CFormat( wxT("%s: Expected hash of part %d: %s")) % GetFileName() % partnumber % GetPartHash(partnumber).Encode() );
2319 AddDebugLogLineM(false, logPartFile, CFormat( wxT("%s: Actual hash of part %d: %s")) % GetFileName() % partnumber % hashresult.Encode() );
2320 return false;
2321 } else {
2322 return true;
2324 } else {
2325 if (hashresult != m_abyFileHash) {
2326 return false;
2327 } else {
2328 return true;
2335 bool CPartFile::IsCorruptedPart(uint16 partnumber)
2337 return std::find(m_corrupted_list.begin(), m_corrupted_list.end(), partnumber)
2338 != m_corrupted_list.end();
2342 void CPartFile::SetDownPriority(uint8 np, bool bSave, bool bRefresh )
2344 if ( m_iDownPriority != np ) {
2345 m_iDownPriority = np;
2346 if ( bRefresh )
2347 UpdateDisplayedInfo(true);
2348 if ( bSave )
2349 SavePartFile();
2354 void CPartFile::StopFile(bool bCancel)
2356 // Kry - Need to set it here to get into SetPartFileStatus(status) correctly
2357 m_stopped = true;
2359 // Barry - Need to tell any connected clients to stop sending the file
2360 PauseFile();
2362 m_LastSearchTimeKad = 0;
2363 m_TotalSearchesKad = 0;
2365 RemoveAllSources(true);
2366 kBpsDown = 0.0;
2367 transferingsrc = 0;
2369 if (!bCancel) {
2370 FlushBuffer();
2373 UpdateDisplayedInfo(true);
2377 void CPartFile::StopPausedFile()
2379 if (!IsStopped()) {
2380 // Once an hour, remove any sources for files which are no longer active downloads
2381 switch (GetStatus()) {
2382 case PS_PAUSED:
2383 case PS_INSUFFICIENT:
2384 case PS_ERROR:
2385 if (time(NULL) - m_iLastPausePurge > (60*60)) {
2386 m_iLastPausePurge = time(NULL);
2387 StopFile();
2389 kBpsDown = 0.0;
2392 // release file handle if unused for some time
2393 m_hpartfile.Release();
2397 void CPartFile::PauseFile(bool bInsufficient)
2399 SetActive(false);
2401 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2402 return;
2405 if (GetKadFileSearchID()) {
2406 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
2407 // If we were in the middle of searching, reset timer so they can resume searching.
2408 m_LastSearchTimeKad = 0;
2411 m_iLastPausePurge = time(NULL);
2413 theApp->downloadqueue->RemoveLocalServerRequest(this);
2415 CPacket packet( OP_CANCELTRANSFER, 0, OP_EDONKEYPROT );
2416 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
2417 CUpDownClient* cur_src = *it++;
2418 if (cur_src->GetDownloadState() == DS_DOWNLOADING) {
2419 if (!cur_src->GetSentCancelTransfer()) {
2420 theStats::AddUpOverheadOther( packet.GetPacketSize() );
2421 AddDebugLogLineM( false, logLocalClient, wxT("Local Client: OP_CANCELTRANSFER to ") + cur_src->GetFullIP() );
2422 cur_src->SendPacket( &packet, false, true );
2423 cur_src->SetSentCancelTransfer( true );
2425 cur_src->SetDownloadState(DS_ONQUEUE);
2426 // Allow immediate reconnect on resume
2427 cur_src->ResetLastAskedTime();
2432 m_insufficient = bInsufficient;
2433 m_paused = true;
2436 kBpsDown = 0.0;
2437 transferingsrc = 0;
2439 SetStatus(status);
2443 void CPartFile::ResumeFile()
2445 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2446 return;
2449 if ( m_insufficient && !CheckFreeDiskSpace() ) {
2450 // Still not enough free discspace
2451 return;
2454 m_paused = false;
2455 m_stopped = false;
2456 m_insufficient = false;
2458 m_lastsearchtime = 0;
2459 SetStatus(status);
2460 SetActive(theApp->IsConnected());
2462 if (m_gaplist.IsComplete() && (GetStatus() == PS_ERROR)) {
2463 // The file has already been hashed at this point
2464 CompleteFile(true);
2467 UpdateDisplayedInfo(true);
2471 bool CPartFile::CheckFreeDiskSpace( uint64 neededSpace )
2473 uint64 free = CPath::GetFreeSpaceAt(GetFilePath());
2474 if (free == static_cast<uint64>(wxInvalidOffset)) {
2475 // If GetFreeSpaceAt() fails, then the path probably does not exist.
2476 return false;
2479 // The very least acceptable diskspace is a single PART
2480 if ( free < PARTSIZE ) {
2481 // Always fail in this case, since we risk losing data if we try to
2482 // write on a full partition.
2483 return false;
2486 // All other checks are only made if the user has enabled them
2487 if ( thePrefs::IsCheckDiskspaceEnabled() ) {
2488 neededSpace += thePrefs::GetMinFreeDiskSpace();
2490 // Due to the the existance of sparse files, we cannot assume that
2491 // writes within the file doesn't cause new blocks to be allocated.
2492 // Therefore, we have to simply stop writing the moment the limit has
2493 // been exceeded.
2494 return free >= neededSpace;
2497 return true;
2501 void CPartFile::SetLastAnsweredTime()
2503 m_ClientSrcAnswered = ::GetTickCount();
2506 void CPartFile::SetLastAnsweredTimeTimeout()
2508 m_ClientSrcAnswered = 2 * CONNECTION_LATENCY + ::GetTickCount() - SOURCECLIENTREASKS;
2511 CPacket *CPartFile::CreateSrcInfoPacket(const CUpDownClient* forClient, uint8 byRequestedVersion, uint16 nRequestedOptions)
2514 if ( m_SrcList.empty() ) {
2515 return NULL;
2518 if(!IsPartFile()) {
2519 return CKnownFile::CreateSrcInfoPacket(forClient, byRequestedVersion, nRequestedOptions);
2522 if (((forClient->GetRequestFile() != this)
2523 && (forClient->GetUploadFile() != this)) || forClient->GetUploadFileID() != GetFileHash()) {
2524 wxString file1 = _("Unknown");
2525 if (forClient->GetRequestFile() && forClient->GetRequestFile()->GetFileName().IsOk()) {
2526 file1 = forClient->GetRequestFile()->GetFileName().GetPrintable();
2527 } else if (forClient->GetUploadFile() && forClient->GetUploadFile()->GetFileName().IsOk()) {
2528 file1 = forClient->GetUploadFile()->GetFileName().GetPrintable();
2530 wxString file2 = _("Unknown");
2531 if (GetFileName().IsOk()) {
2532 file2 = GetFileName().GetPrintable();
2534 AddDebugLogLineM(false, logPartFile, wxT("File mismatch on source packet (P) Sending: ") + file1 + wxT(" From: ") + file2);
2535 return NULL;
2538 if ( !(GetStatus() == PS_READY || GetStatus() == PS_EMPTY)) {
2539 return NULL;
2542 const BitVector& reqstatus = forClient->GetPartStatus();
2543 bool KnowNeededParts = !reqstatus.empty();
2544 //wxASSERT(rcvstatus.size() == GetPartCount()); // Obviously!
2545 if (KnowNeededParts && (reqstatus.size() != GetPartCount())) {
2546 // Yuck. Same file but different part count? Seriously fucked up.
2547 // This happens rather often with reqstatus.size() == 0. Don't log then.
2548 if (reqstatus.size()) {
2549 AddDebugLogLineM(false, logKnownFiles, CFormat(wxT("Impossible situation: different partcounts: %i (client) and %i (file) for %s")) % reqstatus.size() % GetPartCount() % GetFileName());
2551 return NULL;
2554 CMemFile data(1024);
2556 uint8 byUsedVersion;
2557 bool bIsSX2Packet;
2558 if (forClient->SupportsSourceExchange2() && byRequestedVersion > 0){
2559 // the client uses SourceExchange2 and requested the highest version he knows
2560 // and we send the highest version we know, but of course not higher than his request
2561 byUsedVersion = std::min(byRequestedVersion, (uint8)SOURCEEXCHANGE2_VERSION);
2562 bIsSX2Packet = true;
2563 data.WriteUInt8(byUsedVersion);
2565 // we don't support any special SX2 options yet, reserved for later use
2566 if (nRequestedOptions != 0) {
2567 AddDebugLogLineM(false, logKnownFiles, CFormat(wxT("Client requested unknown options for SourceExchange2: %u")) % nRequestedOptions);
2569 } else {
2570 byUsedVersion = forClient->GetSourceExchange1Version();
2571 bIsSX2Packet = false;
2572 if (forClient->SupportsSourceExchange2()) {
2573 AddDebugLogLineM(false, logKnownFiles, wxT("Client which announced to support SX2 sent SX1 packet instead"));
2577 uint16 nCount = 0;
2579 data.WriteHash(m_abyFileHash);
2580 data.WriteUInt16(nCount);
2581 bool bNeeded;
2582 for (SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
2583 bNeeded = false;
2584 CUpDownClient* cur_src = *it;
2586 int state = cur_src->GetDownloadState();
2587 int valid = ( state == DS_DOWNLOADING ) || ( state == DS_ONQUEUE && !cur_src->IsRemoteQueueFull() );
2589 if ( cur_src->HasLowID() || !valid ) {
2590 continue;
2593 // only send source which have needed parts for this client if possible
2594 const BitVector& srcstatus = cur_src->GetPartStatus();
2595 if ( !srcstatus.empty() ) {
2596 //wxASSERT(srcstatus.size() == GetPartCount()); // Obviously!
2597 if (srcstatus.size() != GetPartCount()) {
2598 continue;
2600 if ( KnowNeededParts ) {
2601 // only send sources which have needed parts for this client
2602 for (int x = 0; x < GetPartCount(); ++x) {
2603 if (srcstatus[x] && !reqstatus[x]) {
2604 bNeeded = true;
2605 break;
2608 } else {
2609 // if we don't know the need parts for this client,
2610 // return any source currently a client sends it's
2611 // file status only after it has at least one complete part
2612 if (srcstatus.size() != GetPartCount()) {
2613 continue;
2615 for (int x = 0; x < GetPartCount(); ++x){
2616 if (srcstatus[x]) {
2617 bNeeded = true;
2618 break;
2623 if(bNeeded) {
2624 ++nCount;
2625 uint32 dwID;
2626 if(forClient->GetSourceExchange1Version() > 2) {
2627 dwID = cur_src->GetUserIDHybrid();
2628 } else {
2629 dwID = wxUINT32_SWAP_ALWAYS(cur_src->GetUserIDHybrid());
2631 data.WriteUInt32(dwID);
2632 data.WriteUInt16(cur_src->GetUserPort());
2633 data.WriteUInt32(cur_src->GetServerIP());
2634 data.WriteUInt16(cur_src->GetServerPort());
2636 if (byUsedVersion >= 2) {
2637 data.WriteHash(cur_src->GetUserHash());
2640 if (byUsedVersion >= 4){
2641 // CryptSettings - SourceExchange V4
2642 // 5 Reserved (!)
2643 // 1 CryptLayer Required
2644 // 1 CryptLayer Requested
2645 // 1 CryptLayer Supported
2646 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
2647 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
2648 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
2649 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
2650 data.WriteUInt8(byCryptOptions);
2653 if (nCount > 500) {
2654 break;
2658 if (!nCount) {
2659 return 0;
2661 data.Seek(bIsSX2Packet ? 17 : 16, wxFromStart);
2662 data.WriteUInt16(nCount);
2664 CPacket* result = new CPacket(data, OP_EMULEPROT, bIsSX2Packet ? OP_ANSWERSOURCES2 : OP_ANSWERSOURCES);
2666 // 16+2+501*(4+2+4+2+16) = 14046 bytes max.
2667 if (result->GetPacketSize() > 354) {
2668 result->PackPacket();
2671 return result;
2674 void CPartFile::AddClientSources(CMemFile* sources, unsigned nSourceFrom, uint8 uClientSXVersion, bool bSourceExchange2, const CUpDownClient* /*pClient*/)
2676 // Kad reviewed
2678 if (m_stopped) {
2679 return;
2682 uint32 nCount = 0;
2683 uint8 uPacketSXVersion = 0;
2684 if (!bSourceExchange2) {
2685 nCount = sources->ReadUInt16();
2687 // Check if the data size matches the 'nCount' for v1 or v2 and eventually correct the source
2688 // exchange version while reading the packet data. Otherwise we could experience a higher
2689 // chance in dealing with wrong source data, userhashs and finally duplicate sources.
2690 uint32 uDataSize = sources->GetLength() - sources->GetPosition();
2692 if ((uint32)(nCount*(4+2+4+2)) == uDataSize) { //Checks if version 1 packet is correct size
2693 if(uClientSXVersion != 1) {
2694 return;
2696 uPacketSXVersion = 1;
2697 } else if ((uint32)(nCount*(4+2+4+2+16)) == uDataSize) { // Checks if version 2&3 packet is correct size
2698 if (uClientSXVersion == 2) {
2699 uPacketSXVersion = 2;
2700 } else if (uClientSXVersion > 2) {
2701 uPacketSXVersion = 3;
2702 } else {
2703 return;
2705 } else if (nCount*(4+2+4+2+16+1) == uDataSize) {
2706 if (uClientSXVersion != 4 ) {
2707 return;
2709 uPacketSXVersion = 4;
2710 } else {
2711 // If v5 inserts additional data (like v2), the above code will correctly filter those packets.
2712 // If v5 appends additional data after <count>(<Sources>)[count], we are in trouble with the
2713 // above code. Though a client which does not understand v5+ should never receive such a packet.
2714 AddDebugLogLineM(false, logClient, CFormat(wxT("Received invalid source exchange packet (v%u) of data size %u for %s")) % uClientSXVersion % uDataSize % GetFileName());
2715 return;
2717 } else {
2718 // for SX2:
2719 // We only check if the version is known by us and do a quick sanitize check on known version
2720 // other then SX1, the packet will be ignored if any error appears, sicne it can't be a "misunderstanding" anymore
2721 if (uClientSXVersion > SOURCEEXCHANGE2_VERSION || uClientSXVersion == 0 ){
2722 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Invalid source exchange type version: %i")) % uClientSXVersion);
2723 return;
2726 // all known versions use the first 2 bytes as count and unknown version are already filtered above
2727 nCount = sources->ReadUInt16();
2728 uint32 uDataSize = (uint32)(sources->GetLength() - sources->GetPosition());
2729 bool bError = false;
2730 switch (uClientSXVersion){
2731 case 1:
2732 bError = nCount*(4+2+4+2) != uDataSize;
2733 break;
2734 case 2:
2735 case 3:
2736 bError = nCount*(4+2+4+2+16) != uDataSize;
2737 break;
2738 case 4:
2739 bError = nCount*(4+2+4+2+16+1) != uDataSize;
2740 break;
2741 default:
2742 wxFAIL;
2745 if (bError){
2746 wxFAIL;
2747 AddDebugLogLineM(false, logPartFile, wxT("Invalid source exchange data size."));
2748 return;
2750 uPacketSXVersion = uClientSXVersion;
2753 for (uint16 i = 0;i != nCount;++i) {
2755 uint32 dwID = sources->ReadUInt32();
2756 uint16 nPort = sources->ReadUInt16();
2757 uint32 dwServerIP = sources->ReadUInt32();
2758 uint16 nServerPort = sources->ReadUInt16();
2760 CMD4Hash userHash;
2761 if (uPacketSXVersion > 1) {
2762 userHash = sources->ReadHash();
2765 uint8 byCryptOptions = 0;
2766 if (uPacketSXVersion >= 4) {
2767 byCryptOptions = sources->ReadUInt8();
2770 //Clients send ID's the the Hyrbid format so highID clients with *.*.*.0 won't be falsely switched to a lowID..
2771 uint32 dwIDED2K;
2772 if (uPacketSXVersion >= 3) {
2773 dwIDED2K = wxUINT32_SWAP_ALWAYS(dwID);
2774 } else {
2775 dwIDED2K = dwID;
2778 // check the HighID(IP) - "Filter LAN IPs" and "IPfilter" the received sources IP addresses
2779 if (!IsLowID(dwID)) {
2780 if (!IsGoodIP(dwIDED2K, thePrefs::FilterLanIPs())) {
2781 // check for 0-IP, localhost and optionally for LAN addresses
2782 AddDebugLogLineM(false, logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - bad IP")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2783 continue;
2785 if (theApp->ipfilter->IsFiltered(dwIDED2K)) {
2786 AddDebugLogLineM(false, logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - IPFilter")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2787 continue;
2789 if (theApp->clientlist->IsBannedClient(dwIDED2K)){
2790 continue;
2794 // additionally check for LowID and own IP
2795 if (!CanAddSource(dwID, nPort, dwServerIP, nServerPort, NULL, false)) {
2796 AddDebugLogLineM(false, logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via source exchange")) % Uint32toStringIP(dwIDED2K));
2797 continue;
2800 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
2801 CUpDownClient* newsource = new CUpDownClient(nPort,dwID,dwServerIP,nServerPort,this, (uPacketSXVersion < 3), true);
2802 if (uPacketSXVersion > 1) {
2803 newsource->SetUserHash(userHash);
2806 if (uPacketSXVersion >= 4) {
2807 newsource->SetConnectOptions(byCryptOptions, true, false);
2810 newsource->SetSourceFrom((ESourceFrom)nSourceFrom);
2811 theApp->downloadqueue->CheckAndAddSource(this,newsource);
2813 } else {
2814 break;
2819 void CPartFile::UpdateAutoDownPriority()
2821 if (!IsAutoDownPriority()) {
2822 return;
2824 if (GetSourceCount() <= RARE_FILE) {
2825 if ( GetDownPriority() != PR_HIGH )
2826 SetDownPriority(PR_HIGH, false, false);
2827 } else if (GetSourceCount() < 100) {
2828 if ( GetDownPriority() != PR_NORMAL )
2829 SetDownPriority(PR_NORMAL, false, false);
2830 } else {
2831 if ( GetDownPriority() != PR_LOW )
2832 SetDownPriority(PR_LOW, false, false);
2836 // making this function return a higher when more sources have the extended
2837 // protocol will force you to ask a larger variety of people for sources
2839 int CPartFile::GetCommonFilePenalty()
2841 //TODO: implement, but never return less than MINCOMMONPENALTY!
2842 return MINCOMMONPENALTY;
2845 /* Barry - Replaces BlockReceived()
2847 Originally this only wrote to disk when a full 180k block
2848 had been received from a client, and only asked for data in
2849 180k blocks.
2851 This meant that on average 90k was lost for every connection
2852 to a client data source. That is a lot of wasted data.
2854 To reduce the lost data, packets are now written to a buffer
2855 and flushed to disk regularly regardless of size downloaded.
2856 This includes compressed packets.
2858 Data is also requested only where gaps are, not in 180k blocks.
2859 The requests will still not exceed 180k, but may be smaller to
2860 fill a gap.
2863 // Kry - transize is 32bits, no packet can be more than that (this is
2864 // compressed size). Even 32bits is too much imho.As for the return size,
2865 // look at the lenData below.
2866 uint32 CPartFile::WriteToBuffer(uint32 transize, byte* data, uint64 start, uint64 end, Requested_Block_Struct *block, const CUpDownClient* client)
2868 // Increment transferred bytes counter for this file
2869 transferred += transize;
2871 // This is needed a few times
2872 // Kry - should not need a uint64 here - no block is larger than
2873 // 2GB even after uncompressed.
2874 uint32 lenData = (uint32) (end - start + 1);
2876 if(lenData > transize) {
2877 m_iGainDueToCompression += lenData-transize;
2880 // Occasionally packets are duplicated, no point writing it twice
2881 if (IsComplete(start, end)) {
2882 AddDebugLogLineM(false, logPartFile,
2883 CFormat(wxT("File '%s' has already been written from %u to %u"))
2884 % GetFileName() % start % end);
2885 return 0;
2888 // security sanitize check to make sure we do not write anything into an already hashed complete chunk
2889 const uint64 nStartChunk = start / PARTSIZE;
2890 const uint64 nEndChunk = end / PARTSIZE;
2891 if (IsComplete(nStartChunk)) {
2892 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (start): %u-%u; File=%s")) % start % end % GetFileName());
2893 return 0;
2894 } else if (nStartChunk != nEndChunk) {
2895 if (IsComplete(nEndChunk)) {
2896 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (end): %u-%u; File=%s")) % start % end % GetFileName());
2897 return 0;
2899 #ifdef __DEBUG__
2900 else {
2901 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Received data crosses chunk boundaries: %u-%u; File=%s")) % start % end % GetFileName());
2903 #endif
2906 // log transferinformation in our "blackbox"
2907 m_CorruptionBlackBox->TransferredData(start, end, client->GetIP());
2909 // Create a new buffered queue entry
2910 PartFileBufferedData *item = new PartFileBufferedData(m_hpartfile, data, start, end, block);
2912 // Add to the queue in the correct position (most likely the end)
2913 bool added = false;
2915 std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
2916 for (; it != m_BufferedData_list.end(); ++it) {
2917 PartFileBufferedData* queueItem = *it;
2919 if (item->end <= queueItem->end) {
2920 if (it != m_BufferedData_list.begin()) {
2921 added = true;
2923 m_BufferedData_list.insert(--it, item);
2926 break;
2930 if (!added) {
2931 m_BufferedData_list.push_front(item);
2934 // Increment buffer size marker
2935 m_nTotalBufferData += lenData;
2937 // Mark this small section of the file as filled
2938 FillGap(item->start, item->end);
2940 // Update the flushed mark on the requested block
2941 // The loop here is unfortunate but necessary to detect deleted blocks.
2943 std::list<Requested_Block_Struct*>::iterator it2 = m_requestedblocks_list.begin();
2944 for (; it2 != m_requestedblocks_list.end(); ++it2) {
2945 if (*it2 == item->block) {
2946 item->block->transferred += lenData;
2950 if (m_gaplist.IsComplete()) {
2951 FlushBuffer();
2954 // Return the length of data written to the buffer
2955 return lenData;
2958 void CPartFile::FlushBuffer(bool fromAICHRecoveryDataAvailable)
2960 m_nLastBufferFlushTime = GetTickCount();
2962 if (m_BufferedData_list.empty()) {
2963 return;
2967 uint32 partCount = GetPartCount();
2968 // Remember which parts need to be checked at the end of the flush
2969 std::vector<bool> changedPart(partCount, false);
2971 // Ensure file is big enough to write data to (the last item will be the furthest from the start)
2972 if (!CheckFreeDiskSpace(m_nTotalBufferData)) {
2973 // Not enough free space to write the last item, bail
2974 AddLogLineM(true, CFormat( _("WARNING: Not enough free disk-space! Pausing file: %s") ) % GetFileName());
2976 PauseFile( true );
2977 return;
2980 // Loop through queue
2981 while ( !m_BufferedData_list.empty() ) {
2982 // Get top item and remove it from the queue
2983 CScopedPtr<PartFileBufferedData> item(m_BufferedData_list.front());
2984 m_BufferedData_list.pop_front();
2986 // This is needed a few times
2987 wxASSERT((item->end - item->start) < 0xFFFFFFFF);
2988 uint32 lenData = (uint32)(item->end - item->start + 1);
2990 // SLUGFILLER: SafeHash - could be more than one part
2991 for (uint32 curpart = (item->start/PARTSIZE); curpart <= (item->end/PARTSIZE); ++curpart) {
2992 wxASSERT(curpart < partCount);
2993 changedPart[curpart] = true;
2995 // SLUGFILLER: SafeHash
2997 // Go to the correct position in file and write block of data
2998 try {
2999 item->area.FlushAt(m_hpartfile, item->start, lenData);
3000 // Decrease buffer size
3001 m_nTotalBufferData -= lenData;
3002 } catch (const CIOFailureException& e) {
3003 AddDebugLogLineM(true, logPartFile, wxT("Error while saving part-file: ") + e.what());
3004 SetPartFileStatus(PS_ERROR);
3005 // No need to bang your head against it again and again if it has already failed.
3006 DeleteContents(m_BufferedData_list);
3007 m_nTotalBufferData = 0;
3008 return;
3013 // Update last-changed date
3014 m_lastDateChanged = wxDateTime::GetTimeNow();
3016 try {
3017 // Partfile should never be too large
3018 if (m_hpartfile.GetLength() > GetFileSize()) {
3019 // it's "last chance" correction. the real bugfix has to be applied 'somewhere' else
3020 m_hpartfile.SetLength(GetFileSize());
3022 } catch (const CIOFailureException& e) {
3023 AddDebugLogLineM(true, logPartFile,
3024 CFormat(wxT("Error while truncating part-file (%s): %s"))
3025 % m_PartPath % e.what());
3026 SetPartFileStatus(PS_ERROR);
3031 // Check each part of the file
3032 for (uint16 partNumber = 0; partNumber < partCount; ++partNumber) {
3033 if (changedPart[partNumber] == false) {
3034 continue;
3037 uint32 partRange = GetPartSize(partNumber) - 1;
3039 // Is this 9MB part complete
3040 if (IsComplete(partNumber)) {
3041 // Is part corrupt
3042 if (!HashSinglePart(partNumber)) {
3043 AddLogLineM(true, CFormat(
3044 _("Downloaded part %i is corrupt in file: %s") ) % partNumber % GetFileName() );
3045 AddGap(partNumber);
3046 // add part to corrupted list, if not already there
3047 if (!IsCorruptedPart(partNumber)) {
3048 m_corrupted_list.push_back(partNumber);
3050 // request AICH recovery data
3051 // Don't if called from the AICHRecovery. It's already there and would lead to an infinite recursion.
3052 if (!fromAICHRecoveryDataAvailable) {
3053 RequestAICHRecovery(partNumber);
3055 // Reduce transferred amount by corrupt amount
3056 m_iLostDueToCorruption += (partRange + 1);
3057 } else {
3058 if (!m_hashsetneeded) {
3059 AddDebugLogLineM(false, logPartFile, CFormat(
3060 wxT("Finished part %u of '%s'")) % partNumber % GetFileName());
3063 // tell the blackbox about the verified data
3064 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3066 // if this part was successfully completed (although ICH is active), remove from corrupted list
3067 EraseFirstValue(m_corrupted_list, partNumber);
3069 if (status == PS_EMPTY) {
3070 if (theApp->IsRunning()) { // may be called during shutdown!
3071 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3072 // Successfully completed part, make it available for sharing
3073 SetStatus(PS_READY);
3074 theApp->sharedfiles->SafeAddKFile(this);
3079 } else if ( IsCorruptedPart(partNumber) && // corrupted part:
3080 (thePrefs::IsICHEnabled() // old ICH: rehash whenever we have new data hoping it will be good now
3081 || fromAICHRecoveryDataAvailable)) {// new AICH: one rehash right before performing it (maybe it's already good)
3082 // Try to recover with minimal loss
3083 if (HashSinglePart(partNumber)) {
3084 ++m_iTotalPacketsSavedDueToICH;
3086 uint64 uMissingInPart = m_gaplist.GetGapSize(partNumber);
3087 FillGap(partNumber);
3088 RemoveBlockFromList(PARTSIZE*partNumber,(PARTSIZE*partNumber + partRange));
3090 // tell the blackbox about the verified data
3091 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3093 // remove from corrupted list
3094 EraseFirstValue(m_corrupted_list, partNumber);
3096 AddLogLineM(true, CFormat( _("ICH: Recovered corrupted part %i for %s -> Saved bytes: %s") )
3097 % partNumber
3098 % GetFileName()
3099 % CastItoXBytes(uMissingInPart));
3101 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3102 if (status == PS_EMPTY) {
3103 // Successfully recovered part, make it available for sharing
3104 SetStatus(PS_READY);
3105 if (theApp->IsRunning()) // may be called during shutdown!
3106 theApp->sharedfiles->SafeAddKFile(this);
3113 // Update met file
3114 SavePartFile();
3116 if (theApp->IsRunning()) { // may be called during shutdown!
3117 // Is this file finished ?
3118 if (m_gaplist.IsComplete()) {
3119 CompleteFile(false);
3125 // read data for upload, return false on error
3126 bool CPartFile::ReadData(CFileArea & area, uint64 offset, uint32 toread)
3128 // Sanity check
3129 if (offset + toread > GetFileSize()) {
3130 AddDebugLogLineM(false, logPartFile, CFormat(wxT("tried to read %d bytes past eof of %s"))
3131 % (offset + toread - GetFileSize()) % GetFileName());
3132 wxFAIL;
3133 return false;
3136 area.ReadAt(m_hpartfile, offset, toread);
3137 // if it fails it throws (which the caller should catch)
3138 return true;
3142 void CPartFile::UpdateFileRatingCommentAvail()
3144 bool prevComment = m_hasComment;
3145 int prevRating = m_iUserRating;
3147 m_hasComment = false;
3148 m_iUserRating = 0;
3149 int ratingCount = 0;
3151 SourceSet::iterator it = m_SrcList.begin();
3152 for (; it != m_SrcList.end(); ++it) {
3153 CUpDownClient* cur_src = *it;
3155 if (!cur_src->GetFileComment().IsEmpty()) {
3156 if (thePrefs::IsCommentFiltered(cur_src->GetFileComment())) {
3157 continue;
3159 m_hasComment = true;
3162 uint8 rating = cur_src->GetFileRating();
3163 if (rating) {
3164 wxASSERT(rating <= 5);
3166 ratingCount++;
3167 m_iUserRating += rating;
3171 if (ratingCount) {
3172 m_iUserRating /= ratingCount;
3173 wxASSERT(m_iUserRating > 0 && m_iUserRating <= 5);
3176 if ((prevComment != m_hasComment) || (prevRating != m_iUserRating)) {
3177 UpdateDisplayedInfo();
3182 void CPartFile::SetCategory(uint8 cat)
3184 wxASSERT( cat < theApp->glob_prefs->GetCatCount() );
3186 m_category = cat;
3187 SavePartFile();
3190 bool CPartFile::RemoveSource(CUpDownClient* toremove, bool updatewindow, bool bDoStatsUpdate)
3192 wxASSERT( toremove );
3194 bool result = theApp->downloadqueue->RemoveSource( toremove, updatewindow, bDoStatsUpdate );
3196 // Check if the client should be deleted, but not if the client is already dying
3197 if ( !toremove->GetSocket() && !toremove->HasBeenDeleted() ) {
3198 if ( toremove->Disconnected(wxT("RemoveSource - purged")) ) {
3199 toremove->Safe_Delete();
3203 return result;
3206 void CPartFile::AddDownloadingSource(CUpDownClient* client)
3208 CClientPtrList::iterator it =
3209 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), client);
3210 if (it == m_downloadingSourcesList.end()) {
3211 m_downloadingSourcesList.push_back(client);
3216 void CPartFile::RemoveDownloadingSource(CUpDownClient* client)
3218 CClientPtrList::iterator it =
3219 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), client);
3220 if (it != m_downloadingSourcesList.end()) {
3221 m_downloadingSourcesList.erase(it);
3226 void CPartFile::SetPartFileStatus(uint8 newstatus)
3228 status=newstatus;
3230 if (thePrefs::GetAllcatType()) {
3231 Notify_DownloadCtrlUpdateItem(this);
3234 Notify_DownloadCtrlSort();
3238 uint64 CPartFile::GetNeededSpace()
3240 try {
3241 uint64 length = m_hpartfile.GetLength();
3243 if (length > GetFileSize()) {
3244 return 0; // Shouldn't happen, but just in case
3247 return GetFileSize() - length;
3248 } catch (const CIOFailureException& e) {
3249 AddDebugLogLineM(true, logPartFile,
3250 CFormat(wxT("Error while retrieving file-length (%s): %s"))
3251 % m_PartPath % e.what());
3252 SetPartFileStatus(PS_ERROR);
3253 return 0;
3257 void CPartFile::SetStatus(uint8 in)
3259 wxASSERT( in != PS_PAUSED && in != PS_INSUFFICIENT );
3261 status = in;
3263 if (theApp->IsRunning()) {
3264 UpdateDisplayedInfo( true );
3266 if ( thePrefs::ShowCatTabInfos() ) {
3267 Notify_ShowUpdateCatTabTitles();
3273 void CPartFile::RequestAICHRecovery(uint16 nPart)
3276 if ( !m_pAICHHashSet->HasValidMasterHash() ||
3277 (m_pAICHHashSet->GetStatus() != AICH_TRUSTED && m_pAICHHashSet->GetStatus() != AICH_VERIFIED)){
3278 AddDebugLogLineM( false, logAICHRecovery, wxT("Unable to request AICH Recoverydata because we have no trusted Masterhash") );
3279 return;
3281 if (GetPartSize(nPart) <= EMBLOCKSIZE)
3282 return;
3283 if (CAICHHashSet::IsClientRequestPending(this, nPart)){
3284 AddDebugLogLineM( false, logAICHRecovery, wxT("RequestAICHRecovery: Already a request for this part pending"));
3285 return;
3288 // first check if we have already the recoverydata, no need to rerequest it then
3289 if (m_pAICHHashSet->IsPartDataAvailable(nPart*PARTSIZE)){
3290 AddDebugLogLineM( false, logAICHRecovery, wxT("Found PartRecoveryData in memory"));
3291 AICHRecoveryDataAvailable(nPart);
3292 return;
3295 wxASSERT( nPart < GetPartCount() );
3296 // find some random client which support AICH to ask for the blocks
3297 // first lets see how many we have at all, we prefer high id very much
3298 uint32 cAICHClients = 0;
3299 uint32 cAICHLowIDClients = 0;
3300 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3301 CUpDownClient* pCurClient = *(it);
3302 if ( pCurClient->IsSupportingAICH() &&
3303 pCurClient->GetReqFileAICHHash() != NULL &&
3304 !pCurClient->IsAICHReqPending()
3305 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3307 if (pCurClient->HasLowID()) {
3308 ++cAICHLowIDClients;
3309 } else {
3310 ++cAICHClients;
3314 if ((cAICHClients | cAICHLowIDClients) == 0){
3315 AddDebugLogLineM( false, logAICHRecovery, wxT("Unable to request AICH Recoverydata because found no client who supports it and has the same hash as the trusted one"));
3316 return;
3318 uint32 nSeclectedClient;
3319 if (cAICHClients > 0) {
3320 nSeclectedClient = (rand() % cAICHClients) + 1;
3321 } else {
3322 nSeclectedClient = (rand() % cAICHLowIDClients) + 1;
3324 CUpDownClient* pClient = NULL;
3325 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3326 CUpDownClient* pCurClient = *(it);
3327 if (pCurClient->IsSupportingAICH() && pCurClient->GetReqFileAICHHash() != NULL && !pCurClient->IsAICHReqPending()
3328 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3330 if (cAICHClients > 0){
3331 if (!pCurClient->HasLowID())
3332 nSeclectedClient--;
3334 else{
3335 wxASSERT( pCurClient->HasLowID());
3336 nSeclectedClient--;
3338 if (nSeclectedClient == 0){
3339 pClient = pCurClient;
3340 break;
3344 if (pClient == NULL){
3345 wxFAIL;
3346 return;
3349 AddDebugLogLineM( false, logAICHRecovery, CFormat( wxT("Requesting AICH Hash (%s) form client %s") ) % ( cAICHClients ? wxT("HighId") : wxT("LowID") ) % pClient->GetClientFullInfo() );
3350 pClient->SendAICHRequest(this, nPart);
3355 void CPartFile::AICHRecoveryDataAvailable(uint16 nPart)
3357 if (GetPartCount() < nPart){
3358 wxFAIL;
3359 return;
3362 FlushBuffer(true);
3363 uint32 length = GetPartSize(nPart);
3364 // if the part was already ok, it would now be complete
3365 if (IsComplete(nPart)){
3366 AddDebugLogLineM( false, logAICHRecovery,
3367 wxString::Format( wxT("Processing AICH Recovery data: The part (%u) is already complete, canceling"), nPart ) );
3368 return;
3373 CAICHHashTree* pVerifiedHash = m_pAICHHashSet->m_pHashTree.FindHash(nPart*PARTSIZE, length);
3374 if (pVerifiedHash == NULL || !pVerifiedHash->GetHashValid()){
3375 AddDebugLogLineM( true, logAICHRecovery, wxT("Processing AICH Recovery data: Unable to get verified hash from hashset (should never happen)") );
3376 wxFAIL;
3377 return;
3379 CAICHHashTree htOurHash(pVerifiedHash->GetNDataSize(), pVerifiedHash->GetIsLeftBranch(), pVerifiedHash->GetNBaseSize());
3380 try {
3381 CreateHashFromFile(m_hpartfile, PARTSIZE * nPart, length, NULL, &htOurHash);
3382 } catch (const CIOFailureException& e) {
3383 AddDebugLogLineM(true, logAICHRecovery,
3384 CFormat(wxT("IO failure while hashing part-file '%s': %s"))
3385 % m_hpartfile.GetFilePath() % e.what());
3386 SetPartFileStatus(PS_ERROR);
3387 return;
3390 if (!htOurHash.GetHashValid()){
3391 AddDebugLogLineM( false, logAICHRecovery, wxT("Processing AICH Recovery data: Failed to retrieve AICH Hashset of corrupt part") );
3392 wxFAIL;
3393 return;
3396 // now compare the hash we just did, to the verified hash and readd all blocks which are ok
3397 uint32 nRecovered = 0;
3398 for (uint32 pos = 0; pos < length; pos += EMBLOCKSIZE){
3399 const uint32 nBlockSize = min<uint32>(EMBLOCKSIZE, length - pos);
3400 CAICHHashTree* pVerifiedBlock = pVerifiedHash->FindHash(pos, nBlockSize);
3401 CAICHHashTree* pOurBlock = htOurHash.FindHash(pos, nBlockSize);
3402 if ( pVerifiedBlock == NULL || pOurBlock == NULL || !pVerifiedBlock->GetHashValid() || !pOurBlock->GetHashValid()){
3403 wxFAIL;
3404 continue;
3406 if (pOurBlock->GetHash() == pVerifiedBlock->GetHash()){
3407 FillGap(PARTSIZE*nPart+pos, PARTSIZE*nPart + pos + (nBlockSize-1));
3408 RemoveBlockFromList(PARTSIZE*nPart, PARTSIZE*nPart + (nBlockSize-1));
3409 nRecovered += nBlockSize;
3410 // tell the blackbox about the verified data
3411 m_CorruptionBlackBox->VerifiedData(true, nPart, pos, pos + nBlockSize - 1);
3412 } else {
3413 // inform our "blackbox" about the corrupted block which may ban clients who sent it
3414 m_CorruptionBlackBox->VerifiedData(false, nPart, pos, pos + nBlockSize - 1);
3417 m_CorruptionBlackBox->EvaluateData();
3419 // ok now some sanity checks
3420 if (IsComplete(nPart)){
3421 // this is a bad, but it could probably happen under some rare circumstances
3422 // make sure that MD4 agrres to this fact too
3423 if (!HashSinglePart(nPart)){
3424 AddDebugLogLineM( false, logAICHRecovery,
3425 wxString::Format(wxT("Processing AICH Recovery data: The part (%u) got completed while recovering - but MD4 says it corrupt! Setting hashset to error state, deleting part"), nPart));
3426 // now we are fu... unhappy
3427 m_pAICHHashSet->SetStatus(AICH_ERROR);
3428 AddGap(nPart);
3429 wxFAIL;
3430 return;
3432 else{
3433 AddDebugLogLineM( false, logAICHRecovery, wxString::Format(
3434 wxT("Processing AICH Recovery data: The part (%u) got completed while recovering and MD4 agrees"), nPart) );
3435 if (status == PS_EMPTY && theApp->IsRunning()){
3436 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded){
3437 // Successfully recovered part, make it available for sharing
3438 SetStatus(PS_READY);
3439 theApp->sharedfiles->SafeAddKFile(this);
3443 if (theApp->IsRunning()){
3444 // Is this file finished?
3445 if (m_gaplist.IsComplete()) {
3446 CompleteFile(false);
3450 } // end sanity check
3451 // We did the best we could. If it's still incomplete, then no need to keep
3452 // bashing it with ICH. So remove it from the list of corrupted parts.
3453 EraseFirstValue(m_corrupted_list, nPart);
3454 // Update met file
3455 SavePartFile();
3457 // make sure the user appreciates our great recovering work :P
3458 AddDebugLogLineM( true, logAICHRecovery, CFormat(
3459 wxT("AICH successfully recovered %s of %s from part %u for %s") )
3460 % CastItoXBytes(nRecovered)
3461 % CastItoXBytes(length)
3462 % nPart
3463 % GetFileName() );
3467 void CPartFile::ClientStateChanged( int oldState, int newState )
3469 if ( oldState == newState )
3470 return;
3472 // If the state is -1, then it's an entirely new item
3473 if ( oldState != -1 ) {
3474 // Was the old state a valid state?
3475 if ( oldState == DS_ONQUEUE || oldState == DS_DOWNLOADING ) {
3476 m_validSources--;
3477 } else {
3478 if ( oldState == DS_CONNECTED /* || oldState == DS_REMOTEQUEUEFULL */ ) {
3479 m_validSources--;
3482 m_notCurrentSources--;
3486 // If the state is -1, then the source is being removed
3487 if ( newState != -1 ) {
3488 // Was the old state a valid state?
3489 if ( newState == DS_ONQUEUE || newState == DS_DOWNLOADING ) {
3490 ++m_validSources;
3491 } else {
3492 if ( newState == DS_CONNECTED /* || newState == DS_REMOTEQUEUEFULL */ ) {
3493 ++m_validSources;
3496 ++m_notCurrentSources;
3502 bool CPartFile::AddSource( CUpDownClient* client )
3504 if (m_SrcList.insert( client ).second) {
3505 theStats::AddFoundSource();
3506 theStats::AddSourceOrigin(client->GetSourceFrom());
3507 return true;
3508 } else {
3509 return false;
3514 bool CPartFile::DelSource( CUpDownClient* client )
3516 if (m_SrcList.erase( client )) {
3517 theStats::RemoveSourceOrigin(client->GetSourceFrom());
3518 theStats::RemoveFoundSource();
3519 return true;
3520 } else {
3521 return false;
3526 void CPartFile::UpdatePartsFrequency( CUpDownClient* client, bool increment )
3528 const BitVector& freq = client->GetPartStatus();
3530 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
3531 m_SrcpartFrequency.clear();
3532 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
3534 if ( !increment ) {
3535 return;
3539 unsigned int size = freq.size();
3540 if ( size != m_SrcpartFrequency.size() ) {
3541 return;
3544 if ( increment ) {
3545 for ( unsigned int i = 0; i < size; i++ ) {
3546 if ( freq[i] ) {
3547 m_SrcpartFrequency[i]++;
3550 } else {
3551 for ( unsigned int i = 0; i < size; i++ ) {
3552 if ( freq[i] ) {
3553 m_SrcpartFrequency[i]--;
3559 const FileRatingList &CPartFile::GetRatingAndComments()
3561 m_FileRatingList.clear();
3562 // This can be pre-processed, but is it worth the CPU?
3563 CPartFile::SourceSet::iterator it = m_SrcList.begin();
3564 for ( ; it != m_SrcList.end(); ++it ) {
3565 CUpDownClient *cur_src = *it;
3566 if (cur_src->GetFileComment().Length()>0 || cur_src->GetFileRating()>0) {
3567 // AddDebugLogLineM(false, logPartFile, wxString(wxT("found a comment for ")) << GetFileName());
3568 m_FileRatingList.push_back(SFileRating(*cur_src));
3572 return m_FileRatingList;
3575 #else // CLIENT_GUI
3577 CPartFile::CPartFile(CEC_PartFile_Tag *tag)
3579 Init();
3581 SetFileName(CPath(tag->FileName()));
3582 m_abyFileHash = tag->ID();
3583 SetFileSize(tag->SizeFull());
3584 m_gaplist.Init(GetFileSize(), true); // Init empty
3585 m_partmetfilename = CPath(tag->PartMetName());
3586 m_fullname = m_partmetfilename; // We have only the met number, so show it without path in the detail dialog.
3588 m_SrcpartFrequency.insert(m_SrcpartFrequency.end(), GetPartCount(), 0);
3590 // these are only in CLIENT_GUI and not covered by Init()
3591 m_source_count = 0;
3592 m_kbpsDown = 0;
3593 m_iDownPriorityEC = 0;
3594 m_a4af_source_count = 0;
3598 * Remote gui specific code
3600 CPartFile::~CPartFile()
3604 const FileRatingList &CPartFile::GetRatingAndComments()
3606 return m_FileRatingList;
3609 void CPartFile::SetCategory(uint8 cat)
3611 m_category = cat;
3614 #endif // !CLIENT_GUI
3617 void CPartFile::UpdateDisplayedInfo(bool force)
3619 uint32 curTick = ::GetTickCount();
3621 // Wait 1.5s between each redraw
3622 if(force || curTick-m_lastRefreshedDLDisplay > MINWAIT_BEFORE_DLDISPLAY_WINDOWUPDATE ) {
3623 Notify_DownloadCtrlUpdateItem(this);
3624 m_lastRefreshedDLDisplay = curTick;
3630 void CPartFile::Init()
3632 m_showSources = false;
3633 m_lastsearchtime = 0;
3634 lastpurgetime = ::GetTickCount();
3635 m_paused = false;
3636 m_stopped = false;
3637 m_insufficient = false;
3639 status = PS_EMPTY;
3641 transferred = 0;
3642 m_iLastPausePurge = time(NULL);
3644 if(thePrefs::GetNewAutoDown()) {
3645 m_iDownPriority = PR_HIGH;
3646 m_bAutoDownPriority = true;
3647 } else {
3648 m_iDownPriority = PR_NORMAL;
3649 m_bAutoDownPriority = false;
3652 transferingsrc = 0; // new
3654 kBpsDown = 0.0;
3656 m_hashsetneeded = true;
3657 m_count = 0;
3658 percentcompleted = 0;
3659 completedsize=0;
3660 m_bPreviewing = false;
3661 lastseencomplete = 0;
3662 m_availablePartsCount=0;
3663 m_ClientSrcAnswered = 0;
3664 m_LastNoNeededCheck = 0;
3665 m_iRating = 0;
3666 m_nTotalBufferData = 0;
3667 m_nLastBufferFlushTime = 0;
3668 m_bPercentUpdated = false;
3669 m_bRecoveringArchive = false;
3670 m_iGainDueToCompression = 0;
3671 m_iLostDueToCorruption = 0;
3672 m_iTotalPacketsSavedDueToICH = 0;
3673 m_category = 0;
3674 m_lastRefreshedDLDisplay = 0;
3675 m_nDlActiveTime = 0;
3676 m_tActivated = 0;
3677 m_is_A4AF_auto = false;
3678 m_localSrcReqQueued = false;
3679 m_nCompleteSourcesTime = time(NULL);
3680 m_nCompleteSourcesCount = 0;
3681 m_nCompleteSourcesCountLo = 0;
3682 m_nCompleteSourcesCountHi = 0;
3684 m_validSources = 0;
3685 m_notCurrentSources = 0;
3687 // Kad
3688 m_LastSearchTimeKad = 0;
3689 m_TotalSearchesKad = 0;
3691 m_gapptrlist.Init(&m_gaplist);
3693 #ifndef CLIENT_GUI
3694 m_CorruptionBlackBox = new CCorruptionBlackBox();
3695 #endif
3698 wxString CPartFile::getPartfileStatus() const
3701 wxString mybuffer;
3703 if ((status == PS_HASHING) || (status == PS_WAITINGFORHASH)) {
3704 mybuffer=_("Hashing");
3705 } else if (status == PS_ALLOCATING) {
3706 mybuffer = _("Allocating");
3707 } else {
3708 switch (GetStatus()) {
3709 case PS_COMPLETING:
3710 mybuffer=_("Completing");
3711 break;
3712 case PS_COMPLETE:
3713 mybuffer=_("Complete");
3714 break;
3715 case PS_PAUSED:
3716 mybuffer=_("Paused");
3717 break;
3718 case PS_ERROR:
3719 mybuffer=_("Erroneous");
3720 break;
3721 case PS_INSUFFICIENT:
3722 mybuffer = _("Insufficient disk space");
3723 break;
3724 default:
3725 if (GetTransferingSrcCount()>0) {
3726 mybuffer=_("Downloading");
3727 } else {
3728 mybuffer=_("Waiting");
3730 break;
3732 if (m_stopped && (GetStatus()!=PS_COMPLETE)) {
3733 mybuffer=_("Stopped");
3737 return mybuffer;
3740 int CPartFile::getPartfileStatusRang() const
3743 int tempstatus=0;
3744 if (GetTransferingSrcCount()==0) tempstatus=1;
3745 switch (GetStatus()) {
3746 case PS_HASHING:
3747 case PS_WAITINGFORHASH:
3748 tempstatus=3;
3749 break;
3750 case PS_COMPLETING:
3751 tempstatus=4;
3752 break;
3753 case PS_COMPLETE:
3754 tempstatus=5;
3755 break;
3756 case PS_PAUSED:
3757 tempstatus=2;
3758 break;
3759 case PS_ERROR:
3760 tempstatus=6;
3761 break;
3763 return tempstatus;
3767 wxString CPartFile::GetFeedback() const
3769 wxString retval = CKnownFile::GetFeedback();
3770 if (GetStatus() != PS_COMPLETE) {
3771 retval += wxString(_("Downloaded")) + wxT(": ") + CastItoXBytes(GetCompletedSize()) + wxString::Format(wxT(" (%.2f%%)\n"), GetPercentCompleted())
3772 + _("Sources") + CFormat(wxT(": %u\n")) % GetSourceCount();
3774 return retval + _("Status") + wxT(": ") + getPartfileStatus() + wxT("\n");
3778 sint32 CPartFile::getTimeRemaining() const
3780 if (GetKBpsDown() < 0.001)
3781 return -1;
3782 else
3783 return((GetFileSize()-GetCompletedSize()) / ((int)(GetKBpsDown()*1024.0)));
3786 bool CPartFile::PreviewAvailable()
3788 FileType type = GetFiletype(GetFileName());
3790 return (((type == ftVideo) || (type == ftAudio)) && IsComplete(0, 256*1024));
3793 bool CPartFile::CheckShowItemInGivenCat(int inCategory)
3795 // easy normal cases
3796 bool IsInCat;
3797 bool IsNotFiltered = true;
3799 IsInCat = ((inCategory==0) || (inCategory>0 && inCategory==GetCategory()));
3801 switch (thePrefs::GetAllcatType()) {
3802 case 1:
3803 IsNotFiltered = GetCategory() == 0 || inCategory > 0;
3804 break;
3805 case 2:
3806 IsNotFiltered = IsPartFile();
3807 break;
3808 case 3:
3809 IsNotFiltered = !IsPartFile();
3810 break;
3811 case 4:
3812 IsNotFiltered =
3813 (GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
3814 GetTransferingSrcCount() == 0;
3815 break;
3816 case 5:
3817 IsNotFiltered =
3818 (GetStatus() == PS_READY || GetStatus()==PS_EMPTY) &&
3819 GetTransferingSrcCount() > 0;
3820 break;
3821 case 6:
3822 IsNotFiltered = GetStatus() == PS_ERROR;
3823 break;
3824 case 7:
3825 IsNotFiltered = GetStatus() == PS_PAUSED && !IsStopped();
3826 break;
3827 case 8:
3828 IsNotFiltered = IsStopped();
3829 break;
3830 case 9:
3831 IsNotFiltered = GetFiletype(GetFileName()) == ftVideo;
3832 break;
3833 case 10:
3834 IsNotFiltered = GetFiletype(GetFileName()) == ftAudio;
3835 break;
3836 case 11:
3837 IsNotFiltered = GetFiletype(GetFileName()) == ftArchive;
3838 break;
3839 case 12:
3840 IsNotFiltered = GetFiletype(GetFileName()) == ftCDImage;
3841 break;
3842 case 13:
3843 IsNotFiltered = GetFiletype(GetFileName()) == ftPicture;
3844 break;
3845 case 14:
3846 IsNotFiltered = GetFiletype(GetFileName()) == ftText;
3847 break;
3848 case 15:
3849 IsNotFiltered = !IsStopped() && GetStatus() != PS_PAUSED;
3850 break;
3853 return IsNotFiltered && IsInCat;
3857 void CPartFile::SetActive(bool bActive)
3859 time_t tNow = time(NULL);
3860 if (bActive) {
3861 if (theApp->IsConnected()) {
3862 if (m_tActivated == 0) {
3863 m_tActivated = tNow;
3866 } else {
3867 if (m_tActivated != 0) {
3868 m_nDlActiveTime += tNow - m_tActivated;
3869 m_tActivated = 0;
3875 uint32 CPartFile::GetDlActiveTime() const
3877 uint32 nDlActiveTime = m_nDlActiveTime;
3878 if (m_tActivated != 0) {
3879 nDlActiveTime += time(NULL) - m_tActivated;
3881 return nDlActiveTime;
3885 uint16 CPartFile::GetPartMetNumber() const
3887 long nr;
3888 return m_partmetfilename.RemoveAllExt().GetRaw().ToLong(&nr) ? nr : 0;
3892 #ifndef CLIENT_GUI
3894 uint8 CPartFile::GetStatus(bool ignorepause) const
3896 if ( (!m_paused && !m_insufficient) ||
3897 status == PS_ERROR ||
3898 status == PS_COMPLETING ||
3899 status == PS_COMPLETE ||
3900 ignorepause) {
3901 return status;
3902 } else if ( m_insufficient ) {
3903 return PS_INSUFFICIENT;
3904 } else {
3905 return PS_PAUSED;
3909 void CPartFile::AddDeadSource(const CUpDownClient* client)
3911 m_deadSources.AddDeadSource( client );
3915 bool CPartFile::IsDeadSource(const CUpDownClient* client)
3917 return m_deadSources.IsDeadSource( client );
3920 void CPartFile::SetFileName(const CPath& fileName)
3922 CKnownFile* pFile = theApp->sharedfiles->GetFileByID(GetFileHash());
3924 bool is_shared = (pFile && pFile == this);
3926 if (is_shared) {
3927 // The file is shared, we must clear the search keywords so we don't
3928 // publish the old name anymore.
3929 theApp->sharedfiles->RemoveKeywords(this);
3932 CKnownFile::SetFileName(fileName);
3934 if (is_shared) {
3935 // And of course, we must advertise the new name if the file is shared.
3936 theApp->sharedfiles->AddKeywords(this);
3939 UpdateDisplayedInfo(true);
3943 uint16 CPartFile::GetMaxSources() const
3945 // This is just like this, while we don't import the private max sources per file
3946 return thePrefs::GetMaxSourcePerFile();
3950 uint16 CPartFile::GetMaxSourcePerFileSoft() const
3952 unsigned int temp = ((unsigned int)GetMaxSources() * 9L) / 10;
3953 if (temp > MAX_SOURCES_FILE_SOFT) {
3954 return MAX_SOURCES_FILE_SOFT;
3956 return temp;
3959 uint16 CPartFile::GetMaxSourcePerFileUDP() const
3961 unsigned int temp = ((unsigned int)GetMaxSources() * 3L) / 4;
3962 if (temp > MAX_SOURCES_FILE_UDP) {
3963 return MAX_SOURCES_FILE_UDP;
3965 return temp;
3968 #define DROP_FACTOR 2
3970 CUpDownClient* CPartFile::GetSlowerDownloadingClient(uint32 speed, CUpDownClient* caller) {
3971 // printf("Start slower source calculation\n");
3972 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
3973 CUpDownClient* cur_src = *it++;
3974 if ((cur_src->GetDownloadState() == DS_DOWNLOADING) && (cur_src != caller)) {
3975 uint32 factored_bytes_per_second = static_cast<uint32>(
3976 (cur_src->GetKBpsDown() * 1024) * DROP_FACTOR);
3977 if ( factored_bytes_per_second< speed) {
3978 // printf("Selecting source %p to drop: %d < %d\n", cur_src, factored_bytes_per_second, speed);
3979 // printf("End slower source calculation\n");
3980 return cur_src;
3981 } else {
3982 // printf("Not selecting source %p to drop: %d > %d\n", cur_src, factored_bytes_per_second, speed);
3986 // printf("End slower source calculation\n");
3987 return NULL;
3990 void CPartFile::AllocationFinished()
3992 // see if it can be opened
3993 if (!m_hpartfile.Open(m_PartPath, CFile::read_write)) {
3994 AddLogLineM(false, CFormat(_("ERROR: Failed to open partfile '%s'")) % GetFullName());
3995 SetPartFileStatus(PS_ERROR);
3997 // then close the handle again
3998 m_hpartfile.Release(true);
4001 #endif
4002 // File_checked_for_headers