Upstream tarball 9882
[amule.git] / src / PartFile.cpp
blob5948114f8b0b66a3cdc6b67f962e5a7dfdfec0bb
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #include <wx/wx.h>
28 #include "PartFile.h" // Interface declarations.
30 #ifdef HAVE_CONFIG_H
31 #include "config.h" // Needed for VERSION
32 #endif
34 #include <protocol/kad/Constants.h>
35 #include <protocol/ed2k/Client2Client/TCP.h>
36 #include <protocol/Protocols.h>
37 #include <common/DataFileVersion.h>
38 #include <common/Constants.h>
39 #include <tags/FileTags.h>
41 #include <wx/utils.h>
42 #include <wx/tokenzr.h> // Needed for wxStringTokenizer
44 #include "KnownFileList.h" // Needed for CKnownFileList
45 #include "UploadQueue.h" // Needed for CFileHash
46 #include "IPFilter.h" // Needed for CIPFilter
47 #include "Server.h" // Needed for CServer
48 #include "ServerConnect.h" // Needed for CServerConnect
49 #include "updownclient.h" // Needed for CUpDownClient
50 #include "MemFile.h" // Needed for CMemFile
51 #include "Preferences.h" // Needed for CPreferences
52 #include "DownloadQueue.h" // Needed for CDownloadQueue
53 #include "amule.h" // Needed for theApp
54 #include "ED2KLink.h" // Needed for CED2KLink
55 #include "Packet.h" // Needed for CTag
56 #include "SearchList.h" // Needed for CSearchFile
57 #include "ClientList.h" // Needed for clientlist
58 #include "Statistics.h" // Needed for theStats
59 #include "Logger.h"
60 #include <common/Format.h> // Needed for CFormat
61 #include <common/FileFunctions.h> // Needed for GetLastModificationTime
62 #include "ThreadTasks.h" // Needed for CHashingTask/CCompletionTask/CAllocateFileTask
63 #include "GuiEvents.h" // Needed for Notify_*
64 #include "DataToText.h" // Needed for OriginToText()
65 #include "PlatformSpecific.h" // Needed for CreateSparseFile()
66 #include "FileArea.h" // Needed for CFileArea
67 #include "ScopedPtr.h" // Needed for CScopedArray
68 #include "CorruptionBlackBox.h"
70 #include "kademlia/kademlia/Kademlia.h"
71 #include "kademlia/kademlia/Search.h"
74 SFileRating::SFileRating(const wxString &u, const wxString &f, sint16 r, const wxString &c)
76 UserName(u),
77 FileName(f),
78 Rating(r),
79 Comment(c)
84 SFileRating::SFileRating(const SFileRating &fr)
86 UserName(fr.UserName),
87 FileName(fr.FileName),
88 Rating(fr.Rating),
89 Comment(fr.Comment)
94 SFileRating::SFileRating(const CUpDownClient &client)
96 UserName(client.GetUserName()),
97 FileName(client.GetClientFilename()),
98 Rating(client.GetFileRating()),
99 Comment(client.GetFileComment())
104 SFileRating::~SFileRating()
109 class PartFileBufferedData
111 public:
112 CFileArea area; // File area to be written
113 uint64 start; // This is the start offset of the data
114 uint64 end; // This is the end offset of the data
115 Requested_Block_Struct *block; // This is the requested block that this data relates to
117 PartFileBufferedData(CFileAutoClose& file, byte * data, uint64 _start, uint64 _end, Requested_Block_Struct *_block)
118 : start(_start), end(_end), block(_block)
120 area.StartWriteAt(file, start, end-start+1);
121 memcpy(area.GetBuffer(), data, end-start+1);
126 typedef std::list<Chunk> ChunkList;
129 #ifndef CLIENT_GUI
131 CPartFile::CPartFile()
133 Init();
136 CPartFile::CPartFile(CSearchFile* searchresult)
138 Init();
140 m_abyFileHash = searchresult->GetFileHash();
141 SetFileName(searchresult->GetFileName());
142 SetFileSize(searchresult->GetFileSize());
144 for (unsigned int i = 0; i < searchresult->m_taglist.size(); ++i){
145 const CTag& pTag = searchresult->m_taglist[i];
147 bool bTagAdded = false;
148 if (pTag.GetNameID() == 0 && !pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
149 static const struct {
150 wxString pszName;
151 uint8 nType;
152 } _aMetaTags[] =
154 { wxT(FT_ED2K_MEDIA_ARTIST), 2 },
155 { wxT(FT_ED2K_MEDIA_ALBUM), 2 },
156 { wxT(FT_ED2K_MEDIA_TITLE), 2 },
157 { wxT(FT_ED2K_MEDIA_LENGTH), 2 },
158 { wxT(FT_ED2K_MEDIA_BITRATE), 3 },
159 { wxT(FT_ED2K_MEDIA_CODEC), 2 }
162 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
163 if ( pTag.GetType() == _aMetaTags[t].nType &&
164 (pTag.GetName() == _aMetaTags[t].pszName)) {
165 // skip string tags with empty string values
166 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
167 break;
170 // skip "length" tags with "0: 0" values
171 if (pTag.GetName() == wxT(FT_ED2K_MEDIA_LENGTH)) {
172 if (pTag.GetStr().IsSameAs(wxT("0: 0")) ||
173 pTag.GetStr().IsSameAs(wxT("0:0"))) {
174 break;
178 // skip "bitrate" tags with '0' values
179 if ((pTag.GetName() == wxT(FT_ED2K_MEDIA_BITRATE)) && !pTag.GetInt()) {
180 break;
183 AddDebugLogLineM( false, logPartFile,
184 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
185 pTag.GetFullInfo() );
186 m_taglist.push_back(pTag);
187 bTagAdded = true;
188 break;
191 } else if (pTag.GetNameID() != 0 && pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
192 static const struct {
193 uint8 nID;
194 uint8 nType;
195 } _aMetaTags[] =
197 { FT_FILETYPE, 2 },
198 { FT_FILEFORMAT, 2 }
200 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
201 if (pTag.GetType() == _aMetaTags[t].nType && pTag.GetNameID() == _aMetaTags[t].nID) {
202 // skip string tags with empty string values
203 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
204 break;
207 AddDebugLogLineM( false, logPartFile,
208 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
209 pTag.GetFullInfo() );
210 m_taglist.push_back(pTag);
211 bTagAdded = true;
212 break;
217 if (!bTagAdded) {
218 AddDebugLogLineM( false, logPartFile,
219 wxT("CPartFile::CPartFile(CSearchFile*): ignored tag ") +
220 pTag.GetFullInfo() );
224 CreatePartFile();
228 CPartFile::CPartFile(const CED2KFileLink* fileLink)
230 Init();
232 SetFileName(CPath(fileLink->GetName()));
233 SetFileSize(fileLink->GetSize());
234 m_abyFileHash = fileLink->GetHashKey();
236 CreatePartFile();
238 if (fileLink->m_hashset) {
239 if (!LoadHashsetFromFile(fileLink->m_hashset, true)) {
240 AddDebugLogLineM(true, logPartFile, wxT("eD2K link contained invalid hashset: ") + fileLink->GetLink());
246 CPartFile::~CPartFile()
248 // if it's not opened, it was completed or deleted
249 if (m_hpartfile.IsOpened()) {
250 FlushBuffer();
251 m_hpartfile.Close();
252 // Update met file (with current directory entry)
253 SavePartFile();
256 DeleteContents(m_BufferedData_list);
257 delete m_CorruptionBlackBox;
259 wxASSERT(m_SrcList.empty());
260 wxASSERT(m_A4AFsrclist.empty());
263 void CPartFile::CreatePartFile()
265 // use lowest free partfilenumber for free file (InterCeptor)
266 int i = 0;
267 do {
268 ++i;
269 m_partmetfilename = CPath(wxString::Format(wxT("%03i.part.met"), i));
270 m_fullname = thePrefs::GetTempDir().JoinPaths(m_partmetfilename);
271 } while (m_fullname.FileExists());
273 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
275 wxString strPartName = m_partmetfilename.RemoveExt().GetRaw();
276 m_taglist.push_back(CTagString(FT_PARTFILENAME, strPartName ));
278 m_gaplist.Init(GetFileSize(), true); // Init empty
280 m_PartPath = m_fullname.RemoveExt();
281 bool fileCreated;
282 if (thePrefs::GetAllocFullFile()) {
283 fileCreated = m_hpartfile.Create(m_PartPath, true);
284 m_hpartfile.Close();
285 } else {
286 fileCreated = PlatformSpecific::CreateSparseFile(m_PartPath, GetFileSize());
288 if (!fileCreated) {
289 AddLogLineM(false,_("ERROR: Failed to create partfile)"));
290 SetPartFileStatus(PS_ERROR);
293 SetFilePath(thePrefs::GetTempDir());
295 if (thePrefs::GetAllocFullFile()) {
296 SetPartFileStatus(PS_ALLOCATING);
297 CThreadScheduler::AddTask(new CAllocateFileTask(this, thePrefs::AddNewFilesPaused()));
298 } else {
299 AllocationFinished();
302 m_hashsetneeded = (GetED2KPartHashCount() > 0);
304 SavePartFile(true);
305 SetActive(theApp->IsConnected());
309 uint8 CPartFile::LoadPartFile(const CPath& in_directory, const CPath& filename, bool from_backup, bool getsizeonly)
311 bool isnewstyle = false;
312 uint8 version,partmettype=PMT_UNKNOWN;
314 std::map<uint16, Gap_Struct*> gap_map; // Slugfiller
315 transferred = 0;
317 m_partmetfilename = filename;
318 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
319 m_filePath = in_directory;
320 m_fullname = m_filePath.JoinPaths(m_partmetfilename);
321 m_PartPath = m_fullname.RemoveExt();
323 // readfile data form part.met file
324 CPath curMetFilename = m_fullname;
325 if (from_backup) {
326 curMetFilename = curMetFilename.AppendExt(PARTMET_BAK_EXT);
327 AddLogLineM(false, CFormat( _("Trying to load backup of met-file from %s") )
328 % curMetFilename );
331 try {
332 CFile metFile(curMetFilename, CFile::read);
333 if (!metFile.IsOpened()) {
334 AddLogLineM(false, CFormat( _("ERROR: Failed to open part.met file: %s ==> %s") )
335 % curMetFilename
336 % GetFileName() );
338 return false;
339 } else if (metFile.GetLength() == 0) {
340 AddLogLineM(false, CFormat( _("ERROR: part.met file is 0 size: %s ==> %s") )
341 % m_partmetfilename
342 % GetFileName() );
344 return false;
347 version = metFile.ReadUInt8();
348 if (version != PARTFILE_VERSION && version != PARTFILE_SPLITTEDVERSION && version != PARTFILE_VERSION_LARGEFILE){
349 metFile.Close();
350 //if (version == 83) return ImportShareazaTempFile(...)
351 AddLogLineM(false, CFormat( _("ERROR: Invalid part.met file version: %s ==> %s") )
352 % m_partmetfilename
353 % GetFileName() );
354 return false;
357 isnewstyle = (version == PARTFILE_SPLITTEDVERSION);
358 partmettype = isnewstyle ? PMT_SPLITTED : PMT_DEFAULTOLD;
360 if (version == PARTFILE_VERSION) {// Do we still need this check ?
361 uint8 test[4]; // It will fail for certain files.
362 metFile.Seek(24, wxFromStart);
363 metFile.Read(test,4);
365 metFile.Seek(1, wxFromStart);
366 if (test[0]==0 && test[1]==0 && test[2]==2 && test[3]==1) {
367 isnewstyle=true; // edonkeys so called "old part style"
368 partmettype=PMT_NEWOLD;
372 if (isnewstyle) {
373 uint32 temp = metFile.ReadUInt32();
375 if (temp==0) { // 0.48 partmets - different again
376 LoadHashsetFromFile(&metFile, false);
377 } else {
378 metFile.Seek(2, wxFromStart);
379 LoadDateFromFile(&metFile);
380 m_abyFileHash = metFile.ReadHash();
383 } else {
384 LoadDateFromFile(&metFile);
385 LoadHashsetFromFile(&metFile, false);
388 uint32 tagcount = metFile.ReadUInt32();
390 for (uint32 j = 0; j < tagcount; ++j) {
391 CTag newtag(metFile,true);
392 if ( !getsizeonly ||
393 (getsizeonly &&
394 (newtag.GetNameID() == FT_FILESIZE ||
395 newtag.GetNameID() == FT_FILENAME))) {
396 switch(newtag.GetNameID()) {
397 case FT_FILENAME: {
398 if (!GetFileName().IsOk()) {
399 // If it's not empty, we already loaded the unicoded one
400 SetFileName(CPath(newtag.GetStr()));
402 break;
404 case FT_LASTSEENCOMPLETE: {
405 lastseencomplete = newtag.GetInt();
406 break;
408 case FT_FILESIZE: {
409 SetFileSize(newtag.GetInt());
410 break;
412 case FT_TRANSFERRED: {
413 transferred = newtag.GetInt();
414 break;
416 case FT_FILETYPE:{
417 //#warning needs setfiletype string
418 //SetFileType(newtag.GetStr());
419 break;
421 case FT_CATEGORY: {
422 m_category = newtag.GetInt();
423 if (m_category > theApp->glob_prefs->GetCatCount() - 1 ) {
424 m_category = 0;
426 break;
428 case FT_OLDDLPRIORITY:
429 case FT_DLPRIORITY: {
430 if (!isnewstyle){
431 m_iDownPriority = newtag.GetInt();
432 if( m_iDownPriority == PR_AUTO ){
433 m_iDownPriority = PR_HIGH;
434 SetAutoDownPriority(true);
436 else{
437 if ( m_iDownPriority != PR_LOW &&
438 m_iDownPriority != PR_NORMAL &&
439 m_iDownPriority != PR_HIGH)
440 m_iDownPriority = PR_NORMAL;
441 SetAutoDownPriority(false);
444 break;
446 case FT_STATUS: {
447 m_paused = (newtag.GetInt() == 1);
448 m_stopped = m_paused;
449 break;
451 case FT_OLDULPRIORITY:
452 case FT_ULPRIORITY: {
453 if (!isnewstyle){
454 SetUpPriority(newtag.GetInt(), false);
455 if( GetUpPriority() == PR_AUTO ){
456 SetUpPriority(PR_HIGH, false);
457 SetAutoUpPriority(true);
458 } else {
459 SetAutoUpPriority(false);
462 break;
464 case FT_KADLASTPUBLISHSRC:{
465 SetLastPublishTimeKadSrc(newtag.GetInt(), 0);
466 if(GetLastPublishTimeKadSrc() > (uint32)time(NULL)+KADEMLIAREPUBLISHTIMES) {
467 //There may be a posibility of an older client that saved a random number here.. This will check for that..
468 SetLastPublishTimeKadSrc(0,0);
470 break;
472 case FT_KADLASTPUBLISHNOTES:{
473 SetLastPublishTimeKadNotes(newtag.GetInt());
474 break;
476 // old tags: as long as they are not needed, take the chance to purge them
477 case FT_PERMISSIONS:
478 case FT_KADLASTPUBLISHKEY:
479 break;
480 case FT_DL_ACTIVE_TIME:
481 if (newtag.IsInt()) {
482 m_nDlActiveTime = newtag.GetInt();
484 break;
485 case FT_CORRUPTEDPARTS: {
486 wxASSERT(m_corrupted_list.empty());
487 wxString strCorruptedParts(newtag.GetStr());
488 wxStringTokenizer tokenizer(strCorruptedParts, wxT(","));
489 while ( tokenizer.HasMoreTokens() ) {
490 wxString token = tokenizer.GetNextToken();
491 unsigned long uPart;
492 if (token.ToULong(&uPart)) {
493 if (uPart < GetPartCount() && !IsCorruptedPart(uPart)) {
494 m_corrupted_list.push_back(uPart);
498 break;
500 case FT_AICH_HASH:{
501 CAICHHash hash;
502 bool hashSizeOk =
503 hash.DecodeBase32(newtag.GetStr()) == CAICHHash::GetHashSize();
504 wxASSERT(hashSizeOk);
505 if (hashSizeOk) {
506 m_pAICHHashSet->SetMasterHash(hash, AICH_VERIFIED);
508 break;
510 case FT_ATTRANSFERRED:{
511 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (uint64)newtag.GetInt());
512 break;
514 case FT_ATTRANSFERREDHI:{
515 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (((uint64)newtag.GetInt()) << 32));
516 break;
518 case FT_ATREQUESTED:{
519 statistic.SetAllTimeRequests(newtag.GetInt());
520 break;
522 case FT_ATACCEPTED:{
523 statistic.SetAllTimeAccepts(newtag.GetInt());
524 break;
526 default: {
527 // Start Changes by Slugfiller for better exception handling
529 wxCharBuffer tag_ansi_name = newtag.GetName().ToAscii();
530 char gap_mark = tag_ansi_name ? tag_ansi_name[0u] : 0;
531 if ( newtag.IsInt() && (newtag.GetName().Length() > 1) &&
532 ((gap_mark == FT_GAPSTART) ||
533 (gap_mark == FT_GAPEND))) {
534 Gap_Struct *gap = NULL;
535 unsigned long int gapkey;
536 if (newtag.GetName().Mid(1).ToULong(&gapkey)) {
537 if ( gap_map.find( gapkey ) == gap_map.end() ) {
538 gap = new Gap_Struct;
539 gap_map[gapkey] = gap;
540 gap->start = (uint64)-1;
541 gap->end = (uint64)-1;
542 } else {
543 gap = gap_map[ gapkey ];
545 if (gap_mark == FT_GAPSTART) {
546 gap->start = newtag.GetInt();
548 if (gap_mark == FT_GAPEND) {
549 gap->end = newtag.GetInt()-1;
551 } else {
552 AddDebugLogLineN(logPartFile, wxT("Wrong gap map key while reading met file!"));
553 wxFAIL;
555 // End Changes by Slugfiller for better exception handling
556 } else {
557 m_taglist.push_back(newtag);
561 } else {
562 // Nothing. Else, nothing.
566 // load the hashsets from the hybridstylepartmet
567 if (isnewstyle && !getsizeonly && (metFile.GetPosition()<metFile.GetLength()) ) {
568 metFile.Seek(1, wxFromCurrent);
570 uint16 parts=GetPartCount(); // assuming we will get all hashsets
572 for (uint16 i = 0; i < parts && (metFile.GetPosition()+16<metFile.GetLength()); ++i){
573 CMD4Hash cur_hash = metFile.ReadHash();
574 m_hashlist.push_back(cur_hash);
577 CMD4Hash checkhash;
578 if (!m_hashlist.empty()) {
579 CreateHashFromHashlist(m_hashlist, &checkhash);
581 bool flag=false;
582 if (m_abyFileHash == checkhash) {
583 flag=true;
584 } else {
585 m_hashlist.clear();
586 flag=false;
589 } catch (const CInvalidPacket& e) {
590 AddLogLineM(true, CFormat(wxT("Error: %s (%s) is corrupt (bad tags: %s), unable to load file."))
591 % m_partmetfilename
592 % GetFileName()
593 % e.what());
594 return false;
595 } catch (const CIOFailureException& e) {
596 AddDebugLogLineM(true, logPartFile, CFormat( wxT("IO failure while loading '%s': %s") )
597 % m_partmetfilename
598 % e.what() );
599 return false;
600 } catch (const CEOFException& WXUNUSED(e)) {
601 AddLogLineM(true, CFormat( _("ERROR: %s (%s) is corrupt (wrong tagcount), unable to load file.") )
602 % m_partmetfilename
603 % GetFileName() );
604 AddLogLineM(true, _("Trying to recover file info..."));
606 // Safe file is that who have
607 // - FileSize
608 if (GetFileSize()) {
609 // We have filesize, try other needed info
611 // Do we need to check gaps? I think not,
612 // because they are checked below. Worst
613 // scenario will only mark file as 0 bytes downloaded.
615 // -Filename
616 if (!GetFileName().IsOk()) {
617 // Not critical, let's put a random filename.
618 AddLogLineM(true, _(
619 "Recovering no-named file - will try to recover it as RecoveredFile.dat"));
620 SetFileName(CPath(wxT("RecoveredFile.dat")));
623 AddLogLineM(true,
624 _("Recovered all available file info :D - Trying to use it..."));
625 } else {
626 AddLogLineM(true, _("Unable to recover file info :("));
627 return false;
631 if (getsizeonly) {
632 return partmettype;
634 // Init Gaplist
635 m_gaplist.Init(GetFileSize(), false); // Init full, then add gaps
636 // Now to flush the map into the list (Slugfiller)
637 std::map<uint16, Gap_Struct*>::iterator it = gap_map.begin();
638 for ( ; it != gap_map.end(); ++it ) {
639 Gap_Struct* gap = it->second;
640 // SLUGFILLER: SafeHash - revised code, and extra safety
641 if ( (gap->start != (uint64)-1) &&
642 (gap->end != (uint64)-1) &&
643 gap->start <= gap->end &&
644 gap->start < GetFileSize()) {
645 if (gap->end >= GetFileSize()) {
646 gap->end = GetFileSize()-1; // Clipping
648 m_gaplist.AddGap(gap->start, gap->end); // All tags accounted for, use safe adding
650 delete gap;
651 // SLUGFILLER: SafeHash
654 //check if this is a backup
655 if ( m_fullname.GetExt().MakeLower() == wxT("backup" )) {
656 m_fullname = m_fullname.RemoveExt();
659 // open permanent handle
660 if ( !m_hpartfile.Open(m_PartPath, CFile::read_write)) {
661 AddLogLineM(false, CFormat( _("Failed to open %s (%s)") )
662 % m_fullname
663 % GetFileName() );
664 return false;
667 SetPartFileStatus(PS_EMPTY);
669 try {
670 // SLUGFILLER: SafeHash - final safety, make sure any missing part of the file is gap
671 if (m_hpartfile.GetLength() < GetFileSize())
672 AddGap(m_hpartfile.GetLength(), GetFileSize()-1);
673 // Goes both ways - Partfile should never be too large
674 if (m_hpartfile.GetLength() > GetFileSize()) {
675 AddDebugLogLineM( true, logPartFile, CFormat( wxT("Partfile \"%s\" is too large! Truncating %llu bytes.") ) % GetFileName() % (m_hpartfile.GetLength() - GetFileSize()));
676 m_hpartfile.SetLength(GetFileSize());
678 // SLUGFILLER: SafeHash
679 } catch (const CIOFailureException& e) {
680 AddDebugLogLineM( true, logPartFile, CFormat( wxT("Error while accessing partfile \"%s\": %s") ) % GetFileName() % e.what());
681 SetPartFileStatus(PS_ERROR);
684 // now close the file again until needed
685 m_hpartfile.Release(true);
687 // check hashcount, file status etc
688 if (GetHashCount() != GetED2KPartHashCount()){
689 m_hashsetneeded = true;
690 return true;
691 } else {
692 m_hashsetneeded = false;
693 for (size_t i = 0; i < m_hashlist.size(); ++i) {
694 if (IsComplete(i)) {
695 SetPartFileStatus(PS_READY);
700 if (m_gaplist.IsComplete()) { // is this file complete already?
701 CompleteFile(false);
702 return true;
705 if (!isnewstyle) { // not for importing
706 const time_t file_date = CPath::GetModificationTime(m_PartPath);
707 if (m_lastDateChanged != file_date) {
708 // It's pointless to rehash an empty file, since the case
709 // where a user has zero'd a file is handled above ...
710 if (m_hpartfile.GetLength()) {
711 AddLogLineM(false, CFormat( _("WARNING: %s might be corrupted (%i)") )
712 % m_PartPath
713 % (m_lastDateChanged - file_date) );
714 // rehash
715 SetPartFileStatus(PS_WAITINGFORHASH);
717 CPath partFileName = m_partmetfilename.RemoveExt();
718 CThreadScheduler::AddTask(new CHashingTask(m_filePath, partFileName, this));
723 UpdateCompletedInfos();
724 if (completedsize > transferred) {
725 m_iGainDueToCompression = completedsize - transferred;
726 } else if (completedsize != transferred) {
727 m_iLostDueToCorruption = transferred - completedsize;
730 return true;
734 bool CPartFile::SavePartFile(bool Initial)
736 switch (status) {
737 case PS_WAITINGFORHASH:
738 case PS_HASHING:
739 case PS_COMPLETE:
740 return false;
743 /* Don't write anything to disk if less than 100 KB of free space is left. */
744 sint64 free = CPath::GetFreeSpaceAt(GetFilePath());
745 if ((free != wxInvalidOffset) && (free < (100 * 1024))) {
746 return false;
749 CFile file;
750 try {
751 if (!m_PartPath.FileExists()) {
752 throw wxString(wxT(".part file not found"));
755 uint32 lsc = lastseencomplete;
757 if (!Initial) {
758 CPath::BackupFile(m_fullname, wxT(".backup"));
759 CPath::RemoveFile(m_fullname);
762 file.Open(m_fullname, CFile::write);
763 if (!file.IsOpened()) {
764 throw wxString(wxT("Failed to open part.met file"));
767 // version
768 file.WriteUInt8(IsLargeFile() ? PARTFILE_VERSION_LARGEFILE : PARTFILE_VERSION);
770 file.WriteUInt32(CPath::GetModificationTime(m_PartPath));
771 // hash
772 file.WriteHash(m_abyFileHash);
773 uint16 parts = m_hashlist.size();
774 file.WriteUInt16(parts);
775 for (int x = 0; x < parts; ++x) {
776 file.WriteHash(m_hashlist[x]);
778 // tags
779 #define FIXED_TAGS 15
780 uint32 tagcount = m_taglist.size() + FIXED_TAGS + (m_gaplist.size()*2);
781 if (!m_corrupted_list.empty()) {
782 ++tagcount;
785 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
786 ++tagcount;
789 if (GetLastPublishTimeKadSrc()){
790 ++tagcount;
793 if (GetLastPublishTimeKadNotes()){
794 ++tagcount;
797 if (GetDlActiveTime()){
798 ++tagcount;
801 file.WriteUInt32(tagcount);
803 //#warning Kry - Where are lost by coruption and gained by compression?
805 // 0 (unicoded part file name)
806 // We write it with BOM to keep eMule compatibility. Note that the 'printable' filename is saved,
807 // as presently the filename does not represent an actual file.
808 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file, utf8strOptBOM );
809 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file ); // 1
811 CTagIntSized( FT_FILESIZE, GetFileSize(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file );// 2
812 CTagIntSized( FT_TRANSFERRED, transferred, IsLargeFile() ? 64 : 32).WriteTagToFile( &file ); // 3
813 CTagInt32( FT_STATUS, (m_paused?1:0)).WriteTagToFile( &file ); // 4
815 if ( IsAutoDownPriority() ) {
816 CTagInt32( FT_DLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 5
817 CTagInt32( FT_OLDDLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 6
818 } else {
819 CTagInt32( FT_DLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 5
820 CTagInt32( FT_OLDDLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 6
823 CTagInt32( FT_LASTSEENCOMPLETE, lsc ).WriteTagToFile( &file ); // 7
825 if ( IsAutoUpPriority() ) {
826 CTagInt32( FT_ULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 8
827 CTagInt32( FT_OLDULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 9
828 } else {
829 CTagInt32( FT_ULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 8
830 CTagInt32( FT_OLDULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 9
833 CTagInt32(FT_CATEGORY, m_category).WriteTagToFile( &file ); // 10
834 CTagInt32(FT_ATTRANSFERRED, statistic.GetAllTimeTransferred() & 0xFFFFFFFF).WriteTagToFile( &file );// 11
835 CTagInt32(FT_ATTRANSFERREDHI, statistic.GetAllTimeTransferred() >>32).WriteTagToFile( &file );// 12
836 CTagInt32(FT_ATREQUESTED, statistic.GetAllTimeRequests()).WriteTagToFile( &file ); // 13
837 CTagInt32(FT_ATACCEPTED, statistic.GetAllTimeAccepts()).WriteTagToFile( &file ); // 14
839 // currupt part infos
840 if (!m_corrupted_list.empty()) {
841 wxString strCorruptedParts;
842 std::list<uint16>::iterator it = m_corrupted_list.begin();
843 for (; it != m_corrupted_list.end(); ++it) {
844 uint16 uCorruptedPart = *it;
845 if (!strCorruptedParts.IsEmpty()) {
846 strCorruptedParts += wxT(",");
848 strCorruptedParts += wxString::Format(wxT("%u"), (unsigned)uCorruptedPart);
850 wxASSERT( !strCorruptedParts.IsEmpty() );
852 CTagString( FT_CORRUPTEDPARTS, strCorruptedParts ).WriteTagToFile( &file); // 11?
855 //AICH Filehash
856 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
857 CTagString aichtag(FT_AICH_HASH, m_pAICHHashSet->GetMasterHash().GetString() );
858 aichtag.WriteTagToFile(&file); // 12?
861 if (GetLastPublishTimeKadSrc()){
862 CTagInt32(FT_KADLASTPUBLISHSRC, GetLastPublishTimeKadSrc()).WriteTagToFile(&file); // 15?
865 if (GetLastPublishTimeKadNotes()){
866 CTagInt32(FT_KADLASTPUBLISHNOTES, GetLastPublishTimeKadNotes()).WriteTagToFile(&file); // 16?
869 if (GetDlActiveTime()){
870 CTagInt32(FT_DL_ACTIVE_TIME, GetDlActiveTime()).WriteTagToFile(&file); // 17
873 for (uint32 j = 0; j < (uint32)m_taglist.size();++j) {
874 m_taglist[j].WriteTagToFile(&file);
877 // gaps
878 unsigned i_pos = 0;
879 for (CGapList::const_iterator it = m_gaplist.begin(); it != m_gaplist.end(); ++it) {
880 wxString tagName = wxString::Format(wxT(" %u"), i_pos);
882 // gap start = first missing byte but gap ends = first non-missing byte
883 // in edonkey but I think its easier to user the real limits
884 tagName[0] = FT_GAPSTART;
885 CTagIntSized(tagName, it.start() , IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
887 tagName[0] = FT_GAPEND;
888 CTagIntSized(tagName, it.end() + 1, IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
890 ++i_pos;
892 } catch (const wxString& error) {
893 AddLogLineNS(CFormat( _("ERROR while saving partfile: %s (%s ==> %s)") )
894 % error
895 % m_partmetfilename
896 % GetFileName() );
898 return false;
899 } catch (const CIOFailureException& e) {
900 AddLogLineCS(_("IO failure while saving partfile: ") + e.what());
902 return false;
905 file.Close();
907 if (!Initial) {
908 CPath::RemoveFile(m_fullname.AppendExt(wxT(".backup")));
911 sint64 metLength = m_fullname.GetFileSize();
912 if (metLength == wxInvalidOffset) {
913 theApp->ShowAlert( CFormat( _("Could not retrieve length of '%s' - using %s file.") )
914 % m_fullname
915 % PARTMET_BAK_EXT,
916 _("Message"), wxOK);
918 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
919 } else if (metLength == 0) {
920 // Don't backup if it's 0 size but raise a warning!!!
921 theApp->ShowAlert( CFormat( _("'%s' is 0 size somehow - using %s file.") )
922 % m_fullname
923 % PARTMET_BAK_EXT,
924 _("Message"), wxOK);
926 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
927 } else {
928 // no error, just backup
929 CPath::BackupFile(m_fullname, PARTMET_BAK_EXT);
932 return true;
936 void CPartFile::SaveSourceSeeds()
938 #define MAX_SAVED_SOURCES 10
940 // Kry - Sources seeds
941 // Based on a Feature request, this saves the last MAX_SAVED_SOURCES
942 // sources of the file, giving a 'seed' for the next run.
943 // We save the last sources because:
944 // 1 - They could be the hardest to get
945 // 2 - They will more probably be available
946 // However, if we have downloading sources, they have preference because
947 // we probably have more credits on them.
948 // Anyway, source exchange will get us the rest of the sources
949 // This feature is currently used only on rare files (< 20 sources)
952 if (GetSourceCount()>20) {
953 return;
956 CClientPtrList source_seeds;
957 int n_sources = 0;
959 CClientPtrList::iterator it = m_downloadingSourcesList.begin();
960 for( ; it != m_downloadingSourcesList.end() && n_sources < MAX_SAVED_SOURCES; ++it) {
961 CUpDownClient *cur_src = *it;
962 if (!cur_src->HasLowID()) {
963 source_seeds.push_back(cur_src);
964 ++n_sources;
968 if (n_sources < MAX_SAVED_SOURCES) {
969 // Not enough downloading sources to fill the list, going to sources list
970 if (GetSourceCount() > 0) {
971 SourceSet::reverse_iterator rit = m_SrcList.rbegin();
972 for ( ; ((rit != m_SrcList.rend()) && (n_sources<MAX_SAVED_SOURCES)); ++rit) {
973 CUpDownClient* cur_src = *rit;
974 if (!cur_src->HasLowID()) {
975 source_seeds.push_back(cur_src);
976 ++n_sources;
982 // Write the file
983 if (!n_sources) {
984 return;
987 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
989 CFile file;
990 file.Create(seedsPath, true);
991 if (!file.IsOpened()) {
992 AddLogLineM(false, CFormat( _("Failed to save part.met.seeds file for %s") )
993 % m_fullname);
994 return;
997 try {
998 file.WriteUInt8(0); // v3, to avoid v2 clients choking on it.
999 file.WriteUInt8(source_seeds.size());
1001 CClientPtrList::iterator it2 = source_seeds.begin();
1002 for (; it2 != source_seeds.end(); ++it2) {
1003 CUpDownClient* cur_src = *it2;
1004 file.WriteUInt32(cur_src->GetUserIDHybrid());
1005 file.WriteUInt16(cur_src->GetUserPort());
1006 file.WriteHash(cur_src->GetUserHash());
1007 // CryptSettings - See SourceExchange V4
1008 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
1009 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
1010 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
1011 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
1012 file.WriteUInt8(byCryptOptions);
1015 /* v2: Added to keep track of too old seeds */
1016 file.WriteUInt32(wxDateTime::Now().GetTicks());
1018 AddLogLineM(false, CFormat( wxPLURAL("Saved %i source seed for partfile: %s (%s)", "Saved %i source seeds for partfile: %s (%s)", n_sources) )
1019 % n_sources
1020 % m_fullname
1021 % GetFileName());
1022 } catch (const CIOFailureException& e) {
1023 AddDebugLogLineM(true, logPartFile, CFormat( wxT("Error saving partfile's seeds file (%s - %s): %s") )
1024 % m_partmetfilename
1025 % GetFileName()
1026 % e.what() );
1028 n_sources = 0;
1029 file.Close();
1030 CPath::RemoveFile(seedsPath);
1034 void CPartFile::LoadSourceSeeds()
1036 CMemFile sources_data;
1038 bool valid_sources = false;
1040 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
1041 if (!seedsPath.FileExists()) {
1042 return;
1045 CFile file(seedsPath, CFile::read);
1046 if (!file.IsOpened()) {
1047 AddLogLineM(false, CFormat( _("Partfile %s (%s) has no seeds file") )
1048 % m_partmetfilename
1049 % GetFileName() );
1050 return;
1054 try {
1055 if (file.GetLength() <= 1) {
1056 AddLogLineM(false, CFormat( _("Partfile %s (%s) has a void seeds file") )
1057 % m_partmetfilename
1058 % GetFileName() );
1059 return;
1062 uint8 src_count = file.ReadUInt8();
1064 bool bUseSX2Format = (src_count == 0);
1066 if (bUseSX2Format) {
1067 // v3 sources seeds
1068 src_count = file.ReadUInt8();
1071 sources_data.WriteUInt16(src_count);
1073 for (int i = 0; i< src_count; ++i) {
1074 uint32 dwID = file.ReadUInt32();
1075 uint16 nPort = file.ReadUInt16();
1077 sources_data.WriteUInt32(bUseSX2Format ? dwID : wxUINT32_SWAP_ALWAYS(dwID));
1078 sources_data.WriteUInt16(nPort);
1079 sources_data.WriteUInt32(0);
1080 sources_data.WriteUInt16(0);
1082 if (bUseSX2Format) {
1083 sources_data.WriteHash(file.ReadHash());
1084 sources_data.WriteUInt8(file.ReadUInt8());
1089 if (!file.Eof()) {
1091 // v2: Added to keep track of too old seeds
1092 time_t time = (time_t)file.ReadUInt32();
1094 // Time frame is 2 hours. More than enough to compile
1095 // your new aMule version!.
1096 if ((time + MIN2S(120)) >= wxDateTime::Now().GetTicks()) {
1097 valid_sources = true;
1100 } else {
1101 // v1 has no time data. We can safely use
1102 // the sources, next time will be saved.
1103 valid_sources = true;
1106 if (valid_sources) {
1107 sources_data.Seek(0);
1108 AddClientSources(&sources_data, SF_SOURCE_SEEDS, bUseSX2Format ? 4 : 1, bUseSX2Format);
1111 } catch (const CSafeIOException& e) {
1112 AddLogLineM(false, CFormat( _("Error reading partfile's seeds file (%s - %s): %s") )
1113 % m_partmetfilename
1114 % GetFileName()
1115 % e.what() );
1118 file.Close();
1121 void CPartFile::PartFileHashFinished(CKnownFile* result)
1123 m_lastDateChanged = result->m_lastDateChanged;
1124 bool errorfound = false;
1125 if (GetED2KPartHashCount() == 0){
1126 if (IsComplete(0, GetFileSize()-1)){
1127 if (result->GetFileHash() != GetFileHash()){
1128 AddLogLineM(false,
1129 CFormat(wxPLURAL(
1130 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1131 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1136 % GetFileName()
1137 % result->GetFileHash().Encode()
1138 % GetFileHash().Encode() );
1139 AddGap(0, GetFileSize()-1);
1140 errorfound = true;
1144 else{
1145 for (size_t i = 0; i < m_hashlist.size(); ++i){
1146 // Kry - trel_ar's completed parts check on rehashing.
1147 // Very nice feature, if a file is completed but .part.met don't believe it,
1148 // update it.
1150 uint64 partStart = i * PARTSIZE;
1151 uint64 partEnd = partStart + GetPartSize(i) - 1;
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 AddLogLineM(false,
1159 CFormat(wxPLURAL(
1160 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1161 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1162 GetED2KPartHashCount())
1164 % ( i + 1 )
1165 % GetED2KPartHashCount()
1166 % GetFileName()
1167 % wronghash.Encode()
1168 % GetPartHash(i).Encode() );
1170 AddGap(i);
1171 errorfound = true;
1173 } else {
1174 if (!IsComplete(i)){
1175 AddLogLineM(false, CFormat( _("Found completed part (%i) in %s") )
1176 % ( i + 1 )
1177 % GetFileName() );
1179 FillGap(i);
1180 RemoveBlockFromList(partStart, partEnd);
1186 if ( !errorfound &&
1187 result->GetAICHHashset()->GetStatus() == AICH_HASHSETCOMPLETE &&
1188 status == PS_COMPLETING) {
1189 delete m_pAICHHashSet;
1190 m_pAICHHashSet = result->GetAICHHashset();
1191 result->SetAICHHashset(NULL);
1192 m_pAICHHashSet->SetOwner(this);
1194 else if (status == PS_COMPLETING) {
1195 AddDebugLogLineM(false, logPartFile,
1196 CFormat(wxT("Failed to store new AICH Hashset for completed file: %s"))
1197 % GetFileName());
1201 delete result;
1202 if (!errorfound){
1203 if (status == PS_COMPLETING){
1204 CompleteFile(true);
1205 return;
1207 else {
1208 AddLogLineM(false, CFormat( _("Finished rehashing %s") ) % GetFileName());
1211 else{
1212 SetStatus(PS_READY);
1213 SavePartFile();
1214 return;
1216 SetStatus(PS_READY);
1217 SavePartFile();
1218 theApp->sharedfiles->SafeAddKFile(this);
1221 void CPartFile::AddGap(uint64 start, uint64 end)
1223 m_gaplist.AddGap(start, end);
1224 UpdateDisplayedInfo();
1227 void CPartFile::AddGap(uint16 part)
1229 m_gaplist.AddGap(part);
1230 UpdateDisplayedInfo();
1233 bool CPartFile::IsAlreadyRequested(uint64 start, uint64 end)
1235 std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
1236 for (; it != m_requestedblocks_list.end(); ++it) {
1237 Requested_Block_Struct* cur_block = *it;
1239 if ((start <= cur_block->EndOffset) && (end >= cur_block->StartOffset)) {
1240 return true;
1243 return false;
1246 bool CPartFile::GetNextEmptyBlockInPart(uint16 partNumber, Requested_Block_Struct *result)
1248 // Find start of this part
1249 uint64 partStart = (PARTSIZE * partNumber);
1250 uint64 start = partStart;
1252 // What is the end limit of this block, i.e. can't go outside part (or filesize)
1253 uint64 partEnd = partStart + GetPartSize(partNumber) - 1;
1254 // Loop until find a suitable gap and return true, or no more gaps and return false
1255 CGapList::const_iterator it = m_gaplist.begin();
1256 while (true) {
1257 bool noGap = true;
1258 uint64 gapStart, end;
1260 // Find the first gap from the start position
1261 for (; it != m_gaplist.end(); ++it) {
1262 gapStart = it.start();
1263 end = it.end();
1265 // Want gaps that overlap start<->partEnd
1266 if (gapStart <= partEnd && end >= start) {
1267 noGap = false;
1268 break;
1269 } else if (gapStart > partEnd) {
1270 break;
1274 // If no gaps after start, exit
1275 if (noGap) {
1276 return false;
1278 // Update start position if gap starts after current pos
1279 if (start < gapStart) {
1280 start = gapStart;
1282 // Find end, keeping within the max block size and the part limit
1283 uint64 blockLimit = partStart + (BLOCKSIZE * (((start - partStart) / BLOCKSIZE) + 1)) - 1;
1284 if (end > blockLimit) {
1285 end = blockLimit;
1287 if (end > partEnd) {
1288 end = partEnd;
1290 // If this gap has not already been requested, we have found a valid entry
1291 if (!IsAlreadyRequested(start, end)) {
1292 // Was this block to be returned
1293 if (result != NULL) {
1294 result->StartOffset = start;
1295 result->EndOffset = end;
1296 md4cpy(result->FileID, GetFileHash().GetHash());
1297 result->transferred = 0;
1299 return true;
1300 } else {
1301 // Reposition to end of that gap
1302 start = end + 1;
1304 // If tried all gaps then break out of the loop
1305 if (end == partEnd) {
1306 break;
1309 // No suitable gap found
1310 return false;
1314 void CPartFile::FillGap(uint64 start, uint64 end)
1316 m_gaplist.FillGap(start, end);
1317 UpdateCompletedInfos();
1318 UpdateDisplayedInfo();
1321 void CPartFile::FillGap(uint16 part)
1323 m_gaplist.FillGap(part);
1324 UpdateCompletedInfos();
1325 UpdateDisplayedInfo();
1329 void CPartFile::UpdateCompletedInfos()
1331 uint64 allgaps = m_gaplist.GetGapSize();
1333 percentcompleted = (1.0 - (double)allgaps/GetFileSize()) * 100.0;
1334 completedsize = GetFileSize() - allgaps;
1338 void CPartFile::WritePartStatus(CMemFile* file)
1340 uint16 parts = GetED2KPartCount();
1341 file->WriteUInt16(parts);
1342 uint16 done = 0;
1343 while (done != parts){
1344 uint8 towrite = 0;
1345 for (uint32 i = 0;i != 8;++i) {
1346 if (IsComplete(done)) {
1347 towrite |= (1<<i);
1349 ++done;
1350 if (done == parts) {
1351 break;
1354 file->WriteUInt8(towrite);
1358 void CPartFile::WriteCompleteSourcesCount(CMemFile* file)
1360 file->WriteUInt16(m_nCompleteSourcesCount);
1363 uint32 CPartFile::Process(uint32 reducedownload/*in percent*/,uint8 m_icounter)
1365 uint16 old_trans;
1366 uint32 dwCurTick = ::GetTickCount();
1368 // If buffer size exceeds limit, or if not written within time limit, flush data
1369 if ( (m_nTotalBufferData > thePrefs::GetFileBufferSize()) ||
1370 (dwCurTick > (m_nLastBufferFlushTime + BUFFER_TIME_LIMIT))) {
1371 // Avoid flushing while copying preview file
1372 if (!m_bPreviewing) {
1373 FlushBuffer();
1378 // check if we want new sources from server --> MOVED for 16.40 version
1379 old_trans=transferingsrc;
1380 transferingsrc = 0;
1381 kBpsDown = 0.0;
1383 if (m_icounter < 10) {
1384 // Update only downloading sources.
1385 CClientPtrList::iterator it = m_downloadingSourcesList.begin();
1386 for( ; it != m_downloadingSourcesList.end(); ) {
1387 CUpDownClient *cur_src = *it++;
1388 if(cur_src->GetDownloadState() == DS_DOWNLOADING) {
1389 ++transferingsrc;
1390 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1393 } else {
1394 // Update all sources (including downloading sources)
1395 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
1396 CUpDownClient* cur_src = *it++;
1397 switch (cur_src->GetDownloadState()) {
1398 case DS_DOWNLOADING: {
1399 ++transferingsrc;
1400 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1401 break;
1403 case DS_BANNED: {
1404 break;
1406 case DS_ERROR: {
1407 break;
1409 case DS_LOWTOLOWIP: {
1410 if (cur_src->HasLowID() && !theApp->CanDoCallback(cur_src)) {
1411 // If we are almost maxed on sources,
1412 // slowly remove these client to see
1413 // if we can find a better source.
1414 if (((dwCurTick - lastpurgetime) > 30000) &&
1415 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8))) {
1416 RemoveSource(cur_src);
1417 lastpurgetime = dwCurTick;
1418 break;
1420 } else {
1421 cur_src->SetDownloadState(DS_ONQUEUE);
1424 break;
1426 case DS_NONEEDEDPARTS: {
1427 // we try to purge noneeded source, even without reaching the limit
1428 if((dwCurTick - lastpurgetime) > 40000) {
1429 if(!cur_src->SwapToAnotherFile(false , false, false , NULL)) {
1430 //however we only delete them if reaching the limit
1431 if (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) {
1432 RemoveSource(cur_src);
1433 lastpurgetime = dwCurTick;
1434 break; //Johnny-B - nothing more to do here (good eye!)
1436 } else {
1437 lastpurgetime = dwCurTick;
1438 break;
1441 // doubled reasktime for no needed parts - save connections and traffic
1442 if ( !((!cur_src->GetLastAskedTime()) ||
1443 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME*2)) {
1444 break;
1446 // Recheck this client to see if still NNP..
1447 // Set to DS_NONE so that we force a TCP reask next time..
1448 cur_src->SetDownloadState(DS_NONE);
1450 break;
1452 case DS_ONQUEUE: {
1453 if( cur_src->IsRemoteQueueFull()) {
1454 if( ((dwCurTick - lastpurgetime) > 60000) &&
1455 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) ) {
1456 RemoveSource( cur_src );
1457 lastpurgetime = dwCurTick;
1458 break; //Johnny-B - nothing more to do here (good eye!)
1462 // Give up to 1 min for UDP to respond..
1463 // If we are within on min on TCP, do not try..
1464 if ( theApp->IsConnected() &&
1465 ( (!cur_src->GetLastAskedTime()) ||
1466 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME-20000)) {
1467 cur_src->UDPReaskForDownload();
1470 // No break here, since the next case takes care of asking for downloads.
1472 case DS_CONNECTING:
1473 case DS_TOOMANYCONNS:
1474 case DS_NONE:
1475 case DS_WAITCALLBACK:
1476 case DS_WAITCALLBACKKAD: {
1477 if ( theApp->IsConnected() &&
1478 ( (!cur_src->GetLastAskedTime()) ||
1479 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME)) {
1480 if (!cur_src->AskForDownload()) {
1481 // I left this break here just as a reminder
1482 // just in case re rearange things..
1483 break;
1486 break;
1491 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
1492 if (IsA4AFAuto() && ((!m_LastNoNeededCheck) || (dwCurTick - m_LastNoNeededCheck > 900000))) {
1493 m_LastNoNeededCheck = dwCurTick;
1494 for ( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
1495 CUpDownClient *cur_source = *it++;
1496 uint8 download_state=cur_source->GetDownloadState();
1497 if( download_state != DS_DOWNLOADING
1498 && cur_source->GetRequestFile()
1499 && ((!cur_source->GetRequestFile()->IsA4AFAuto()) || download_state == DS_NONEEDEDPARTS))
1501 cur_source->SwapToAnotherFile(false, false, false, this);
1505 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
1507 // swap No needed partfiles if possible
1509 if (((old_trans==0) && (transferingsrc>0)) || ((old_trans>0) && (transferingsrc==0))) {
1510 SetPartFileStatus(status);
1513 // Kad source search
1514 if( GetMaxSourcePerFileUDP() > GetSourceCount()){
1515 //Once we can handle lowID users in Kad, we remove the second IsConnected
1516 if (theApp->downloadqueue->DoKademliaFileRequest() && (Kademlia::CKademlia::GetTotalFile() < KADEMLIATOTALFILE) && (dwCurTick > m_LastSearchTimeKad) && Kademlia::CKademlia::IsConnected() && theApp->IsConnected() && !IsStopped()){
1517 //Kademlia
1518 theApp->downloadqueue->SetLastKademliaFileRequest();
1520 if (GetKadFileSearchID()) {
1521 /* This will never happen anyway. We're talking a
1522 1h timespan and searches are at max 45secs */
1523 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1526 Kademlia::CUInt128 kadFileID(GetFileHash().GetHash());
1527 Kademlia::CSearch* pSearch = Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FILE, true, kadFileID);
1528 AddDebugLogLineM(false, logKadSearch, CFormat(wxT("Preparing a Kad Search for '%s'")) % GetFileName());
1529 if (pSearch) {
1530 AddDebugLogLineM(false, logKadSearch, CFormat(wxT("Kad lookup started for '%s'")) % GetFileName());
1531 if(m_TotalSearchesKad < 7) {
1532 m_TotalSearchesKad++;
1534 m_LastSearchTimeKad = dwCurTick + (KADEMLIAREASKTIME*m_TotalSearchesKad);
1535 SetKadFileSearchID(pSearch->GetSearchID());
1538 } else {
1539 if(GetKadFileSearchID()) {
1540 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
1544 // check if we want new sources from server
1545 if ( !m_localSrcReqQueued &&
1546 ( (!m_lastsearchtime) ||
1547 (dwCurTick - m_lastsearchtime) > SERVERREASKTIME) &&
1548 theApp->IsConnectedED2K() &&
1549 thePrefs::GetMaxSourcePerFileSoft() > GetSourceCount() &&
1550 !m_stopped ) {
1551 m_localSrcReqQueued = true;
1552 theApp->downloadqueue->SendLocalSrcRequest(this);
1555 // calculate datarate, set limit etc.
1558 ++m_count;
1560 // Kry - does the 3 / 30 difference produce too much flickering or CPU?
1561 if (m_count >= 30) {
1562 m_count = 0;
1563 UpdateAutoDownPriority();
1564 UpdateDisplayedInfo();
1565 if(m_bPercentUpdated == false) {
1566 UpdateCompletedInfos();
1568 m_bPercentUpdated = false;
1569 if (thePrefs::ShowCatTabInfos()) {
1570 Notify_ShowUpdateCatTabTitles();
1574 // release file handle if unused for some time
1575 m_hpartfile.Release();
1577 return (uint32)(kBpsDown*1024.0);
1580 bool CPartFile::CanAddSource(uint32 userid, uint16 port, uint32 serverip, uint16 serverport, uint8* pdebug_lowiddropped, bool ed2kID)
1583 //The incoming ID could have the userid in the Hybrid format..
1584 uint32 hybridID = 0;
1585 if( ed2kID ) {
1586 if (IsLowID(userid)) {
1587 hybridID = userid;
1588 } else {
1589 hybridID = wxUINT32_SWAP_ALWAYS(userid);
1591 } else {
1592 hybridID = userid;
1593 if (!IsLowID(userid)) {
1594 userid = wxUINT32_SWAP_ALWAYS(userid);
1598 // MOD Note: Do not change this part - Merkur
1599 if (theApp->IsConnectedED2K()) {
1600 if(::IsLowID(theApp->GetED2KID())) {
1601 if(theApp->GetED2KID() == userid && theApp->serverconnect->GetCurrentServer()->GetIP() == serverip && theApp->serverconnect->GetCurrentServer()->GetPort() == serverport ) {
1602 return false;
1604 if(theApp->GetPublicIP() == userid) {
1605 return false;
1607 } else {
1608 if(theApp->GetED2KID() == userid && thePrefs::GetPort() == port) {
1609 return false;
1614 if (Kademlia::CKademlia::IsConnected()) {
1615 if(!Kademlia::CKademlia::IsFirewalled()) {
1616 if(Kademlia::CKademlia::GetIPAddress() == hybridID && thePrefs::GetPort() == port) {
1617 return false;
1622 //This allows *.*.*.0 clients to not be removed if Ed2kID == false
1623 if ( IsLowID(hybridID) && theApp->IsFirewalled()) {
1624 if (pdebug_lowiddropped) {
1625 (*pdebug_lowiddropped)++;
1627 return false;
1629 // MOD Note - end
1630 return true;
1633 void CPartFile::AddSources(CMemFile& sources,uint32 serverip, uint16 serverport, unsigned origin, bool bWithObfuscationAndHash)
1635 uint8 count = sources.ReadUInt8();
1636 uint8 debug_lowiddropped = 0;
1637 uint8 debug_possiblesources = 0;
1638 CMD4Hash achUserHash;
1640 if (m_stopped) {
1641 // since we may received multiple search source UDP results we have to "consume" all data of that packet
1642 AddDebugLogLineM(false, logPartFile, wxT("Trying to add sources for a stopped file"));
1643 sources.Seek(count*(4+2), wxFromCurrent);
1644 return;
1647 for (int i = 0;i != count;++i) {
1648 uint32 userid = sources.ReadUInt32();
1649 uint16 port = sources.ReadUInt16();
1651 uint8 byCryptOptions = 0;
1652 if (bWithObfuscationAndHash){
1653 byCryptOptions = sources.ReadUInt8();
1654 if ((byCryptOptions & 0x80) > 0) {
1655 achUserHash = sources.ReadHash();
1658 if ((thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x01/*supported*/) > 0 && (byCryptOptions & 0x80) == 0)
1659 || (thePrefs::IsClientCryptLayerSupported() && (byCryptOptions & 0x02/*requested*/) > 0 && (byCryptOptions & 0x80) == 0)) {
1660 AddDebugLogLineM(false, logPartFile, wxString::Format(wxT("Server didn't provide UserHash for source %u, even if it was expected to (or local obfuscationsettings changed during serverconnect"), userid));
1661 } else if (!thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x02/*requested*/) == 0 && (byCryptOptions & 0x80) != 0) {
1662 AddDebugLogLineM(false, logPartFile, wxString::Format(wxT("Server provided UserHash for source %u, even if it wasn't expected to (or local obfuscationsettings changed during serverconnect"), userid));
1667 // "Filter LAN IPs" and "IPfilter" the received sources IP addresses
1668 if (!IsLowID(userid)) {
1669 // check for 0-IP, localhost and optionally for LAN addresses
1670 if ( !IsGoodIP(userid, thePrefs::FilterLanIPs()) ) {
1671 continue;
1673 if (theApp->ipfilter->IsFiltered(userid)) {
1674 continue;
1678 if (!CanAddSource(userid, port, serverip, serverport, &debug_lowiddropped)) {
1679 continue;
1682 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
1683 ++debug_possiblesources;
1684 CUpDownClient* newsource = new CUpDownClient(port,userid,serverip,serverport,this, true, true);
1686 newsource->SetSourceFrom((ESourceFrom)origin);
1687 newsource->SetConnectOptions(byCryptOptions, true, false);
1689 if ((byCryptOptions & 0x80) != 0) {
1690 newsource->SetUserHash(achUserHash);
1693 theApp->downloadqueue->CheckAndAddSource(this,newsource);
1694 } else {
1695 AddDebugLogLineM(false, logPartFile, wxT("Consuming a packet because of max sources reached"));
1696 // Since we may receive multiple search source UDP results we have to "consume" all data of that packet
1697 // This '+1' is added because 'i' counts from 0.
1698 sources.Seek((count-(i+1))*(4+2), wxFromCurrent);
1699 if (GetKadFileSearchID()) {
1700 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1702 break;
1707 void CPartFile::UpdatePartsInfo()
1709 if( !IsPartFile() ) {
1710 CKnownFile::UpdatePartsInfo();
1711 return;
1714 // Cache part count
1715 uint16 partcount = GetPartCount();
1716 bool flag = (time(NULL) - m_nCompleteSourcesTime > 0);
1718 // Ensure the frequency-list is ready
1719 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
1720 m_SrcpartFrequency.clear();
1721 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
1724 // Find number of available parts
1725 uint16 availablecounter = 0;
1726 for ( uint16 i = 0; i < partcount; ++i ) {
1727 if ( m_SrcpartFrequency[i] )
1728 ++availablecounter;
1731 if ( ( availablecounter == partcount ) && ( m_availablePartsCount < partcount ) ) {
1732 lastseencomplete = time(NULL);
1735 m_availablePartsCount = availablecounter;
1737 if ( flag ) {
1738 ArrayOfUInts16 count;
1740 count.reserve(GetSourceCount());
1742 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
1743 if ( !(*it)->GetUpPartStatus().empty() && (*it)->GetUpPartCount() == partcount ) {
1744 count.push_back((*it)->GetUpCompleteSourcesCount());
1748 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo = m_nCompleteSourcesCountHi = 0;
1750 for (uint16 i = 0; i < partcount; ++i) {
1751 if( !i ) {
1752 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1754 else if( m_nCompleteSourcesCount > m_SrcpartFrequency[i]) {
1755 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1758 count.push_back(m_nCompleteSourcesCount);
1760 int32 n = count.size();
1761 if (n > 0) {
1762 std::sort(count.begin(), count.end(), std::less<uint16>());
1764 // calculate range
1765 int32 i= n >> 1; // (n / 2)
1766 int32 j= (n * 3) >> 2; // (n * 3) / 4
1767 int32 k= (n * 7) >> 3; // (n * 7) / 8
1769 //When still a part file, adjust your guesses by 20% to what you see..
1772 if (n < 5) {
1773 //Not many sources, so just use what you see..
1774 // welcome to 'plain stupid code'
1775 // m_nCompleteSourcesCount;
1776 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1777 m_nCompleteSourcesCountHi= m_nCompleteSourcesCount;
1778 } else if (n < 20) {
1779 // For low guess and normal guess count
1780 // If we see more sources then the guessed low and normal, use what we see.
1781 // If we see less sources then the guessed low, adjust network accounts for 80%,
1782 // we account for 20% with what we see and make sure we are still above the normal.
1783 // For high guess
1784 // Adjust 80% network and 20% what we see.
1785 if ( count[i] < m_nCompleteSourcesCount ) {
1786 m_nCompleteSourcesCountLo = m_nCompleteSourcesCount;
1787 } else {
1788 m_nCompleteSourcesCountLo =
1789 (uint16)((float)(count[i]*.8) +
1790 (float)(m_nCompleteSourcesCount*.2));
1792 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1793 m_nCompleteSourcesCountHi =
1794 (uint16)((float)(count[j]*.8) +
1795 (float)(m_nCompleteSourcesCount*.2));
1796 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1797 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1799 } else {
1800 // Many sources
1801 // ------------
1802 // For low guess
1803 // Use what we see.
1804 // For normal guess
1805 // Adjust network accounts for 80%, we account for 20% with what
1806 // we see and make sure we are still above the low.
1807 // For high guess
1808 // Adjust network accounts for 80%, we account for 20% with what
1809 // we see and make sure we are still above the normal.
1811 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1812 m_nCompleteSourcesCount= (uint16)((float)(count[j]*.8)+(float)(m_nCompleteSourcesCount*.2));
1813 if( m_nCompleteSourcesCount < m_nCompleteSourcesCountLo ) {
1814 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1816 m_nCompleteSourcesCountHi= (uint16)((float)(count[k]*.8)+(float)(m_nCompleteSourcesCount*.2));
1817 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1818 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1822 m_nCompleteSourcesTime = time(NULL) + (60);
1824 UpdateDisplayedInfo();
1827 // [Maella -Enhanced Chunk Selection- (based on jicxicmic)]
1828 bool CPartFile::GetNextRequestedBlock(CUpDownClient* sender,
1829 std::vector<Requested_Block_Struct*>& toadd, uint16& count)
1832 // The purpose of this function is to return a list of blocks (~180KB) to
1833 // download. To avoid a prematurely stop of the downloading, all blocks that
1834 // are requested from the same source must be located within the same
1835 // chunk (=> part ~9MB).
1837 // The selection of the chunk to download is one of the CRITICAL parts of the
1838 // edonkey network. The selection algorithm must insure the best spreading
1839 // of files.
1841 // The selection is based on 4 criteria:
1842 // 1. Frequency of the chunk (availability), very rare chunks must be downloaded
1843 // as quickly as possible to become a new available source.
1844 // 2. Parts used for preview (first + last chunk), preview or check a
1845 // file (e.g. movie, mp3)
1846 // 3. Request state (downloading in process), try to ask each source for another
1847 // chunk. Spread the requests between all sources.
1848 // 4. Completion (shortest-to-complete), partially retrieved chunks should be
1849 // completed before starting to download other one.
1851 // The frequency criterion defines three zones: very rare (<10%), rare (<50%)
1852 // and common (>30%). Inside each zone, the criteria have a specific weight, used
1853 // to calculate the priority of chunks. The chunk(s) with the highest
1854 // priority (highest=0, lowest=0xffff) is/are selected first.
1856 // very rare (preview) rare common
1857 // 0% <---- +0 pt ----> 10% <----- +10000 pt -----> 50% <---- +20000 pt ----> 100%
1858 // 1. <------- frequency: +25*frequency pt ----------->
1859 // 2. <- preview: +1 pt --><-------------- preview: set to 10000 pt ------------->
1860 // 3. <------ request: download in progress +20000 pt ------>
1861 // 4a. <- completion: 0% +100, 25% +75 .. 100% +0 pt --><-- !req => completion --->
1862 // 4b. <--- req => !completion -->
1864 // Unrolled, the priority scale is:
1866 // 0..xxxx unrequested and requested very rare chunks
1867 // 10000..1xxxx unrequested rare chunks + unrequested preview chunks
1868 // 20000..2xxxx unrequested common chunks (priority to the most complete)
1869 // 30000..3xxxx requested rare chunks + requested preview chunks
1870 // 40000..4xxxx requested common chunks (priority to the least complete)
1872 // This algorithm usually selects first the rarest chunk(s). However, partially
1873 // complete chunk(s) that is/are close to completion may overtake the priority
1874 // (priority inversion).
1875 // For the common chuncks, the algorithm tries to spread the dowload between
1876 // the sources
1879 // Check input parameters
1880 if ( sender->GetPartStatus().empty() ) {
1881 return false;
1883 // Define and create the list of the chunks to download
1884 const uint16 partCount = GetPartCount();
1885 ChunkList chunksList;
1887 // Main loop
1888 uint16 newBlockCount = 0;
1889 while(newBlockCount != count) {
1890 // Create a request block stucture if a chunk has been previously selected
1891 if(sender->GetLastPartAsked() != 0xffff) {
1892 Requested_Block_Struct* pBlock = new Requested_Block_Struct;
1893 if(GetNextEmptyBlockInPart(sender->GetLastPartAsked(), pBlock) == true) {
1894 // Keep a track of all pending requested blocks
1895 m_requestedblocks_list.push_back(pBlock);
1896 // Update list of blocks to return
1897 toadd.push_back(pBlock);
1898 newBlockCount++;
1899 // Skip end of loop (=> CPU load)
1900 continue;
1901 } else {
1902 // All blocks for this chunk have been already requested
1903 delete pBlock;
1904 // => Try to select another chunk
1905 sender->SetLastPartAsked(0xffff);
1909 // Check if a new chunk must be selected (e.g. download starting, previous chunk complete)
1910 if(sender->GetLastPartAsked() == 0xffff) {
1911 // Quantify all chunks (create list of chunks to download)
1912 // This is done only one time and only if it is necessary (=> CPU load)
1913 if(chunksList.empty()) {
1914 // Indentify the locally missing part(s) that this source has
1915 for(uint16 i=0; i < partCount; ++i) {
1916 if(sender->IsPartAvailable(i) == true && GetNextEmptyBlockInPart(i, NULL) == true) {
1917 // Create a new entry for this chunk and add it to the list
1918 Chunk newEntry;
1919 newEntry.part = i;
1920 newEntry.frequency = m_SrcpartFrequency[i];
1921 chunksList.push_back(newEntry);
1925 // Check if any bloks(s) could be downloaded
1926 if(chunksList.empty()) {
1927 break; // Exit main loop while()
1930 // Define the bounds of the three zones (very rare, rare)
1931 // more depending on available sources
1932 uint8 modif=10;
1933 if (GetSourceCount()>800) {
1934 modif=2;
1935 } else if (GetSourceCount()>200) {
1936 modif=5;
1938 uint16 limit= modif*GetSourceCount()/ 100;
1939 if (limit==0) {
1940 limit=1;
1942 const uint16 veryRareBound = limit;
1943 const uint16 rareBound = 2*limit;
1945 // Cache Preview state (Criterion 2)
1946 FileType type = GetFiletype(GetFileName());
1947 const bool isPreviewEnable =
1948 thePrefs::GetPreviewPrio() &&
1949 (type == ftArchive || type == ftVideo);
1951 // Collect and calculate criteria for all chunks
1952 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
1953 Chunk& cur_chunk = *it;
1955 // Offsets of chunk
1956 const uint64 uStart = cur_chunk.part * PARTSIZE;
1957 const uint64 uEnd = uStart + GetPartSize(cur_chunk.part) - 1;
1958 // Criterion 2. Parts used for preview
1959 // Remark: - We need to download the first part and the last part(s).
1960 // - When the last part is very small, it's necessary to
1961 // download the two last parts.
1962 bool critPreview = false;
1963 if(isPreviewEnable == true) {
1964 if(cur_chunk.part == 0) {
1965 critPreview = true; // First chunk
1966 } else if(cur_chunk.part == partCount-1) {
1967 critPreview = true; // Last chunk
1968 } else if(cur_chunk.part == partCount-2) {
1969 // Last chunk - 1 (only if last chunk is too small)
1970 const uint32 sizeOfLastChunk = GetFileSize() - uEnd;
1971 if(sizeOfLastChunk < PARTSIZE/3) {
1972 critPreview = true; // Last chunk - 1
1977 // Criterion 3. Request state (downloading in process from other source(s))
1978 // => CPU load
1979 const bool critRequested =
1980 cur_chunk.frequency > veryRareBound &&
1981 IsAlreadyRequested(uStart, uEnd);
1983 // Criterion 4. Completion
1984 // PARTSIZE instead of GetPartSize() favours the last chunk - but that may be intentional
1985 uint32 partSize = PARTSIZE - m_gaplist.GetGapSize(cur_chunk.part);
1986 const uint16 critCompletion = (uint16)(partSize/(PARTSIZE/100)); // in [%]
1988 // Calculate priority with all criteria
1989 if(cur_chunk.frequency <= veryRareBound) {
1990 // 0..xxxx unrequested + requested very rare chunks
1991 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
1992 ((critPreview == true) ? 0 : 1) + // Criterion 2
1993 (100 - critCompletion); // Criterion 4
1994 } else if(critPreview == true) {
1995 // 10000..10100 unrequested preview chunks
1996 // 30000..30100 requested preview chunks
1997 cur_chunk.rank = ((critRequested == false) ? 10000 : 30000) + // Criterion 3
1998 (100 - critCompletion); // Criterion 4
1999 } else if(cur_chunk.frequency <= rareBound) {
2000 // 10101..1xxxx unrequested rare chunks
2001 // 30101..3xxxx requested rare chunks
2002 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
2003 ((critRequested == false) ? 10101 : 30101) + // Criterion 3
2004 (100 - critCompletion); // Criterion 4
2005 } else {
2006 // common chunk
2007 if(critRequested == false) { // Criterion 3
2008 // 20000..2xxxx unrequested common chunks
2009 cur_chunk.rank = 20000 + // Criterion 3
2010 (100 - critCompletion); // Criterion 4
2011 } else {
2012 // 40000..4xxxx requested common chunks
2013 // Remark: The weight of the completion criterion is inversed
2014 // to spead the requests over the completing chunks.
2015 // Without this, the chunk closest to completion will
2016 // received every new sources.
2017 cur_chunk.rank = 40000 + // Criterion 3
2018 (critCompletion); // Criterion 4
2024 // Select the next chunk to download
2025 if(!chunksList.empty()) {
2026 // Find and count the chunck(s) with the highest priority
2027 uint16 chunkCount = 0; // Number of found chunks with same priority
2028 uint16 rank = 0xffff; // Highest priority found
2030 // Collect and calculate criteria for all chunks
2031 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2032 const Chunk& cur_chunk = *it;
2033 if(cur_chunk.rank < rank) {
2034 chunkCount = 1;
2035 rank = cur_chunk.rank;
2036 } else if(cur_chunk.rank == rank) {
2037 ++chunkCount;
2041 // Use a random access to avoid that everybody tries to download the
2042 // same chunks at the same time (=> spread the selected chunk among clients)
2043 uint16 randomness = 1 + (int) (((float)(chunkCount-1))*rand()/(RAND_MAX+1.0));
2045 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2046 const Chunk& cur_chunk = *it;
2047 if(cur_chunk.rank == rank) {
2048 randomness--;
2049 if(randomness == 0) {
2050 // Selection process is over
2051 sender->SetLastPartAsked(cur_chunk.part);
2052 // Remark: this list might be reused up to *count times
2053 chunksList.erase(it);
2054 break; // exit loop for()
2058 } else {
2059 // There is no remaining chunk to download
2060 break; // Exit main loop while()
2064 // Return the number of the blocks
2065 count = newBlockCount;
2066 // Return
2067 return (newBlockCount > 0);
2069 // Maella end
2072 void CPartFile::RemoveBlockFromList(uint64 start,uint64 end)
2074 std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
2075 while (it != m_requestedblocks_list.end()) {
2076 std::list<Requested_Block_Struct*>::iterator it2 = it++;
2078 if ((*it2)->StartOffset <= start && (*it2)->EndOffset >= end) {
2079 m_requestedblocks_list.erase(it2);
2085 void CPartFile::RemoveAllRequestedBlocks(void)
2087 m_requestedblocks_list.clear();
2091 void CPartFile::CompleteFile(bool bIsHashingDone)
2093 if (GetKadFileSearchID()) {
2094 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
2097 theApp->downloadqueue->RemoveLocalServerRequest(this);
2099 AddDebugLogLineM( false, logPartFile, wxString( wxT("CPartFile::CompleteFile: Hash ") ) + ( bIsHashingDone ? wxT("done") : wxT("not done") ) );
2101 if (!bIsHashingDone) {
2102 SetPartFileStatus(PS_COMPLETING);
2103 kBpsDown = 0.0;
2105 CPath partFile = m_partmetfilename.RemoveExt();
2106 CThreadScheduler::AddTask(new CHashingTask(GetFilePath(), partFile, this));
2107 return;
2108 } else {
2109 StopFile();
2110 m_is_A4AF_auto=false;
2111 SetPartFileStatus(PS_COMPLETING);
2112 // guess I was wrong about not need to spaw a thread ...
2113 // It is if the temp and incoming dirs are on different
2114 // partitions/drives and the file is large...[oz]
2117 PerformFileComplete();
2121 if (thePrefs::ShowCatTabInfos()) {
2122 Notify_ShowUpdateCatTabTitles();
2124 UpdateDisplayedInfo(true);
2128 void CPartFile::CompleteFileEnded(bool errorOccured, const CPath& newname)
2130 if (errorOccured) {
2131 m_paused = true;
2132 SetPartFileStatus(PS_ERROR);
2133 AddLogLineM(true, CFormat( _("Unexpected error while completing %s. File paused") )% GetFileName() );
2134 } else {
2135 m_fullname = newname;
2137 SetFilePath(m_fullname.GetPath());
2138 SetFileName(m_fullname.GetFullName());
2139 m_lastDateChanged = CPath::GetModificationTime(m_fullname);
2141 SetPartFileStatus(PS_COMPLETE);
2142 m_paused = false;
2143 ClearPriority();
2145 // TODO: What the f*** if it is already known?
2146 theApp->knownfiles->SafeAddKFile(this);
2148 // remove the file from the suspended uploads list
2149 theApp->uploadqueue->ResumeUpload(GetFileHash());
2150 theApp->downloadqueue->RemoveFile(this);
2151 theApp->sharedfiles->SafeAddKFile(this);
2152 UpdateDisplayedInfo(true);
2154 // republish that file to the ed2k-server to update the 'FT_COMPLETE_SOURCES' counter on the server.
2155 theApp->sharedfiles->RepublishFile(this);
2157 // Ensure that completed shows the correct value
2158 completedsize = GetFileSize();
2160 // clear the blackbox to free up memory
2161 m_CorruptionBlackBox->Free();
2163 AddLogLineM(true, CFormat( _("Finished downloading: %s") ) % GetFileName() );
2166 theApp->downloadqueue->StartNextFile(this);
2170 void CPartFile::PerformFileComplete()
2172 // add this file to the suspended uploads list
2173 theApp->uploadqueue->SuspendUpload(GetFileHash());
2174 FlushBuffer();
2176 // close permanent handle
2177 if (m_hpartfile.IsOpened()) {
2178 m_hpartfile.Close();
2181 // Schedule task for completion of the file
2182 CThreadScheduler::AddTask(new CCompletionTask(this));
2186 void CPartFile::RemoveAllSources(bool bTryToSwap)
2188 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end();) {
2189 CUpDownClient* cur_src = *it++;
2190 if (bTryToSwap) {
2191 if (!cur_src->SwapToAnotherFile(true, true, true, NULL)) {
2192 RemoveSource(cur_src,true,false);
2193 // If it was not swapped, it's not on any file anymore, and should die
2195 } else {
2196 RemoveSource(cur_src,true,false);
2200 UpdatePartsInfo();
2202 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
2203 // remove all links A4AF in sources to this file
2204 if(!m_A4AFsrclist.empty()) {
2205 for( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
2206 CUpDownClient* cur_src = *it++;
2207 if ( cur_src->DeleteFileRequest( this ) ) {
2208 Notify_DownloadCtrlRemoveSource(cur_src, this);
2211 m_A4AFsrclist.clear();
2213 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
2214 UpdateFileRatingCommentAvail();
2218 void CPartFile::Delete()
2220 AddLogLineM(false, CFormat(_("Deleting file: %s")) % GetFileName());
2221 // Barry - Need to tell any connected clients to stop sending the file
2222 StopFile(true);
2223 AddDebugLogLineM(false, logPartFile, wxT("\tStopped"));
2225 uint16 removed = theApp->uploadqueue->SuspendUpload(GetFileHash());
2226 AddDebugLogLineM(false, logPartFile, CFormat(wxT("\tSuspended upload to %d clients")) % removed);
2227 theApp->sharedfiles->RemoveFile(this);
2228 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved from shared"));
2229 theApp->downloadqueue->RemoveFile(this);
2230 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved from download queue"));
2231 Notify_DownloadCtrlRemoveFile(this);
2232 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved from transferwnd"));
2234 if (m_hpartfile.IsOpened()) {
2235 m_hpartfile.Close();
2238 AddDebugLogLineM(false, logPartFile, wxT("\tClosed"));
2240 if (!CPath::RemoveFile(m_fullname)) {
2241 AddDebugLogLineM(true, logPartFile, CFormat(wxT("\tFailed to delete '%s'")) % m_fullname);
2242 } else {
2243 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .part.met"));
2246 if (!CPath::RemoveFile(m_PartPath)) {
2247 AddDebugLogLineM(true, logPartFile, CFormat(wxT("Failed to delete '%s'")) % m_PartPath);
2248 } else {
2249 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .part"));
2252 CPath BAKName = m_fullname.AppendExt(PARTMET_BAK_EXT);
2253 if (!CPath::RemoveFile(BAKName)) {
2254 AddDebugLogLineM(true, logPartFile, CFormat(wxT("Failed to delete '%s'")) % BAKName);
2255 } else {
2256 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .BAK"));
2259 CPath SEEDSName = m_fullname.AppendExt(wxT(".seeds"));
2260 if (SEEDSName.FileExists()) {
2261 if (CPath::RemoveFile(SEEDSName)) {
2262 AddDebugLogLineM(false, logPartFile, wxT("\tRemoved .seeds"));
2263 } else {
2264 AddDebugLogLineM(true, logPartFile, CFormat(wxT("Failed to delete '%s'")) % SEEDSName);
2268 AddDebugLogLineM(false, logPartFile, wxT("Done"));
2270 delete this;
2274 bool CPartFile::HashSinglePart(uint16 partnumber)
2276 if ((GetHashCount() <= partnumber) && (GetPartCount() > 1)) {
2277 AddLogLineM(true,
2278 CFormat( _("WARNING: Unable to hash downloaded part - hashset incomplete for '%s'") )
2279 % GetFileName() );
2280 m_hashsetneeded = true;
2281 return true;
2282 } else if ((GetHashCount() <= partnumber) && GetPartCount() != 1) {
2283 AddLogLineM(true, CFormat( _("ERROR: Unable to hash downloaded part - hashset incomplete (%s). This should never happen")) % GetFileName() );
2284 m_hashsetneeded = true;
2285 return true;
2286 } else {
2287 CMD4Hash hashresult;
2288 uint64 offset = PARTSIZE * partnumber;
2289 uint32 length = GetPartSize(partnumber);
2290 try {
2291 CreateHashFromFile(m_hpartfile, offset, length, &hashresult, NULL);
2292 } catch (const CIOFailureException& e) {
2293 AddLogLineM(true, CFormat( wxT("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2294 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2295 SetPartFileStatus(PS_ERROR);
2296 return false;
2297 } catch (const CEOFException& e) {
2298 AddLogLineM(true, CFormat( wxT("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2299 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2300 return false;
2303 if (GetPartCount() > 1) {
2304 if (hashresult != GetPartHash(partnumber)) {
2305 AddDebugLogLineM(false, logPartFile, CFormat( wxT("%s: Expected hash of part %d: %s")) % GetFileName() % partnumber % GetPartHash(partnumber).Encode() );
2306 AddDebugLogLineM(false, logPartFile, CFormat( wxT("%s: Actual hash of part %d: %s")) % GetFileName() % partnumber % hashresult.Encode() );
2307 return false;
2308 } else {
2309 return true;
2311 } else {
2312 if (hashresult != m_abyFileHash) {
2313 return false;
2314 } else {
2315 return true;
2322 bool CPartFile::IsCorruptedPart(uint16 partnumber)
2324 return std::find(m_corrupted_list.begin(), m_corrupted_list.end(), partnumber)
2325 != m_corrupted_list.end();
2329 void CPartFile::SetDownPriority(uint8 np, bool bSave, bool bRefresh )
2331 if ( m_iDownPriority != np ) {
2332 m_iDownPriority = np;
2333 if ( bRefresh )
2334 UpdateDisplayedInfo(true);
2335 if ( bSave )
2336 SavePartFile();
2341 void CPartFile::StopFile(bool bCancel)
2343 // Kry - Need to set it here to get into SetPartFileStatus(status) correctly
2344 m_stopped = true;
2346 // Barry - Need to tell any connected clients to stop sending the file
2347 PauseFile();
2349 m_LastSearchTimeKad = 0;
2350 m_TotalSearchesKad = 0;
2352 RemoveAllSources(true);
2353 kBpsDown = 0.0;
2354 transferingsrc = 0;
2356 if (!bCancel) {
2357 FlushBuffer();
2360 UpdateDisplayedInfo(true);
2364 void CPartFile::StopPausedFile()
2366 if (!IsStopped()) {
2367 // Once an hour, remove any sources for files which are no longer active downloads
2368 switch (GetStatus()) {
2369 case PS_PAUSED:
2370 case PS_INSUFFICIENT:
2371 case PS_ERROR:
2372 if (time(NULL) - m_iLastPausePurge > (60*60)) {
2373 m_iLastPausePurge = time(NULL);
2374 StopFile();
2376 kBpsDown = 0.0;
2379 // release file handle if unused for some time
2380 m_hpartfile.Release();
2384 void CPartFile::PauseFile(bool bInsufficient)
2386 SetActive(false);
2388 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2389 return;
2392 if (GetKadFileSearchID()) {
2393 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
2394 // If we were in the middle of searching, reset timer so they can resume searching.
2395 m_LastSearchTimeKad = 0;
2398 m_iLastPausePurge = time(NULL);
2400 theApp->downloadqueue->RemoveLocalServerRequest(this);
2402 CPacket packet( OP_CANCELTRANSFER, 0, OP_EDONKEYPROT );
2403 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
2404 CUpDownClient* cur_src = *it++;
2405 if (cur_src->GetDownloadState() == DS_DOWNLOADING) {
2406 if (!cur_src->GetSentCancelTransfer()) {
2407 theStats::AddUpOverheadOther( packet.GetPacketSize() );
2408 AddDebugLogLineM( false, logLocalClient, wxT("Local Client: OP_CANCELTRANSFER to ") + cur_src->GetFullIP() );
2409 cur_src->SendPacket( &packet, false, true );
2410 cur_src->SetSentCancelTransfer( true );
2412 cur_src->SetDownloadState(DS_ONQUEUE);
2413 // Allow immediate reconnect on resume
2414 cur_src->ResetLastAskedTime();
2419 m_insufficient = bInsufficient;
2420 m_paused = true;
2423 kBpsDown = 0.0;
2424 transferingsrc = 0;
2426 SetStatus(status);
2430 void CPartFile::ResumeFile()
2432 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2433 return;
2436 if ( m_insufficient && !CheckFreeDiskSpace() ) {
2437 // Still not enough free discspace
2438 return;
2441 m_paused = false;
2442 m_stopped = false;
2443 m_insufficient = false;
2445 m_lastsearchtime = 0;
2446 SetStatus(status);
2447 SetActive(theApp->IsConnected());
2449 if (m_gaplist.IsComplete() && (GetStatus() == PS_ERROR)) {
2450 // The file has already been hashed at this point
2451 CompleteFile(true);
2454 UpdateDisplayedInfo(true);
2458 bool CPartFile::CheckFreeDiskSpace( uint64 neededSpace )
2460 uint64 free = CPath::GetFreeSpaceAt(GetFilePath());
2461 if (free == static_cast<uint64>(wxInvalidOffset)) {
2462 // If GetFreeSpaceAt() fails, then the path probably does not exist.
2463 return false;
2466 // The very least acceptable diskspace is a single PART
2467 if ( free < PARTSIZE ) {
2468 // Always fail in this case, since we risk losing data if we try to
2469 // write on a full partition.
2470 return false;
2473 // All other checks are only made if the user has enabled them
2474 if ( thePrefs::IsCheckDiskspaceEnabled() ) {
2475 neededSpace += thePrefs::GetMinFreeDiskSpace();
2477 // Due to the the existance of sparse files, we cannot assume that
2478 // writes within the file doesn't cause new blocks to be allocated.
2479 // Therefore, we have to simply stop writing the moment the limit has
2480 // been exceeded.
2481 return free >= neededSpace;
2484 return true;
2488 void CPartFile::SetLastAnsweredTime()
2490 m_ClientSrcAnswered = ::GetTickCount();
2493 void CPartFile::SetLastAnsweredTimeTimeout()
2495 m_ClientSrcAnswered = 2 * CONNECTION_LATENCY + ::GetTickCount() - SOURCECLIENTREASKS;
2498 CPacket *CPartFile::CreateSrcInfoPacket(const CUpDownClient* forClient, uint8 byRequestedVersion, uint16 nRequestedOptions)
2501 if ( m_SrcList.empty() ) {
2502 return NULL;
2505 if(!IsPartFile()) {
2506 return CKnownFile::CreateSrcInfoPacket(forClient, byRequestedVersion, nRequestedOptions);
2509 if (((forClient->GetRequestFile() != this)
2510 && (forClient->GetUploadFile() != this)) || forClient->GetUploadFileID() != GetFileHash()) {
2511 wxString file1 = _("Unknown");
2512 if (forClient->GetRequestFile() && forClient->GetRequestFile()->GetFileName().IsOk()) {
2513 file1 = forClient->GetRequestFile()->GetFileName().GetPrintable();
2514 } else if (forClient->GetUploadFile() && forClient->GetUploadFile()->GetFileName().IsOk()) {
2515 file1 = forClient->GetUploadFile()->GetFileName().GetPrintable();
2517 wxString file2 = _("Unknown");
2518 if (GetFileName().IsOk()) {
2519 file2 = GetFileName().GetPrintable();
2521 AddDebugLogLineM(false, logPartFile, wxT("File mismatch on source packet (P) Sending: ") + file1 + wxT(" From: ") + file2);
2522 return NULL;
2525 if ( !(GetStatus() == PS_READY || GetStatus() == PS_EMPTY)) {
2526 return NULL;
2529 const BitVector& reqstatus = forClient->GetPartStatus();
2530 bool KnowNeededParts = !reqstatus.empty();
2531 //wxASSERT(rcvstatus.size() == GetPartCount()); // Obviously!
2532 if (KnowNeededParts && (reqstatus.size() != GetPartCount())) {
2533 // Yuck. Same file but different part count? Seriously fucked up.
2534 // This happens rather often with reqstatus.size() == 0. Don't log then.
2535 if (reqstatus.size()) {
2536 AddDebugLogLineM(false, logKnownFiles, CFormat(wxT("Impossible situation: different partcounts: %i (client) and %i (file) for %s")) % reqstatus.size() % GetPartCount() % GetFileName());
2538 return NULL;
2541 CMemFile data(1024);
2543 uint8 byUsedVersion;
2544 bool bIsSX2Packet;
2545 if (forClient->SupportsSourceExchange2() && byRequestedVersion > 0){
2546 // the client uses SourceExchange2 and requested the highest version he knows
2547 // and we send the highest version we know, but of course not higher than his request
2548 byUsedVersion = std::min(byRequestedVersion, (uint8)SOURCEEXCHANGE2_VERSION);
2549 bIsSX2Packet = true;
2550 data.WriteUInt8(byUsedVersion);
2552 // we don't support any special SX2 options yet, reserved for later use
2553 if (nRequestedOptions != 0) {
2554 AddDebugLogLineM(false, logKnownFiles, CFormat(wxT("Client requested unknown options for SourceExchange2: %u")) % nRequestedOptions);
2556 } else {
2557 byUsedVersion = forClient->GetSourceExchange1Version();
2558 bIsSX2Packet = false;
2559 if (forClient->SupportsSourceExchange2()) {
2560 AddDebugLogLineM(false, logKnownFiles, wxT("Client which announced to support SX2 sent SX1 packet instead"));
2564 uint16 nCount = 0;
2566 data.WriteHash(m_abyFileHash);
2567 data.WriteUInt16(nCount);
2568 bool bNeeded;
2569 for (SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
2570 bNeeded = false;
2571 CUpDownClient* cur_src = *it;
2573 int state = cur_src->GetDownloadState();
2574 int valid = ( state == DS_DOWNLOADING ) || ( state == DS_ONQUEUE && !cur_src->IsRemoteQueueFull() );
2576 if ( cur_src->HasLowID() || !valid ) {
2577 continue;
2580 // only send source which have needed parts for this client if possible
2581 const BitVector& srcstatus = cur_src->GetPartStatus();
2582 if ( !srcstatus.empty() ) {
2583 //wxASSERT(srcstatus.size() == GetPartCount()); // Obviously!
2584 if (srcstatus.size() != GetPartCount()) {
2585 continue;
2587 if ( KnowNeededParts ) {
2588 // only send sources which have needed parts for this client
2589 for (int x = 0; x < GetPartCount(); ++x) {
2590 if (srcstatus[x] && !reqstatus[x]) {
2591 bNeeded = true;
2592 break;
2595 } else {
2596 // if we don't know the need parts for this client,
2597 // return any source currently a client sends it's
2598 // file status only after it has at least one complete part
2599 if (srcstatus.size() != GetPartCount()) {
2600 continue;
2602 for (int x = 0; x < GetPartCount(); ++x){
2603 if (srcstatus[x]) {
2604 bNeeded = true;
2605 break;
2610 if(bNeeded) {
2611 ++nCount;
2612 uint32 dwID;
2613 if(forClient->GetSourceExchange1Version() > 2) {
2614 dwID = cur_src->GetUserIDHybrid();
2615 } else {
2616 dwID = wxUINT32_SWAP_ALWAYS(cur_src->GetUserIDHybrid());
2618 data.WriteUInt32(dwID);
2619 data.WriteUInt16(cur_src->GetUserPort());
2620 data.WriteUInt32(cur_src->GetServerIP());
2621 data.WriteUInt16(cur_src->GetServerPort());
2623 if (byUsedVersion >= 2) {
2624 data.WriteHash(cur_src->GetUserHash());
2627 if (byUsedVersion >= 4){
2628 // CryptSettings - SourceExchange V4
2629 // 5 Reserved (!)
2630 // 1 CryptLayer Required
2631 // 1 CryptLayer Requested
2632 // 1 CryptLayer Supported
2633 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
2634 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
2635 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
2636 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
2637 data.WriteUInt8(byCryptOptions);
2640 if (nCount > 500) {
2641 break;
2645 if (!nCount) {
2646 return 0;
2648 data.Seek(bIsSX2Packet ? 17 : 16, wxFromStart);
2649 data.WriteUInt16(nCount);
2651 CPacket* result = new CPacket(data, OP_EMULEPROT, bIsSX2Packet ? OP_ANSWERSOURCES2 : OP_ANSWERSOURCES);
2653 // 16+2+501*(4+2+4+2+16) = 14046 bytes max.
2654 if (result->GetPacketSize() > 354) {
2655 result->PackPacket();
2658 return result;
2661 void CPartFile::AddClientSources(CMemFile* sources, unsigned nSourceFrom, uint8 uClientSXVersion, bool bSourceExchange2, const CUpDownClient* /*pClient*/)
2663 // Kad reviewed
2665 if (m_stopped) {
2666 return;
2669 uint32 nCount = 0;
2670 uint8 uPacketSXVersion = 0;
2671 if (!bSourceExchange2) {
2672 nCount = sources->ReadUInt16();
2674 // Check if the data size matches the 'nCount' for v1 or v2 and eventually correct the source
2675 // exchange version while reading the packet data. Otherwise we could experience a higher
2676 // chance in dealing with wrong source data, userhashs and finally duplicate sources.
2677 uint32 uDataSize = sources->GetLength() - sources->GetPosition();
2679 if ((uint32)(nCount*(4+2+4+2)) == uDataSize) { //Checks if version 1 packet is correct size
2680 if(uClientSXVersion != 1) {
2681 return;
2683 uPacketSXVersion = 1;
2684 } else if ((uint32)(nCount*(4+2+4+2+16)) == uDataSize) { // Checks if version 2&3 packet is correct size
2685 if (uClientSXVersion == 2) {
2686 uPacketSXVersion = 2;
2687 } else if (uClientSXVersion > 2) {
2688 uPacketSXVersion = 3;
2689 } else {
2690 return;
2692 } else if (nCount*(4+2+4+2+16+1) == uDataSize) {
2693 if (uClientSXVersion != 4 ) {
2694 return;
2696 uPacketSXVersion = 4;
2697 } else {
2698 // If v5 inserts additional data (like v2), the above code will correctly filter those packets.
2699 // If v5 appends additional data after <count>(<Sources>)[count], we are in trouble with the
2700 // above code. Though a client which does not understand v5+ should never receive such a packet.
2701 AddDebugLogLineM(false, logClient, CFormat(wxT("Received invalid source exchange packet (v%u) of data size %u for %s")) % uClientSXVersion % uDataSize % GetFileName());
2702 return;
2704 } else {
2705 // for SX2:
2706 // We only check if the version is known by us and do a quick sanitize check on known version
2707 // other then SX1, the packet will be ignored if any error appears, sicne it can't be a "misunderstanding" anymore
2708 if (uClientSXVersion > SOURCEEXCHANGE2_VERSION || uClientSXVersion == 0 ){
2709 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Invalid source exchange type version: %i")) % uClientSXVersion);
2710 return;
2713 // all known versions use the first 2 bytes as count and unknown version are already filtered above
2714 nCount = sources->ReadUInt16();
2715 uint32 uDataSize = (uint32)(sources->GetLength() - sources->GetPosition());
2716 bool bError = false;
2717 switch (uClientSXVersion){
2718 case 1:
2719 bError = nCount*(4+2+4+2) != uDataSize;
2720 break;
2721 case 2:
2722 case 3:
2723 bError = nCount*(4+2+4+2+16) != uDataSize;
2724 break;
2725 case 4:
2726 bError = nCount*(4+2+4+2+16+1) != uDataSize;
2727 break;
2728 default:
2729 wxFAIL;
2732 if (bError){
2733 wxFAIL;
2734 AddDebugLogLineM(false, logPartFile, wxT("Invalid source exchange data size."));
2735 return;
2737 uPacketSXVersion = uClientSXVersion;
2740 for (uint16 i = 0;i != nCount;++i) {
2742 uint32 dwID = sources->ReadUInt32();
2743 uint16 nPort = sources->ReadUInt16();
2744 uint32 dwServerIP = sources->ReadUInt32();
2745 uint16 nServerPort = sources->ReadUInt16();
2747 CMD4Hash userHash;
2748 if (uPacketSXVersion > 1) {
2749 userHash = sources->ReadHash();
2752 uint8 byCryptOptions = 0;
2753 if (uPacketSXVersion >= 4) {
2754 byCryptOptions = sources->ReadUInt8();
2757 //Clients send ID's the the Hyrbid format so highID clients with *.*.*.0 won't be falsely switched to a lowID..
2758 uint32 dwIDED2K;
2759 if (uPacketSXVersion >= 3) {
2760 dwIDED2K = wxUINT32_SWAP_ALWAYS(dwID);
2761 } else {
2762 dwIDED2K = dwID;
2765 // check the HighID(IP) - "Filter LAN IPs" and "IPfilter" the received sources IP addresses
2766 if (!IsLowID(dwID)) {
2767 if (!IsGoodIP(dwIDED2K, thePrefs::FilterLanIPs())) {
2768 // check for 0-IP, localhost and optionally for LAN addresses
2769 AddDebugLogLineM(false, logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - bad IP")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2770 continue;
2772 if (theApp->ipfilter->IsFiltered(dwIDED2K)) {
2773 AddDebugLogLineM(false, logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - IPFilter")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2774 continue;
2776 if (theApp->clientlist->IsBannedClient(dwIDED2K)){
2777 continue;
2781 // additionally check for LowID and own IP
2782 if (!CanAddSource(dwID, nPort, dwServerIP, nServerPort, NULL, false)) {
2783 AddDebugLogLineM(false, logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via source exchange")) % Uint32toStringIP(dwIDED2K));
2784 continue;
2787 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
2788 CUpDownClient* newsource = new CUpDownClient(nPort,dwID,dwServerIP,nServerPort,this, (uPacketSXVersion < 3), true);
2789 if (uPacketSXVersion > 1) {
2790 newsource->SetUserHash(userHash);
2793 if (uPacketSXVersion >= 4) {
2794 newsource->SetConnectOptions(byCryptOptions, true, false);
2797 newsource->SetSourceFrom((ESourceFrom)nSourceFrom);
2798 theApp->downloadqueue->CheckAndAddSource(this,newsource);
2800 } else {
2801 break;
2806 void CPartFile::UpdateAutoDownPriority()
2808 if (!IsAutoDownPriority()) {
2809 return;
2811 if (GetSourceCount() <= RARE_FILE) {
2812 if ( GetDownPriority() != PR_HIGH )
2813 SetDownPriority(PR_HIGH, false, false);
2814 } else if (GetSourceCount() < 100) {
2815 if ( GetDownPriority() != PR_NORMAL )
2816 SetDownPriority(PR_NORMAL, false, false);
2817 } else {
2818 if ( GetDownPriority() != PR_LOW )
2819 SetDownPriority(PR_LOW, false, false);
2823 // making this function return a higher when more sources have the extended
2824 // protocol will force you to ask a larger variety of people for sources
2826 int CPartFile::GetCommonFilePenalty()
2828 //TODO: implement, but never return less than MINCOMMONPENALTY!
2829 return MINCOMMONPENALTY;
2832 /* Barry - Replaces BlockReceived()
2834 Originally this only wrote to disk when a full 180k block
2835 had been received from a client, and only asked for data in
2836 180k blocks.
2838 This meant that on average 90k was lost for every connection
2839 to a client data source. That is a lot of wasted data.
2841 To reduce the lost data, packets are now written to a buffer
2842 and flushed to disk regularly regardless of size downloaded.
2843 This includes compressed packets.
2845 Data is also requested only where gaps are, not in 180k blocks.
2846 The requests will still not exceed 180k, but may be smaller to
2847 fill a gap.
2850 // Kry - transize is 32bits, no packet can be more than that (this is
2851 // compressed size). Even 32bits is too much imho.As for the return size,
2852 // look at the lenData below.
2853 uint32 CPartFile::WriteToBuffer(uint32 transize, byte* data, uint64 start, uint64 end, Requested_Block_Struct *block, const CUpDownClient* client)
2855 // Increment transferred bytes counter for this file
2856 transferred += transize;
2858 // This is needed a few times
2859 // Kry - should not need a uint64 here - no block is larger than
2860 // 2GB even after uncompressed.
2861 uint32 lenData = (uint32) (end - start + 1);
2863 if(lenData > transize) {
2864 m_iGainDueToCompression += lenData-transize;
2867 // Occasionally packets are duplicated, no point writing it twice
2868 if (IsComplete(start, end)) {
2869 AddDebugLogLineM(false, logPartFile,
2870 CFormat(wxT("File '%s' has already been written from %u to %u"))
2871 % GetFileName() % start % end);
2872 return 0;
2875 // security sanitize check to make sure we do not write anything into an already hashed complete chunk
2876 const uint64 nStartChunk = start / PARTSIZE;
2877 const uint64 nEndChunk = end / PARTSIZE;
2878 if (IsComplete(nStartChunk)) {
2879 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (start): %u-%u; File=%s")) % start % end % GetFileName());
2880 return 0;
2881 } else if (nStartChunk != nEndChunk) {
2882 if (IsComplete(nEndChunk)) {
2883 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (end): %u-%u; File=%s")) % start % end % GetFileName());
2884 return 0;
2886 #ifdef __DEBUG__
2887 else {
2888 AddDebugLogLineM(false, logPartFile, CFormat(wxT("Received data crosses chunk boundaries: %u-%u; File=%s")) % start % end % GetFileName());
2890 #endif
2893 // log transferinformation in our "blackbox"
2894 m_CorruptionBlackBox->TransferredData(start, end, client->GetIP());
2896 // Create a new buffered queue entry
2897 PartFileBufferedData *item = new PartFileBufferedData(m_hpartfile, data, start, end, block);
2899 // Add to the queue in the correct position (most likely the end)
2900 bool added = false;
2902 std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
2903 for (; it != m_BufferedData_list.end(); ++it) {
2904 PartFileBufferedData* queueItem = *it;
2906 if (item->end <= queueItem->end) {
2907 if (it != m_BufferedData_list.begin()) {
2908 added = true;
2910 m_BufferedData_list.insert(--it, item);
2913 break;
2917 if (!added) {
2918 m_BufferedData_list.push_front(item);
2921 // Increment buffer size marker
2922 m_nTotalBufferData += lenData;
2924 // Mark this small section of the file as filled
2925 FillGap(item->start, item->end);
2927 // Update the flushed mark on the requested block
2928 // The loop here is unfortunate but necessary to detect deleted blocks.
2930 std::list<Requested_Block_Struct*>::iterator it2 = m_requestedblocks_list.begin();
2931 for (; it2 != m_requestedblocks_list.end(); ++it2) {
2932 if (*it2 == item->block) {
2933 item->block->transferred += lenData;
2937 if (m_gaplist.IsComplete()) {
2938 FlushBuffer();
2941 // Return the length of data written to the buffer
2942 return lenData;
2945 void CPartFile::FlushBuffer(bool fromAICHRecoveryDataAvailable)
2947 m_nLastBufferFlushTime = GetTickCount();
2949 if (m_BufferedData_list.empty()) {
2950 return;
2954 uint32 partCount = GetPartCount();
2955 // Remember which parts need to be checked at the end of the flush
2956 std::vector<bool> changedPart(partCount, false);
2958 // Ensure file is big enough to write data to (the last item will be the furthest from the start)
2959 if (!CheckFreeDiskSpace(m_nTotalBufferData)) {
2960 // Not enough free space to write the last item, bail
2961 AddLogLineM(true, CFormat( _("WARNING: Not enough free disk-space! Pausing file: %s") ) % GetFileName());
2963 PauseFile( true );
2964 return;
2967 // Loop through queue
2968 while ( !m_BufferedData_list.empty() ) {
2969 // Get top item and remove it from the queue
2970 CScopedPtr<PartFileBufferedData> item(m_BufferedData_list.front());
2971 m_BufferedData_list.pop_front();
2973 // This is needed a few times
2974 wxASSERT((item->end - item->start) < 0xFFFFFFFF);
2975 uint32 lenData = (uint32)(item->end - item->start + 1);
2977 // SLUGFILLER: SafeHash - could be more than one part
2978 for (uint32 curpart = (item->start/PARTSIZE); curpart <= (item->end/PARTSIZE); ++curpart) {
2979 wxASSERT(curpart < partCount);
2980 changedPart[curpart] = true;
2982 // SLUGFILLER: SafeHash
2984 // Go to the correct position in file and write block of data
2985 try {
2986 item->area.FlushAt(m_hpartfile, item->start, lenData);
2987 // Decrease buffer size
2988 m_nTotalBufferData -= lenData;
2989 } catch (const CIOFailureException& e) {
2990 AddDebugLogLineM(true, logPartFile, wxT("Error while saving part-file: ") + e.what());
2991 SetPartFileStatus(PS_ERROR);
2992 // No need to bang your head against it again and again if it has already failed.
2993 DeleteContents(m_BufferedData_list);
2994 m_nTotalBufferData = 0;
2995 return;
3000 // Update last-changed date
3001 m_lastDateChanged = wxDateTime::GetTimeNow();
3003 try {
3004 // Partfile should never be too large
3005 if (m_hpartfile.GetLength() > GetFileSize()) {
3006 // it's "last chance" correction. the real bugfix has to be applied 'somewhere' else
3007 m_hpartfile.SetLength(GetFileSize());
3009 } catch (const CIOFailureException& e) {
3010 AddDebugLogLineM(true, logPartFile,
3011 CFormat(wxT("Error while truncating part-file (%s): %s"))
3012 % m_PartPath % e.what());
3013 SetPartFileStatus(PS_ERROR);
3018 // Check each part of the file
3019 for (uint16 partNumber = 0; partNumber < partCount; ++partNumber) {
3020 if (changedPart[partNumber] == false) {
3021 continue;
3024 uint32 partRange = GetPartSize(partNumber) - 1;
3026 // Is this 9MB part complete
3027 if (IsComplete(partNumber)) {
3028 // Is part corrupt
3029 if (!HashSinglePart(partNumber)) {
3030 AddLogLineM(true, CFormat(
3031 _("Downloaded part %i is corrupt in file: %s") ) % partNumber % GetFileName() );
3032 AddGap(partNumber);
3033 // add part to corrupted list, if not already there
3034 if (!IsCorruptedPart(partNumber)) {
3035 m_corrupted_list.push_back(partNumber);
3037 // request AICH recovery data
3038 // Don't if called from the AICHRecovery. It's already there and would lead to an infinite recursion.
3039 if (!fromAICHRecoveryDataAvailable) {
3040 RequestAICHRecovery(partNumber);
3042 // Reduce transferred amount by corrupt amount
3043 m_iLostDueToCorruption += (partRange + 1);
3044 } else {
3045 if (!m_hashsetneeded) {
3046 AddDebugLogLineM(false, logPartFile, CFormat(
3047 wxT("Finished part %u of '%s'")) % partNumber % GetFileName());
3050 // tell the blackbox about the verified data
3051 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3053 // if this part was successfully completed (although ICH is active), remove from corrupted list
3054 EraseFirstValue(m_corrupted_list, partNumber);
3056 if (status == PS_EMPTY) {
3057 if (theApp->IsRunning()) { // may be called during shutdown!
3058 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3059 // Successfully completed part, make it available for sharing
3060 SetStatus(PS_READY);
3061 theApp->sharedfiles->SafeAddKFile(this);
3066 } else if ( IsCorruptedPart(partNumber) && // corrupted part:
3067 (thePrefs::IsICHEnabled() // old ICH: rehash whenever we have new data hoping it will be good now
3068 || fromAICHRecoveryDataAvailable)) {// new AICH: one rehash right before performing it (maybe it's already good)
3069 // Try to recover with minimal loss
3070 if (HashSinglePart(partNumber)) {
3071 ++m_iTotalPacketsSavedDueToICH;
3073 uint64 uMissingInPart = m_gaplist.GetGapSize(partNumber);
3074 FillGap(partNumber);
3075 RemoveBlockFromList(PARTSIZE*partNumber,(PARTSIZE*partNumber + partRange));
3077 // tell the blackbox about the verified data
3078 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3080 // remove from corrupted list
3081 EraseFirstValue(m_corrupted_list, partNumber);
3083 AddLogLineM(true, CFormat( _("ICH: Recovered corrupted part %i for %s -> Saved bytes: %s") )
3084 % partNumber
3085 % GetFileName()
3086 % CastItoXBytes(uMissingInPart));
3088 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3089 if (status == PS_EMPTY) {
3090 // Successfully recovered part, make it available for sharing
3091 SetStatus(PS_READY);
3092 if (theApp->IsRunning()) // may be called during shutdown!
3093 theApp->sharedfiles->SafeAddKFile(this);
3100 // Update met file
3101 SavePartFile();
3103 if (theApp->IsRunning()) { // may be called during shutdown!
3104 // Is this file finished ?
3105 if (m_gaplist.IsComplete()) {
3106 CompleteFile(false);
3112 // read data for upload, return false on error
3113 bool CPartFile::ReadData(CFileArea & area, uint64 offset, uint32 toread)
3115 // Sanity check
3116 if (offset + toread > GetFileSize()) {
3117 AddDebugLogLineM(false, logPartFile, CFormat(wxT("tried to read %d bytes past eof of %s"))
3118 % (offset + toread - GetFileSize()) % GetFileName());
3119 wxFAIL;
3120 return false;
3123 area.ReadAt(m_hpartfile, offset, toread);
3124 // if it fails it throws (which the caller should catch)
3125 return true;
3129 void CPartFile::UpdateFileRatingCommentAvail()
3131 bool prevComment = m_hasComment;
3132 int prevRating = m_iUserRating;
3134 m_hasComment = false;
3135 m_iUserRating = 0;
3136 int ratingCount = 0;
3138 SourceSet::iterator it = m_SrcList.begin();
3139 for (; it != m_SrcList.end(); ++it) {
3140 CUpDownClient* cur_src = *it;
3142 if (!cur_src->GetFileComment().IsEmpty()) {
3143 if (thePrefs::IsCommentFiltered(cur_src->GetFileComment())) {
3144 continue;
3146 m_hasComment = true;
3149 uint8 rating = cur_src->GetFileRating();
3150 if (rating) {
3151 wxASSERT(rating <= 5);
3153 ratingCount++;
3154 m_iUserRating += rating;
3158 if (ratingCount) {
3159 m_iUserRating /= ratingCount;
3160 wxASSERT(m_iUserRating > 0 && m_iUserRating <= 5);
3163 if ((prevComment != m_hasComment) || (prevRating != m_iUserRating)) {
3164 UpdateDisplayedInfo();
3169 void CPartFile::SetCategory(uint8 cat)
3171 wxASSERT( cat < theApp->glob_prefs->GetCatCount() );
3173 m_category = cat;
3174 SavePartFile();
3177 bool CPartFile::RemoveSource(CUpDownClient* toremove, bool updatewindow, bool bDoStatsUpdate)
3179 wxASSERT( toremove );
3181 bool result = theApp->downloadqueue->RemoveSource( toremove, updatewindow, bDoStatsUpdate );
3183 // Check if the client should be deleted, but not if the client is already dying
3184 if ( !toremove->GetSocket() && !toremove->HasBeenDeleted() ) {
3185 if ( toremove->Disconnected(wxT("RemoveSource - purged")) ) {
3186 toremove->Safe_Delete();
3190 return result;
3193 void CPartFile::AddDownloadingSource(CUpDownClient* client)
3195 CClientPtrList::iterator it =
3196 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), client);
3197 if (it == m_downloadingSourcesList.end()) {
3198 m_downloadingSourcesList.push_back(client);
3203 void CPartFile::RemoveDownloadingSource(CUpDownClient* client)
3205 CClientPtrList::iterator it =
3206 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), client);
3207 if (it != m_downloadingSourcesList.end()) {
3208 m_downloadingSourcesList.erase(it);
3213 void CPartFile::SetPartFileStatus(uint8 newstatus)
3215 status=newstatus;
3217 if (thePrefs::GetAllcatType()) {
3218 Notify_DownloadCtrlUpdateItem(this);
3221 Notify_DownloadCtrlSort();
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 AddDebugLogLineM(true, logPartFile,
3237 CFormat(wxT("Error while retrieving file-length (%s): %s"))
3238 % m_PartPath % e.what());
3239 SetPartFileStatus(PS_ERROR);
3240 return 0;
3244 void CPartFile::SetStatus(uint8 in)
3246 wxASSERT( in != PS_PAUSED && in != PS_INSUFFICIENT );
3248 status = in;
3250 if (theApp->IsRunning()) {
3251 UpdateDisplayedInfo( true );
3253 if ( thePrefs::ShowCatTabInfos() ) {
3254 Notify_ShowUpdateCatTabTitles();
3260 void CPartFile::RequestAICHRecovery(uint16 nPart)
3263 if ( !m_pAICHHashSet->HasValidMasterHash() ||
3264 (m_pAICHHashSet->GetStatus() != AICH_TRUSTED && m_pAICHHashSet->GetStatus() != AICH_VERIFIED)){
3265 AddDebugLogLineM( false, logAICHRecovery, wxT("Unable to request AICH Recoverydata because we have no trusted Masterhash") );
3266 return;
3268 if (GetPartSize(nPart) <= EMBLOCKSIZE)
3269 return;
3270 if (CAICHHashSet::IsClientRequestPending(this, nPart)){
3271 AddDebugLogLineM( false, logAICHRecovery, wxT("RequestAICHRecovery: Already a request for this part pending"));
3272 return;
3275 // first check if we have already the recoverydata, no need to rerequest it then
3276 if (m_pAICHHashSet->IsPartDataAvailable(nPart*PARTSIZE)){
3277 AddDebugLogLineM( false, logAICHRecovery, wxT("Found PartRecoveryData in memory"));
3278 AICHRecoveryDataAvailable(nPart);
3279 return;
3282 wxASSERT( nPart < GetPartCount() );
3283 // find some random client which support AICH to ask for the blocks
3284 // first lets see how many we have at all, we prefer high id very much
3285 uint32 cAICHClients = 0;
3286 uint32 cAICHLowIDClients = 0;
3287 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3288 CUpDownClient* pCurClient = *(it);
3289 if ( pCurClient->IsSupportingAICH() &&
3290 pCurClient->GetReqFileAICHHash() != NULL &&
3291 !pCurClient->IsAICHReqPending()
3292 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3294 if (pCurClient->HasLowID()) {
3295 ++cAICHLowIDClients;
3296 } else {
3297 ++cAICHClients;
3301 if ((cAICHClients | cAICHLowIDClients) == 0){
3302 AddDebugLogLineM( false, logAICHRecovery, wxT("Unable to request AICH Recoverydata because found no client who supports it and has the same hash as the trusted one"));
3303 return;
3305 uint32 nSeclectedClient;
3306 if (cAICHClients > 0) {
3307 nSeclectedClient = (rand() % cAICHClients) + 1;
3308 } else {
3309 nSeclectedClient = (rand() % cAICHLowIDClients) + 1;
3311 CUpDownClient* pClient = NULL;
3312 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3313 CUpDownClient* pCurClient = *(it);
3314 if (pCurClient->IsSupportingAICH() && pCurClient->GetReqFileAICHHash() != NULL && !pCurClient->IsAICHReqPending()
3315 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3317 if (cAICHClients > 0){
3318 if (!pCurClient->HasLowID())
3319 nSeclectedClient--;
3321 else{
3322 wxASSERT( pCurClient->HasLowID());
3323 nSeclectedClient--;
3325 if (nSeclectedClient == 0){
3326 pClient = pCurClient;
3327 break;
3331 if (pClient == NULL){
3332 wxFAIL;
3333 return;
3336 AddDebugLogLineM( false, logAICHRecovery, CFormat( wxT("Requesting AICH Hash (%s) form client %s") ) % ( cAICHClients ? wxT("HighId") : wxT("LowID") ) % pClient->GetClientFullInfo() );
3337 pClient->SendAICHRequest(this, nPart);
3342 void CPartFile::AICHRecoveryDataAvailable(uint16 nPart)
3344 if (GetPartCount() < nPart){
3345 wxFAIL;
3346 return;
3349 FlushBuffer(true);
3350 uint32 length = GetPartSize(nPart);
3351 // if the part was already ok, it would now be complete
3352 if (IsComplete(nPart)){
3353 AddDebugLogLineM( false, logAICHRecovery,
3354 wxString::Format( wxT("Processing AICH Recovery data: The part (%u) is already complete, canceling"), nPart ) );
3355 return;
3360 CAICHHashTree* pVerifiedHash = m_pAICHHashSet->m_pHashTree.FindHash(nPart*PARTSIZE, length);
3361 if (pVerifiedHash == NULL || !pVerifiedHash->GetHashValid()){
3362 AddDebugLogLineM( true, logAICHRecovery, wxT("Processing AICH Recovery data: Unable to get verified hash from hashset (should never happen)") );
3363 wxFAIL;
3364 return;
3366 CAICHHashTree htOurHash(pVerifiedHash->GetNDataSize(), pVerifiedHash->GetIsLeftBranch(), pVerifiedHash->GetNBaseSize());
3367 try {
3368 CreateHashFromFile(m_hpartfile, PARTSIZE * nPart, length, NULL, &htOurHash);
3369 } catch (const CIOFailureException& e) {
3370 AddDebugLogLineM(true, logAICHRecovery,
3371 CFormat(wxT("IO failure while hashing part-file '%s': %s"))
3372 % m_hpartfile.GetFilePath() % e.what());
3373 SetPartFileStatus(PS_ERROR);
3374 return;
3377 if (!htOurHash.GetHashValid()){
3378 AddDebugLogLineM( false, logAICHRecovery, wxT("Processing AICH Recovery data: Failed to retrieve AICH Hashset of corrupt part") );
3379 wxFAIL;
3380 return;
3383 // now compare the hash we just did, to the verified hash and readd all blocks which are ok
3384 uint32 nRecovered = 0;
3385 for (uint32 pos = 0; pos < length; pos += EMBLOCKSIZE){
3386 const uint32 nBlockSize = min<uint32>(EMBLOCKSIZE, length - pos);
3387 CAICHHashTree* pVerifiedBlock = pVerifiedHash->FindHash(pos, nBlockSize);
3388 CAICHHashTree* pOurBlock = htOurHash.FindHash(pos, nBlockSize);
3389 if ( pVerifiedBlock == NULL || pOurBlock == NULL || !pVerifiedBlock->GetHashValid() || !pOurBlock->GetHashValid()){
3390 wxFAIL;
3391 continue;
3393 if (pOurBlock->GetHash() == pVerifiedBlock->GetHash()){
3394 FillGap(PARTSIZE*nPart+pos, PARTSIZE*nPart + pos + (nBlockSize-1));
3395 RemoveBlockFromList(PARTSIZE*nPart, PARTSIZE*nPart + (nBlockSize-1));
3396 nRecovered += nBlockSize;
3397 // tell the blackbox about the verified data
3398 m_CorruptionBlackBox->VerifiedData(true, nPart, pos, pos + nBlockSize - 1);
3399 } else {
3400 // inform our "blackbox" about the corrupted block which may ban clients who sent it
3401 m_CorruptionBlackBox->VerifiedData(false, nPart, pos, pos + nBlockSize - 1);
3404 m_CorruptionBlackBox->EvaluateData();
3406 // ok now some sanity checks
3407 if (IsComplete(nPart)){
3408 // this is a bad, but it could probably happen under some rare circumstances
3409 // make sure that MD4 agrres to this fact too
3410 if (!HashSinglePart(nPart)){
3411 AddDebugLogLineM( false, logAICHRecovery,
3412 wxString::Format(wxT("Processing AICH Recovery data: The part (%u) got completed while recovering - but MD4 says it corrupt! Setting hashset to error state, deleting part"), nPart));
3413 // now we are fu... unhappy
3414 m_pAICHHashSet->SetStatus(AICH_ERROR);
3415 AddGap(nPart);
3416 wxFAIL;
3417 return;
3419 else{
3420 AddDebugLogLineM( false, logAICHRecovery, wxString::Format(
3421 wxT("Processing AICH Recovery data: The part (%u) got completed while recovering and MD4 agrees"), nPart) );
3422 if (status == PS_EMPTY && theApp->IsRunning()){
3423 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded){
3424 // Successfully recovered part, make it available for sharing
3425 SetStatus(PS_READY);
3426 theApp->sharedfiles->SafeAddKFile(this);
3430 if (theApp->IsRunning()){
3431 // Is this file finished?
3432 if (m_gaplist.IsComplete()) {
3433 CompleteFile(false);
3437 } // end sanity check
3438 // We did the best we could. If it's still incomplete, then no need to keep
3439 // bashing it with ICH. So remove it from the list of corrupted parts.
3440 EraseFirstValue(m_corrupted_list, nPart);
3441 // Update met file
3442 SavePartFile();
3444 // make sure the user appreciates our great recovering work :P
3445 AddDebugLogLineM( true, logAICHRecovery, CFormat(
3446 wxT("AICH successfully recovered %s of %s from part %u for %s") )
3447 % CastItoXBytes(nRecovered)
3448 % CastItoXBytes(length)
3449 % nPart
3450 % GetFileName() );
3454 void CPartFile::ClientStateChanged( int oldState, int newState )
3456 if ( oldState == newState )
3457 return;
3459 // If the state is -1, then it's an entirely new item
3460 if ( oldState != -1 ) {
3461 // Was the old state a valid state?
3462 if ( oldState == DS_ONQUEUE || oldState == DS_DOWNLOADING ) {
3463 m_validSources--;
3464 } else {
3465 if ( oldState == DS_CONNECTED /* || oldState == DS_REMOTEQUEUEFULL */ ) {
3466 m_validSources--;
3469 m_notCurrentSources--;
3473 // If the state is -1, then the source is being removed
3474 if ( newState != -1 ) {
3475 // Was the old state a valid state?
3476 if ( newState == DS_ONQUEUE || newState == DS_DOWNLOADING ) {
3477 ++m_validSources;
3478 } else {
3479 if ( newState == DS_CONNECTED /* || newState == DS_REMOTEQUEUEFULL */ ) {
3480 ++m_validSources;
3483 ++m_notCurrentSources;
3489 bool CPartFile::AddSource( CUpDownClient* client )
3491 if (m_SrcList.insert( client ).second) {
3492 theStats::AddFoundSource();
3493 theStats::AddSourceOrigin(client->GetSourceFrom());
3494 return true;
3495 } else {
3496 return false;
3501 bool CPartFile::DelSource( CUpDownClient* client )
3503 if (m_SrcList.erase( client )) {
3504 theStats::RemoveSourceOrigin(client->GetSourceFrom());
3505 theStats::RemoveFoundSource();
3506 return true;
3507 } else {
3508 return false;
3513 void CPartFile::UpdatePartsFrequency( CUpDownClient* client, bool increment )
3515 const BitVector& freq = client->GetPartStatus();
3517 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
3518 m_SrcpartFrequency.clear();
3519 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
3521 if ( !increment ) {
3522 return;
3526 unsigned int size = freq.size();
3527 if ( size != m_SrcpartFrequency.size() ) {
3528 return;
3531 if ( increment ) {
3532 for ( unsigned int i = 0; i < size; i++ ) {
3533 if ( freq[i] ) {
3534 m_SrcpartFrequency[i]++;
3537 } else {
3538 for ( unsigned int i = 0; i < size; i++ ) {
3539 if ( freq[i] ) {
3540 m_SrcpartFrequency[i]--;
3546 const FileRatingList &CPartFile::GetRatingAndComments()
3548 m_FileRatingList.clear();
3549 // This can be pre-processed, but is it worth the CPU?
3550 CPartFile::SourceSet::iterator it = m_SrcList.begin();
3551 for ( ; it != m_SrcList.end(); ++it ) {
3552 CUpDownClient *cur_src = *it;
3553 if (cur_src->GetFileComment().Length()>0 || cur_src->GetFileRating()>0) {
3554 // AddDebugLogLineM(false, logPartFile, wxString(wxT("found a comment for ")) << GetFileName());
3555 m_FileRatingList.push_back(SFileRating(*cur_src));
3559 return m_FileRatingList;
3562 #else // CLIENT_GUI
3564 CPartFile::CPartFile(CEC_PartFile_Tag *tag)
3566 Init();
3568 SetFileName(CPath(tag->FileName()));
3569 m_abyFileHash = tag->ID();
3570 SetFileSize(tag->SizeFull());
3571 m_gaplist.Init(GetFileSize(), true); // Init empty
3572 m_partmetfilename = CPath(tag->PartMetName());
3573 m_fullname = m_partmetfilename; // We have only the met number, so show it without path in the detail dialog.
3575 m_SrcpartFrequency.insert(m_SrcpartFrequency.end(), GetPartCount(), 0);
3577 // these are only in CLIENT_GUI and not covered by Init()
3578 m_source_count = 0;
3579 m_kbpsDown = 0;
3580 m_iDownPriorityEC = 0;
3581 m_a4af_source_count = 0;
3585 * Remote gui specific code
3587 CPartFile::~CPartFile()
3591 const FileRatingList &CPartFile::GetRatingAndComments()
3593 return m_FileRatingList;
3595 #endif // !CLIENT_GUI
3598 void CPartFile::UpdateDisplayedInfo(bool force)
3600 uint32 curTick = ::GetTickCount();
3602 // Wait 1.5s between each redraw
3603 if(force || curTick-m_lastRefreshedDLDisplay > MINWAIT_BEFORE_DLDISPLAY_WINDOWUPDATE ) {
3604 Notify_DownloadCtrlUpdateItem(this);
3605 m_lastRefreshedDLDisplay = curTick;
3611 void CPartFile::Init()
3613 m_showSources = false;
3614 m_lastsearchtime = 0;
3615 lastpurgetime = ::GetTickCount();
3616 m_paused = false;
3617 m_stopped = false;
3618 m_insufficient = false;
3620 status = PS_EMPTY;
3622 transferred = 0;
3623 m_iLastPausePurge = time(NULL);
3625 if(thePrefs::GetNewAutoDown()) {
3626 m_iDownPriority = PR_HIGH;
3627 m_bAutoDownPriority = true;
3628 } else {
3629 m_iDownPriority = PR_NORMAL;
3630 m_bAutoDownPriority = false;
3633 transferingsrc = 0; // new
3635 kBpsDown = 0.0;
3637 m_hashsetneeded = true;
3638 m_count = 0;
3639 percentcompleted = 0;
3640 completedsize=0;
3641 m_bPreviewing = false;
3642 lastseencomplete = 0;
3643 m_availablePartsCount=0;
3644 m_ClientSrcAnswered = 0;
3645 m_LastNoNeededCheck = 0;
3646 m_iRating = 0;
3647 m_nTotalBufferData = 0;
3648 m_nLastBufferFlushTime = 0;
3649 m_bPercentUpdated = false;
3650 m_bRecoveringArchive = false;
3651 m_iGainDueToCompression = 0;
3652 m_iLostDueToCorruption = 0;
3653 m_iTotalPacketsSavedDueToICH = 0;
3654 m_category = 0;
3655 m_lastRefreshedDLDisplay = 0;
3656 m_nDlActiveTime = 0;
3657 m_tActivated = 0;
3658 m_is_A4AF_auto = false;
3659 m_localSrcReqQueued = false;
3660 m_nCompleteSourcesTime = time(NULL);
3661 m_nCompleteSourcesCount = 0;
3662 m_nCompleteSourcesCountLo = 0;
3663 m_nCompleteSourcesCountHi = 0;
3665 m_validSources = 0;
3666 m_notCurrentSources = 0;
3668 // Kad
3669 m_LastSearchTimeKad = 0;
3670 m_TotalSearchesKad = 0;
3672 m_gapptrlist.Init(&m_gaplist);
3674 #ifndef CLIENT_GUI
3675 m_CorruptionBlackBox = new CCorruptionBlackBox();
3676 #endif
3679 wxString CPartFile::getPartfileStatus() const
3682 wxString mybuffer;
3684 if ((status == PS_HASHING) || (status == PS_WAITINGFORHASH)) {
3685 mybuffer=_("Hashing");
3686 } else if (status == PS_ALLOCATING) {
3687 mybuffer = _("Allocating");
3688 } else {
3689 switch (GetStatus()) {
3690 case PS_COMPLETING:
3691 mybuffer=_("Completing");
3692 break;
3693 case PS_COMPLETE:
3694 mybuffer=_("Complete");
3695 break;
3696 case PS_PAUSED:
3697 mybuffer=_("Paused");
3698 break;
3699 case PS_ERROR:
3700 mybuffer=_("Erroneous");
3701 break;
3702 case PS_INSUFFICIENT:
3703 mybuffer = _("Insufficient disk space");
3704 break;
3705 default:
3706 if (GetTransferingSrcCount()>0) {
3707 mybuffer=_("Downloading");
3708 } else {
3709 mybuffer=_("Waiting");
3711 break;
3713 if (m_stopped && (GetStatus()!=PS_COMPLETE)) {
3714 mybuffer=_("Stopped");
3718 return mybuffer;
3721 int CPartFile::getPartfileStatusRang() const
3724 int tempstatus=0;
3725 if (GetTransferingSrcCount()==0) tempstatus=1;
3726 switch (GetStatus()) {
3727 case PS_HASHING:
3728 case PS_WAITINGFORHASH:
3729 tempstatus=3;
3730 break;
3731 case PS_COMPLETING:
3732 tempstatus=4;
3733 break;
3734 case PS_COMPLETE:
3735 tempstatus=5;
3736 break;
3737 case PS_PAUSED:
3738 tempstatus=2;
3739 break;
3740 case PS_ERROR:
3741 tempstatus=6;
3742 break;
3744 return tempstatus;
3748 wxString CPartFile::GetFeedback() const
3750 wxString retval = CKnownFile::GetFeedback();
3751 if (GetStatus() != PS_COMPLETE) {
3752 retval += wxString(_("Downloaded")) + wxT(": ") + CastItoXBytes(GetCompletedSize()) + wxString::Format(wxT(" (%.2f%%)\n"), GetPercentCompleted())
3753 + _("Sources") + CFormat(wxT(": %u\n")) % GetSourceCount();
3755 return retval + _("Status") + wxT(": ") + getPartfileStatus() + wxT("\n");
3759 sint32 CPartFile::getTimeRemaining() const
3761 if (GetKBpsDown() < 0.001)
3762 return -1;
3763 else
3764 return((GetFileSize()-GetCompletedSize()) / ((int)(GetKBpsDown()*1024.0)));
3767 bool CPartFile::PreviewAvailable()
3769 FileType type = GetFiletype(GetFileName());
3771 return (((type == ftVideo) || (type == ftAudio)) && IsComplete(0, 256*1024));
3774 bool CPartFile::CheckShowItemInGivenCat(int inCategory)
3776 // easy normal cases
3777 bool IsInCat;
3778 bool IsNotFiltered = true;
3780 IsInCat = ((inCategory==0) || (inCategory>0 && inCategory==GetCategory()));
3782 switch (thePrefs::GetAllcatType()) {
3783 case 1:
3784 IsNotFiltered = GetCategory() == 0 || inCategory > 0;
3785 break;
3786 case 2:
3787 IsNotFiltered = IsPartFile();
3788 break;
3789 case 3:
3790 IsNotFiltered = !IsPartFile();
3791 break;
3792 case 4:
3793 IsNotFiltered =
3794 (GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
3795 GetTransferingSrcCount() == 0;
3796 break;
3797 case 5:
3798 IsNotFiltered =
3799 (GetStatus() == PS_READY || GetStatus()==PS_EMPTY) &&
3800 GetTransferingSrcCount() > 0;
3801 break;
3802 case 6:
3803 IsNotFiltered = GetStatus() == PS_ERROR;
3804 break;
3805 case 7:
3806 IsNotFiltered = GetStatus() == PS_PAUSED && !IsStopped();
3807 break;
3808 case 8:
3809 IsNotFiltered = IsStopped();
3810 break;
3811 case 9:
3812 IsNotFiltered = GetFiletype(GetFileName()) == ftVideo;
3813 break;
3814 case 10:
3815 IsNotFiltered = GetFiletype(GetFileName()) == ftAudio;
3816 break;
3817 case 11:
3818 IsNotFiltered = GetFiletype(GetFileName()) == ftArchive;
3819 break;
3820 case 12:
3821 IsNotFiltered = GetFiletype(GetFileName()) == ftCDImage;
3822 break;
3823 case 13:
3824 IsNotFiltered = GetFiletype(GetFileName()) == ftPicture;
3825 break;
3826 case 14:
3827 IsNotFiltered = GetFiletype(GetFileName()) == ftText;
3828 break;
3829 case 15:
3830 IsNotFiltered = !IsStopped() && GetStatus() != PS_PAUSED;
3831 break;
3834 return IsNotFiltered && IsInCat;
3838 void CPartFile::SetActive(bool bActive)
3840 time_t tNow = time(NULL);
3841 if (bActive) {
3842 if (theApp->IsConnected()) {
3843 if (m_tActivated == 0) {
3844 m_tActivated = tNow;
3847 } else {
3848 if (m_tActivated != 0) {
3849 m_nDlActiveTime += tNow - m_tActivated;
3850 m_tActivated = 0;
3856 uint32 CPartFile::GetDlActiveTime() const
3858 uint32 nDlActiveTime = m_nDlActiveTime;
3859 if (m_tActivated != 0) {
3860 nDlActiveTime += time(NULL) - m_tActivated;
3862 return nDlActiveTime;
3865 #ifndef CLIENT_GUI
3867 uint8 CPartFile::GetStatus(bool ignorepause) const
3869 if ( (!m_paused && !m_insufficient) ||
3870 status == PS_ERROR ||
3871 status == PS_COMPLETING ||
3872 status == PS_COMPLETE ||
3873 ignorepause) {
3874 return status;
3875 } else if ( m_insufficient ) {
3876 return PS_INSUFFICIENT;
3877 } else {
3878 return PS_PAUSED;
3882 void CPartFile::AddDeadSource(const CUpDownClient* client)
3884 m_deadSources.AddDeadSource( client );
3888 bool CPartFile::IsDeadSource(const CUpDownClient* client)
3890 return m_deadSources.IsDeadSource( client );
3893 void CPartFile::SetFileName(const CPath& fileName)
3895 CKnownFile* pFile = theApp->sharedfiles->GetFileByID(GetFileHash());
3897 bool is_shared = (pFile && pFile == this);
3899 if (is_shared) {
3900 // The file is shared, we must clear the search keywords so we don't
3901 // publish the old name anymore.
3902 theApp->sharedfiles->RemoveKeywords(this);
3905 CKnownFile::SetFileName(fileName);
3907 if (is_shared) {
3908 // And of course, we must advertise the new name if the file is shared.
3909 theApp->sharedfiles->AddKeywords(this);
3912 UpdateDisplayedInfo(true);
3916 uint16 CPartFile::GetMaxSources() const
3918 // This is just like this, while we don't import the private max sources per file
3919 return thePrefs::GetMaxSourcePerFile();
3923 uint16 CPartFile::GetMaxSourcePerFileSoft() const
3925 unsigned int temp = ((unsigned int)GetMaxSources() * 9L) / 10;
3926 if (temp > MAX_SOURCES_FILE_SOFT) {
3927 return MAX_SOURCES_FILE_SOFT;
3929 return temp;
3932 uint16 CPartFile::GetMaxSourcePerFileUDP() const
3934 unsigned int temp = ((unsigned int)GetMaxSources() * 3L) / 4;
3935 if (temp > MAX_SOURCES_FILE_UDP) {
3936 return MAX_SOURCES_FILE_UDP;
3938 return temp;
3941 #define DROP_FACTOR 2
3943 CUpDownClient* CPartFile::GetSlowerDownloadingClient(uint32 speed, CUpDownClient* caller) {
3944 // printf("Start slower source calculation\n");
3945 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
3946 CUpDownClient* cur_src = *it++;
3947 if ((cur_src->GetDownloadState() == DS_DOWNLOADING) && (cur_src != caller)) {
3948 uint32 factored_bytes_per_second = static_cast<uint32>(
3949 (cur_src->GetKBpsDown() * 1024) * DROP_FACTOR);
3950 if ( factored_bytes_per_second< speed) {
3951 // printf("Selecting source %p to drop: %d < %d\n", cur_src, factored_bytes_per_second, speed);
3952 // printf("End slower source calculation\n");
3953 return cur_src;
3954 } else {
3955 // printf("Not selecting source %p to drop: %d > %d\n", cur_src, factored_bytes_per_second, speed);
3959 // printf("End slower source calculation\n");
3960 return NULL;
3963 void CPartFile::AllocationFinished()
3965 // see if it can be opened
3966 if (!m_hpartfile.Open(m_PartPath, CFile::read_write)) {
3967 AddLogLineM(false, CFormat(_("ERROR: Failed to open partfile '%s'")) % GetFullName());
3968 SetPartFileStatus(PS_ERROR);
3970 // then close the handle again
3971 m_hpartfile.Release(true);
3974 #endif
3975 // File_checked_for_headers