Reduced variable scope
[amule.git] / src / PartFile.cpp
blob7f302a5abc094f34dbb2adcd5f248f88db551083
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net )
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.
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
51 #ifdef CLIENT_GUI
52 #include "UpDownClientEC.h" // Needed for CUpDownClient
53 #else
54 #include "updownclient.h" // Needed for CUpDownClient
55 #endif
57 #include "MemFile.h" // Needed for CMemFile
58 #include "Preferences.h" // Needed for CPreferences
59 #include "DownloadQueue.h" // Needed for CDownloadQueue
60 #include "amule.h" // Needed for theApp
61 #include "ED2KLink.h" // Needed for CED2KLink
62 #include "Packet.h" // Needed for CTag
63 #include "SearchList.h" // Needed for CSearchFile
64 #include "ClientList.h" // Needed for clientlist
65 #include "Statistics.h" // Needed for theStats
66 #include "Logger.h"
67 #include <common/Format.h> // Needed for CFormat
68 #include <common/FileFunctions.h> // Needed for GetLastModificationTime
69 #include "ThreadTasks.h" // Needed for CHashingTask/CCompletionTask/CAllocateFileTask
70 #include "GuiEvents.h" // Needed for Notify_*
71 #include "DataToText.h" // Needed for OriginToText()
72 #include "PlatformSpecific.h" // Needed for CreateSparseFile()
73 #include "FileArea.h" // Needed for CFileArea
74 #include "ScopedPtr.h" // Needed for CScopedArray
75 #include "CorruptionBlackBox.h"
77 #include "kademlia/kademlia/Kademlia.h"
78 #include "kademlia/kademlia/Search.h"
81 SFileRating::SFileRating(const wxString &u, const wxString &f, sint16 r, const wxString &c)
83 UserName(u),
84 FileName(f),
85 Rating(r),
86 Comment(c)
91 SFileRating::SFileRating(const SFileRating &fr)
93 UserName(fr.UserName),
94 FileName(fr.FileName),
95 Rating(fr.Rating),
96 Comment(fr.Comment)
101 #ifndef CLIENT_GUI
102 SFileRating::SFileRating(const CUpDownClient &client)
104 UserName(client.GetUserName()),
105 FileName(client.GetClientFilename()),
106 Rating(client.GetFileRating()),
107 Comment(client.GetFileComment())
110 #endif
113 SFileRating::~SFileRating()
118 class PartFileBufferedData
120 public:
121 CFileArea area; // File area to be written
122 uint64 start; // This is the start offset of the data
123 uint64 end; // This is the end offset of the data
124 Requested_Block_Struct *block; // This is the requested block that this data relates to
126 PartFileBufferedData(CFileAutoClose& file, byte * data, uint64 _start, uint64 _end, Requested_Block_Struct *_block)
127 : start(_start), end(_end), block(_block)
129 area.StartWriteAt(file, start, end-start+1);
130 memcpy(area.GetBuffer(), data, end-start+1);
135 typedef std::list<Chunk> ChunkList;
138 #ifndef CLIENT_GUI
140 CPartFile::CPartFile()
142 Init();
145 CPartFile::CPartFile(CSearchFile* searchresult)
147 Init();
149 m_abyFileHash = searchresult->GetFileHash();
150 SetFileName(searchresult->GetFileName());
151 SetFileSize(searchresult->GetFileSize());
153 for (unsigned int i = 0; i < searchresult->m_taglist.size(); ++i){
154 const CTag& pTag = searchresult->m_taglist[i];
156 bool bTagAdded = false;
157 if (pTag.GetNameID() == 0 && !pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
158 static const struct {
159 wxString pszName;
160 uint8 nType;
161 } _aMetaTags[] =
163 { wxT(FT_ED2K_MEDIA_ARTIST), 2 },
164 { wxT(FT_ED2K_MEDIA_ALBUM), 2 },
165 { wxT(FT_ED2K_MEDIA_TITLE), 2 },
166 { wxT(FT_ED2K_MEDIA_LENGTH), 2 },
167 { wxT(FT_ED2K_MEDIA_BITRATE), 3 },
168 { wxT(FT_ED2K_MEDIA_CODEC), 2 }
171 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
172 if ( pTag.GetType() == _aMetaTags[t].nType &&
173 (pTag.GetName() == _aMetaTags[t].pszName)) {
174 // skip string tags with empty string values
175 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
176 break;
179 // skip "length" tags with "0: 0" values
180 if (pTag.GetName() == wxT(FT_ED2K_MEDIA_LENGTH)) {
181 if (pTag.GetStr().IsSameAs(wxT("0: 0")) ||
182 pTag.GetStr().IsSameAs(wxT("0:0"))) {
183 break;
187 // skip "bitrate" tags with '0' values
188 if ((pTag.GetName() == wxT(FT_ED2K_MEDIA_BITRATE)) && !pTag.GetInt()) {
189 break;
192 AddDebugLogLineN( logPartFile,
193 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
194 pTag.GetFullInfo() );
195 m_taglist.push_back(pTag);
196 bTagAdded = true;
197 break;
200 } else if (pTag.GetNameID() != 0 && pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
201 static const struct {
202 uint8 nID;
203 uint8 nType;
204 } _aMetaTags[] =
206 { FT_FILETYPE, 2 },
207 { FT_FILEFORMAT, 2 }
209 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
210 if (pTag.GetType() == _aMetaTags[t].nType && pTag.GetNameID() == _aMetaTags[t].nID) {
211 // skip string tags with empty string values
212 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
213 break;
216 AddDebugLogLineN( logPartFile,
217 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
218 pTag.GetFullInfo() );
219 m_taglist.push_back(pTag);
220 bTagAdded = true;
221 break;
226 if (!bTagAdded) {
227 AddDebugLogLineN( logPartFile,
228 wxT("CPartFile::CPartFile(CSearchFile*): ignored tag ") +
229 pTag.GetFullInfo() );
233 CreatePartFile();
237 CPartFile::CPartFile(const CED2KFileLink* fileLink)
239 Init();
241 SetFileName(CPath(fileLink->GetName()));
242 SetFileSize(fileLink->GetSize());
243 m_abyFileHash = fileLink->GetHashKey();
245 CreatePartFile();
247 if (fileLink->m_hashset) {
248 if (!LoadHashsetFromFile(fileLink->m_hashset, true)) {
249 AddDebugLogLineC(logPartFile, wxT("eD2K link contained invalid hashset: ") + fileLink->GetLink());
255 CPartFile::~CPartFile()
257 // if it's not opened, it was completed or deleted
258 if (m_hpartfile.IsOpened()) {
259 FlushBuffer();
260 m_hpartfile.Close();
261 // Update met file (with current directory entry)
262 SavePartFile();
265 DeleteContents(m_BufferedData_list);
266 delete m_CorruptionBlackBox;
268 wxASSERT(m_SrcList.empty());
269 wxASSERT(m_A4AFsrclist.empty());
272 void CPartFile::CreatePartFile()
274 // use lowest free partfilenumber for free file (InterCeptor)
275 int i = 0;
276 do {
277 ++i;
278 m_partmetfilename = CPath(CFormat(wxT("%03i.part.met")) % i);
279 m_fullname = thePrefs::GetTempDir().JoinPaths(m_partmetfilename);
280 } while (m_fullname.FileExists());
282 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
284 wxString strPartName = m_partmetfilename.RemoveExt().GetRaw();
285 m_taglist.push_back(CTagString(FT_PARTFILENAME, strPartName ));
287 m_gaplist.Init(GetFileSize(), true); // Init empty
289 m_PartPath = m_fullname.RemoveExt();
290 bool fileCreated;
291 if (thePrefs::GetAllocFullFile()) {
292 fileCreated = m_hpartfile.Create(m_PartPath, true);
293 m_hpartfile.Close();
294 } else {
295 fileCreated = PlatformSpecific::CreateSparseFile(m_PartPath, GetFileSize());
297 if (!fileCreated) {
298 AddLogLineN(_("ERROR: Failed to create partfile"));
299 SetStatus(PS_ERROR);
302 SetFilePath(thePrefs::GetTempDir());
304 if (thePrefs::GetAllocFullFile()) {
305 SetStatus(PS_ALLOCATING);
306 CThreadScheduler::AddTask(new CAllocateFileTask(this, thePrefs::AddNewFilesPaused()));
307 } else {
308 AllocationFinished();
311 m_hashsetneeded = (GetED2KPartHashCount() > 0);
313 SavePartFile(true);
314 SetActive(theApp->IsConnected());
318 uint8 CPartFile::LoadPartFile(const CPath& in_directory, const CPath& filename, bool from_backup, bool getsizeonly)
320 bool isnewstyle = false;
321 uint8 version,partmettype=PMT_UNKNOWN;
323 std::map<uint16, Gap_Struct*> gap_map; // Slugfiller
324 transferred = 0;
326 m_partmetfilename = filename;
327 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
328 m_filePath = in_directory;
329 m_fullname = m_filePath.JoinPaths(m_partmetfilename);
330 m_PartPath = m_fullname.RemoveExt();
332 // readfile data form part.met file
333 CPath curMetFilename = m_fullname;
334 if (from_backup) {
335 curMetFilename = curMetFilename.AppendExt(PARTMET_BAK_EXT);
336 AddLogLineN(CFormat( _("Trying to load backup of met-file from %s") )
337 % curMetFilename );
340 try {
341 CFile metFile(curMetFilename, CFile::read);
342 if (!metFile.IsOpened()) {
343 AddLogLineN(CFormat( _("ERROR: Failed to open part.met file: %s ==> %s") )
344 % curMetFilename
345 % GetFileName() );
347 return false;
348 } else if (metFile.GetLength() == 0) {
349 AddLogLineN(CFormat( _("ERROR: part.met file is 0 size: %s ==> %s") )
350 % m_partmetfilename
351 % GetFileName() );
353 return false;
356 version = metFile.ReadUInt8();
357 if (version != PARTFILE_VERSION && version != PARTFILE_SPLITTEDVERSION && version != PARTFILE_VERSION_LARGEFILE){
358 metFile.Close();
359 //if (version == 83) return ImportShareazaTempFile(...)
360 AddLogLineN(CFormat( _("ERROR: Invalid part.met file version: %s ==> %s") )
361 % m_partmetfilename
362 % GetFileName() );
363 return false;
366 isnewstyle = (version == PARTFILE_SPLITTEDVERSION);
367 partmettype = isnewstyle ? PMT_SPLITTED : PMT_DEFAULTOLD;
369 if (version == PARTFILE_VERSION) {// Do we still need this check ?
370 uint8 test[4]; // It will fail for certain files.
371 metFile.Seek(24, wxFromStart);
372 metFile.Read(test,4);
374 metFile.Seek(1, wxFromStart);
375 if (test[0]==0 && test[1]==0 && test[2]==2 && test[3]==1) {
376 isnewstyle=true; // edonkeys so called "old part style"
377 partmettype=PMT_NEWOLD;
381 if (isnewstyle) {
382 uint32 temp = metFile.ReadUInt32();
384 if (temp==0) { // 0.48 partmets - different again
385 LoadHashsetFromFile(&metFile, false);
386 } else {
387 metFile.Seek(2, wxFromStart);
388 LoadDateFromFile(&metFile);
389 m_abyFileHash = metFile.ReadHash();
392 } else {
393 LoadDateFromFile(&metFile);
394 LoadHashsetFromFile(&metFile, false);
397 uint32 tagcount = metFile.ReadUInt32();
399 for (uint32 j = 0; j < tagcount; ++j) {
400 CTag newtag(metFile,true);
401 if ( !getsizeonly ||
402 (getsizeonly &&
403 (newtag.GetNameID() == FT_FILESIZE ||
404 newtag.GetNameID() == FT_FILENAME))) {
405 switch(newtag.GetNameID()) {
406 case FT_FILENAME: {
407 if (!GetFileName().IsOk()) {
408 // If it's not empty, we already loaded the unicoded one
409 SetFileName(CPath(newtag.GetStr()));
411 break;
413 case FT_LASTSEENCOMPLETE: {
414 lastseencomplete = newtag.GetInt();
415 break;
417 case FT_FILESIZE: {
418 SetFileSize(newtag.GetInt());
419 break;
421 case FT_TRANSFERRED: {
422 transferred = newtag.GetInt();
423 break;
425 case FT_FILETYPE:{
426 //#warning needs setfiletype string
427 //SetFileType(newtag.GetStr());
428 break;
430 case FT_CATEGORY: {
431 m_category = newtag.GetInt();
432 if (m_category > theApp->glob_prefs->GetCatCount() - 1 ) {
433 m_category = 0;
435 break;
437 case FT_OLDDLPRIORITY:
438 case FT_DLPRIORITY: {
439 if (!isnewstyle){
440 m_iDownPriority = newtag.GetInt();
441 if( m_iDownPriority == PR_AUTO ){
442 m_iDownPriority = PR_HIGH;
443 SetAutoDownPriority(true);
445 else{
446 if ( m_iDownPriority != PR_LOW &&
447 m_iDownPriority != PR_NORMAL &&
448 m_iDownPriority != PR_HIGH)
449 m_iDownPriority = PR_NORMAL;
450 SetAutoDownPriority(false);
453 break;
455 case FT_STATUS: {
456 m_paused = (newtag.GetInt() == 1);
457 m_stopped = m_paused;
458 break;
460 case FT_OLDULPRIORITY:
461 case FT_ULPRIORITY: {
462 if (!isnewstyle){
463 SetUpPriority(newtag.GetInt(), false);
464 if( GetUpPriority() == PR_AUTO ){
465 SetUpPriority(PR_HIGH, false);
466 SetAutoUpPriority(true);
467 } else {
468 SetAutoUpPriority(false);
471 break;
473 case FT_KADLASTPUBLISHSRC:{
474 SetLastPublishTimeKadSrc(newtag.GetInt(), 0);
475 if(GetLastPublishTimeKadSrc() > (uint32)time(NULL)+KADEMLIAREPUBLISHTIMES) {
476 //There may be a posibility of an older client that saved a random number here.. This will check for that..
477 SetLastPublishTimeKadSrc(0,0);
479 break;
481 case FT_KADLASTPUBLISHNOTES:{
482 SetLastPublishTimeKadNotes(newtag.GetInt());
483 break;
485 // old tags: as long as they are not needed, take the chance to purge them
486 case FT_PERMISSIONS:
487 case FT_KADLASTPUBLISHKEY:
488 break;
489 case FT_DL_ACTIVE_TIME:
490 if (newtag.IsInt()) {
491 m_nDlActiveTime = newtag.GetInt();
493 break;
494 case FT_CORRUPTEDPARTS: {
495 wxASSERT(m_corrupted_list.empty());
496 wxString strCorruptedParts(newtag.GetStr());
497 wxStringTokenizer tokenizer(strCorruptedParts, wxT(","));
498 while ( tokenizer.HasMoreTokens() ) {
499 wxString token = tokenizer.GetNextToken();
500 unsigned long uPart;
501 if (token.ToULong(&uPart)) {
502 if (uPart < GetPartCount() && !IsCorruptedPart(uPart)) {
503 m_corrupted_list.push_back(uPart);
507 break;
509 case FT_AICH_HASH:{
510 CAICHHash hash;
511 bool hashSizeOk =
512 hash.DecodeBase32(newtag.GetStr()) == CAICHHash::GetHashSize();
513 wxASSERT(hashSizeOk);
514 if (hashSizeOk) {
515 m_pAICHHashSet->SetMasterHash(hash, AICH_VERIFIED);
517 break;
519 case FT_ATTRANSFERRED:{
520 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (uint64)newtag.GetInt());
521 break;
523 case FT_ATTRANSFERREDHI:{
524 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (((uint64)newtag.GetInt()) << 32));
525 break;
527 case FT_ATREQUESTED:{
528 statistic.SetAllTimeRequests(newtag.GetInt());
529 break;
531 case FT_ATACCEPTED:{
532 statistic.SetAllTimeAccepts(newtag.GetInt());
533 break;
535 default: {
536 // Start Changes by Slugfiller for better exception handling
538 wxCharBuffer tag_ansi_name = newtag.GetName().ToAscii();
539 char gap_mark = tag_ansi_name ? tag_ansi_name[0u] : 0;
540 if ( newtag.IsInt() && (newtag.GetName().Length() > 1) &&
541 ((gap_mark == FT_GAPSTART) ||
542 (gap_mark == FT_GAPEND))) {
543 Gap_Struct *gap = NULL;
544 unsigned long int gapkey;
545 if (newtag.GetName().Mid(1).ToULong(&gapkey)) {
546 if ( gap_map.find( gapkey ) == gap_map.end() ) {
547 gap = new Gap_Struct;
548 gap_map[gapkey] = gap;
549 gap->start = (uint64)-1;
550 gap->end = (uint64)-1;
551 } else {
552 gap = gap_map[ gapkey ];
554 if (gap_mark == FT_GAPSTART) {
555 gap->start = newtag.GetInt();
557 if (gap_mark == FT_GAPEND) {
558 gap->end = newtag.GetInt()-1;
560 } else {
561 AddDebugLogLineN(logPartFile, wxT("Wrong gap map key while reading met file!"));
562 wxFAIL;
564 // End Changes by Slugfiller for better exception handling
565 } else {
566 m_taglist.push_back(newtag);
570 } else {
571 // Nothing. Else, nothing.
575 // load the hashsets from the hybridstylepartmet
576 if (isnewstyle && !getsizeonly && (metFile.GetPosition()<metFile.GetLength()) ) {
577 metFile.Seek(1, wxFromCurrent);
579 uint16 parts=GetPartCount(); // assuming we will get all hashsets
581 for (uint16 i = 0; i < parts && (metFile.GetPosition()+16<metFile.GetLength()); ++i){
582 CMD4Hash cur_hash = metFile.ReadHash();
583 m_hashlist.push_back(cur_hash);
586 CMD4Hash checkhash;
587 if (!m_hashlist.empty()) {
588 CreateHashFromHashlist(m_hashlist, &checkhash);
590 if (m_abyFileHash != checkhash) {
591 m_hashlist.clear();
594 } catch (const CInvalidPacket& e) {
595 AddLogLineC(CFormat(_("Error: %s (%s) is corrupt (bad tags: %s), unable to load file."))
596 % m_partmetfilename
597 % GetFileName()
598 % e.what());
599 return false;
600 } catch (const CIOFailureException& e) {
601 AddDebugLogLineC(logPartFile, CFormat( wxT("IO failure while loading '%s': %s") )
602 % m_partmetfilename
603 % e.what() );
604 return false;
605 } catch (const CEOFException& WXUNUSED(e)) {
606 AddLogLineC(CFormat( _("ERROR: %s (%s) is corrupt (wrong tagcount), unable to load file.") )
607 % m_partmetfilename
608 % GetFileName() );
609 AddLogLineC(_("Trying to recover file info..."));
611 // Safe file is that who have
612 // - FileSize
613 if (GetFileSize()) {
614 // We have filesize, try other needed info
616 // Do we need to check gaps? I think not,
617 // because they are checked below. Worst
618 // scenario will only mark file as 0 bytes downloaded.
620 // -Filename
621 if (!GetFileName().IsOk()) {
622 // Not critical, let's put a random filename.
623 AddLogLineC(_(
624 "Recovering no-named file - will try to recover it as RecoveredFile.dat"));
625 SetFileName(CPath(wxT("RecoveredFile.dat")));
628 AddLogLineC(_("Recovered all available file info :D - Trying to use it..."));
629 } else {
630 AddLogLineC(_("Unable to recover file info :("));
631 return false;
635 if (getsizeonly) {
636 return partmettype;
638 // Init Gaplist
639 m_gaplist.Init(GetFileSize(), false); // Init full, then add gaps
640 // Now to flush the map into the list (Slugfiller)
641 std::map<uint16, Gap_Struct*>::iterator it = gap_map.begin();
642 for ( ; it != gap_map.end(); ++it ) {
643 Gap_Struct* gap = it->second;
644 // SLUGFILLER: SafeHash - revised code, and extra safety
645 if ( (gap->start != (uint64)-1) &&
646 (gap->end != (uint64)-1) &&
647 gap->start <= gap->end &&
648 gap->start < GetFileSize()) {
649 if (gap->end >= GetFileSize()) {
650 gap->end = GetFileSize()-1; // Clipping
652 m_gaplist.AddGap(gap->start, gap->end); // All tags accounted for, use safe adding
654 delete gap;
655 // SLUGFILLER: SafeHash
658 //check if this is a backup
659 if ( m_fullname.GetExt().MakeLower() == wxT("backup" )) {
660 m_fullname = m_fullname.RemoveExt();
663 // open permanent handle
664 if ( !m_hpartfile.Open(m_PartPath, CFile::read_write)) {
665 AddLogLineN(CFormat( _("Failed to open %s (%s)") )
666 % m_fullname
667 % GetFileName() );
668 return false;
671 SetStatus(PS_EMPTY);
673 try {
674 // SLUGFILLER: SafeHash - final safety, make sure any missing part of the file is gap
675 if (m_hpartfile.GetLength() < GetFileSize())
676 AddGap(m_hpartfile.GetLength(), GetFileSize()-1);
677 // Goes both ways - Partfile should never be too large
678 if (m_hpartfile.GetLength() > GetFileSize()) {
679 AddDebugLogLineC(logPartFile, CFormat( wxT("Partfile \"%s\" is too large! Truncating %llu bytes.") ) % GetFileName() % (m_hpartfile.GetLength() - GetFileSize()));
680 m_hpartfile.SetLength(GetFileSize());
682 // SLUGFILLER: SafeHash
683 } catch (const CIOFailureException& e) {
684 AddDebugLogLineC(logPartFile, CFormat( wxT("Error while accessing partfile \"%s\": %s") ) % GetFileName() % e.what());
685 SetStatus(PS_ERROR);
688 // now close the file again until needed
689 m_hpartfile.Release(true);
691 // check hashcount, file status etc
692 if (GetHashCount() != GetED2KPartHashCount()){
693 m_hashsetneeded = true;
694 return true;
695 } else {
696 m_hashsetneeded = false;
697 for (size_t i = 0; i < m_hashlist.size(); ++i) {
698 if (IsComplete(i)) {
699 SetStatus(PS_READY);
704 if (m_gaplist.IsComplete()) { // is this file complete already?
705 CompleteFile(false);
706 return true;
709 if (!isnewstyle) { // not for importing
710 const time_t file_date = CPath::GetModificationTime(m_PartPath);
711 if (m_lastDateChanged != file_date) {
712 // It's pointless to rehash an empty file, since the case
713 // where a user has zero'd a file is handled above ...
714 if (m_hpartfile.GetLength()) {
715 AddLogLineN(CFormat( _("WARNING: %s might be corrupted (%i)") )
716 % m_PartPath
717 % (m_lastDateChanged - file_date) );
718 // rehash
719 SetStatus(PS_WAITINGFORHASH);
721 CPath partFileName = m_partmetfilename.RemoveExt();
722 CThreadScheduler::AddTask(new CHashingTask(m_filePath, partFileName, this));
727 UpdateCompletedInfos();
728 if (completedsize > transferred) {
729 m_iGainDueToCompression = completedsize - transferred;
730 } else if (completedsize != transferred) {
731 m_iLostDueToCorruption = transferred - completedsize;
734 return true;
738 bool CPartFile::SavePartFile(bool Initial)
740 switch (status) {
741 case PS_WAITINGFORHASH:
742 case PS_HASHING:
743 case PS_COMPLETE:
744 return false;
747 /* Don't write anything to disk if less than 100 KB of free space is left. */
748 sint64 free = CPath::GetFreeSpaceAt(GetFilePath());
749 if ((free != wxInvalidOffset) && (free < (100 * 1024))) {
750 return false;
753 CFile file;
754 try {
755 if (!m_PartPath.FileExists()) {
756 throw wxString(wxT(".part file not found"));
759 uint32 lsc = lastseencomplete;
761 if (!Initial) {
762 CPath::BackupFile(m_fullname, wxT(".backup"));
763 CPath::RemoveFile(m_fullname);
766 file.Open(m_fullname, CFile::write);
767 if (!file.IsOpened()) {
768 throw wxString(wxT("Failed to open part.met file"));
771 // version
772 file.WriteUInt8(IsLargeFile() ? PARTFILE_VERSION_LARGEFILE : PARTFILE_VERSION);
774 file.WriteUInt32(CPath::GetModificationTime(m_PartPath));
775 // hash
776 file.WriteHash(m_abyFileHash);
777 uint16 parts = m_hashlist.size();
778 file.WriteUInt16(parts);
779 for (int x = 0; x < parts; ++x) {
780 file.WriteHash(m_hashlist[x]);
782 // tags
783 #define FIXED_TAGS 15
784 uint32 tagcount = m_taglist.size() + FIXED_TAGS + (m_gaplist.size()*2);
785 if (!m_corrupted_list.empty()) {
786 ++tagcount;
789 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
790 ++tagcount;
793 if (GetLastPublishTimeKadSrc()){
794 ++tagcount;
797 if (GetLastPublishTimeKadNotes()){
798 ++tagcount;
801 if (GetDlActiveTime()){
802 ++tagcount;
805 file.WriteUInt32(tagcount);
807 //#warning Kry - Where are lost by coruption and gained by compression?
809 // 0 (unicoded part file name)
810 // We write it with BOM to keep eMule compatibility. Note that the 'printable' filename is saved,
811 // as presently the filename does not represent an actual file.
812 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file, utf8strOptBOM );
813 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file ); // 1
815 CTagIntSized( FT_FILESIZE, GetFileSize(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file );// 2
816 CTagIntSized( FT_TRANSFERRED, transferred, IsLargeFile() ? 64 : 32).WriteTagToFile( &file ); // 3
817 CTagInt32( FT_STATUS, (m_paused?1:0)).WriteTagToFile( &file ); // 4
819 if ( IsAutoDownPriority() ) {
820 CTagInt32( FT_DLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 5
821 CTagInt32( FT_OLDDLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 6
822 } else {
823 CTagInt32( FT_DLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 5
824 CTagInt32( FT_OLDDLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 6
827 CTagInt32( FT_LASTSEENCOMPLETE, lsc ).WriteTagToFile( &file ); // 7
829 if ( IsAutoUpPriority() ) {
830 CTagInt32( FT_ULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 8
831 CTagInt32( FT_OLDULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 9
832 } else {
833 CTagInt32( FT_ULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 8
834 CTagInt32( FT_OLDULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 9
837 CTagInt32(FT_CATEGORY, m_category).WriteTagToFile( &file ); // 10
838 CTagInt32(FT_ATTRANSFERRED, statistic.GetAllTimeTransferred() & 0xFFFFFFFF).WriteTagToFile( &file );// 11
839 CTagInt32(FT_ATTRANSFERREDHI, statistic.GetAllTimeTransferred() >>32).WriteTagToFile( &file );// 12
840 CTagInt32(FT_ATREQUESTED, statistic.GetAllTimeRequests()).WriteTagToFile( &file ); // 13
841 CTagInt32(FT_ATACCEPTED, statistic.GetAllTimeAccepts()).WriteTagToFile( &file ); // 14
843 // currupt part infos
844 if (!m_corrupted_list.empty()) {
845 wxString strCorruptedParts;
846 std::list<uint16>::iterator it = m_corrupted_list.begin();
847 for (; it != m_corrupted_list.end(); ++it) {
848 uint16 uCorruptedPart = *it;
849 if (!strCorruptedParts.IsEmpty()) {
850 strCorruptedParts += wxT(",");
852 strCorruptedParts += CFormat(wxT("%u")) % uCorruptedPart;
854 wxASSERT( !strCorruptedParts.IsEmpty() );
856 CTagString( FT_CORRUPTEDPARTS, strCorruptedParts ).WriteTagToFile( &file); // 11?
859 //AICH Filehash
860 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
861 CTagString aichtag(FT_AICH_HASH, m_pAICHHashSet->GetMasterHash().GetString() );
862 aichtag.WriteTagToFile(&file); // 12?
865 if (GetLastPublishTimeKadSrc()){
866 CTagInt32(FT_KADLASTPUBLISHSRC, GetLastPublishTimeKadSrc()).WriteTagToFile(&file); // 15?
869 if (GetLastPublishTimeKadNotes()){
870 CTagInt32(FT_KADLASTPUBLISHNOTES, GetLastPublishTimeKadNotes()).WriteTagToFile(&file); // 16?
873 if (GetDlActiveTime()){
874 CTagInt32(FT_DL_ACTIVE_TIME, GetDlActiveTime()).WriteTagToFile(&file); // 17
877 for (uint32 j = 0; j < (uint32)m_taglist.size();++j) {
878 m_taglist[j].WriteTagToFile(&file);
881 // gaps
882 unsigned i_pos = 0;
883 for (CGapList::const_iterator it = m_gaplist.begin(); it != m_gaplist.end(); ++it) {
884 wxString tagName = CFormat(wxT(" %u")) % i_pos;
886 // gap start = first missing byte but gap ends = first non-missing byte
887 // in edonkey but I think its easier to user the real limits
888 tagName[0] = FT_GAPSTART;
889 CTagIntSized(tagName, it.start(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
891 tagName[0] = FT_GAPEND;
892 CTagIntSized(tagName, it.end() + 1, IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
894 ++i_pos;
896 } catch (const wxString& error) {
897 AddLogLineNS(CFormat( _("ERROR while saving partfile: %s (%s ==> %s)") )
898 % error
899 % m_partmetfilename
900 % GetFileName() );
902 return false;
903 } catch (const CIOFailureException& e) {
904 AddLogLineCS(_("IO failure while saving partfile: ") + e.what());
906 return false;
909 file.Close();
911 if (!Initial) {
912 CPath::RemoveFile(m_fullname.AppendExt(wxT(".backup")));
915 sint64 metLength = m_fullname.GetFileSize();
916 if (metLength == wxInvalidOffset) {
917 theApp->ShowAlert( CFormat( _("Could not retrieve length of '%s' - using %s file.") )
918 % m_fullname
919 % PARTMET_BAK_EXT,
920 _("Message"), wxOK);
922 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
923 } else if (metLength == 0) {
924 // Don't backup if it's 0 size but raise a warning!!!
925 theApp->ShowAlert( CFormat( _("'%s' is 0 size somehow - using %s file.") )
926 % m_fullname
927 % PARTMET_BAK_EXT,
928 _("Message"), wxOK);
930 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
931 } else {
932 // no error, just backup
933 CPath::BackupFile(m_fullname, PARTMET_BAK_EXT);
936 return true;
940 void CPartFile::SaveSourceSeeds()
942 #define MAX_SAVED_SOURCES 10
944 // Kry - Sources seeds
945 // Based on a Feature request, this saves the last MAX_SAVED_SOURCES
946 // sources of the file, giving a 'seed' for the next run.
947 // We save the last sources because:
948 // 1 - They could be the hardest to get
949 // 2 - They will more probably be available
950 // However, if we have downloading sources, they have preference because
951 // we probably have more credits on them.
952 // Anyway, source exchange will get us the rest of the sources
953 // This feature is currently used only on rare files (< 20 sources)
956 if (GetSourceCount()>20) {
957 return;
960 CClientRefList source_seeds;
961 int n_sources = 0;
963 CClientRefList::iterator it = m_downloadingSourcesList.begin();
964 for( ; it != m_downloadingSourcesList.end() && n_sources < MAX_SAVED_SOURCES; ++it) {
965 if (!it->HasLowID()) {
966 source_seeds.push_back(*it);
967 ++n_sources;
971 if (n_sources < MAX_SAVED_SOURCES) {
972 // Not enough downloading sources to fill the list, going to sources list
973 if (GetSourceCount() > 0) {
974 SourceSet::reverse_iterator rit = m_SrcList.rbegin();
975 for ( ; ((rit != m_SrcList.rend()) && (n_sources<MAX_SAVED_SOURCES)); ++rit) {
976 if (!rit->HasLowID()) {
977 source_seeds.push_back(*rit);
978 ++n_sources;
984 // Write the file
985 if (!n_sources) {
986 return;
989 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
991 CFile file;
992 file.Create(seedsPath, true);
993 if (!file.IsOpened()) {
994 AddLogLineN(CFormat( _("Failed to save part.met.seeds file for %s") )
995 % m_fullname);
996 return;
999 try {
1000 file.WriteUInt8(0); // v3, to avoid v2 clients choking on it.
1001 file.WriteUInt8(source_seeds.size());
1003 CClientRefList::iterator it2 = source_seeds.begin();
1004 for (; it2 != source_seeds.end(); ++it2) {
1005 CUpDownClient* cur_src = it2->GetClient();
1006 file.WriteUInt32(cur_src->GetUserIDHybrid());
1007 file.WriteUInt16(cur_src->GetUserPort());
1008 file.WriteHash(cur_src->GetUserHash());
1009 // CryptSettings - See SourceExchange V4
1010 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
1011 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
1012 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
1013 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
1014 file.WriteUInt8(byCryptOptions);
1017 /* v2: Added to keep track of too old seeds */
1018 file.WriteUInt32(wxDateTime::Now().GetTicks());
1020 AddLogLineN(CFormat( wxPLURAL("Saved %i source seed for partfile: %s (%s)", "Saved %i source seeds for partfile: %s (%s)", n_sources) )
1021 % n_sources
1022 % m_fullname
1023 % GetFileName());
1024 } catch (const CIOFailureException& e) {
1025 AddDebugLogLineC( logPartFile, CFormat( wxT("Error saving partfile's seeds file (%s - %s): %s") )
1026 % m_partmetfilename
1027 % GetFileName()
1028 % e.what() );
1030 n_sources = 0;
1031 file.Close();
1032 CPath::RemoveFile(seedsPath);
1036 void CPartFile::LoadSourceSeeds()
1038 CMemFile sources_data;
1040 bool valid_sources = false;
1042 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
1043 if (!seedsPath.FileExists()) {
1044 return;
1047 CFile file(seedsPath, CFile::read);
1048 if (!file.IsOpened()) {
1049 // Exists but can't be opened. Should not happen. Probably permission problem, try to remove it.
1050 AddLogLineN(CFormat( _("Can't read seeds file for Partfile %s (%s)") )
1051 % m_partmetfilename
1052 % GetFileName() );
1053 CPath::RemoveFile(seedsPath);
1054 return;
1057 bool badSeedsFile = false;
1058 try {
1059 uint8 src_count = file.ReadUInt8();
1061 bool bUseSX2Format = (src_count == 0);
1063 if (bUseSX2Format) {
1064 // v3 sources seeds
1065 src_count = file.ReadUInt8();
1068 sources_data.WriteUInt16(src_count);
1070 for (int i = 0; i< src_count; ++i) {
1071 uint32 dwID = file.ReadUInt32();
1072 uint16 nPort = file.ReadUInt16();
1074 sources_data.WriteUInt32(bUseSX2Format ? dwID : wxUINT32_SWAP_ALWAYS(dwID));
1075 sources_data.WriteUInt16(nPort);
1076 sources_data.WriteUInt32(0);
1077 sources_data.WriteUInt16(0);
1079 if (bUseSX2Format) {
1080 sources_data.WriteHash(file.ReadHash());
1081 sources_data.WriteUInt8(file.ReadUInt8());
1086 if (!file.Eof()) {
1088 // v2: Added to keep track of too old seeds
1089 time_t time = (time_t)file.ReadUInt32();
1091 // Time frame is 2 hours. More than enough to compile
1092 // your new aMule version!.
1093 if ((time + MIN2S(120)) >= wxDateTime::Now().GetTicks()) {
1094 valid_sources = true;
1097 } else {
1098 // v1 has no time data. We can safely use
1099 // the sources, next time will be saved.
1100 valid_sources = true;
1103 if (valid_sources) {
1104 sources_data.Seek(0);
1105 AddClientSources(&sources_data, SF_SOURCE_SEEDS, bUseSX2Format ? 4 : 1, bUseSX2Format);
1108 } catch (const CSafeIOException& e) {
1109 AddLogLineN(CFormat( _("Error reading partfile's seeds file (%s - %s): %s") )
1110 % m_partmetfilename
1111 % GetFileName()
1112 % e.what() );
1113 badSeedsFile = true;
1116 file.Close();
1117 if (badSeedsFile) {
1118 // If we got an exception reading it remove it.
1119 CPath::RemoveFile(seedsPath);
1123 void CPartFile::PartFileHashFinished(CKnownFile* result)
1125 m_lastDateChanged = result->m_lastDateChanged;
1126 bool errorfound = false;
1127 if (GetED2KPartHashCount() == 0){
1128 if (IsComplete(0, GetFileSize()-1)){
1129 if (result->GetFileHash() != GetFileHash()){
1130 // cppcheck-suppress zerodiv
1131 AddLogLineN(CFormat(wxPLURAL(
1132 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1133 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1138 % GetFileName()
1139 % result->GetFileHash().Encode()
1140 % GetFileHash().Encode() );
1141 AddGap(0, GetFileSize()-1);
1142 errorfound = true;
1146 else{
1147 for (size_t i = 0; i < m_hashlist.size(); ++i){
1148 // Kry - trel_ar's completed parts check on rehashing.
1149 // Very nice feature, if a file is completed but .part.met don't believe it,
1150 // update it.
1152 if (!( i < result->GetHashCount() && (result->GetPartHash(i) == GetPartHash(i)))){
1153 if (IsComplete(i)) {
1154 CMD4Hash wronghash;
1155 if ( i < result->GetHashCount() )
1156 wronghash = result->GetPartHash(i);
1158 AddLogLineN(CFormat(wxPLURAL(
1159 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1160 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1161 GetED2KPartHashCount())
1163 % ( i + 1 )
1164 % GetED2KPartHashCount()
1165 % GetFileName()
1166 % wronghash.Encode()
1167 % GetPartHash(i).Encode() );
1169 AddGap(i);
1170 errorfound = true;
1172 } else {
1173 if (!IsComplete(i)){
1174 AddLogLineN(CFormat( _("Found completed part (%i) in %s") )
1175 % ( i + 1 )
1176 % GetFileName() );
1178 FillGap(i);
1179 uint64 partStart = i * PARTSIZE;
1180 uint64 partEnd = partStart + GetPartSize(i) - 1;
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 AddDebugLogLineN(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 AddLogLineN(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 FlushBuffer();
1376 // check if we want new sources from server --> MOVED for 16.40 version
1377 old_trans=transferingsrc;
1378 transferingsrc = 0;
1379 kBpsDown = 0.0;
1381 if (m_icounter < 10) {
1382 // Update only downloading sources.
1383 CClientRefList::iterator it = m_downloadingSourcesList.begin();
1384 for( ; it != m_downloadingSourcesList.end(); ) {
1385 CUpDownClient *cur_src = it++->GetClient();
1386 if(cur_src->GetDownloadState() == DS_DOWNLOADING) {
1387 ++transferingsrc;
1388 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1391 } else {
1392 // Update all sources (including downloading sources)
1393 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
1394 CUpDownClient* cur_src = it++->GetClient();
1395 switch (cur_src->GetDownloadState()) {
1396 case DS_DOWNLOADING: {
1397 ++transferingsrc;
1398 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1399 break;
1401 case DS_BANNED: {
1402 break;
1404 case DS_ERROR: {
1405 break;
1407 case DS_LOWTOLOWIP: {
1408 if (cur_src->HasLowID() && !theApp->CanDoCallback(cur_src->GetServerIP(), cur_src->GetServerPort())) {
1409 // If we are almost maxed on sources,
1410 // slowly remove these client to see
1411 // if we can find a better source.
1412 if (((dwCurTick - lastpurgetime) > 30000) &&
1413 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8))) {
1414 RemoveSource(cur_src);
1415 lastpurgetime = dwCurTick;
1416 break;
1418 } else {
1419 cur_src->SetDownloadState(DS_ONQUEUE);
1422 break;
1424 case DS_NONEEDEDPARTS: {
1425 // we try to purge noneeded source, even without reaching the limit
1426 if((dwCurTick - lastpurgetime) > 40000) {
1427 if(!cur_src->SwapToAnotherFile(false , false, false , NULL)) {
1428 //however we only delete them if reaching the limit
1429 if (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) {
1430 RemoveSource(cur_src);
1431 lastpurgetime = dwCurTick;
1432 break; //Johnny-B - nothing more to do here (good eye!)
1434 } else {
1435 lastpurgetime = dwCurTick;
1436 break;
1439 // doubled reasktime for no needed parts - save connections and traffic
1440 if ( !((!cur_src->GetLastAskedTime()) ||
1441 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME*2)) {
1442 break;
1444 // Recheck this client to see if still NNP..
1445 // Set to DS_NONE so that we force a TCP reask next time..
1446 cur_src->SetDownloadState(DS_NONE);
1448 break;
1450 case DS_ONQUEUE: {
1451 if( cur_src->IsRemoteQueueFull()) {
1452 if( ((dwCurTick - lastpurgetime) > 60000) &&
1453 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) ) {
1454 RemoveSource( cur_src );
1455 lastpurgetime = dwCurTick;
1456 break; //Johnny-B - nothing more to do here (good eye!)
1460 // Give up to 1 min for UDP to respond..
1461 // If we are within on min on TCP, do not try..
1462 if ( theApp->IsConnected() &&
1463 ( (!cur_src->GetLastAskedTime()) ||
1464 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME-20000)) {
1465 cur_src->UDPReaskForDownload();
1468 // No break here, since the next case takes care of asking for downloads.
1470 case DS_CONNECTING:
1471 case DS_TOOMANYCONNS:
1472 case DS_NONE:
1473 case DS_WAITCALLBACK:
1474 case DS_WAITCALLBACKKAD: {
1475 if ( theApp->IsConnected() &&
1476 ( (!cur_src->GetLastAskedTime()) ||
1477 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME)) {
1478 if (!cur_src->AskForDownload()) {
1479 // I left this break here just as a reminder
1480 // just in case re rearange things..
1481 break;
1484 break;
1489 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
1490 if (IsA4AFAuto() && ((!m_LastNoNeededCheck) || (dwCurTick - m_LastNoNeededCheck > 900000))) {
1491 m_LastNoNeededCheck = dwCurTick;
1492 for ( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
1493 CUpDownClient *cur_source = it++->GetClient();
1494 uint8 download_state=cur_source->GetDownloadState();
1495 if( download_state != DS_DOWNLOADING
1496 && cur_source->GetRequestFile()
1497 && ((!cur_source->GetRequestFile()->IsA4AFAuto()) || download_state == DS_NONEEDEDPARTS))
1499 cur_source->SwapToAnotherFile(false, false, false, this);
1503 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
1505 // swap No needed partfiles if possible
1507 if (((old_trans==0) && (transferingsrc>0)) || ((old_trans>0) && (transferingsrc==0))) {
1508 SetStatus(status);
1511 // Kad source search
1512 if( GetMaxSourcePerFileUDP() > GetSourceCount()){
1513 //Once we can handle lowID users in Kad, we remove the second IsConnected
1514 if (theApp->downloadqueue->DoKademliaFileRequest() && (Kademlia::CKademlia::GetTotalFile() < KADEMLIATOTALFILE) && (dwCurTick > m_LastSearchTimeKad) && Kademlia::CKademlia::IsConnected() && theApp->IsConnected() && !IsStopped()){
1515 //Kademlia
1516 theApp->downloadqueue->SetLastKademliaFileRequest();
1518 if (GetKadFileSearchID()) {
1519 /* This will never happen anyway. We're talking a
1520 1h timespan and searches are at max 45secs */
1521 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1524 Kademlia::CUInt128 kadFileID(GetFileHash().GetHash());
1525 Kademlia::CSearch* pSearch = Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FILE, true, kadFileID);
1526 AddDebugLogLineN(logKadSearch, CFormat(wxT("Preparing a Kad Search for '%s'")) % GetFileName());
1527 if (pSearch) {
1528 AddDebugLogLineN(logKadSearch, CFormat(wxT("Kad lookup started for '%s'")) % GetFileName());
1529 if(m_TotalSearchesKad < 7) {
1530 m_TotalSearchesKad++;
1532 m_LastSearchTimeKad = dwCurTick + (KADEMLIAREASKTIME*m_TotalSearchesKad);
1533 SetKadFileSearchID(pSearch->GetSearchID());
1536 } else {
1537 if(GetKadFileSearchID()) {
1538 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
1542 // check if we want new sources from server
1543 if ( !m_localSrcReqQueued &&
1544 ( (!m_lastsearchtime) ||
1545 (dwCurTick - m_lastsearchtime) > SERVERREASKTIME) &&
1546 theApp->IsConnectedED2K() &&
1547 thePrefs::GetMaxSourcePerFileSoft() > GetSourceCount() &&
1548 !m_stopped ) {
1549 m_localSrcReqQueued = true;
1550 theApp->downloadqueue->SendLocalSrcRequest(this);
1553 // calculate datarate, set limit etc.
1556 ++m_count;
1558 // Kry - does the 3 / 30 difference produce too much flickering or CPU?
1559 if (m_count >= 30) {
1560 m_count = 0;
1561 UpdateAutoDownPriority();
1562 UpdateDisplayedInfo();
1563 if(m_bPercentUpdated == false) {
1564 UpdateCompletedInfos();
1566 m_bPercentUpdated = false;
1569 // release file handle if unused for some time
1570 m_hpartfile.Release();
1572 return (uint32)(kBpsDown*1024.0);
1575 bool CPartFile::CanAddSource(uint32 userid, uint16 port, uint32 serverip, uint16 serverport, uint8* pdebug_lowiddropped, bool ed2kID)
1578 //The incoming ID could have the userid in the Hybrid format..
1579 uint32 hybridID = 0;
1580 if( ed2kID ) {
1581 if (IsLowID(userid)) {
1582 hybridID = userid;
1583 } else {
1584 hybridID = wxUINT32_SWAP_ALWAYS(userid);
1586 } else {
1587 hybridID = userid;
1588 if (!IsLowID(userid)) {
1589 userid = wxUINT32_SWAP_ALWAYS(userid);
1593 // MOD Note: Do not change this part - Merkur
1594 if (theApp->IsConnectedED2K()) {
1595 if(::IsLowID(theApp->GetED2KID())) {
1596 if(theApp->GetED2KID() == userid && theApp->serverconnect->GetCurrentServer()->GetIP() == serverip && theApp->serverconnect->GetCurrentServer()->GetPort() == serverport ) {
1597 return false;
1599 if(theApp->GetPublicIP() == userid) {
1600 return false;
1602 } else {
1603 if(theApp->GetED2KID() == userid && thePrefs::GetPort() == port) {
1604 return false;
1609 if (Kademlia::CKademlia::IsConnected()) {
1610 if(!Kademlia::CKademlia::IsFirewalled()) {
1611 if(Kademlia::CKademlia::GetIPAddress() == hybridID && thePrefs::GetPort() == port) {
1612 return false;
1617 //This allows *.*.*.0 clients to not be removed if Ed2kID == false
1618 if ( IsLowID(hybridID) && theApp->IsFirewalled()) {
1619 if (pdebug_lowiddropped) {
1620 (*pdebug_lowiddropped)++;
1622 return false;
1624 // MOD Note - end
1625 return true;
1628 void CPartFile::AddSources(CMemFile& sources,uint32 serverip, uint16 serverport, unsigned origin, bool bWithObfuscationAndHash)
1630 uint8 count = sources.ReadUInt8();
1631 uint8 debug_lowiddropped = 0;
1632 uint8 debug_possiblesources = 0;
1633 CMD4Hash achUserHash;
1635 if (m_stopped) {
1636 // since we may received multiple search source UDP results we have to "consume" all data of that packet
1637 AddDebugLogLineN(logPartFile, wxT("Trying to add sources for a stopped file"));
1638 sources.Seek(count*(4+2), wxFromCurrent);
1639 return;
1642 for (int i = 0;i != count;++i) {
1643 uint32 userid = sources.ReadUInt32();
1644 uint16 port = sources.ReadUInt16();
1646 uint8 byCryptOptions = 0;
1647 if (bWithObfuscationAndHash){
1648 byCryptOptions = sources.ReadUInt8();
1649 if ((byCryptOptions & 0x80) > 0) {
1650 achUserHash = sources.ReadHash();
1653 if ((thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x01/*supported*/) > 0 && (byCryptOptions & 0x80) == 0)
1654 || (thePrefs::IsClientCryptLayerSupported() && (byCryptOptions & 0x02/*requested*/) > 0 && (byCryptOptions & 0x80) == 0)) {
1655 AddDebugLogLineN(logPartFile, CFormat(wxT("Server didn't provide UserHash for source %u, even if it was expected to (or local obfuscationsettings changed during serverconnect")) % userid);
1656 } else if (!thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x02/*requested*/) == 0 && (byCryptOptions & 0x80) != 0) {
1657 AddDebugLogLineN(logPartFile, CFormat(wxT("Server provided UserHash for source %u, even if it wasn't expected to (or local obfuscationsettings changed during serverconnect")) % userid);
1662 // "Filter LAN IPs" and "IPfilter" the received sources IP addresses
1663 if (!IsLowID(userid)) {
1664 // check for 0-IP, localhost and optionally for LAN addresses
1665 if ( !IsGoodIP(userid, thePrefs::FilterLanIPs()) ) {
1666 continue;
1668 if (theApp->ipfilter->IsFiltered(userid)) {
1669 continue;
1673 if (!CanAddSource(userid, port, serverip, serverport, &debug_lowiddropped)) {
1674 continue;
1677 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
1678 ++debug_possiblesources;
1679 CUpDownClient* newsource = new CUpDownClient(port,userid,serverip,serverport,this, true, true);
1681 newsource->SetSourceFrom((ESourceFrom)origin);
1682 newsource->SetConnectOptions(byCryptOptions, true, false);
1684 if ((byCryptOptions & 0x80) != 0) {
1685 newsource->SetUserHash(achUserHash);
1688 theApp->downloadqueue->CheckAndAddSource(this,newsource);
1689 } else {
1690 AddDebugLogLineN(logPartFile, wxT("Consuming a packet because of max sources reached"));
1691 // Since we may receive multiple search source UDP results we have to "consume" all data of that packet
1692 // This '+1' is added because 'i' counts from 0.
1693 sources.Seek((count-(i+1))*(4+2), wxFromCurrent);
1694 if (GetKadFileSearchID()) {
1695 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1697 break;
1702 void CPartFile::UpdatePartsInfo()
1704 if( !IsPartFile() ) {
1705 CKnownFile::UpdatePartsInfo();
1706 return;
1709 // Cache part count
1710 uint16 partcount = GetPartCount();
1711 bool flag = (time(NULL) - m_nCompleteSourcesTime > 0);
1713 // Ensure the frequency-list is ready
1714 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
1715 m_SrcpartFrequency.clear();
1716 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
1719 // Find number of available parts
1720 uint16 availablecounter = 0;
1721 for ( uint16 i = 0; i < partcount; ++i ) {
1722 if ( m_SrcpartFrequency[i] )
1723 ++availablecounter;
1726 if ( ( availablecounter == partcount ) && ( m_availablePartsCount < partcount ) ) {
1727 lastseencomplete = time(NULL);
1730 m_availablePartsCount = availablecounter;
1732 if ( flag ) {
1733 ArrayOfUInts16 count;
1735 count.reserve(GetSourceCount());
1737 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
1738 CUpDownClient* client = it->GetClient();
1739 if ( !client->GetUpPartStatus().empty() && client->GetUpPartCount() == partcount ) {
1740 count.push_back(client->GetUpCompleteSourcesCount());
1744 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo = m_nCompleteSourcesCountHi = 0;
1746 for (uint16 i = 0; i < partcount; ++i) {
1747 if( !i ) {
1748 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1750 else if( m_nCompleteSourcesCount > m_SrcpartFrequency[i]) {
1751 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1754 count.push_back(m_nCompleteSourcesCount);
1756 int32 n = count.size();
1757 if (n > 0) {
1758 std::sort(count.begin(), count.end(), std::less<uint16>());
1760 // calculate range
1761 int32 i= n >> 1; // (n / 2)
1762 int32 j= (n * 3) >> 2; // (n * 3) / 4
1763 int32 k= (n * 7) >> 3; // (n * 7) / 8
1765 //When still a part file, adjust your guesses by 20% to what you see..
1768 if (n < 5) {
1769 //Not many sources, so just use what you see..
1770 // welcome to 'plain stupid code'
1771 // m_nCompleteSourcesCount;
1772 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1773 m_nCompleteSourcesCountHi= m_nCompleteSourcesCount;
1774 } else if (n < 20) {
1775 // For low guess and normal guess count
1776 // If we see more sources then the guessed low and normal, use what we see.
1777 // If we see less sources then the guessed low, adjust network accounts for 80%,
1778 // we account for 20% with what we see and make sure we are still above the normal.
1779 // For high guess
1780 // Adjust 80% network and 20% what we see.
1781 if ( count[i] < m_nCompleteSourcesCount ) {
1782 m_nCompleteSourcesCountLo = m_nCompleteSourcesCount;
1783 } else {
1784 m_nCompleteSourcesCountLo =
1785 (uint16)((float)(count[i]*.8) +
1786 (float)(m_nCompleteSourcesCount*.2));
1788 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1789 m_nCompleteSourcesCountHi =
1790 (uint16)((float)(count[j]*.8) +
1791 (float)(m_nCompleteSourcesCount*.2));
1792 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1793 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1795 } else {
1796 // Many sources
1797 // ------------
1798 // For low guess
1799 // Use what we see.
1800 // For normal guess
1801 // Adjust network accounts for 80%, we account for 20% with what
1802 // we see and make sure we are still above the low.
1803 // For high guess
1804 // Adjust network accounts for 80%, we account for 20% with what
1805 // we see and make sure we are still above the normal.
1807 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1808 m_nCompleteSourcesCount= (uint16)((float)(count[j]*.8)+(float)(m_nCompleteSourcesCount*.2));
1809 if( m_nCompleteSourcesCount < m_nCompleteSourcesCountLo ) {
1810 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1812 m_nCompleteSourcesCountHi= (uint16)((float)(count[k]*.8)+(float)(m_nCompleteSourcesCount*.2));
1813 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1814 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1818 m_nCompleteSourcesTime = time(NULL) + (60);
1820 UpdateDisplayedInfo();
1823 // [Maella -Enhanced Chunk Selection- (based on jicxicmic)]
1824 bool CPartFile::GetNextRequestedBlock(CUpDownClient* sender,
1825 std::vector<Requested_Block_Struct*>& toadd, uint16& count)
1828 // The purpose of this function is to return a list of blocks (~180KB) to
1829 // download. To avoid a prematurely stop of the downloading, all blocks that
1830 // are requested from the same source must be located within the same
1831 // chunk (=> part ~9MB).
1833 // The selection of the chunk to download is one of the CRITICAL parts of the
1834 // edonkey network. The selection algorithm must insure the best spreading
1835 // of files.
1837 // The selection is based on 4 criteria:
1838 // 1. Frequency of the chunk (availability), very rare chunks must be downloaded
1839 // as quickly as possible to become a new available source.
1840 // 2. Parts used for preview (first + last chunk), preview or check a
1841 // file (e.g. movie, mp3)
1842 // 3. Request state (downloading in process), try to ask each source for another
1843 // chunk. Spread the requests between all sources.
1844 // 4. Completion (shortest-to-complete), partially retrieved chunks should be
1845 // completed before starting to download other one.
1847 // The frequency criterion defines three zones: very rare (<10%), rare (<50%)
1848 // and common (>30%). Inside each zone, the criteria have a specific weight, used
1849 // to calculate the priority of chunks. The chunk(s) with the highest
1850 // priority (highest=0, lowest=0xffff) is/are selected first.
1852 // very rare (preview) rare common
1853 // 0% <---- +0 pt ----> 10% <----- +10000 pt -----> 50% <---- +20000 pt ----> 100%
1854 // 1. <------- frequency: +25*frequency pt ----------->
1855 // 2. <- preview: +1 pt --><-------------- preview: set to 10000 pt ------------->
1856 // 3. <------ request: download in progress +20000 pt ------>
1857 // 4a. <- completion: 0% +100, 25% +75 .. 100% +0 pt --><-- !req => completion --->
1858 // 4b. <--- req => !completion -->
1860 // Unrolled, the priority scale is:
1862 // 0..xxxx unrequested and requested very rare chunks
1863 // 10000..1xxxx unrequested rare chunks + unrequested preview chunks
1864 // 20000..2xxxx unrequested common chunks (priority to the most complete)
1865 // 30000..3xxxx requested rare chunks + requested preview chunks
1866 // 40000..4xxxx requested common chunks (priority to the least complete)
1868 // This algorithm usually selects first the rarest chunk(s). However, partially
1869 // complete chunk(s) that is/are close to completion may overtake the priority
1870 // (priority inversion).
1871 // For the common chuncks, the algorithm tries to spread the dowload between
1872 // the sources
1875 // Check input parameters
1876 if ( sender->GetPartStatus().empty() ) {
1877 return false;
1879 // Define and create the list of the chunks to download
1880 const uint16 partCount = GetPartCount();
1881 ChunkList chunksList;
1883 // Main loop
1884 uint16 newBlockCount = 0;
1885 while(newBlockCount != count) {
1886 // Create a request block stucture if a chunk has been previously selected
1887 if(sender->GetLastPartAsked() != 0xffff) {
1888 Requested_Block_Struct* pBlock = new Requested_Block_Struct;
1889 if(GetNextEmptyBlockInPart(sender->GetLastPartAsked(), pBlock) == true) {
1890 // Keep a track of all pending requested blocks
1891 m_requestedblocks_list.push_back(pBlock);
1892 // Update list of blocks to return
1893 toadd.push_back(pBlock);
1894 newBlockCount++;
1895 // Skip end of loop (=> CPU load)
1896 continue;
1897 } else {
1898 // All blocks for this chunk have been already requested
1899 delete pBlock;
1900 // => Try to select another chunk
1901 sender->SetLastPartAsked(0xffff);
1905 // Check if a new chunk must be selected (e.g. download starting, previous chunk complete)
1906 if(sender->GetLastPartAsked() == 0xffff) {
1907 // Quantify all chunks (create list of chunks to download)
1908 // This is done only one time and only if it is necessary (=> CPU load)
1909 if(chunksList.empty()) {
1910 // Indentify the locally missing part(s) that this source has
1911 for(uint16 i=0; i < partCount; ++i) {
1912 if(sender->IsPartAvailable(i) == true && GetNextEmptyBlockInPart(i, NULL) == true) {
1913 // Create a new entry for this chunk and add it to the list
1914 Chunk newEntry;
1915 newEntry.part = i;
1916 newEntry.frequency = m_SrcpartFrequency[i];
1917 chunksList.push_back(newEntry);
1921 // Check if any bloks(s) could be downloaded
1922 if(chunksList.empty()) {
1923 break; // Exit main loop while()
1926 // Define the bounds of the three zones (very rare, rare)
1927 // more depending on available sources
1928 uint8 modif=10;
1929 if (GetSourceCount()>800) {
1930 modif=2;
1931 } else if (GetSourceCount()>200) {
1932 modif=5;
1934 uint16 limit= modif*GetSourceCount()/ 100;
1935 if (limit==0) {
1936 limit=1;
1938 const uint16 veryRareBound = limit;
1939 const uint16 rareBound = 2*limit;
1941 // Cache Preview state (Criterion 2)
1942 FileType type = GetFiletype(GetFileName());
1943 const bool isPreviewEnable =
1944 thePrefs::GetPreviewPrio() &&
1945 (type == ftArchive || type == ftVideo);
1947 // Collect and calculate criteria for all chunks
1948 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
1949 Chunk& cur_chunk = *it;
1951 // Offsets of chunk
1952 const uint64 uStart = cur_chunk.part * PARTSIZE;
1953 const uint64 uEnd = uStart + GetPartSize(cur_chunk.part) - 1;
1954 // Criterion 2. Parts used for preview
1955 // Remark: - We need to download the first part and the last part(s).
1956 // - When the last part is very small, it's necessary to
1957 // download the two last parts.
1958 bool critPreview = false;
1959 if(isPreviewEnable == true) {
1960 if(cur_chunk.part == 0) {
1961 critPreview = true; // First chunk
1962 } else if(cur_chunk.part == partCount-1) {
1963 critPreview = true; // Last chunk
1964 } else if(cur_chunk.part == partCount-2) {
1965 // Last chunk - 1 (only if last chunk is too small)
1966 const uint32 sizeOfLastChunk = GetFileSize() - uEnd;
1967 if(sizeOfLastChunk < PARTSIZE/3) {
1968 critPreview = true; // Last chunk - 1
1973 // Criterion 3. Request state (downloading in process from other source(s))
1974 // => CPU load
1975 const bool critRequested =
1976 cur_chunk.frequency > veryRareBound &&
1977 IsAlreadyRequested(uStart, uEnd);
1979 // Criterion 4. Completion
1980 // PARTSIZE instead of GetPartSize() favours the last chunk - but that may be intentional
1981 uint32 partSize = PARTSIZE - m_gaplist.GetGapSize(cur_chunk.part);
1982 const uint16 critCompletion = (uint16)(partSize/(PARTSIZE/100)); // in [%]
1984 // Calculate priority with all criteria
1985 if(cur_chunk.frequency <= veryRareBound) {
1986 // 0..xxxx unrequested + requested very rare chunks
1987 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
1988 ((critPreview == true) ? 0 : 1) + // Criterion 2
1989 (100 - critCompletion); // Criterion 4
1990 } else if(critPreview == true) {
1991 // 10000..10100 unrequested preview chunks
1992 // 30000..30100 requested preview chunks
1993 cur_chunk.rank = ((critRequested == false) ? 10000 : 30000) + // Criterion 3
1994 (100 - critCompletion); // Criterion 4
1995 } else if(cur_chunk.frequency <= rareBound) {
1996 // 10101..1xxxx unrequested rare chunks
1997 // 30101..3xxxx requested rare chunks
1998 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
1999 ((critRequested == false) ? 10101 : 30101) + // Criterion 3
2000 (100 - critCompletion); // Criterion 4
2001 } else {
2002 // common chunk
2003 if(critRequested == false) { // Criterion 3
2004 // 20000..2xxxx unrequested common chunks
2005 cur_chunk.rank = 20000 + // Criterion 3
2006 (100 - critCompletion); // Criterion 4
2007 } else {
2008 // 40000..4xxxx requested common chunks
2009 // Remark: The weight of the completion criterion is inversed
2010 // to spead the requests over the completing chunks.
2011 // Without this, the chunk closest to completion will
2012 // received every new sources.
2013 cur_chunk.rank = 40000 + // Criterion 3
2014 (critCompletion); // Criterion 4
2020 // Select the next chunk to download
2021 if(!chunksList.empty()) {
2022 // Find and count the chunck(s) with the highest priority
2023 uint16 chunkCount = 0; // Number of found chunks with same priority
2024 uint16 rank = 0xffff; // Highest priority found
2026 // Collect and calculate criteria for all chunks
2027 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2028 const Chunk& cur_chunk = *it;
2029 if(cur_chunk.rank < rank) {
2030 chunkCount = 1;
2031 rank = cur_chunk.rank;
2032 } else if(cur_chunk.rank == rank) {
2033 ++chunkCount;
2037 // Use a random access to avoid that everybody tries to download the
2038 // same chunks at the same time (=> spread the selected chunk among clients)
2039 uint16 randomness = 1 + (int) (((float)(chunkCount-1))*rand()/(RAND_MAX+1.0));
2041 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2042 const Chunk& cur_chunk = *it;
2043 if(cur_chunk.rank == rank) {
2044 randomness--;
2045 if(randomness == 0) {
2046 // Selection process is over
2047 sender->SetLastPartAsked(cur_chunk.part);
2048 // Remark: this list might be reused up to *count times
2049 chunksList.erase(it);
2050 break; // exit loop for()
2054 } else {
2055 // There is no remaining chunk to download
2056 break; // Exit main loop while()
2060 // Return the number of the blocks
2061 count = newBlockCount;
2062 // Return
2063 return (newBlockCount > 0);
2065 // Maella end
2068 void CPartFile::RemoveBlockFromList(uint64 start,uint64 end)
2070 std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
2071 while (it != m_requestedblocks_list.end()) {
2072 std::list<Requested_Block_Struct*>::iterator it2 = it++;
2074 if ((*it2)->StartOffset <= start && (*it2)->EndOffset >= end) {
2075 m_requestedblocks_list.erase(it2);
2081 void CPartFile::RemoveAllRequestedBlocks(void)
2083 m_requestedblocks_list.clear();
2087 void CPartFile::CompleteFile(bool bIsHashingDone)
2089 if (GetKadFileSearchID()) {
2090 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
2093 theApp->downloadqueue->RemoveLocalServerRequest(this);
2095 AddDebugLogLineN( logPartFile, wxString( wxT("CPartFile::CompleteFile: Hash ") ) + ( bIsHashingDone ? wxT("done") : wxT("not done") ) );
2097 if (!bIsHashingDone) {
2098 SetStatus(PS_COMPLETING);
2099 kBpsDown = 0.0;
2101 CPath partFile = m_partmetfilename.RemoveExt();
2102 CThreadScheduler::AddTask(new CHashingTask(GetFilePath(), partFile, this));
2103 return;
2104 } else {
2105 StopFile();
2106 m_is_A4AF_auto=false;
2107 SetStatus(PS_COMPLETING);
2108 // guess I was wrong about not need to spaw a thread ...
2109 // It is if the temp and incoming dirs are on different
2110 // partitions/drives and the file is large...[oz]
2113 PerformFileComplete();
2117 if (thePrefs::ShowCatTabInfos()) {
2118 Notify_ShowUpdateCatTabTitles();
2120 UpdateDisplayedInfo(true);
2124 void CPartFile::CompleteFileEnded(bool errorOccured, const CPath& newname)
2126 if (errorOccured) {
2127 m_paused = true;
2128 SetStatus(PS_ERROR);
2129 AddLogLineC(CFormat( _("Unexpected error while completing %s. File paused") )% GetFileName() );
2130 } else {
2131 m_fullname = newname;
2133 SetFilePath(m_fullname.GetPath());
2134 SetFileName(m_fullname.GetFullName());
2135 m_lastDateChanged = CPath::GetModificationTime(m_fullname);
2137 SetStatus(PS_COMPLETE);
2138 m_paused = false;
2139 ClearPriority();
2142 // Remove from list of canceled files in case it was canceled once upon a time
2143 if (theApp->canceledfiles->Remove(GetFileHash())) {
2144 theApp->canceledfiles->Save();
2147 // Mark as known (checks if it's already known),
2148 // also updates search files
2149 theApp->knownfiles->SafeAddKFile(this);
2151 // remove the file from the suspended uploads list
2152 theApp->uploadqueue->ResumeUpload(GetFileHash());
2153 theApp->downloadqueue->RemoveFile(this, true);
2154 theApp->sharedfiles->SafeAddKFile(this);
2155 UpdateDisplayedInfo(true);
2157 // republish that file to the ed2k-server to update the 'FT_COMPLETE_SOURCES' counter on the server.
2158 theApp->sharedfiles->RepublishFile(this);
2160 // Ensure that completed shows the correct value
2161 completedsize = GetFileSize();
2163 // clear the blackbox to free up memory
2164 m_CorruptionBlackBox->Free();
2166 AddLogLineC(CFormat( _("Finished downloading: %s") ) % GetFileName() );
2169 theApp->downloadqueue->StartNextFile(this);
2173 void CPartFile::PerformFileComplete()
2175 // add this file to the suspended uploads list
2176 theApp->uploadqueue->SuspendUpload(GetFileHash(), false);
2177 FlushBuffer();
2179 // close permanent handle
2180 if (m_hpartfile.IsOpened()) {
2181 m_hpartfile.Close();
2184 // Schedule task for completion of the file
2185 CThreadScheduler::AddTask(new CCompletionTask(this));
2189 void CPartFile::RemoveAllSources(bool bTryToSwap)
2191 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end();) {
2192 CUpDownClient* cur_src = it++->GetClient();
2193 if (bTryToSwap) {
2194 if (!cur_src->SwapToAnotherFile(true, true, true, NULL)) {
2195 RemoveSource(cur_src,true,false);
2196 // If it was not swapped, it's not on any file anymore, and should die
2198 } else {
2199 RemoveSource(cur_src,true,false);
2203 UpdatePartsInfo();
2205 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
2206 // remove all links A4AF in sources to this file
2207 if(!m_A4AFsrclist.empty()) {
2208 for( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
2209 CUpDownClient* cur_src = it++->GetClient();
2210 if ( cur_src->DeleteFileRequest( this ) ) {
2211 Notify_SourceCtrlRemoveSource(cur_src->ECID(), this);
2214 m_A4AFsrclist.clear();
2216 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
2217 UpdateFileRatingCommentAvail();
2221 void CPartFile::Delete()
2223 AddLogLineN(CFormat(_("Deleting file: %s")) % GetFileName());
2224 // Barry - Need to tell any connected clients to stop sending the file
2225 StopFile(true);
2226 AddDebugLogLineN(logPartFile, wxT("\tStopped"));
2228 #ifdef __DEBUG__
2229 uint16 removed =
2230 #endif
2231 theApp->uploadqueue->SuspendUpload(GetFileHash(), true);
2232 AddDebugLogLineN(logPartFile, CFormat(wxT("\tSuspended upload to %d clients")) % removed);
2233 theApp->sharedfiles->RemoveFile(this);
2234 AddDebugLogLineN(logPartFile, wxT("\tRemoved from shared"));
2235 theApp->downloadqueue->RemoveFile(this);
2236 AddDebugLogLineN(logPartFile, wxT("\tRemoved from download queue"));
2237 Notify_DownloadCtrlRemoveFile(this);
2238 AddDebugLogLineN(logPartFile, wxT("\tRemoved from transferwnd"));
2239 if (theApp->canceledfiles->Add(GetFileHash())) {
2240 theApp->canceledfiles->Save();
2242 AddDebugLogLineN(logPartFile, wxT("\tAdded to canceled file list"));
2243 theApp->searchlist->UpdateSearchFileByHash(GetFileHash()); // Update file in the search dialog if it's still open
2245 if (m_hpartfile.IsOpened()) {
2246 m_hpartfile.Close();
2249 AddDebugLogLineN(logPartFile, wxT("\tClosed"));
2251 // cppcheck-suppress duplicateBranch
2252 if (!CPath::RemoveFile(m_fullname)) {
2253 AddDebugLogLineC(logPartFile, CFormat(wxT("\tFailed to delete '%s'")) % m_fullname);
2254 } else {
2255 AddDebugLogLineN(logPartFile, wxT("\tRemoved .part.met"));
2258 // cppcheck-suppress duplicateBranch
2259 if (!CPath::RemoveFile(m_PartPath)) {
2260 AddDebugLogLineC(logPartFile, CFormat(wxT("Failed to delete '%s'")) % m_PartPath);
2261 } else {
2262 AddDebugLogLineN(logPartFile, wxT("\tRemoved .part"));
2265 CPath BAKName = m_fullname.AppendExt(PARTMET_BAK_EXT);
2266 // cppcheck-suppress duplicateBranch
2267 if (!CPath::RemoveFile(BAKName)) {
2268 AddDebugLogLineC(logPartFile, CFormat(wxT("Failed to delete '%s'")) % BAKName);
2269 } else {
2270 AddDebugLogLineN(logPartFile, wxT("\tRemoved .bak"));
2273 CPath SEEDSName = m_fullname.AppendExt(wxT(".seeds"));
2274 if (SEEDSName.FileExists()) {
2275 // cppcheck-suppress duplicateBranch
2276 if (CPath::RemoveFile(SEEDSName)) {
2277 AddDebugLogLineN(logPartFile, wxT("\tRemoved .seeds"));
2278 } else {
2279 AddDebugLogLineC(logPartFile, CFormat(wxT("Failed to delete '%s'")) % SEEDSName);
2283 AddDebugLogLineN(logPartFile, wxT("Done"));
2285 delete this;
2289 bool CPartFile::HashSinglePart(uint16 partnumber)
2291 if ((GetHashCount() <= partnumber) && (GetPartCount() > 1)) {
2292 AddLogLineC(CFormat( _("WARNING: Unable to hash downloaded part - hashset incomplete for '%s'") )
2293 % GetFileName() );
2294 m_hashsetneeded = true;
2295 return true;
2296 } else if ((GetHashCount() <= partnumber) && GetPartCount() != 1) {
2297 AddLogLineC(CFormat( _("ERROR: Unable to hash downloaded part - hashset incomplete (%s). This should never happen")) % GetFileName() );
2298 m_hashsetneeded = true;
2299 return true;
2300 } else {
2301 CMD4Hash hashresult;
2302 uint64 offset = PARTSIZE * partnumber;
2303 uint32 length = GetPartSize(partnumber);
2304 try {
2305 CreateHashFromFile(m_hpartfile, offset, length, &hashresult, NULL);
2306 } catch (const CIOFailureException& e) {
2307 AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2308 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2309 SetStatus(PS_ERROR);
2310 return false;
2311 } catch (const CEOFException& e) {
2312 AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2313 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2314 SetStatus(PS_ERROR);
2315 return false;
2318 if (GetPartCount() > 1) {
2319 if (hashresult != GetPartHash(partnumber)) {
2320 AddDebugLogLineN(logPartFile, CFormat( wxT("%s: Expected hash of part %d: %s")) % GetFileName() % partnumber % GetPartHash(partnumber).Encode() );
2321 AddDebugLogLineN(logPartFile, CFormat( wxT("%s: Actual hash of part %d: %s")) % GetFileName() % partnumber % hashresult.Encode() );
2322 return false;
2323 } else {
2324 return true;
2326 } else {
2327 if (hashresult != m_abyFileHash) {
2328 return false;
2329 } else {
2330 return true;
2337 bool CPartFile::IsCorruptedPart(uint16 partnumber)
2339 return std::find(m_corrupted_list.begin(), m_corrupted_list.end(), partnumber)
2340 != m_corrupted_list.end();
2344 void CPartFile::SetDownPriority(uint8 np, bool bSave, bool bRefresh )
2346 if ( m_iDownPriority != np ) {
2347 m_iDownPriority = np;
2348 if ( bRefresh )
2349 UpdateDisplayedInfo(true);
2350 if ( bSave )
2351 SavePartFile();
2356 void CPartFile::StopFile(bool bCancel)
2358 // Kry - Need to set it here to get into SetStatus(status) correctly
2359 m_stopped = true;
2361 // Barry - Need to tell any connected clients to stop sending the file
2362 PauseFile();
2364 m_LastSearchTimeKad = 0;
2365 m_TotalSearchesKad = 0;
2367 RemoveAllSources(true);
2368 kBpsDown = 0.0;
2369 transferingsrc = 0;
2371 if (!bCancel) {
2372 FlushBuffer();
2375 UpdateDisplayedInfo(true);
2379 void CPartFile::StopPausedFile()
2381 if (!IsStopped()) {
2382 // Once an hour, remove any sources for files which are no longer active downloads
2383 switch (GetStatus()) {
2384 case PS_PAUSED:
2385 case PS_INSUFFICIENT:
2386 case PS_ERROR:
2387 if (time(NULL) - m_iLastPausePurge > (60*60)) {
2388 m_iLastPausePurge = time(NULL);
2389 StopFile();
2391 kBpsDown = 0.0;
2394 // release file handle if unused for some time
2395 m_hpartfile.Release();
2399 void CPartFile::PauseFile(bool bInsufficient)
2401 SetActive(false);
2403 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2404 return;
2407 if (GetKadFileSearchID()) {
2408 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
2409 // If we were in the middle of searching, reset timer so they can resume searching.
2410 m_LastSearchTimeKad = 0;
2413 m_iLastPausePurge = time(NULL);
2415 theApp->downloadqueue->RemoveLocalServerRequest(this);
2417 CPacket packet( OP_CANCELTRANSFER, 0, OP_EDONKEYPROT );
2418 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
2419 CUpDownClient* cur_src = it++->GetClient();
2420 if (cur_src->GetDownloadState() == DS_DOWNLOADING) {
2421 if (!cur_src->GetSentCancelTransfer()) {
2422 theStats::AddUpOverheadOther( packet.GetPacketSize() );
2423 AddDebugLogLineN( logLocalClient, wxT("Local Client: OP_CANCELTRANSFER to ") + cur_src->GetFullIP() );
2424 cur_src->SendPacket( &packet, false, true );
2425 cur_src->SetSentCancelTransfer( true );
2427 cur_src->SetDownloadState(DS_ONQUEUE);
2428 // Allow immediate reconnect on resume
2429 cur_src->ResetLastAskedTime();
2434 m_insufficient = bInsufficient;
2435 m_paused = true;
2438 kBpsDown = 0.0;
2439 transferingsrc = 0;
2441 SetStatus(status);
2445 void CPartFile::ResumeFile()
2447 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2448 return;
2451 if ( m_insufficient && !CheckFreeDiskSpace() ) {
2452 // Still not enough free discspace
2453 return;
2456 m_paused = false;
2457 m_stopped = false;
2458 m_insufficient = false;
2460 m_lastsearchtime = 0;
2461 SetStatus(status);
2462 SetActive(theApp->IsConnected());
2464 if (m_gaplist.IsComplete() && (GetStatus() == PS_ERROR)) {
2465 // The file has already been hashed at this point
2466 CompleteFile(true);
2469 UpdateDisplayedInfo(true);
2473 bool CPartFile::CheckFreeDiskSpace( uint64 neededSpace )
2475 uint64 free = CPath::GetFreeSpaceAt(GetFilePath());
2476 if (free == static_cast<uint64>(wxInvalidOffset)) {
2477 // If GetFreeSpaceAt() fails, then the path probably does not exist.
2478 return false;
2481 // The very least acceptable diskspace is a single PART
2482 if ( free < PARTSIZE ) {
2483 // Always fail in this case, since we risk losing data if we try to
2484 // write on a full partition.
2485 return false;
2488 // All other checks are only made if the user has enabled them
2489 if ( thePrefs::IsCheckDiskspaceEnabled() ) {
2490 neededSpace += thePrefs::GetMinFreeDiskSpace();
2492 // Due to the the existance of sparse files, we cannot assume that
2493 // writes within the file doesn't cause new blocks to be allocated.
2494 // Therefore, we have to simply stop writing the moment the limit has
2495 // been exceeded.
2496 return free >= neededSpace;
2499 return true;
2503 void CPartFile::SetLastAnsweredTime()
2505 m_ClientSrcAnswered = ::GetTickCount();
2508 void CPartFile::SetLastAnsweredTimeTimeout()
2510 m_ClientSrcAnswered = 2 * CONNECTION_LATENCY + ::GetTickCount() - SOURCECLIENTREASKS;
2513 CPacket *CPartFile::CreateSrcInfoPacket(const CUpDownClient* forClient, uint8 byRequestedVersion, uint16 nRequestedOptions)
2516 if ( m_SrcList.empty() ) {
2517 return NULL;
2520 if(!IsPartFile()) {
2521 return CKnownFile::CreateSrcInfoPacket(forClient, byRequestedVersion, nRequestedOptions);
2524 if (((forClient->GetRequestFile() != this)
2525 && (forClient->GetUploadFile() != this)) || forClient->GetUploadFileID() != GetFileHash()) {
2526 wxString file1 = _("Unknown");
2527 if (forClient->GetRequestFile() && forClient->GetRequestFile()->GetFileName().IsOk()) {
2528 file1 = forClient->GetRequestFile()->GetFileName().GetPrintable();
2529 } else if (forClient->GetUploadFile() && forClient->GetUploadFile()->GetFileName().IsOk()) {
2530 file1 = forClient->GetUploadFile()->GetFileName().GetPrintable();
2532 wxString file2 = _("Unknown");
2533 if (GetFileName().IsOk()) {
2534 file2 = GetFileName().GetPrintable();
2536 AddDebugLogLineN(logPartFile, wxT("File mismatch on source packet (P) Sending: ") + file1 + wxT(" From: ") + file2);
2537 return NULL;
2540 if ( !(GetStatus() == PS_READY || GetStatus() == PS_EMPTY)) {
2541 return NULL;
2544 const BitVector& reqstatus = forClient->GetPartStatus();
2545 bool KnowNeededParts = !reqstatus.empty();
2546 //wxASSERT(rcvstatus.size() == GetPartCount()); // Obviously!
2547 if (KnowNeededParts && (reqstatus.size() != GetPartCount())) {
2548 // Yuck. Same file but different part count? Seriously fucked up.
2549 // This happens rather often with reqstatus.size() == 0. Don't log then.
2550 if (reqstatus.size()) {
2551 AddDebugLogLineN(logKnownFiles, CFormat(wxT("Impossible situation: different partcounts: %i (client) and %i (file) for %s")) % reqstatus.size() % GetPartCount() % GetFileName());
2553 return NULL;
2556 CMemFile data(1024);
2558 uint8 byUsedVersion;
2559 bool bIsSX2Packet;
2560 if (forClient->SupportsSourceExchange2() && byRequestedVersion > 0){
2561 // the client uses SourceExchange2 and requested the highest version he knows
2562 // and we send the highest version we know, but of course not higher than his request
2563 byUsedVersion = std::min(byRequestedVersion, (uint8)SOURCEEXCHANGE2_VERSION);
2564 bIsSX2Packet = true;
2565 data.WriteUInt8(byUsedVersion);
2567 // we don't support any special SX2 options yet, reserved for later use
2568 if (nRequestedOptions != 0) {
2569 AddDebugLogLineN(logKnownFiles, CFormat(wxT("Client requested unknown options for SourceExchange2: %u")) % nRequestedOptions);
2571 } else {
2572 byUsedVersion = forClient->GetSourceExchange1Version();
2573 bIsSX2Packet = false;
2574 if (forClient->SupportsSourceExchange2()) {
2575 AddDebugLogLineN(logKnownFiles, wxT("Client which announced to support SX2 sent SX1 packet instead"));
2579 uint16 nCount = 0;
2581 data.WriteHash(m_abyFileHash);
2582 data.WriteUInt16(nCount);
2583 bool bNeeded;
2584 for (SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
2585 bNeeded = false;
2586 CUpDownClient* cur_src = it->GetClient();
2588 int state = cur_src->GetDownloadState();
2589 int valid = ( state == DS_DOWNLOADING ) || ( state == DS_ONQUEUE && !cur_src->IsRemoteQueueFull() );
2591 if ( cur_src->HasLowID() || !valid ) {
2592 continue;
2595 // only send source which have needed parts for this client if possible
2596 const BitVector& srcstatus = cur_src->GetPartStatus();
2597 if ( !srcstatus.empty() ) {
2598 //wxASSERT(srcstatus.size() == GetPartCount()); // Obviously!
2599 if (srcstatus.size() != GetPartCount()) {
2600 continue;
2602 if ( KnowNeededParts ) {
2603 // only send sources which have needed parts for this client
2604 for (int x = 0; x < GetPartCount(); ++x) {
2605 if (srcstatus.get(x) && !reqstatus.get(x)) {
2606 bNeeded = true;
2607 break;
2610 } else {
2611 // if we don't know the need parts for this client,
2612 // return any source currently a client sends it's
2613 // file status only after it has at least one complete part
2614 if (srcstatus.size() != GetPartCount()) {
2615 continue;
2617 for (int x = 0; x < GetPartCount(); ++x){
2618 if (srcstatus.get(x)) {
2619 bNeeded = true;
2620 break;
2625 if(bNeeded) {
2626 ++nCount;
2627 uint32 dwID;
2628 if(forClient->GetSourceExchange1Version() > 2) {
2629 dwID = cur_src->GetUserIDHybrid();
2630 } else {
2631 dwID = wxUINT32_SWAP_ALWAYS(cur_src->GetUserIDHybrid());
2633 data.WriteUInt32(dwID);
2634 data.WriteUInt16(cur_src->GetUserPort());
2635 data.WriteUInt32(cur_src->GetServerIP());
2636 data.WriteUInt16(cur_src->GetServerPort());
2638 if (byUsedVersion >= 2) {
2639 data.WriteHash(cur_src->GetUserHash());
2642 if (byUsedVersion >= 4){
2643 // CryptSettings - SourceExchange V4
2644 // 5 Reserved (!)
2645 // 1 CryptLayer Required
2646 // 1 CryptLayer Requested
2647 // 1 CryptLayer Supported
2648 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
2649 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
2650 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
2651 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
2652 data.WriteUInt8(byCryptOptions);
2655 if (nCount > 500) {
2656 break;
2660 if (!nCount) {
2661 return 0;
2663 data.Seek(bIsSX2Packet ? 17 : 16, wxFromStart);
2664 data.WriteUInt16(nCount);
2666 CPacket* result = new CPacket(data, OP_EMULEPROT, bIsSX2Packet ? OP_ANSWERSOURCES2 : OP_ANSWERSOURCES);
2668 // 16+2+501*(4+2+4+2+16) = 14046 bytes max.
2669 if (result->GetPacketSize() > 354) {
2670 result->PackPacket();
2673 return result;
2676 void CPartFile::AddClientSources(CMemFile* sources, unsigned nSourceFrom, uint8 uClientSXVersion, bool bSourceExchange2, const CUpDownClient* /*pClient*/)
2678 // Kad reviewed
2680 if (m_stopped) {
2681 return;
2684 uint32 nCount = 0;
2685 uint8 uPacketSXVersion = 0;
2686 if (!bSourceExchange2) {
2687 nCount = sources->ReadUInt16();
2689 // Check if the data size matches the 'nCount' for v1 or v2 and eventually correct the source
2690 // exchange version while reading the packet data. Otherwise we could experience a higher
2691 // chance in dealing with wrong source data, userhashs and finally duplicate sources.
2692 uint32 uDataSize = sources->GetLength() - sources->GetPosition();
2694 if ((uint32)(nCount*(4+2+4+2)) == uDataSize) { //Checks if version 1 packet is correct size
2695 if(uClientSXVersion != 1) {
2696 return;
2698 uPacketSXVersion = 1;
2699 } else if ((uint32)(nCount*(4+2+4+2+16)) == uDataSize) { // Checks if version 2&3 packet is correct size
2700 if (uClientSXVersion == 2) {
2701 uPacketSXVersion = 2;
2702 } else if (uClientSXVersion > 2) {
2703 uPacketSXVersion = 3;
2704 } else {
2705 return;
2707 } else if (nCount*(4+2+4+2+16+1) == uDataSize) {
2708 if (uClientSXVersion != 4 ) {
2709 return;
2711 uPacketSXVersion = 4;
2712 } else {
2713 // If v5 inserts additional data (like v2), the above code will correctly filter those packets.
2714 // If v5 appends additional data after <count>(<Sources>)[count], we are in trouble with the
2715 // above code. Though a client which does not understand v5+ should never receive such a packet.
2716 AddDebugLogLineN(logClient, CFormat(wxT("Received invalid source exchange packet (v%u) of data size %u for %s")) % uClientSXVersion % uDataSize % GetFileName());
2717 return;
2719 } else {
2720 // for SX2:
2721 // We only check if the version is known by us and do a quick sanitize check on known version
2722 // other then SX1, the packet will be ignored if any error appears, sicne it can't be a "misunderstanding" anymore
2723 if (uClientSXVersion > SOURCEEXCHANGE2_VERSION || uClientSXVersion == 0 ){
2724 AddDebugLogLineN(logPartFile, CFormat(wxT("Invalid source exchange type version: %i")) % uClientSXVersion);
2725 return;
2728 // all known versions use the first 2 bytes as count and unknown version are already filtered above
2729 nCount = sources->ReadUInt16();
2730 uint32 uDataSize = (uint32)(sources->GetLength() - sources->GetPosition());
2731 bool bError = false;
2732 switch (uClientSXVersion){
2733 case 1:
2734 bError = nCount*(4+2+4+2) != uDataSize;
2735 break;
2736 case 2:
2737 case 3:
2738 bError = nCount*(4+2+4+2+16) != uDataSize;
2739 break;
2740 case 4:
2741 bError = nCount*(4+2+4+2+16+1) != uDataSize;
2742 break;
2743 default:
2744 wxFAIL;
2747 if (bError){
2748 wxFAIL;
2749 AddDebugLogLineN(logPartFile, wxT("Invalid source exchange data size."));
2750 return;
2752 uPacketSXVersion = uClientSXVersion;
2755 for (uint16 i = 0;i != nCount;++i) {
2757 uint32 dwID = sources->ReadUInt32();
2758 uint16 nPort = sources->ReadUInt16();
2759 uint32 dwServerIP = sources->ReadUInt32();
2760 uint16 nServerPort = sources->ReadUInt16();
2762 CMD4Hash userHash;
2763 if (uPacketSXVersion > 1) {
2764 userHash = sources->ReadHash();
2767 uint8 byCryptOptions = 0;
2768 if (uPacketSXVersion >= 4) {
2769 byCryptOptions = sources->ReadUInt8();
2772 //Clients send ID's the the Hyrbid format so highID clients with *.*.*.0 won't be falsely switched to a lowID..
2773 uint32 dwIDED2K;
2774 if (uPacketSXVersion >= 3) {
2775 dwIDED2K = wxUINT32_SWAP_ALWAYS(dwID);
2776 } else {
2777 dwIDED2K = dwID;
2780 // check the HighID(IP) - "Filter LAN IPs" and "IPfilter" the received sources IP addresses
2781 if (!IsLowID(dwID)) {
2782 if (!IsGoodIP(dwIDED2K, thePrefs::FilterLanIPs())) {
2783 // check for 0-IP, localhost and optionally for LAN addresses
2784 AddDebugLogLineN(logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - bad IP")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2785 continue;
2787 if (theApp->ipfilter->IsFiltered(dwIDED2K)) {
2788 AddDebugLogLineN(logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - IPFilter")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2789 continue;
2791 if (theApp->clientlist->IsBannedClient(dwIDED2K)){
2792 continue;
2796 // additionally check for LowID and own IP
2797 if (!CanAddSource(dwID, nPort, dwServerIP, nServerPort, NULL, false)) {
2798 AddDebugLogLineN(logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via source exchange")) % Uint32toStringIP(dwIDED2K));
2799 continue;
2802 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
2803 CUpDownClient* newsource = new CUpDownClient(nPort,dwID,dwServerIP,nServerPort,this, (uPacketSXVersion < 3), true);
2804 if (uPacketSXVersion > 1) {
2805 newsource->SetUserHash(userHash);
2808 if (uPacketSXVersion >= 4) {
2809 newsource->SetConnectOptions(byCryptOptions, true, false);
2812 newsource->SetSourceFrom((ESourceFrom)nSourceFrom);
2813 theApp->downloadqueue->CheckAndAddSource(this,newsource);
2815 } else {
2816 break;
2821 void CPartFile::UpdateAutoDownPriority()
2823 if (!IsAutoDownPriority()) {
2824 return;
2826 if (GetSourceCount() <= theApp->downloadqueue->GetRareFileThreshold()) {
2827 if ( GetDownPriority() != PR_HIGH )
2828 SetDownPriority(PR_HIGH, false, false);
2829 } else if (GetSourceCount() < theApp->downloadqueue->GetCommonFileThreshold()) {
2830 if ( GetDownPriority() != PR_NORMAL )
2831 SetDownPriority(PR_NORMAL, false, false);
2832 } else {
2833 if ( GetDownPriority() != PR_LOW )
2834 SetDownPriority(PR_LOW, false, false);
2838 // making this function return a higher when more sources have the extended
2839 // protocol will force you to ask a larger variety of people for sources
2841 int CPartFile::GetCommonFilePenalty()
2843 //TODO: implement, but never return less than MINCOMMONPENALTY!
2844 return MINCOMMONPENALTY;
2847 /* Barry - Replaces BlockReceived()
2849 Originally this only wrote to disk when a full 180k block
2850 had been received from a client, and only asked for data in
2851 180k blocks.
2853 This meant that on average 90k was lost for every connection
2854 to a client data source. That is a lot of wasted data.
2856 To reduce the lost data, packets are now written to a buffer
2857 and flushed to disk regularly regardless of size downloaded.
2858 This includes compressed packets.
2860 Data is also requested only where gaps are, not in 180k blocks.
2861 The requests will still not exceed 180k, but may be smaller to
2862 fill a gap.
2865 // Kry - transize is 32bits, no packet can be more than that (this is
2866 // compressed size). Even 32bits is too much imho.As for the return size,
2867 // look at the lenData below.
2868 uint32 CPartFile::WriteToBuffer(uint32 transize, byte* data, uint64 start, uint64 end, Requested_Block_Struct *block, const CUpDownClient* client)
2870 // Increment transferred bytes counter for this file
2871 transferred += transize;
2873 // This is needed a few times
2874 // Kry - should not need a uint64 here - no block is larger than
2875 // 2GB even after uncompressed.
2876 uint32 lenData = (uint32) (end - start + 1);
2878 if(lenData > transize) {
2879 m_iGainDueToCompression += lenData-transize;
2882 // Occasionally packets are duplicated, no point writing it twice
2883 if (IsComplete(start, end)) {
2884 AddDebugLogLineN(logPartFile,
2885 CFormat(wxT("File '%s' has already been written from %u to %u"))
2886 % GetFileName() % start % end);
2887 return 0;
2890 // security sanitize check to make sure we do not write anything into an already hashed complete chunk
2891 const uint64 nStartChunk = start / PARTSIZE;
2892 const uint64 nEndChunk = end / PARTSIZE;
2893 if (IsComplete(nStartChunk)) {
2894 AddDebugLogLineN(logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (start): %u-%u; File=%s")) % start % end % GetFileName());
2895 return 0;
2896 } else if (nStartChunk != nEndChunk) {
2897 if (IsComplete(nEndChunk)) {
2898 AddDebugLogLineN(logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (end): %u-%u; File=%s")) % start % end % GetFileName());
2899 return 0;
2900 } else {
2901 AddDebugLogLineN(logPartFile, CFormat(wxT("Received data crosses chunk boundaries: %u-%u; File=%s")) % start % end % GetFileName());
2905 // log transferinformation in our "blackbox"
2906 m_CorruptionBlackBox->TransferredData(start, end, client->GetIP());
2908 // Create a new buffered queue entry
2909 PartFileBufferedData *item = new PartFileBufferedData(m_hpartfile, data, start, end, block);
2911 // Add to the queue in the correct position (most likely the end)
2912 bool added = false;
2914 std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
2915 for (; it != m_BufferedData_list.end(); ++it) {
2916 PartFileBufferedData* queueItem = *it;
2918 if (item->end <= queueItem->end) {
2919 if (it != m_BufferedData_list.begin()) {
2920 added = true;
2922 m_BufferedData_list.insert(--it, item);
2925 break;
2929 if (!added) {
2930 m_BufferedData_list.push_front(item);
2933 // Increment buffer size marker
2934 m_nTotalBufferData += lenData;
2936 // Mark this small section of the file as filled
2937 FillGap(item->start, item->end);
2939 // Update the flushed mark on the requested block
2940 // The loop here is unfortunate but necessary to detect deleted blocks.
2942 std::list<Requested_Block_Struct*>::iterator it2 = m_requestedblocks_list.begin();
2943 for (; it2 != m_requestedblocks_list.end(); ++it2) {
2944 if (*it2 == item->block) {
2945 item->block->transferred += lenData;
2949 if (m_gaplist.IsComplete()) {
2950 FlushBuffer();
2953 // Return the length of data written to the buffer
2954 return lenData;
2957 void CPartFile::FlushBuffer(bool fromAICHRecoveryDataAvailable)
2959 m_nLastBufferFlushTime = GetTickCount();
2961 if (m_BufferedData_list.empty()) {
2962 return;
2966 uint32 partCount = GetPartCount();
2967 // Remember which parts need to be checked at the end of the flush
2968 std::vector<bool> changedPart(partCount, false);
2970 // Ensure file is big enough to write data to (the last item will be the furthest from the start)
2971 if (!CheckFreeDiskSpace(m_nTotalBufferData)) {
2972 // Not enough free space to write the last item, bail
2973 AddLogLineC(CFormat( _("WARNING: Not enough free disk-space! Pausing file: %s") ) % GetFileName());
2975 PauseFile( true );
2976 return;
2979 // Loop through queue
2980 while ( !m_BufferedData_list.empty() ) {
2981 // Get top item and remove it from the queue
2982 CScopedPtr<PartFileBufferedData> item(m_BufferedData_list.front());
2983 m_BufferedData_list.pop_front();
2985 // This is needed a few times
2986 wxASSERT((item->end - item->start) < 0xFFFFFFFF);
2987 uint32 lenData = (uint32)(item->end - item->start + 1);
2989 // SLUGFILLER: SafeHash - could be more than one part
2990 for (uint32 curpart = (item->start/PARTSIZE); curpart <= (item->end/PARTSIZE); ++curpart) {
2991 wxASSERT(curpart < partCount);
2992 changedPart[curpart] = true;
2994 // SLUGFILLER: SafeHash
2996 // Go to the correct position in file and write block of data
2997 try {
2998 item->area.FlushAt(m_hpartfile, item->start, lenData);
2999 // Decrease buffer size
3000 m_nTotalBufferData -= lenData;
3001 } catch (const CIOFailureException& e) {
3002 AddDebugLogLineC(logPartFile, wxT("Error while saving part-file: ") + e.what());
3003 SetStatus(PS_ERROR);
3004 // No need to bang your head against it again and again if it has already failed.
3005 DeleteContents(m_BufferedData_list);
3006 m_nTotalBufferData = 0;
3007 return;
3012 // Update last-changed date
3013 m_lastDateChanged = wxDateTime::GetTimeNow();
3015 try {
3016 // Partfile should never be too large
3017 if (m_hpartfile.GetLength() > GetFileSize()) {
3018 // it's "last chance" correction. the real bugfix has to be applied 'somewhere' else
3019 m_hpartfile.SetLength(GetFileSize());
3021 } catch (const CIOFailureException& e) {
3022 AddDebugLogLineC(logPartFile,
3023 CFormat(wxT("Error while truncating part-file (%s): %s"))
3024 % m_PartPath % e.what());
3025 SetStatus(PS_ERROR);
3030 // Check each part of the file
3031 for (uint16 partNumber = 0; partNumber < partCount; ++partNumber) {
3032 if (changedPart[partNumber] == false) {
3033 continue;
3036 uint32 partRange = GetPartSize(partNumber) - 1;
3038 // Is this 9MB part complete
3039 if (IsComplete(partNumber)) {
3040 // Is part corrupt
3041 if (!HashSinglePart(partNumber)) {
3042 AddLogLineC(CFormat(
3043 _("Downloaded part %i is corrupt in file: %s") ) % partNumber % GetFileName() );
3044 AddGap(partNumber);
3045 // add part to corrupted list, if not already there
3046 if (!IsCorruptedPart(partNumber)) {
3047 m_corrupted_list.push_back(partNumber);
3049 // request AICH recovery data
3050 // Don't if called from the AICHRecovery. It's already there and would lead to an infinite recursion.
3051 if (!fromAICHRecoveryDataAvailable) {
3052 RequestAICHRecovery(partNumber);
3054 // Reduce transferred amount by corrupt amount
3055 m_iLostDueToCorruption += (partRange + 1);
3056 } else {
3057 if (!m_hashsetneeded) {
3058 AddDebugLogLineN(logPartFile, CFormat(
3059 wxT("Finished part %u of '%s'")) % partNumber % GetFileName());
3062 // tell the blackbox about the verified data
3063 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3065 // if this part was successfully completed (although ICH is active), remove from corrupted list
3066 EraseFirstValue(m_corrupted_list, partNumber);
3068 if (status == PS_EMPTY) {
3069 if (theApp->IsRunning()) { // may be called during shutdown!
3070 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3071 // Successfully completed part, make it available for sharing
3072 SetStatus(PS_READY);
3073 theApp->sharedfiles->SafeAddKFile(this);
3078 } else if ( IsCorruptedPart(partNumber) && // corrupted part:
3079 (thePrefs::IsICHEnabled() // old ICH: rehash whenever we have new data hoping it will be good now
3080 || fromAICHRecoveryDataAvailable)) {// new AICH: one rehash right before performing it (maybe it's already good)
3081 // Try to recover with minimal loss
3082 if (HashSinglePart(partNumber)) {
3083 ++m_iTotalPacketsSavedDueToICH;
3085 uint64 uMissingInPart = m_gaplist.GetGapSize(partNumber);
3086 FillGap(partNumber);
3087 RemoveBlockFromList(PARTSIZE*partNumber,(PARTSIZE*partNumber + partRange));
3089 // tell the blackbox about the verified data
3090 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3092 // remove from corrupted list
3093 EraseFirstValue(m_corrupted_list, partNumber);
3095 AddLogLineC(CFormat( _("ICH: Recovered corrupted part %i for %s -> Saved bytes: %s") )
3096 % partNumber
3097 % GetFileName()
3098 % CastItoXBytes(uMissingInPart));
3100 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3101 if (status == PS_EMPTY) {
3102 // Successfully recovered part, make it available for sharing
3103 SetStatus(PS_READY);
3104 if (theApp->IsRunning()) // may be called during shutdown!
3105 theApp->sharedfiles->SafeAddKFile(this);
3112 // Update met file
3113 SavePartFile();
3115 if (theApp->IsRunning()) { // may be called during shutdown!
3116 // Is this file finished ?
3117 if (m_gaplist.IsComplete()) {
3118 CompleteFile(false);
3124 // read data for upload, return false on error
3125 bool CPartFile::ReadData(CFileArea & area, uint64 offset, uint32 toread)
3127 // Sanity check
3128 if (offset + toread > GetFileSize()) {
3129 AddDebugLogLineN(logPartFile, CFormat(wxT("tried to read %d bytes past eof of %s"))
3130 % (offset + toread - GetFileSize()) % GetFileName());
3131 wxFAIL;
3132 return false;
3135 area.ReadAt(m_hpartfile, offset, toread);
3136 // if it fails it throws (which the caller should catch)
3137 return true;
3141 void CPartFile::UpdateFileRatingCommentAvail()
3143 bool prevComment = m_hasComment;
3144 int prevRating = m_iUserRating;
3146 m_hasComment = false;
3147 m_iUserRating = 0;
3148 int ratingCount = 0;
3150 SourceSet::iterator it = m_SrcList.begin();
3151 for (; it != m_SrcList.end(); ++it) {
3152 CUpDownClient* cur_src = it->GetClient();
3154 if (!cur_src->GetFileComment().IsEmpty()) {
3155 if (thePrefs::IsCommentFiltered(cur_src->GetFileComment())) {
3156 continue;
3158 m_hasComment = true;
3161 uint8 rating = cur_src->GetFileRating();
3162 if (rating) {
3163 wxASSERT(rating <= 5);
3165 ratingCount++;
3166 m_iUserRating += rating;
3170 if (ratingCount) {
3171 m_iUserRating /= ratingCount;
3172 wxASSERT(m_iUserRating > 0 && m_iUserRating <= 5);
3175 if ((prevComment != m_hasComment) || (prevRating != m_iUserRating)) {
3176 UpdateDisplayedInfo();
3181 void CPartFile::SetCategory(uint8 cat)
3183 wxASSERT( cat < theApp->glob_prefs->GetCatCount() );
3185 m_category = cat;
3186 SavePartFile();
3189 bool CPartFile::RemoveSource(CUpDownClient* toremove, bool updatewindow, bool bDoStatsUpdate)
3191 wxASSERT( toremove );
3193 bool result = theApp->downloadqueue->RemoveSource( toremove, updatewindow, bDoStatsUpdate );
3195 // Check if the client should be deleted, but not if the client is already dying
3196 if ( !toremove->GetSocket() && !toremove->HasBeenDeleted() ) {
3197 if ( toremove->Disconnected(wxT("RemoveSource - purged")) ) {
3198 toremove->Safe_Delete();
3202 return result;
3205 void CPartFile::AddDownloadingSource(CUpDownClient* client)
3207 CClientRefList::iterator it =
3208 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), CCLIENTREF(client, wxEmptyString));
3209 if (it == m_downloadingSourcesList.end()) {
3210 m_downloadingSourcesList.push_back(CCLIENTREF(client, wxT("CPartFile::AddDownloadingSource")));
3215 void CPartFile::RemoveDownloadingSource(CUpDownClient* client)
3217 CClientRefList::iterator it =
3218 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), CCLIENTREF(client, wxEmptyString));
3219 if (it != m_downloadingSourcesList.end()) {
3220 m_downloadingSourcesList.erase(it);
3225 uint64 CPartFile::GetNeededSpace()
3227 try {
3228 uint64 length = m_hpartfile.GetLength();
3230 if (length > GetFileSize()) {
3231 return 0; // Shouldn't happen, but just in case
3234 return GetFileSize() - length;
3235 } catch (const CIOFailureException& e) {
3236 AddDebugLogLineC(logPartFile,
3237 CFormat(wxT("Error while retrieving file-length (%s): %s"))
3238 % m_PartPath % e.what());
3239 SetStatus(PS_ERROR);
3240 return 0;
3244 void CPartFile::SetStatus(uint8 in)
3246 // PAUSED and INSUFFICIENT have extra flag variables m_paused and m_insufficient
3247 // - they are never to be stored in status
3248 wxASSERT( in != PS_PAUSED && in != PS_INSUFFICIENT );
3250 status = in;
3252 if (theApp->IsRunning()) {
3253 UpdateDisplayedInfo( true );
3255 if ( thePrefs::ShowCatTabInfos() ) {
3256 Notify_ShowUpdateCatTabTitles();
3258 Notify_DownloadCtrlSort();
3263 void CPartFile::RequestAICHRecovery(uint16 nPart)
3266 if ( !m_pAICHHashSet->HasValidMasterHash() ||
3267 (m_pAICHHashSet->GetStatus() != AICH_TRUSTED && m_pAICHHashSet->GetStatus() != AICH_VERIFIED)){
3268 AddDebugLogLineN( logAICHRecovery, wxT("Unable to request AICH Recoverydata because we have no trusted Masterhash") );
3269 return;
3271 if (GetPartSize(nPart) <= EMBLOCKSIZE)
3272 return;
3273 if (CAICHHashSet::IsClientRequestPending(this, nPart)){
3274 AddDebugLogLineN( logAICHRecovery, wxT("RequestAICHRecovery: Already a request for this part pending"));
3275 return;
3278 // first check if we have already the recoverydata, no need to rerequest it then
3279 if (m_pAICHHashSet->IsPartDataAvailable(nPart*PARTSIZE)){
3280 AddDebugLogLineN( logAICHRecovery, wxT("Found PartRecoveryData in memory"));
3281 AICHRecoveryDataAvailable(nPart);
3282 return;
3285 wxASSERT( nPart < GetPartCount() );
3286 // find some random client which support AICH to ask for the blocks
3287 // first lets see how many we have at all, we prefer high id very much
3288 uint32 cAICHClients = 0;
3289 uint32 cAICHLowIDClients = 0;
3290 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3291 CUpDownClient* pCurClient = it->GetClient();
3292 if ( pCurClient->IsSupportingAICH() &&
3293 pCurClient->GetReqFileAICHHash() != NULL &&
3294 !pCurClient->IsAICHReqPending()
3295 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3297 if (pCurClient->HasLowID()) {
3298 ++cAICHLowIDClients;
3299 } else {
3300 ++cAICHClients;
3304 if ((cAICHClients | cAICHLowIDClients) == 0){
3305 AddDebugLogLineN( logAICHRecovery, wxT("Unable to request AICH Recoverydata because found no client who supports it and has the same hash as the trusted one"));
3306 return;
3308 uint32 nSeclectedClient;
3309 if (cAICHClients > 0) {
3310 nSeclectedClient = (rand() % cAICHClients) + 1;
3311 } else {
3312 nSeclectedClient = (rand() % cAICHLowIDClients) + 1;
3314 CUpDownClient* pClient = NULL;
3315 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3316 CUpDownClient* pCurClient = it->GetClient();
3317 if (pCurClient->IsSupportingAICH() && pCurClient->GetReqFileAICHHash() != NULL && !pCurClient->IsAICHReqPending()
3318 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3320 if (cAICHClients > 0){
3321 if (!pCurClient->HasLowID())
3322 nSeclectedClient--;
3324 else{
3325 wxASSERT( pCurClient->HasLowID());
3326 nSeclectedClient--;
3328 if (nSeclectedClient == 0){
3329 pClient = pCurClient;
3330 break;
3334 if (pClient == NULL){
3335 wxFAIL;
3336 return;
3339 AddDebugLogLineN( logAICHRecovery, CFormat( wxT("Requesting AICH Hash (%s) form client %s") ) % ( cAICHClients ? wxT("HighId") : wxT("LowID") ) % pClient->GetClientFullInfo() );
3340 pClient->SendAICHRequest(this, nPart);
3345 void CPartFile::AICHRecoveryDataAvailable(uint16 nPart)
3347 if (GetPartCount() < nPart){
3348 wxFAIL;
3349 return;
3352 FlushBuffer(true);
3353 uint32 length = GetPartSize(nPart);
3354 // if the part was already ok, it would now be complete
3355 if (IsComplete(nPart)) {
3356 AddDebugLogLineN(logAICHRecovery, CFormat(wxT("Processing AICH Recovery data: The part (%u) is already complete, canceling")) % nPart);
3357 return;
3362 CAICHHashTree* pVerifiedHash = m_pAICHHashSet->m_pHashTree.FindHash(nPart*PARTSIZE, length);
3363 if (pVerifiedHash == NULL || !pVerifiedHash->GetHashValid()){
3364 AddDebugLogLineC( logAICHRecovery, wxT("Processing AICH Recovery data: Unable to get verified hash from hashset (should never happen)") );
3365 wxFAIL;
3366 return;
3368 CAICHHashTree htOurHash(pVerifiedHash->GetNDataSize(), pVerifiedHash->GetIsLeftBranch(), pVerifiedHash->GetNBaseSize());
3369 try {
3370 CreateHashFromFile(m_hpartfile, PARTSIZE * nPart, length, NULL, &htOurHash);
3371 } catch (const CIOFailureException& e) {
3372 AddDebugLogLineC(logAICHRecovery,
3373 CFormat(wxT("IO failure while hashing part-file '%s': %s"))
3374 % m_hpartfile.GetFilePath() % e.what());
3375 SetStatus(PS_ERROR);
3376 return;
3379 if (!htOurHash.GetHashValid()){
3380 AddDebugLogLineN( logAICHRecovery, wxT("Processing AICH Recovery data: Failed to retrieve AICH Hashset of corrupt part") );
3381 wxFAIL;
3382 return;
3385 // now compare the hash we just did, to the verified hash and readd all blocks which are ok
3386 uint32 nRecovered = 0;
3387 for (uint32 pos = 0; pos < length; pos += EMBLOCKSIZE){
3388 const uint32 nBlockSize = min<uint32>(EMBLOCKSIZE, length - pos);
3389 CAICHHashTree* pVerifiedBlock = pVerifiedHash->FindHash(pos, nBlockSize);
3390 CAICHHashTree* pOurBlock = htOurHash.FindHash(pos, nBlockSize);
3391 if ( pVerifiedBlock == NULL || pOurBlock == NULL || !pVerifiedBlock->GetHashValid() || !pOurBlock->GetHashValid()){
3392 wxFAIL;
3393 continue;
3395 if (pOurBlock->GetHash() == pVerifiedBlock->GetHash()){
3396 FillGap(PARTSIZE*nPart+pos, PARTSIZE*nPart + pos + (nBlockSize-1));
3397 RemoveBlockFromList(PARTSIZE*nPart, PARTSIZE*nPart + (nBlockSize-1));
3398 nRecovered += nBlockSize;
3399 // tell the blackbox about the verified data
3400 m_CorruptionBlackBox->VerifiedData(true, nPart, pos, pos + nBlockSize - 1);
3401 } else {
3402 // inform our "blackbox" about the corrupted block which may ban clients who sent it
3403 m_CorruptionBlackBox->VerifiedData(false, nPart, pos, pos + nBlockSize - 1);
3406 m_CorruptionBlackBox->EvaluateData();
3408 // ok now some sanity checks
3409 if (IsComplete(nPart)) {
3410 // this is bad, but it could probably happen under some rare circumstances
3411 // make sure that MD4 agrees to this fact too
3412 if (!HashSinglePart(nPart)) {
3413 AddDebugLogLineN(logAICHRecovery,
3414 CFormat(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);
3415 // now we are fu... unhappy
3416 m_pAICHHashSet->SetStatus(AICH_ERROR);
3417 AddGap(nPart);
3418 wxFAIL;
3419 return;
3420 } else {
3421 AddDebugLogLineN(logAICHRecovery,
3422 CFormat(wxT("Processing AICH Recovery data: The part (%u) got completed while recovering and MD4 agrees")) % nPart);
3423 if (status == PS_EMPTY && theApp->IsRunning()) {
3424 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3425 // Successfully recovered part, make it available for sharing
3426 SetStatus(PS_READY);
3427 theApp->sharedfiles->SafeAddKFile(this);
3431 if (theApp->IsRunning()) {
3432 // Is this file finished?
3433 if (m_gaplist.IsComplete()) {
3434 CompleteFile(false);
3438 } // end sanity check
3439 // We did the best we could. If it's still incomplete, then no need to keep
3440 // bashing it with ICH. So remove it from the list of corrupted parts.
3441 EraseFirstValue(m_corrupted_list, nPart);
3442 // Update met file
3443 SavePartFile();
3445 // make sure the user appreciates our great recovering work :P
3446 AddDebugLogLineC( logAICHRecovery, CFormat(
3447 wxT("AICH successfully recovered %s of %s from part %u for %s") )
3448 % CastItoXBytes(nRecovered)
3449 % CastItoXBytes(length)
3450 % nPart
3451 % GetFileName() );
3455 void CPartFile::ClientStateChanged( int oldState, int newState )
3457 if ( oldState == newState )
3458 return;
3460 // If the state is -1, then it's an entirely new item
3461 if ( oldState != -1 ) {
3462 // Was the old state a valid state?
3463 if ( oldState == DS_ONQUEUE || oldState == DS_DOWNLOADING ) {
3464 m_validSources--;
3465 } else {
3466 if ( oldState == DS_CONNECTED /* || oldState == DS_REMOTEQUEUEFULL */ ) {
3467 m_validSources--;
3470 m_notCurrentSources--;
3474 // If the state is -1, then the source is being removed
3475 if ( newState != -1 ) {
3476 // Was the old state a valid state?
3477 if ( newState == DS_ONQUEUE || newState == DS_DOWNLOADING ) {
3478 ++m_validSources;
3479 } else {
3480 if ( newState == DS_CONNECTED /* || newState == DS_REMOTEQUEUEFULL */ ) {
3481 ++m_validSources;
3484 ++m_notCurrentSources;
3490 bool CPartFile::AddSource( CUpDownClient* client )
3492 if (m_SrcList.insert(CCLIENTREF(client, wxT("CPartFile::AddSource"))).second) {
3493 theStats::AddFoundSource();
3494 theStats::AddSourceOrigin(client->GetSourceFrom());
3495 return true;
3496 } else {
3497 return false;
3502 bool CPartFile::DelSource( CUpDownClient* client )
3504 if (m_SrcList.erase(CCLIENTREF(client, wxEmptyString))) {
3505 theStats::RemoveSourceOrigin(client->GetSourceFrom());
3506 theStats::RemoveFoundSource();
3507 return true;
3508 } else {
3509 return false;
3514 void CPartFile::UpdatePartsFrequency( CUpDownClient* client, bool increment )
3516 const BitVector& freq = client->GetPartStatus();
3518 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
3519 m_SrcpartFrequency.clear();
3520 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
3522 if ( !increment ) {
3523 return;
3527 unsigned int size = freq.size();
3528 if ( size != m_SrcpartFrequency.size() ) {
3529 return;
3532 if ( increment ) {
3533 for ( unsigned int i = 0; i < size; i++ ) {
3534 if ( freq.get(i) ) {
3535 m_SrcpartFrequency[i]++;
3538 } else {
3539 for ( unsigned int i = 0; i < size; i++ ) {
3540 if ( freq.get(i) ) {
3541 m_SrcpartFrequency[i]--;
3547 void CPartFile::GetRatingAndComments(FileRatingList & list) const
3549 list.clear();
3550 // This can be pre-processed, but is it worth the CPU?
3551 CPartFile::SourceSet::const_iterator it = m_SrcList.begin();
3552 for ( ; it != m_SrcList.end(); ++it ) {
3553 CUpDownClient *cur_src = it->GetClient();
3554 if (cur_src->GetFileComment().Length()>0 || cur_src->GetFileRating()>0) {
3555 // AddDebugLogLineN(logPartFile, wxString(wxT("found a comment for ")) << GetFileName());
3556 list.push_back(SFileRating(*cur_src));
3561 #else // CLIENT_GUI
3563 CPartFile::CPartFile(const CEC_PartFile_Tag *tag) : CKnownFile(tag)
3565 Init();
3567 SetFileName(CPath(tag->FileName()));
3568 m_abyFileHash = tag->FileHash();
3569 SetFileSize(tag->SizeFull());
3570 m_gaplist.Init(GetFileSize(), true); // Init empty
3571 m_partmetfilename = CPath(tag->PartMetName());
3572 m_fullname = m_partmetfilename; // We have only the met number, so show it without path in the detail dialog.
3574 m_SrcpartFrequency.insert(m_SrcpartFrequency.end(), GetPartCount(), 0);
3576 // these are only in CLIENT_GUI and not covered by Init()
3577 m_source_count = 0;
3578 m_kbpsDown = 0;
3579 m_iDownPriorityEC = 0;
3580 m_a4af_source_count = 0;
3581 m_isShared = false;
3585 * Remote gui specific code
3587 CPartFile::~CPartFile()
3591 void CPartFile::GetRatingAndComments(FileRatingList & list) const
3593 list = m_FileRatingList;
3596 void CPartFile::SetCategory(uint8 cat)
3598 m_category = cat;
3602 bool CPartFile::AddSource(CUpDownClient* client)
3604 return m_SrcList.insert(CCLIENTREF(client, wxT("CPartFile::AddSource"))).second != 0;
3608 bool CPartFile::DelSource(CUpDownClient* client)
3610 return m_SrcList.erase(CCLIENTREF(client, wxEmptyString)) != 0;
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;
3629 void CPartFile::Init()
3631 m_lastsearchtime = 0;
3632 lastpurgetime = ::GetTickCount();
3633 m_paused = false;
3634 m_stopped = false;
3635 m_insufficient = false;
3637 status = PS_EMPTY;
3639 transferred = 0;
3640 m_iLastPausePurge = time(NULL);
3642 if(thePrefs::GetNewAutoDown()) {
3643 m_iDownPriority = PR_HIGH;
3644 m_bAutoDownPriority = true;
3645 } else {
3646 m_iDownPriority = PR_NORMAL;
3647 m_bAutoDownPriority = false;
3650 transferingsrc = 0; // new
3652 kBpsDown = 0.0;
3654 m_hashsetneeded = true;
3655 m_count = 0;
3656 percentcompleted = 0;
3657 completedsize=0;
3658 lastseencomplete = 0;
3659 m_availablePartsCount=0;
3660 m_ClientSrcAnswered = 0;
3661 m_LastNoNeededCheck = 0;
3662 m_iRating = 0;
3663 m_nTotalBufferData = 0;
3664 m_nLastBufferFlushTime = 0;
3665 m_bPercentUpdated = false;
3666 m_iGainDueToCompression = 0;
3667 m_iLostDueToCorruption = 0;
3668 m_iTotalPacketsSavedDueToICH = 0;
3669 m_category = 0;
3670 m_lastRefreshedDLDisplay = 0;
3671 m_nDlActiveTime = 0;
3672 m_tActivated = 0;
3673 m_is_A4AF_auto = false;
3674 m_localSrcReqQueued = false;
3675 m_nCompleteSourcesTime = time(NULL);
3676 m_nCompleteSourcesCount = 0;
3677 m_nCompleteSourcesCountLo = 0;
3678 m_nCompleteSourcesCountHi = 0;
3680 m_validSources = 0;
3681 m_notCurrentSources = 0;
3683 // Kad
3684 m_LastSearchTimeKad = 0;
3685 m_TotalSearchesKad = 0;
3687 #ifndef CLIENT_GUI
3688 m_CorruptionBlackBox = new CCorruptionBlackBox();
3689 #endif
3692 wxString CPartFile::getPartfileStatus() const
3695 wxString mybuffer;
3697 if ((status == PS_HASHING) || (status == PS_WAITINGFORHASH)) {
3698 mybuffer=_("Hashing");
3699 } else if (status == PS_ALLOCATING) {
3700 mybuffer = _("Allocating");
3701 } else {
3702 switch (GetStatus()) {
3703 case PS_COMPLETING:
3704 mybuffer=_("Completing");
3705 break;
3706 case PS_COMPLETE:
3707 mybuffer=_("Complete");
3708 break;
3709 case PS_PAUSED:
3710 mybuffer=_("Paused");
3711 break;
3712 case PS_ERROR:
3713 mybuffer=_("Erroneous");
3714 break;
3715 case PS_INSUFFICIENT:
3716 mybuffer = _("Insufficient disk space");
3717 break;
3718 default:
3719 if (GetTransferingSrcCount()>0) {
3720 mybuffer=_("Downloading");
3721 } else {
3722 mybuffer=_("Waiting");
3724 break;
3726 if (m_stopped && (GetStatus()!=PS_COMPLETE)) {
3727 mybuffer=_("Stopped");
3731 return mybuffer;
3734 int CPartFile::getPartfileStatusRang() const
3737 int tempstatus=0;
3738 if (GetTransferingSrcCount()==0) tempstatus=1;
3739 switch (GetStatus()) {
3740 case PS_HASHING:
3741 case PS_WAITINGFORHASH:
3742 tempstatus=3;
3743 break;
3744 case PS_COMPLETING:
3745 tempstatus=4;
3746 break;
3747 case PS_COMPLETE:
3748 tempstatus=5;
3749 break;
3750 case PS_PAUSED:
3751 tempstatus=2;
3752 break;
3753 case PS_ERROR:
3754 tempstatus=6;
3755 break;
3757 return tempstatus;
3761 wxString CPartFile::GetFeedback() const
3763 wxString retval = CKnownFile::GetFeedback();
3764 if (GetStatus() != PS_COMPLETE) {
3765 retval += CFormat(wxT("%s: %s (%.2f%%)\n%s: %u\n"))
3766 % _("Downloaded") % CastItoXBytes(GetCompletedSize()) % GetPercentCompleted() % _("Sources") % GetSourceCount();
3768 return retval + _("Status") + wxT(": ") + getPartfileStatus() + wxT("\n");
3772 sint32 CPartFile::getTimeRemaining() const
3774 if (GetKBpsDown() < 0.001)
3775 return -1;
3776 else
3777 return((GetFileSize()-GetCompletedSize()) / ((int)(GetKBpsDown()*1024.0)));
3780 bool CPartFile::PreviewAvailable()
3782 const uint64 minSizeForPreview = 256 * 1024;
3783 FileType type = GetFiletype(GetFileName());
3785 return (type == ftVideo || type == ftAudio) &&
3786 GetFileSize() >= minSizeForPreview &&
3787 IsComplete(0, minSizeForPreview);
3790 bool CPartFile::CheckShowItemInGivenCat(int inCategory)
3792 // first check if item belongs in this cat in principle
3793 if (inCategory > 0 && inCategory != GetCategory()) {
3794 return false;
3797 // if yes apply filter
3798 bool show = true;
3800 switch (thePrefs::GetAllcatFilter()) {
3801 case acfAllOthers:
3802 show = GetCategory() == 0 || inCategory > 0;
3803 break;
3804 case acfIncomplete:
3805 show = IsPartFile();
3806 break;
3807 case acfCompleted:
3808 show = !IsPartFile();
3809 break;
3810 case acfWaiting:
3811 show =
3812 (GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
3813 GetTransferingSrcCount() == 0;
3814 break;
3815 case acfDownloading:
3816 show =
3817 (GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
3818 GetTransferingSrcCount() > 0;
3819 break;
3820 case acfErroneous:
3821 show = GetStatus() == PS_ERROR;
3822 break;
3823 case acfPaused:
3824 show = GetStatus() == PS_PAUSED && !IsStopped();
3825 break;
3826 case acfStopped:
3827 show = IsStopped();
3828 break;
3829 case acfVideo:
3830 show = GetFiletype(GetFileName()) == ftVideo;
3831 break;
3832 case acfAudio:
3833 show = GetFiletype(GetFileName()) == ftAudio;
3834 break;
3835 case acfArchive:
3836 show = GetFiletype(GetFileName()) == ftArchive;
3837 break;
3838 case acfCDImages:
3839 show = GetFiletype(GetFileName()) == ftCDImage;
3840 break;
3841 case acfPictures:
3842 show = GetFiletype(GetFileName()) == ftPicture;
3843 break;
3844 case acfText:
3845 show = GetFiletype(GetFileName()) == ftText;
3846 break;
3847 case acfActive:
3848 show = !IsStopped() && GetStatus() != PS_PAUSED;
3849 break;
3850 default:
3851 show = true;
3852 break;
3855 return show;
3859 void CPartFile::RemoveCategory(uint8 cat)
3861 if (m_category == cat) {
3862 // Reset the category
3863 m_category = 0;
3864 } else if (m_category > cat) {
3865 // Set to the new position of the original category
3866 m_category--;
3871 void CPartFile::SetActive(bool bActive)
3873 time_t tNow = time(NULL);
3874 if (bActive) {
3875 if (theApp->IsConnected()) {
3876 if (m_tActivated == 0) {
3877 m_tActivated = tNow;
3880 } else {
3881 if (m_tActivated != 0) {
3882 m_nDlActiveTime += tNow - m_tActivated;
3883 m_tActivated = 0;
3889 uint32 CPartFile::GetDlActiveTime() const
3891 uint32 nDlActiveTime = m_nDlActiveTime;
3892 if (m_tActivated != 0) {
3893 nDlActiveTime += time(NULL) - m_tActivated;
3895 return nDlActiveTime;
3899 uint16 CPartFile::GetPartMetNumber() const
3901 long nr;
3902 return m_partmetfilename.RemoveAllExt().GetRaw().ToLong(&nr) ? nr : 0;
3906 void CPartFile::SetHashingProgress(uint16 part) const
3908 m_hashingProgress = part;
3909 Notify_DownloadCtrlUpdateItem(this);
3913 #ifndef CLIENT_GUI
3915 uint8 CPartFile::GetStatus(bool ignorepause) const
3917 if ( (!m_paused && !m_insufficient) ||
3918 status == PS_ERROR ||
3919 status == PS_COMPLETING ||
3920 status == PS_COMPLETE ||
3921 ignorepause) {
3922 return status;
3923 } else if ( m_insufficient ) {
3924 return PS_INSUFFICIENT;
3925 } else {
3926 return PS_PAUSED;
3930 void CPartFile::AddDeadSource(const CUpDownClient* client)
3932 m_deadSources.AddDeadSource( client );
3936 bool CPartFile::IsDeadSource(const CUpDownClient* client)
3938 return m_deadSources.IsDeadSource( client );
3941 void CPartFile::SetFileName(const CPath& fileName)
3943 CKnownFile* pFile = theApp->sharedfiles->GetFileByID(GetFileHash());
3945 bool is_shared = (pFile && pFile == this);
3947 if (is_shared) {
3948 // The file is shared, we must clear the search keywords so we don't
3949 // publish the old name anymore.
3950 theApp->sharedfiles->RemoveKeywords(this);
3953 CKnownFile::SetFileName(fileName);
3955 if (is_shared) {
3956 // And of course, we must advertise the new name if the file is shared.
3957 theApp->sharedfiles->AddKeywords(this);
3960 UpdateDisplayedInfo(true);
3964 uint16 CPartFile::GetMaxSources() const
3966 // This is just like this, while we don't import the private max sources per file
3967 return thePrefs::GetMaxSourcePerFile();
3971 uint16 CPartFile::GetMaxSourcePerFileSoft() const
3973 unsigned int temp = ((unsigned int)GetMaxSources() * 9L) / 10;
3974 if (temp > MAX_SOURCES_FILE_SOFT) {
3975 return MAX_SOURCES_FILE_SOFT;
3977 return temp;
3980 uint16 CPartFile::GetMaxSourcePerFileUDP() const
3982 unsigned int temp = ((unsigned int)GetMaxSources() * 3L) / 4;
3983 if (temp > MAX_SOURCES_FILE_UDP) {
3984 return MAX_SOURCES_FILE_UDP;
3986 return temp;
3989 #define DROP_FACTOR 2
3991 CUpDownClient* CPartFile::GetSlowerDownloadingClient(uint32 speed, CUpDownClient* caller) {
3992 // printf("Start slower source calculation\n");
3993 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
3994 CUpDownClient* cur_src = it++->GetClient();
3995 if ((cur_src->GetDownloadState() == DS_DOWNLOADING) && (cur_src != caller)) {
3996 uint32 factored_bytes_per_second = static_cast<uint32>(
3997 (cur_src->GetKBpsDown() * 1024) * DROP_FACTOR);
3998 if ( factored_bytes_per_second< speed) {
3999 // printf("Selecting source %p to drop: %d < %d\n", cur_src, factored_bytes_per_second, speed);
4000 // printf("End slower source calculation\n");
4001 return cur_src;
4002 } else {
4003 // printf("Not selecting source %p to drop: %d > %d\n", cur_src, factored_bytes_per_second, speed);
4007 // printf("End slower source calculation\n");
4008 return NULL;
4011 void CPartFile::AllocationFinished()
4013 // see if it can be opened
4014 if (!m_hpartfile.Open(m_PartPath, CFile::read_write)) {
4015 AddLogLineN(CFormat(_("ERROR: Failed to open partfile '%s'")) % GetFullName());
4016 SetStatus(PS_ERROR);
4018 // then close the handle again
4019 m_hpartfile.Release(true);
4022 #endif
4023 // File_checked_for_headers