Remove do-nothing command and add warning about it
[amule.git] / src / PartFile.cpp
blob932ffea2b71b4fd8af4a888fd82aa2e490db1793
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #include <wx/wx.h>
28 #include "PartFile.h" // Interface declarations.
29 #include "config.h" // Needed for VERSION
30 #include <protocol/kad/Constants.h>
31 #include <protocol/ed2k/Client2Client/TCP.h>
32 #include <protocol/Protocols.h>
33 #include <common/DataFileVersion.h>
34 #include <common/Constants.h>
35 #include <tags/FileTags.h>
37 #include <wx/utils.h>
38 #include <wx/tokenzr.h> // Needed for wxStringTokenizer
39 #if wxCHECK_VERSION(2,9,0)
40 #ifndef AMULE_DAEMON
41 #include <wx/notifmsg.h> // Needed for wxNotificationMessage
42 #endif
43 #endif
45 #include "KnownFileList.h" // Needed for CKnownFileList
46 #include "CanceledFileList.h"
47 #include "UploadQueue.h" // Needed for CFileHash
48 #include "IPFilter.h" // Needed for CIPFilter
49 #include "Server.h" // Needed for CServer
50 #include "ServerConnect.h" // Needed for CServerConnect
52 #ifdef CLIENT_GUI
53 #include "UpDownClientEC.h" // Needed for CUpDownClient
54 #else
55 #include "updownclient.h" // Needed for CUpDownClient
56 #endif
58 #include "MemFile.h" // Needed for CMemFile
59 #include "Preferences.h" // Needed for CPreferences
60 #include "DownloadQueue.h" // Needed for CDownloadQueue
61 #include "amule.h" // Needed for theApp
62 #include "ED2KLink.h" // Needed for CED2KLink
63 #include "Packet.h" // Needed for CTag
64 #include "SearchList.h" // Needed for CSearchFile
65 #include "ClientList.h" // Needed for clientlist
66 #include "Statistics.h" // Needed for theStats
67 #include "Logger.h"
68 #include <common/Format.h> // Needed for CFormat
69 #include <common/FileFunctions.h> // Needed for GetLastModificationTime
70 #include "ThreadTasks.h" // Needed for CHashingTask/CCompletionTask/CAllocateFileTask
71 #include "GuiEvents.h" // Needed for Notify_*
72 #include "DataToText.h" // Needed for OriginToText()
73 #include "PlatformSpecific.h" // Needed for CreateSparseFile()
74 #include "FileArea.h" // Needed for CFileArea
75 #include "ScopedPtr.h" // Needed for CScopedArray
76 #include "CorruptionBlackBox.h"
78 #include "kademlia/kademlia/Kademlia.h"
79 #include "kademlia/kademlia/Search.h"
82 SFileRating::SFileRating(const wxString &u, const wxString &f, sint16 r, const wxString &c)
84 UserName(u),
85 FileName(f),
86 Rating(r),
87 Comment(c)
92 SFileRating::SFileRating(const SFileRating &fr)
94 UserName(fr.UserName),
95 FileName(fr.FileName),
96 Rating(fr.Rating),
97 Comment(fr.Comment)
102 #ifndef CLIENT_GUI
103 SFileRating::SFileRating(const CUpDownClient &client)
105 UserName(client.GetUserName()),
106 FileName(client.GetClientFilename()),
107 Rating(client.GetFileRating()),
108 Comment(client.GetFileComment())
111 #endif
114 SFileRating::~SFileRating()
119 class PartFileBufferedData
121 public:
122 CFileArea area; // File area to be written
123 uint64 start; // This is the start offset of the data
124 uint64 end; // This is the end offset of the data
125 Requested_Block_Struct *block; // This is the requested block that this data relates to
127 PartFileBufferedData(CFileAutoClose& file, uint8_t * data, uint64 _start, uint64 _end, Requested_Block_Struct *_block)
128 : start(_start), end(_end), block(_block)
130 area.StartWriteAt(file, start, end-start+1);
131 memcpy(area.GetBuffer(), data, end-start+1);
136 typedef std::list<Chunk> ChunkList;
139 #ifndef CLIENT_GUI
141 CPartFile::CPartFile()
143 Init();
146 CPartFile::CPartFile(CSearchFile* searchresult)
148 Init();
150 m_abyFileHash = searchresult->GetFileHash();
151 SetFileName(searchresult->GetFileName());
152 SetFileSize(searchresult->GetFileSize());
154 for (unsigned int i = 0; i < searchresult->m_taglist.size(); ++i){
155 const CTag& pTag = searchresult->m_taglist[i];
157 bool bTagAdded = false;
158 if (pTag.GetNameID() == 0 && !pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
159 static const struct {
160 wxString pszName;
161 uint8 nType;
162 } _aMetaTags[] =
164 { wxT(FT_ED2K_MEDIA_ARTIST), 2 },
165 { wxT(FT_ED2K_MEDIA_ALBUM), 2 },
166 { wxT(FT_ED2K_MEDIA_TITLE), 2 },
167 { wxT(FT_ED2K_MEDIA_LENGTH), 2 },
168 { wxT(FT_ED2K_MEDIA_BITRATE), 3 },
169 { wxT(FT_ED2K_MEDIA_CODEC), 2 }
172 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
173 if ( pTag.GetType() == _aMetaTags[t].nType &&
174 (pTag.GetName() == _aMetaTags[t].pszName)) {
175 // skip string tags with empty string values
176 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
177 break;
180 // skip "length" tags with "0: 0" values
181 if (pTag.GetName() == wxT(FT_ED2K_MEDIA_LENGTH)) {
182 if (pTag.GetStr().IsSameAs(wxT("0: 0")) ||
183 pTag.GetStr().IsSameAs(wxT("0:0"))) {
184 break;
188 // skip "bitrate" tags with '0' values
189 if ((pTag.GetName() == wxT(FT_ED2K_MEDIA_BITRATE)) && !pTag.GetInt()) {
190 break;
193 AddDebugLogLineN( logPartFile,
194 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
195 pTag.GetFullInfo() );
196 m_taglist.push_back(pTag);
197 bTagAdded = true;
198 break;
201 } else if (pTag.GetNameID() != 0 && pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
202 static const struct {
203 uint8 nID;
204 uint8 nType;
205 } _aMetaTags[] =
207 { FT_FILETYPE, 2 },
208 { FT_FILEFORMAT, 2 }
210 for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
211 if (pTag.GetType() == _aMetaTags[t].nType && pTag.GetNameID() == _aMetaTags[t].nID) {
212 // skip string tags with empty string values
213 if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
214 break;
217 AddDebugLogLineN( logPartFile,
218 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
219 pTag.GetFullInfo() );
220 m_taglist.push_back(pTag);
221 bTagAdded = true;
222 break;
227 if (!bTagAdded) {
228 AddDebugLogLineN( logPartFile,
229 wxT("CPartFile::CPartFile(CSearchFile*): ignored tag ") +
230 pTag.GetFullInfo() );
234 CreatePartFile();
238 CPartFile::CPartFile(const CED2KFileLink* fileLink)
240 Init();
242 SetFileName(CPath(fileLink->GetName()));
243 SetFileSize(fileLink->GetSize());
244 m_abyFileHash = fileLink->GetHashKey();
246 CreatePartFile();
248 if (fileLink->m_hashset) {
249 if (!LoadHashsetFromFile(fileLink->m_hashset, true)) {
250 AddDebugLogLineC(logPartFile, wxT("eD2K link contained invalid hashset: ") + fileLink->GetLink());
256 CPartFile::~CPartFile()
258 // if it's not opened, it was completed or deleted
259 if (m_hpartfile.IsOpened()) {
260 FlushBuffer();
261 m_hpartfile.Close();
262 // Update met file (with current directory entry)
263 SavePartFile();
266 DeleteContents(m_BufferedData_list);
267 delete m_CorruptionBlackBox;
269 wxASSERT(m_SrcList.empty());
270 wxASSERT(m_A4AFsrclist.empty());
273 void CPartFile::CreatePartFile(bool isImporting)
275 // use lowest free partfilenumber for free file (InterCeptor)
276 int i = 0;
277 do {
278 ++i;
279 m_partmetfilename = CPath(CFormat(wxT("%03i.part.met")) % i);
280 m_fullname = thePrefs::GetTempDir().JoinPaths(m_partmetfilename);
281 } while (m_fullname.FileExists());
283 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
285 m_gaplist.Init(GetFileSize(), true); // Init empty
287 m_PartPath = m_fullname.RemoveExt();
288 bool fileCreated;
289 if (thePrefs::GetAllocFullFile() || !thePrefs::CreateFilesSparse()) {
290 fileCreated = m_hpartfile.Create(m_PartPath, true);
291 m_hpartfile.Close();
292 } else {
293 fileCreated = PlatformSpecific::CreateSparseFile(m_PartPath, GetFileSize());
295 if (!fileCreated) {
296 AddLogLineN(_("ERROR: Failed to create partfile"));
297 SetStatus(PS_ERROR);
300 SetFilePath(thePrefs::GetTempDir());
302 if (!isImporting && thePrefs::GetAllocFullFile()) {
303 SetStatus(PS_ALLOCATING);
304 CThreadScheduler::AddTask(new CAllocateFileTask(this, thePrefs::AddNewFilesPaused()));
305 } else {
306 AllocationFinished();
309 m_hashsetneeded = (GetED2KPartHashCount() > 0);
311 SavePartFile(true);
312 SetActive(theApp->IsConnected());
316 uint8 CPartFile::LoadPartFile(const CPath& in_directory, const CPath& filename, bool from_backup, bool getsizeonly)
318 bool isnewstyle = false;
319 uint8 version,partmettype=PMT_UNKNOWN;
321 std::map<uint16, Gap_Struct*> gap_map; // Slugfiller
322 transferred = 0;
324 m_partmetfilename = filename;
325 m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
326 m_filePath = in_directory;
327 m_fullname = m_filePath.JoinPaths(m_partmetfilename);
328 m_PartPath = m_fullname.RemoveExt();
330 // readfile data form part.met file
331 CPath curMetFilename = m_fullname;
332 if (from_backup) {
333 curMetFilename = curMetFilename.AppendExt(PARTMET_BAK_EXT);
334 AddLogLineN(CFormat( _("Trying to load backup of met-file from %s") )
335 % curMetFilename );
338 try {
339 CFile metFile(curMetFilename, CFile::read);
340 if (!metFile.IsOpened()) {
341 AddLogLineN(CFormat( _("ERROR: Failed to open part.met file: %s ==> %s") )
342 % curMetFilename
343 % GetFileName() );
345 return false;
346 } else if (metFile.GetLength() == 0) {
347 AddLogLineN(CFormat( _("ERROR: part.met file is 0 size: %s ==> %s") )
348 % m_partmetfilename
349 % GetFileName() );
351 return false;
354 version = metFile.ReadUInt8();
355 if (version != PARTFILE_VERSION && version != PARTFILE_SPLITTEDVERSION && version != PARTFILE_VERSION_LARGEFILE){
356 metFile.Close();
357 //if (version == 83) return ImportShareazaTempFile(...)
358 AddLogLineN(CFormat( _("ERROR: Invalid part.met file version: %s ==> %s") )
359 % m_partmetfilename
360 % GetFileName() );
361 return false;
364 isnewstyle = (version == PARTFILE_SPLITTEDVERSION);
365 partmettype = isnewstyle ? PMT_SPLITTED : PMT_DEFAULTOLD;
367 if (version == PARTFILE_VERSION) {// Do we still need this check ?
368 uint8 test[4]; // It will fail for certain files.
369 metFile.Seek(24, wxFromStart);
370 metFile.Read(test,4);
372 metFile.Seek(1, wxFromStart);
373 if (test[0]==0 && test[1]==0 && test[2]==2 && test[3]==1) {
374 isnewstyle=true; // edonkeys so called "old part style"
375 partmettype=PMT_NEWOLD;
379 if (isnewstyle) {
380 uint32 temp = metFile.ReadUInt32();
382 if (temp==0) { // 0.48 partmets - different again
383 LoadHashsetFromFile(&metFile, false);
384 } else {
385 metFile.Seek(2, wxFromStart);
386 LoadDateFromFile(&metFile);
387 m_abyFileHash = metFile.ReadHash();
390 } else {
391 LoadDateFromFile(&metFile);
392 LoadHashsetFromFile(&metFile, false);
395 uint32 tagcount = metFile.ReadUInt32();
397 for (uint32 j = 0; j < tagcount; ++j) {
398 CTag newtag(metFile,true);
399 if ( !getsizeonly ||
400 (getsizeonly &&
401 (newtag.GetNameID() == FT_FILESIZE ||
402 newtag.GetNameID() == FT_FILENAME))) {
403 switch(newtag.GetNameID()) {
404 case FT_FILENAME: {
405 if (!GetFileName().IsOk()) {
406 // If it's not empty, we already loaded the unicoded one
407 SetFileName(CPath(newtag.GetStr()));
409 break;
411 case FT_LASTSEENCOMPLETE: {
412 lastseencomplete = newtag.GetInt();
413 break;
415 case FT_FILESIZE: {
416 SetFileSize(newtag.GetInt());
417 break;
419 case FT_TRANSFERRED: {
420 transferred = newtag.GetInt();
421 break;
423 case FT_FILETYPE:{
424 //#warning needs setfiletype string
425 //SetFileType(newtag.GetStr());
426 break;
428 case FT_CATEGORY: {
429 m_category = newtag.GetInt();
430 if (m_category > theApp->glob_prefs->GetCatCount() - 1 ) {
431 m_category = 0;
433 break;
435 case FT_OLDDLPRIORITY:
436 case FT_DLPRIORITY: {
437 if (!isnewstyle){
438 m_iDownPriority = newtag.GetInt();
439 if( m_iDownPriority == PR_AUTO ){
440 m_iDownPriority = PR_HIGH;
441 SetAutoDownPriority(true);
443 else{
444 if ( m_iDownPriority != PR_LOW &&
445 m_iDownPriority != PR_NORMAL &&
446 m_iDownPriority != PR_HIGH)
447 m_iDownPriority = PR_NORMAL;
448 SetAutoDownPriority(false);
451 break;
453 case FT_STATUS: {
454 m_paused = (newtag.GetInt() == 1);
455 m_stopped = m_paused;
456 break;
458 case FT_OLDULPRIORITY:
459 case FT_ULPRIORITY: {
460 if (!isnewstyle){
461 SetUpPriority(newtag.GetInt(), false);
462 if( GetUpPriority() == PR_AUTO ){
463 SetUpPriority(PR_HIGH, false);
464 SetAutoUpPriority(true);
465 } else {
466 SetAutoUpPriority(false);
469 break;
471 case FT_KADLASTPUBLISHSRC:{
472 SetLastPublishTimeKadSrc(newtag.GetInt(), 0);
473 if(GetLastPublishTimeKadSrc() > (uint32)time(NULL)+KADEMLIAREPUBLISHTIMES) {
474 //There may be a posibility of an older client that saved a random number here.. This will check for that..
475 SetLastPublishTimeKadSrc(0,0);
477 break;
479 case FT_KADLASTPUBLISHNOTES:{
480 SetLastPublishTimeKadNotes(newtag.GetInt());
481 break;
483 // old tags: as long as they are not needed, take the chance to purge them
484 case FT_PERMISSIONS:
485 case FT_KADLASTPUBLISHKEY:
486 case FT_PARTFILENAME:
487 break;
488 case FT_DL_ACTIVE_TIME:
489 if (newtag.IsInt()) {
490 m_nDlActiveTime = newtag.GetInt();
492 break;
493 case FT_CORRUPTEDPARTS: {
494 wxASSERT(m_corrupted_list.empty());
495 wxString strCorruptedParts(newtag.GetStr());
496 wxStringTokenizer tokenizer(strCorruptedParts, wxT(","));
497 while ( tokenizer.HasMoreTokens() ) {
498 wxString token = tokenizer.GetNextToken();
499 unsigned long uPart;
500 if (token.ToULong(&uPart)) {
501 if (uPart < GetPartCount() && !IsCorruptedPart(uPart)) {
502 m_corrupted_list.push_back(uPart);
506 break;
508 case FT_AICH_HASH:{
509 CAICHHash hash;
510 bool hashSizeOk =
511 hash.DecodeBase32(newtag.GetStr()) == CAICHHash::GetHashSize();
512 wxASSERT(hashSizeOk);
513 if (hashSizeOk) {
514 m_pAICHHashSet->SetMasterHash(hash, AICH_VERIFIED);
516 break;
518 case FT_ATTRANSFERRED:{
519 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (uint64)newtag.GetInt());
520 break;
522 case FT_ATTRANSFERREDHI:{
523 statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (((uint64)newtag.GetInt()) << 32));
524 break;
526 case FT_ATREQUESTED:{
527 statistic.SetAllTimeRequests(newtag.GetInt());
528 break;
530 case FT_ATACCEPTED:{
531 statistic.SetAllTimeAccepts(newtag.GetInt());
532 break;
534 default: {
535 // Start Changes by Slugfiller for better exception handling
537 wxCharBuffer tag_ansi_name = newtag.GetName().ToAscii();
538 char gap_mark = tag_ansi_name.data() ? tag_ansi_name[0u] : 0;
539 if ( newtag.IsInt() && (newtag.GetName().Length() > 1) &&
540 ((gap_mark == FT_GAPSTART) ||
541 (gap_mark == FT_GAPEND))) {
542 Gap_Struct *gap = NULL;
543 unsigned long int gapkey;
544 if (newtag.GetName().Mid(1).ToULong(&gapkey)) {
545 if ( gap_map.find( gapkey ) == gap_map.end() ) {
546 gap = new Gap_Struct;
547 gap_map[gapkey] = gap;
548 gap->start = (uint64)-1;
549 gap->end = (uint64)-1;
550 } else {
551 gap = gap_map[ gapkey ];
553 if (gap_mark == FT_GAPSTART) {
554 gap->start = newtag.GetInt();
556 if (gap_mark == FT_GAPEND) {
557 gap->end = newtag.GetInt()-1;
559 } else {
560 AddDebugLogLineN(logPartFile, wxT("Wrong gap map key while reading met file!"));
561 wxFAIL;
563 // End Changes by Slugfiller for better exception handling
564 } else {
565 m_taglist.push_back(newtag);
569 } else {
570 // Nothing. Else, nothing.
574 // load the hashsets from the hybridstylepartmet
575 if (isnewstyle && !getsizeonly && (metFile.GetPosition()<metFile.GetLength()) ) {
576 metFile.Seek(1, wxFromCurrent);
578 uint16 parts=GetPartCount(); // assuming we will get all hashsets
580 for (uint16 i = 0; i < parts && (metFile.GetPosition()+16<metFile.GetLength()); ++i){
581 CMD4Hash cur_hash = metFile.ReadHash();
582 m_hashlist.push_back(cur_hash);
585 CMD4Hash checkhash;
586 if (!m_hashlist.empty()) {
587 CreateHashFromHashlist(m_hashlist, &checkhash);
589 if (m_abyFileHash != checkhash) {
590 m_hashlist.clear();
593 } catch (const CInvalidPacket& e) {
594 AddLogLineC(CFormat(_("Error: %s (%s) is corrupt (bad tags: %s), unable to load file."))
595 % m_partmetfilename
596 % GetFileName()
597 % e.what());
598 return false;
599 } catch (const CIOFailureException& e) {
600 AddDebugLogLineC(logPartFile, CFormat( wxT("IO failure while loading '%s': %s") )
601 % m_partmetfilename
602 % e.what() );
603 return false;
604 } catch (const CEOFException& WXUNUSED(e)) {
605 AddLogLineC(CFormat( _("ERROR: %s (%s) is corrupt (wrong tagcount), unable to load file.") )
606 % m_partmetfilename
607 % GetFileName() );
608 AddLogLineC(_("Trying to recover file info..."));
610 // Safe file is that who have
611 // - FileSize
612 if (GetFileSize()) {
613 // We have filesize, try other needed info
615 // Do we need to check gaps? I think not,
616 // because they are checked below. Worst
617 // scenario will only mark file as 0 bytes downloaded.
619 // -Filename
620 if (!GetFileName().IsOk()) {
621 // Not critical, let's put a random filename.
622 AddLogLineC(_(
623 "Recovering no-named file - will try to recover it as RecoveredFile.dat"));
624 SetFileName(CPath(wxT("RecoveredFile.dat")));
627 AddLogLineC(_("Recovered all available file info :D - Trying to use it..."));
628 } else {
629 AddLogLineC(_("Unable to recover file info :("));
630 return false;
634 if (getsizeonly) {
635 return partmettype;
637 // Init Gaplist
638 m_gaplist.Init(GetFileSize(), false); // Init full, then add gaps
639 // Now to flush the map into the list (Slugfiller)
640 std::map<uint16, Gap_Struct*>::iterator it = gap_map.begin();
641 for ( ; it != gap_map.end(); ++it ) {
642 Gap_Struct* gap = it->second;
643 // SLUGFILLER: SafeHash - revised code, and extra safety
644 if ( (gap->start != (uint64)-1) &&
645 (gap->end != (uint64)-1) &&
646 gap->start <= gap->end &&
647 gap->start < GetFileSize()) {
648 if (gap->end >= GetFileSize()) {
649 gap->end = GetFileSize()-1; // Clipping
651 m_gaplist.AddGap(gap->start, gap->end); // All tags accounted for, use safe adding
653 delete gap;
654 // SLUGFILLER: SafeHash
657 //check if this is a backup
658 if ( m_fullname.GetExt().MakeLower() == wxT("backup" )) {
659 m_fullname = m_fullname.RemoveExt();
662 // open permanent handle
663 if ( !m_hpartfile.Open(m_PartPath, CFile::read_write)) {
664 AddLogLineN(CFormat( _("Failed to open %s (%s)") )
665 % m_fullname
666 % GetFileName() );
667 return false;
670 SetStatus(PS_EMPTY);
672 try {
673 // SLUGFILLER: SafeHash - final safety, make sure any missing part of the file is gap
674 if (m_hpartfile.GetLength() < GetFileSize())
675 AddGap(m_hpartfile.GetLength(), GetFileSize()-1);
676 // Goes both ways - Partfile should never be too large
677 if (m_hpartfile.GetLength() > GetFileSize()) {
678 AddDebugLogLineC(logPartFile, CFormat( wxT("Partfile \"%s\" is too large! Truncating %llu bytes.") ) % GetFileName() % (m_hpartfile.GetLength() - GetFileSize()));
679 m_hpartfile.SetLength(GetFileSize());
681 // SLUGFILLER: SafeHash
682 } catch (const CIOFailureException& e) {
683 AddDebugLogLineC(logPartFile, CFormat( wxT("Error while accessing partfile \"%s\": %s") ) % GetFileName() % e.what());
684 SetStatus(PS_ERROR);
687 // now close the file again until needed
688 m_hpartfile.Release(true);
690 // check hashcount, file status etc
691 if (GetHashCount() != GetED2KPartHashCount()){
692 m_hashsetneeded = true;
693 return true;
694 } else {
695 m_hashsetneeded = false;
696 for (size_t i = 0; i < m_hashlist.size(); ++i) {
697 if (IsComplete(i)) {
698 SetStatus(PS_READY);
703 if (m_gaplist.IsComplete()) { // is this file complete already?
704 CompleteFile(false);
705 return true;
708 if (!isnewstyle) { // not for importing
709 const time_t file_date = CPath::GetModificationTime(m_PartPath);
710 if (m_lastDateChanged != file_date) {
711 // It's pointless to rehash an empty file, since the case
712 // where a user has zero'd a file is handled above ...
713 if (m_hpartfile.GetLength()) {
714 AddLogLineN(CFormat( _("WARNING: %s might be corrupted (%i)") )
715 % m_PartPath
716 % (m_lastDateChanged - file_date) );
717 // rehash
718 SetStatus(PS_WAITINGFORHASH);
720 CPath partFileName = m_partmetfilename.RemoveExt();
721 CThreadScheduler::AddTask(new CHashingTask(m_filePath, partFileName, this));
726 UpdateCompletedInfos();
727 if (completedsize > transferred) {
728 m_iGainDueToCompression = completedsize - transferred;
729 } else if (completedsize != transferred) {
730 m_iLostDueToCorruption = transferred - completedsize;
733 return true;
737 bool CPartFile::SavePartFile(bool Initial)
739 switch (status) {
740 case PS_WAITINGFORHASH:
741 case PS_HASHING:
742 case PS_COMPLETE:
743 return false;
746 /* Don't write anything to disk if less than 100 KB of free space is left. */
747 sint64 free = CPath::GetFreeSpaceAt(GetFilePath());
748 if ((free != wxInvalidOffset) && (free < (100 * 1024))) {
749 return false;
752 CFile file;
753 try {
754 if (!m_PartPath.FileExists()) {
755 throw wxString(wxT(".part file not found"));
758 uint32 lsc = lastseencomplete;
760 if (!Initial) {
761 CPath::BackupFile(m_fullname, wxT(".backup"));
762 CPath::RemoveFile(m_fullname);
765 file.Open(m_fullname, CFile::write);
766 if (!file.IsOpened()) {
767 throw wxString(wxT("Failed to open part.met file"));
770 // version
771 file.WriteUInt8(IsLargeFile() ? PARTFILE_VERSION_LARGEFILE : PARTFILE_VERSION);
773 file.WriteUInt32(CPath::GetModificationTime(m_PartPath));
774 // hash
775 file.WriteHash(m_abyFileHash);
776 uint16 parts = m_hashlist.size();
777 file.WriteUInt16(parts);
778 for (int x = 0; x < parts; ++x) {
779 file.WriteHash(m_hashlist[x]);
781 // tags
782 #define FIXED_TAGS 15
783 uint32 tagcount = m_taglist.size() + FIXED_TAGS + (m_gaplist.size()*2);
784 if (!m_corrupted_list.empty()) {
785 ++tagcount;
788 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
789 ++tagcount;
792 if (GetLastPublishTimeKadSrc()){
793 ++tagcount;
796 if (GetLastPublishTimeKadNotes()){
797 ++tagcount;
800 if (GetDlActiveTime()){
801 ++tagcount;
804 file.WriteUInt32(tagcount);
806 //#warning Kry - Where are lost by coruption and gained by compression?
808 // 0 (unicoded part file name)
809 // We write it with BOM to keep eMule compatibility. Note that the 'printable' filename is saved,
810 // as presently the filename does not represent an actual file.
811 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file, utf8strOptBOM );
812 CTagString( FT_FILENAME, GetFileName().GetPrintable()).WriteTagToFile( &file ); // 1
814 CTagIntSized( FT_FILESIZE, GetFileSize(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file );// 2
815 CTagIntSized( FT_TRANSFERRED, transferred, IsLargeFile() ? 64 : 32).WriteTagToFile( &file ); // 3
816 CTagInt32( FT_STATUS, (m_paused?1:0)).WriteTagToFile( &file ); // 4
818 if ( IsAutoDownPriority() ) {
819 CTagInt32( FT_DLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 5
820 CTagInt32( FT_OLDDLPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 6
821 } else {
822 CTagInt32( FT_DLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 5
823 CTagInt32( FT_OLDDLPRIORITY, m_iDownPriority ).WriteTagToFile( &file ); // 6
826 CTagInt32( FT_LASTSEENCOMPLETE, lsc ).WriteTagToFile( &file ); // 7
828 if ( IsAutoUpPriority() ) {
829 CTagInt32( FT_ULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 8
830 CTagInt32( FT_OLDULPRIORITY, (uint8)PR_AUTO ).WriteTagToFile( &file ); // 9
831 } else {
832 CTagInt32( FT_ULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 8
833 CTagInt32( FT_OLDULPRIORITY, GetUpPriority() ).WriteTagToFile( &file ); // 9
836 CTagInt32(FT_CATEGORY, m_category).WriteTagToFile( &file ); // 10
837 CTagInt32(FT_ATTRANSFERRED, statistic.GetAllTimeTransferred() & 0xFFFFFFFF).WriteTagToFile( &file );// 11
838 CTagInt32(FT_ATTRANSFERREDHI, statistic.GetAllTimeTransferred() >>32).WriteTagToFile( &file );// 12
839 CTagInt32(FT_ATREQUESTED, statistic.GetAllTimeRequests()).WriteTagToFile( &file ); // 13
840 CTagInt32(FT_ATACCEPTED, statistic.GetAllTimeAccepts()).WriteTagToFile( &file ); // 14
842 // currupt part infos
843 if (!m_corrupted_list.empty()) {
844 wxString strCorruptedParts;
845 std::list<uint16>::iterator it = m_corrupted_list.begin();
846 for (; it != m_corrupted_list.end(); ++it) {
847 uint16 uCorruptedPart = *it;
848 if (!strCorruptedParts.IsEmpty()) {
849 strCorruptedParts += wxT(",");
851 strCorruptedParts += CFormat(wxT("%u")) % uCorruptedPart;
853 wxASSERT( !strCorruptedParts.IsEmpty() );
855 CTagString( FT_CORRUPTEDPARTS, strCorruptedParts ).WriteTagToFile( &file); // 11?
858 //AICH Filehash
859 if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
860 CTagString aichtag(FT_AICH_HASH, m_pAICHHashSet->GetMasterHash().GetString() );
861 aichtag.WriteTagToFile(&file); // 12?
864 if (GetLastPublishTimeKadSrc()){
865 CTagInt32(FT_KADLASTPUBLISHSRC, GetLastPublishTimeKadSrc()).WriteTagToFile(&file); // 15?
868 if (GetLastPublishTimeKadNotes()){
869 CTagInt32(FT_KADLASTPUBLISHNOTES, GetLastPublishTimeKadNotes()).WriteTagToFile(&file); // 16?
872 if (GetDlActiveTime()){
873 CTagInt32(FT_DL_ACTIVE_TIME, GetDlActiveTime()).WriteTagToFile(&file); // 17
876 for (uint32 j = 0; j < (uint32)m_taglist.size();++j) {
877 m_taglist[j].WriteTagToFile(&file);
880 // gaps
881 unsigned i_pos = 0;
882 for (CGapList::const_iterator it = m_gaplist.begin(); it != m_gaplist.end(); ++it) {
883 wxString tagName = CFormat(wxT(" %u")) % i_pos;
885 // gap start = first missing byte but gap ends = first non-missing byte
886 // in edonkey but I think its easier to user the real limits
887 tagName[0] = FT_GAPSTART;
888 CTagIntSized(tagName, it.start(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
890 tagName[0] = FT_GAPEND;
891 CTagIntSized(tagName, it.end() + 1, IsLargeFile() ? 64 : 32).WriteTagToFile( &file );
893 ++i_pos;
895 } catch (const wxString& error) {
896 AddLogLineNS(CFormat( _("ERROR while saving partfile: %s (%s ==> %s)") )
897 % error
898 % m_partmetfilename
899 % GetFileName() );
901 return false;
902 } catch (const CIOFailureException& e) {
903 AddLogLineCS(_("IO failure while saving partfile: ") + e.what());
905 return false;
908 file.Close();
910 if (!Initial) {
911 CPath::RemoveFile(m_fullname.AppendExt(wxT(".backup")));
914 sint64 metLength = m_fullname.GetFileSize();
915 if (metLength == wxInvalidOffset) {
916 theApp->ShowAlert( CFormat( _("Could not retrieve length of '%s' - using %s file.") )
917 % m_fullname
918 % PARTMET_BAK_EXT,
919 _("Message"), wxOK);
921 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
922 } else if (metLength == 0) {
923 // Don't backup if it's 0 size but raise a warning!!!
924 theApp->ShowAlert( CFormat( _("'%s' is 0 size somehow - using %s file.") )
925 % m_fullname
926 % PARTMET_BAK_EXT,
927 _("Message"), wxOK);
929 CPath::CloneFile(m_fullname.AppendExt(PARTMET_BAK_EXT), m_fullname, true);
930 } else {
931 // no error, just backup
932 CPath::BackupFile(m_fullname, PARTMET_BAK_EXT);
935 return true;
939 void CPartFile::SaveSourceSeeds()
941 #define MAX_SAVED_SOURCES 10
943 // Kry - Sources seeds
944 // Based on a Feature request, this saves the last MAX_SAVED_SOURCES
945 // sources of the file, giving a 'seed' for the next run.
946 // We save the last sources because:
947 // 1 - They could be the hardest to get
948 // 2 - They will more probably be available
949 // However, if we have downloading sources, they have preference because
950 // we probably have more credits on them.
951 // Anyway, source exchange will get us the rest of the sources
952 // This feature is currently used only on rare files (< 20 sources)
955 if (GetSourceCount()>20) {
956 return;
959 CClientRefList source_seeds;
960 int n_sources = 0;
962 CClientRefList::iterator it = m_downloadingSourcesList.begin();
963 for( ; it != m_downloadingSourcesList.end() && n_sources < MAX_SAVED_SOURCES; ++it) {
964 if (!it->HasLowID()) {
965 source_seeds.push_back(*it);
966 ++n_sources;
970 if (n_sources < MAX_SAVED_SOURCES) {
971 // Not enough downloading sources to fill the list, going to sources list
972 if (GetSourceCount() > 0) {
973 SourceSet::reverse_iterator rit = m_SrcList.rbegin();
974 for ( ; ((rit != m_SrcList.rend()) && (n_sources<MAX_SAVED_SOURCES)); ++rit) {
975 if (!rit->HasLowID()) {
976 source_seeds.push_back(*rit);
977 ++n_sources;
983 // Write the file
984 if (!n_sources) {
985 return;
988 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
990 CFile file;
991 file.Create(seedsPath, true);
992 if (!file.IsOpened()) {
993 AddLogLineN(CFormat( _("Failed to save part.met.seeds file for %s") )
994 % m_fullname);
995 return;
998 try {
999 file.WriteUInt8(0); // v3, to avoid v2 clients choking on it.
1000 file.WriteUInt8(source_seeds.size());
1002 CClientRefList::iterator it2 = source_seeds.begin();
1003 for (; it2 != source_seeds.end(); ++it2) {
1004 CUpDownClient* cur_src = it2->GetClient();
1005 file.WriteUInt32(cur_src->GetUserIDHybrid());
1006 file.WriteUInt16(cur_src->GetUserPort());
1007 file.WriteHash(cur_src->GetUserHash());
1008 // CryptSettings - See SourceExchange V4
1009 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
1010 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
1011 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
1012 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
1013 file.WriteUInt8(byCryptOptions);
1016 /* v2: Added to keep track of too old seeds */
1017 file.WriteUInt32(wxDateTime::Now().GetTicks());
1019 AddLogLineN(CFormat( wxPLURAL("Saved %i source seed for partfile: %s (%s)", "Saved %i source seeds for partfile: %s (%s)", n_sources) )
1020 % n_sources
1021 % m_fullname
1022 % GetFileName());
1023 } catch (const CIOFailureException& e) {
1024 AddDebugLogLineC( logPartFile, CFormat( wxT("Error saving partfile's seeds file (%s - %s): %s") )
1025 % m_partmetfilename
1026 % GetFileName()
1027 % e.what() );
1029 n_sources = 0;
1030 file.Close();
1031 CPath::RemoveFile(seedsPath);
1035 void CPartFile::LoadSourceSeeds()
1037 CMemFile sources_data;
1039 bool valid_sources = false;
1041 const CPath seedsPath = m_fullname.AppendExt(wxT(".seeds"));
1042 if (!seedsPath.FileExists()) {
1043 return;
1046 CFile file(seedsPath, CFile::read);
1047 if (!file.IsOpened()) {
1048 // Exists but can't be opened. Should not happen. Probably permission problem, try to remove it.
1049 AddLogLineN(CFormat( _("Can't read seeds file for Partfile %s (%s)") )
1050 % m_partmetfilename
1051 % GetFileName() );
1052 CPath::RemoveFile(seedsPath);
1053 return;
1056 bool badSeedsFile = false;
1057 try {
1058 uint8 src_count = file.ReadUInt8();
1060 bool bUseSX2Format = (src_count == 0);
1062 if (bUseSX2Format) {
1063 // v3 sources seeds
1064 src_count = file.ReadUInt8();
1067 sources_data.WriteUInt16(src_count);
1069 for (int i = 0; i< src_count; ++i) {
1070 uint32 dwID = file.ReadUInt32();
1071 uint16 nPort = file.ReadUInt16();
1073 sources_data.WriteUInt32(bUseSX2Format ? dwID : wxUINT32_SWAP_ALWAYS(dwID));
1074 sources_data.WriteUInt16(nPort);
1075 sources_data.WriteUInt32(0);
1076 sources_data.WriteUInt16(0);
1078 if (bUseSX2Format) {
1079 sources_data.WriteHash(file.ReadHash());
1080 sources_data.WriteUInt8(file.ReadUInt8());
1085 if (!file.Eof()) {
1087 // v2: Added to keep track of too old seeds
1088 time_t time = (time_t)file.ReadUInt32();
1090 // Time frame is 2 hours. More than enough to compile
1091 // your new aMule version!.
1092 if ((time + MIN2S(120)) >= wxDateTime::Now().GetTicks()) {
1093 valid_sources = true;
1096 } else {
1097 // v1 has no time data. We can safely use
1098 // the sources, next time will be saved.
1099 valid_sources = true;
1102 if (valid_sources) {
1103 sources_data.Seek(0);
1104 AddClientSources(&sources_data, SF_SOURCE_SEEDS, bUseSX2Format ? 4 : 1, bUseSX2Format);
1107 } catch (const CSafeIOException& e) {
1108 AddLogLineN(CFormat( _("Error reading partfile's seeds file (%s - %s): %s") )
1109 % m_partmetfilename
1110 % GetFileName()
1111 % e.what() );
1112 badSeedsFile = true;
1115 file.Close();
1116 if (badSeedsFile) {
1117 // If we got an exception reading it remove it.
1118 CPath::RemoveFile(seedsPath);
1122 void CPartFile::PartFileHashFinished(CKnownFile* result)
1124 m_lastDateChanged = result->m_lastDateChanged;
1125 bool errorfound = false;
1126 if (GetED2KPartHashCount() == 0){
1127 if (IsComplete(0, GetFileSize()-1)){
1128 if (result->GetFileHash() != GetFileHash()){
1129 // cppcheck-suppress zerodiv
1130 AddLogLineN(CFormat(wxPLURAL(
1131 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1132 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1137 % GetFileName()
1138 % result->GetFileHash().Encode()
1139 % GetFileHash().Encode() );
1140 AddGap(0, GetFileSize()-1);
1141 errorfound = true;
1145 else{
1146 for (size_t i = 0; i < m_hashlist.size(); ++i){
1147 // Kry - trel_ar's completed parts check on rehashing.
1148 // Very nice feature, if a file is completed but .part.met don't believe it,
1149 // update it.
1151 if (!( i < result->GetHashCount() && (result->GetPartHash(i) == GetPartHash(i)))){
1152 if (IsComplete(i)) {
1153 CMD4Hash wronghash;
1154 if ( i < result->GetHashCount() )
1155 wronghash = result->GetPartHash(i);
1157 AddLogLineN(CFormat(wxPLURAL(
1158 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1159 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1160 GetED2KPartHashCount())
1162 % ( i + 1 )
1163 % GetED2KPartHashCount()
1164 % GetFileName()
1165 % wronghash.Encode()
1166 % GetPartHash(i).Encode() );
1168 AddGap(i);
1169 errorfound = true;
1171 } else {
1172 if (!IsComplete(i)){
1173 AddLogLineN(CFormat( _("Found completed part (%i) in %s") )
1174 % ( i + 1 )
1175 % GetFileName() );
1177 FillGap(i);
1178 uint64 partStart = i * PARTSIZE;
1179 uint64 partEnd = partStart + GetPartSize(i) - 1;
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 AddDebugLogLineN(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 AddLogLineN(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 FlushBuffer();
1375 // check if we want new sources from server --> MOVED for 16.40 version
1376 old_trans=transferingsrc;
1377 transferingsrc = 0;
1378 kBpsDown = 0.0;
1380 if (m_icounter < 10) {
1381 // Update only downloading sources.
1382 CClientRefList::iterator it = m_downloadingSourcesList.begin();
1383 for( ; it != m_downloadingSourcesList.end(); ) {
1384 CUpDownClient *cur_src = it++->GetClient();
1385 if(cur_src->GetDownloadState() == DS_DOWNLOADING) {
1386 ++transferingsrc;
1387 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1390 } else {
1391 // Update all sources (including downloading sources)
1392 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
1393 CUpDownClient* cur_src = it++->GetClient();
1394 switch (cur_src->GetDownloadState()) {
1395 case DS_DOWNLOADING: {
1396 ++transferingsrc;
1397 kBpsDown += cur_src->SetDownloadLimit(reducedownload);
1398 break;
1400 case DS_BANNED: {
1401 break;
1403 case DS_ERROR: {
1404 break;
1406 case DS_LOWTOLOWIP: {
1407 if (cur_src->HasLowID() && !theApp->CanDoCallback(cur_src->GetServerIP(), cur_src->GetServerPort())) {
1408 // If we are almost maxed on sources,
1409 // slowly remove these client to see
1410 // if we can find a better source.
1411 if (((dwCurTick - lastpurgetime) > 30000) &&
1412 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8))) {
1413 RemoveSource(cur_src);
1414 lastpurgetime = dwCurTick;
1415 break;
1417 } else {
1418 cur_src->SetDownloadState(DS_ONQUEUE);
1421 break;
1423 case DS_NONEEDEDPARTS: {
1424 // we try to purge noneeded source, even without reaching the limit
1425 if((dwCurTick - lastpurgetime) > 40000) {
1426 if(!cur_src->SwapToAnotherFile(false , false, false , NULL)) {
1427 //however we only delete them if reaching the limit
1428 if (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) {
1429 RemoveSource(cur_src);
1430 lastpurgetime = dwCurTick;
1431 break; //Johnny-B - nothing more to do here (good eye!)
1433 } else {
1434 lastpurgetime = dwCurTick;
1435 break;
1438 // doubled reasktime for no needed parts - save connections and traffic
1439 if ( !((!cur_src->GetLastAskedTime()) ||
1440 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME*2)) {
1441 break;
1443 // Recheck this client to see if still NNP..
1444 // Set to DS_NONE so that we force a TCP reask next time..
1445 cur_src->SetDownloadState(DS_NONE);
1447 break;
1449 case DS_ONQUEUE: {
1450 if( cur_src->IsRemoteQueueFull()) {
1451 if( ((dwCurTick - lastpurgetime) > 60000) &&
1452 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) ) {
1453 RemoveSource( cur_src );
1454 lastpurgetime = dwCurTick;
1455 break; //Johnny-B - nothing more to do here (good eye!)
1459 // Give up to 1 min for UDP to respond..
1460 // If we are within on min on TCP, do not try..
1461 if ( theApp->IsConnected() &&
1462 ( (!cur_src->GetLastAskedTime()) ||
1463 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME-20000)) {
1464 cur_src->UDPReaskForDownload();
1467 // No break here, since the next case takes care of asking for downloads.
1469 /* fall through */
1470 case DS_CONNECTING:
1471 case DS_TOOMANYCONNS:
1472 case DS_NONE:
1473 case DS_WAITCALLBACK:
1474 case DS_WAITCALLBACKKAD: {
1475 if ( theApp->IsConnected() &&
1476 ( (!cur_src->GetLastAskedTime()) ||
1477 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME)) {
1478 if (!cur_src->AskForDownload()) {
1479 // I left this break here just as a reminder
1480 // just in case re rearange things..
1481 break;
1484 break;
1489 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
1490 if (IsA4AFAuto() && ((!m_LastNoNeededCheck) || (dwCurTick - m_LastNoNeededCheck > 900000))) {
1491 m_LastNoNeededCheck = dwCurTick;
1492 for ( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
1493 CUpDownClient *cur_source = it++->GetClient();
1494 uint8 download_state=cur_source->GetDownloadState();
1495 if( download_state != DS_DOWNLOADING
1496 && cur_source->GetRequestFile()
1497 && ((!cur_source->GetRequestFile()->IsA4AFAuto()) || download_state == DS_NONEEDEDPARTS))
1499 cur_source->SwapToAnotherFile(false, false, false, this);
1503 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
1505 // swap No needed partfiles if possible
1507 if (((old_trans==0) && (transferingsrc>0)) || ((old_trans>0) && (transferingsrc==0))) {
1508 SetStatus(status);
1511 // Kad source search
1512 if( GetMaxSourcePerFileUDP() > GetSourceCount()){
1513 //Once we can handle lowID users in Kad, we remove the second IsConnected
1514 if (theApp->downloadqueue->DoKademliaFileRequest() && (Kademlia::CKademlia::GetTotalFile() < KADEMLIATOTALFILE) && (dwCurTick > m_LastSearchTimeKad) && Kademlia::CKademlia::IsConnected() && theApp->IsConnected() && !IsStopped()){
1515 //Kademlia
1516 theApp->downloadqueue->SetLastKademliaFileRequest();
1518 if (GetKadFileSearchID()) {
1519 /* This will never happen anyway. We're talking a
1520 1h timespan and searches are at max 45secs */
1521 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1524 Kademlia::CUInt128 kadFileID(GetFileHash().GetHash());
1525 Kademlia::CSearch* pSearch = Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FILE, true, kadFileID);
1526 AddDebugLogLineN(logKadSearch, CFormat(wxT("Preparing a Kad Search for '%s'")) % GetFileName());
1527 if (pSearch) {
1528 AddDebugLogLineN(logKadSearch, CFormat(wxT("Kad lookup started for '%s'")) % GetFileName());
1529 if(m_TotalSearchesKad < 7) {
1530 m_TotalSearchesKad++;
1532 m_LastSearchTimeKad = dwCurTick + (KADEMLIAREASKTIME*m_TotalSearchesKad);
1533 SetKadFileSearchID(pSearch->GetSearchID());
1536 } else {
1537 if(GetKadFileSearchID()) {
1538 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
1542 // check if we want new sources from server
1543 if ( !m_localSrcReqQueued &&
1544 ( (!m_lastsearchtime) ||
1545 (dwCurTick - m_lastsearchtime) > SERVERREASKTIME) &&
1546 theApp->IsConnectedED2K() &&
1547 thePrefs::GetMaxSourcePerFileSoft() > GetSourceCount() &&
1548 !m_stopped ) {
1549 m_localSrcReqQueued = true;
1550 theApp->downloadqueue->SendLocalSrcRequest(this);
1553 // calculate datarate, set limit etc.
1556 ++m_count;
1558 // Kry - does the 3 / 30 difference produce too much flickering or CPU?
1559 if (m_count >= 30) {
1560 m_count = 0;
1561 UpdateAutoDownPriority();
1562 UpdateDisplayedInfo();
1563 if(m_bPercentUpdated == false) {
1564 UpdateCompletedInfos();
1566 m_bPercentUpdated = false;
1569 // release file handle if unused for some time
1570 m_hpartfile.Release();
1572 return (uint32)(kBpsDown*1024.0);
1575 bool CPartFile::CanAddSource(uint32 userid, uint16 port, uint32 serverip, uint16 serverport, uint8* pdebug_lowiddropped, bool ed2kID)
1578 //The incoming ID could have the userid in the Hybrid format..
1579 uint32 hybridID = 0;
1580 if( ed2kID ) {
1581 if (IsLowID(userid)) {
1582 hybridID = userid;
1583 } else {
1584 hybridID = wxUINT32_SWAP_ALWAYS(userid);
1586 } else {
1587 hybridID = userid;
1588 if (!IsLowID(userid)) {
1589 userid = wxUINT32_SWAP_ALWAYS(userid);
1593 // MOD Note: Do not change this part - Merkur
1594 if (theApp->IsConnectedED2K()) {
1595 if(::IsLowID(theApp->GetED2KID())) {
1596 if(theApp->GetED2KID() == userid && theApp->serverconnect->GetCurrentServer()->GetIP() == serverip && theApp->serverconnect->GetCurrentServer()->GetPort() == serverport ) {
1597 return false;
1599 if(theApp->GetPublicIP() == userid) {
1600 return false;
1602 } else {
1603 if(theApp->GetED2KID() == userid && thePrefs::GetPort() == port) {
1604 return false;
1609 if (Kademlia::CKademlia::IsConnected()) {
1610 if(!Kademlia::CKademlia::IsFirewalled()) {
1611 if(Kademlia::CKademlia::GetIPAddress() == hybridID && thePrefs::GetPort() == port) {
1612 return false;
1617 //This allows *.*.*.0 clients to not be removed if Ed2kID == false
1618 if ( IsLowID(hybridID) && theApp->IsFirewalled()) {
1619 if (pdebug_lowiddropped) {
1620 (*pdebug_lowiddropped)++;
1622 return false;
1624 // MOD Note - end
1625 return true;
1628 void CPartFile::AddSources(CMemFile& sources,uint32 serverip, uint16 serverport, unsigned origin, bool bWithObfuscationAndHash)
1630 uint8 count = sources.ReadUInt8();
1631 uint8 debug_lowiddropped = 0;
1632 uint8 debug_possiblesources = 0;
1633 CMD4Hash achUserHash;
1635 if (m_stopped) {
1636 // since we may received multiple search source UDP results we have to "consume" all data of that packet
1637 AddDebugLogLineN(logPartFile, wxT("Trying to add sources for a stopped file"));
1638 sources.Seek(count*(4+2), wxFromCurrent);
1639 return;
1642 for (int i = 0;i != count;++i) {
1643 uint32 userid = sources.ReadUInt32();
1644 uint16 port = sources.ReadUInt16();
1646 uint8 byCryptOptions = 0;
1647 if (bWithObfuscationAndHash){
1648 byCryptOptions = sources.ReadUInt8();
1649 if ((byCryptOptions & 0x80) > 0) {
1650 achUserHash = sources.ReadHash();
1653 if ((thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x01/*supported*/) > 0 && (byCryptOptions & 0x80) == 0)
1654 || (thePrefs::IsClientCryptLayerSupported() && (byCryptOptions & 0x02/*requested*/) > 0 && (byCryptOptions & 0x80) == 0)) {
1655 AddDebugLogLineN(logPartFile, CFormat(wxT("Server didn't provide UserHash for source %u, even if it was expected to (or local obfuscationsettings changed during serverconnect")) % userid);
1656 } else if (!thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x02/*requested*/) == 0 && (byCryptOptions & 0x80) != 0) {
1657 AddDebugLogLineN(logPartFile, CFormat(wxT("Server provided UserHash for source %u, even if it wasn't expected to (or local obfuscationsettings changed during serverconnect")) % userid);
1662 // "Filter LAN IPs" and "IPfilter" the received sources IP addresses
1663 if (!IsLowID(userid)) {
1664 // check for 0-IP, localhost and optionally for LAN addresses
1665 if ( !IsGoodIP(userid, thePrefs::FilterLanIPs()) ) {
1666 continue;
1668 if (theApp->ipfilter->IsFiltered(userid)) {
1669 continue;
1673 if (!CanAddSource(userid, port, serverip, serverport, &debug_lowiddropped)) {
1674 continue;
1677 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
1678 ++debug_possiblesources;
1679 CUpDownClient* newsource = new CUpDownClient(port,userid,serverip,serverport,this, true, true);
1681 newsource->SetSourceFrom((ESourceFrom)origin);
1682 newsource->SetConnectOptions(byCryptOptions, true, false);
1684 if ((byCryptOptions & 0x80) != 0) {
1685 newsource->SetUserHash(achUserHash);
1688 theApp->downloadqueue->CheckAndAddSource(this,newsource);
1689 } else {
1690 AddDebugLogLineN(logPartFile, wxT("Consuming a packet because of max sources reached"));
1691 // Since we may receive multiple search source UDP results we have to "consume" all data of that packet
1692 // This '+1' is added because 'i' counts from 0.
1693 sources.Seek((count-(i+1))*(4+2), wxFromCurrent);
1694 if (GetKadFileSearchID()) {
1695 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1697 break;
1702 void CPartFile::UpdatePartsInfo()
1704 if( !IsPartFile() ) {
1705 CKnownFile::UpdatePartsInfo();
1706 return;
1709 // Cache part count
1710 uint16 partcount = GetPartCount();
1711 bool flag = (time(NULL) - m_nCompleteSourcesTime > 0);
1713 // Ensure the frequency-list is ready
1714 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
1715 m_SrcpartFrequency.clear();
1716 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
1719 // Find number of available parts
1720 uint16 availablecounter = 0;
1721 for ( uint16 i = 0; i < partcount; ++i ) {
1722 if ( m_SrcpartFrequency[i] )
1723 ++availablecounter;
1726 if ( ( availablecounter == partcount ) && ( m_availablePartsCount < partcount ) ) {
1727 lastseencomplete = time(NULL);
1730 m_availablePartsCount = availablecounter;
1732 if ( flag ) {
1733 ArrayOfUInts16 count;
1735 count.reserve(GetSourceCount());
1737 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
1738 CUpDownClient* client = it->GetClient();
1739 if ( !client->GetUpPartStatus().empty() && client->GetUpPartCount() == partcount ) {
1740 count.push_back(client->GetUpCompleteSourcesCount());
1744 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo = m_nCompleteSourcesCountHi = 0;
1746 for (uint16 i = 0; i < partcount; ++i) {
1747 if( !i ) {
1748 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1750 else if( m_nCompleteSourcesCount > m_SrcpartFrequency[i]) {
1751 m_nCompleteSourcesCount = m_SrcpartFrequency[i];
1754 count.push_back(m_nCompleteSourcesCount);
1756 int32 n = count.size();
1757 if (n > 0) {
1758 std::sort(count.begin(), count.end(), std::less<uint16>());
1760 // calculate range
1761 int32 i= n >> 1; // (n / 2)
1762 int32 j= (n * 3) >> 2; // (n * 3) / 4
1763 int32 k= (n * 7) >> 3; // (n * 7) / 8
1765 //When still a part file, adjust your guesses by 20% to what you see..
1768 if (n < 5) {
1769 //Not many sources, so just use what you see..
1770 // welcome to 'plain stupid code'
1771 // m_nCompleteSourcesCount;
1772 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1773 m_nCompleteSourcesCountHi= m_nCompleteSourcesCount;
1774 } else if (n < 20) {
1775 // For low guess and normal guess count
1776 // If we see more sources then the guessed low and normal, use what we see.
1777 // If we see less sources then the guessed low, adjust network accounts for 80%,
1778 // we account for 20% with what we see and make sure we are still above the normal.
1779 // For high guess
1780 // Adjust 80% network and 20% what we see.
1781 if ( count[i] < m_nCompleteSourcesCount ) {
1782 m_nCompleteSourcesCountLo = m_nCompleteSourcesCount;
1783 } else {
1784 m_nCompleteSourcesCountLo =
1785 (uint16)((float)(count[i]*.8) +
1786 (float)(m_nCompleteSourcesCount*.2));
1788 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1789 m_nCompleteSourcesCountHi =
1790 (uint16)((float)(count[j]*.8) +
1791 (float)(m_nCompleteSourcesCount*.2));
1792 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1793 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1795 } else {
1796 // Many sources
1797 // ------------
1798 // For low guess
1799 // Use what we see.
1800 // For normal guess
1801 // Adjust network accounts for 80%, we account for 20% with what
1802 // we see and make sure we are still above the low.
1803 // For high guess
1804 // Adjust network accounts for 80%, we account for 20% with what
1805 // we see and make sure we are still above the normal.
1807 m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
1808 m_nCompleteSourcesCount= (uint16)((float)(count[j]*.8)+(float)(m_nCompleteSourcesCount*.2));
1809 if( m_nCompleteSourcesCount < m_nCompleteSourcesCountLo ) {
1810 m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
1812 m_nCompleteSourcesCountHi= (uint16)((float)(count[k]*.8)+(float)(m_nCompleteSourcesCount*.2));
1813 if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
1814 m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
1818 m_nCompleteSourcesTime = time(NULL) + (60);
1820 UpdateDisplayedInfo();
1823 // [Maella -Enhanced Chunk Selection- (based on jicxicmic)]
1824 bool CPartFile::GetNextRequestedBlock(CUpDownClient* sender,
1825 std::vector<Requested_Block_Struct*>& toadd, uint16& count)
1828 // The purpose of this function is to return a list of blocks (~180KB) to
1829 // download. To avoid a prematurely stop of the downloading, all blocks that
1830 // are requested from the same source must be located within the same
1831 // chunk (=> part ~9MB).
1833 // The selection of the chunk to download is one of the CRITICAL parts of the
1834 // edonkey network. The selection algorithm must insure the best spreading
1835 // of files.
1837 // The selection is based on 4 criteria:
1838 // 1. Frequency of the chunk (availability), very rare chunks must be downloaded
1839 // as quickly as possible to become a new available source.
1840 // 2. Parts used for preview (first + last chunk), preview or check a
1841 // file (e.g. movie, mp3)
1842 // 3. Request state (downloading in process), try to ask each source for another
1843 // chunk. Spread the requests between all sources.
1844 // 4. Completion (shortest-to-complete), partially retrieved chunks should be
1845 // completed before starting to download other one.
1847 // The frequency criterion defines three zones: very rare (<10%), rare (<50%)
1848 // and common (>30%). Inside each zone, the criteria have a specific weight, used
1849 // to calculate the priority of chunks. The chunk(s) with the highest
1850 // priority (highest=0, lowest=0xffff) is/are selected first.
1852 // very rare (preview) rare common
1853 // 0% <---- +0 pt ----> 10% <----- +10000 pt -----> 50% <---- +20000 pt ----> 100%
1854 // 1. <------- frequency: +25*frequency pt ----------->
1855 // 2. <- preview: +1 pt --><-------------- preview: set to 10000 pt ------------->
1856 // 3. <------ request: download in progress +20000 pt ------>
1857 // 4a. <- completion: 0% +100, 25% +75 .. 100% +0 pt --><-- !req => completion --->
1858 // 4b. <--- req => !completion -->
1860 // Unrolled, the priority scale is:
1862 // 0..xxxx unrequested and requested very rare chunks
1863 // 10000..1xxxx unrequested rare chunks + unrequested preview chunks
1864 // 20000..2xxxx unrequested common chunks (priority to the most complete)
1865 // 30000..3xxxx requested rare chunks + requested preview chunks
1866 // 40000..4xxxx requested common chunks (priority to the least complete)
1868 // This algorithm usually selects first the rarest chunk(s). However, partially
1869 // complete chunk(s) that is/are close to completion may overtake the priority
1870 // (priority inversion).
1871 // For the common chuncks, the algorithm tries to spread the dowload between
1872 // the sources
1875 // Check input parameters
1876 if ( sender->GetPartStatus().empty() ) {
1877 return false;
1879 // Define and create the list of the chunks to download
1880 const uint16 partCount = GetPartCount();
1881 ChunkList chunksList;
1883 // Main loop
1884 uint16 newBlockCount = 0;
1885 while(newBlockCount != count) {
1886 // Create a request block stucture if a chunk has been previously selected
1887 if(sender->GetLastPartAsked() != 0xffff) {
1888 Requested_Block_Struct* pBlock = new Requested_Block_Struct;
1889 if(GetNextEmptyBlockInPart(sender->GetLastPartAsked(), pBlock) == true) {
1890 // Keep a track of all pending requested blocks
1891 m_requestedblocks_list.push_back(pBlock);
1892 // Update list of blocks to return
1893 toadd.push_back(pBlock);
1894 newBlockCount++;
1895 // Skip end of loop (=> CPU load)
1896 continue;
1897 } else {
1898 // All blocks for this chunk have been already requested
1899 delete pBlock;
1900 // => Try to select another chunk
1901 sender->SetLastPartAsked(0xffff);
1905 // Check if a new chunk must be selected (e.g. download starting, previous chunk complete)
1906 if(sender->GetLastPartAsked() == 0xffff) {
1907 // Quantify all chunks (create list of chunks to download)
1908 // This is done only one time and only if it is necessary (=> CPU load)
1909 if(chunksList.empty()) {
1910 // Indentify the locally missing part(s) that this source has
1911 for(uint16 i=0; i < partCount; ++i) {
1912 if(sender->IsPartAvailable(i) == true && GetNextEmptyBlockInPart(i, NULL) == true) {
1913 // Create a new entry for this chunk and add it to the list
1914 Chunk newEntry;
1915 newEntry.part = i;
1916 newEntry.frequency = m_SrcpartFrequency[i];
1917 chunksList.push_back(newEntry);
1921 // Check if any bloks(s) could be downloaded
1922 if(chunksList.empty()) {
1923 break; // Exit main loop while()
1926 // Define the bounds of the three zones (very rare, rare)
1927 // more depending on available sources
1928 uint8 modif=10;
1929 if (GetSourceCount()>800) {
1930 modif=2;
1931 } else if (GetSourceCount()>200) {
1932 modif=5;
1934 uint16 limit= modif*GetSourceCount()/ 100;
1935 if (limit==0) {
1936 limit=1;
1938 const uint16 veryRareBound = limit;
1939 const uint16 rareBound = 2*limit;
1941 // Cache Preview state (Criterion 2)
1942 FileType type = GetFiletype(GetFileName());
1943 const bool isPreviewEnable =
1944 thePrefs::GetPreviewPrio() &&
1945 (type == ftArchive || type == ftVideo);
1947 // Collect and calculate criteria for all chunks
1948 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
1949 Chunk& cur_chunk = *it;
1951 // Offsets of chunk
1952 const uint64 uStart = cur_chunk.part * PARTSIZE;
1953 const uint64 uEnd = uStart + GetPartSize(cur_chunk.part) - 1;
1954 // Criterion 2. Parts used for preview
1955 // Remark: - We need to download the first part and the last part(s).
1956 // - When the last part is very small, it's necessary to
1957 // download the two last parts.
1958 bool critPreview = false;
1959 if(isPreviewEnable == true) {
1960 if(cur_chunk.part == 0) {
1961 critPreview = true; // First chunk
1962 } else if(cur_chunk.part == partCount-1) {
1963 critPreview = true; // Last chunk
1964 } else if(cur_chunk.part == partCount-2) {
1965 // Last chunk - 1 (only if last chunk is too small)
1966 const uint32 sizeOfLastChunk = GetFileSize() - uEnd;
1967 if(sizeOfLastChunk < PARTSIZE/3) {
1968 critPreview = true; // Last chunk - 1
1973 // Criterion 3. Request state (downloading in process from other source(s))
1974 // => CPU load
1975 const bool critRequested =
1976 cur_chunk.frequency > veryRareBound &&
1977 IsAlreadyRequested(uStart, uEnd);
1979 // Criterion 4. Completion
1980 // PARTSIZE instead of GetPartSize() favours the last chunk - but that may be intentional
1981 uint32 partSize = PARTSIZE - m_gaplist.GetGapSize(cur_chunk.part);
1982 const uint16 critCompletion = (uint16)(partSize/(PARTSIZE/100)); // in [%]
1984 // Calculate priority with all criteria
1985 if(cur_chunk.frequency <= veryRareBound) {
1986 // 0..xxxx unrequested + requested very rare chunks
1987 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
1988 ((critPreview == true) ? 0 : 1) + // Criterion 2
1989 (100 - critCompletion); // Criterion 4
1990 } else if(critPreview == true) {
1991 // 10000..10100 unrequested preview chunks
1992 // 30000..30100 requested preview chunks
1993 cur_chunk.rank = ((critRequested == false) ? 10000 : 30000) + // Criterion 3
1994 (100 - critCompletion); // Criterion 4
1995 } else if(cur_chunk.frequency <= rareBound) {
1996 // 10101..1xxxx unrequested rare chunks
1997 // 30101..3xxxx requested rare chunks
1998 cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
1999 ((critRequested == false) ? 10101 : 30101) + // Criterion 3
2000 (100 - critCompletion); // Criterion 4
2001 } else {
2002 // common chunk
2003 if(critRequested == false) { // Criterion 3
2004 // 20000..2xxxx unrequested common chunks
2005 cur_chunk.rank = 20000 + // Criterion 3
2006 (100 - critCompletion); // Criterion 4
2007 } else {
2008 // 40000..4xxxx requested common chunks
2009 // Remark: The weight of the completion criterion is inversed
2010 // to spead the requests over the completing chunks.
2011 // Without this, the chunk closest to completion will
2012 // received every new sources.
2013 cur_chunk.rank = 40000 + // Criterion 3
2014 (critCompletion); // Criterion 4
2020 // Select the next chunk to download
2021 if(!chunksList.empty()) {
2022 // Find and count the chunck(s) with the highest priority
2023 uint16 chunkCount = 0; // Number of found chunks with same priority
2024 uint16 rank = 0xffff; // Highest priority found
2026 // Collect and calculate criteria for all chunks
2027 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2028 const Chunk& cur_chunk = *it;
2029 if(cur_chunk.rank < rank) {
2030 chunkCount = 1;
2031 rank = cur_chunk.rank;
2032 } else if(cur_chunk.rank == rank) {
2033 ++chunkCount;
2037 // Use a random access to avoid that everybody tries to download the
2038 // same chunks at the same time (=> spread the selected chunk among clients)
2039 uint16 randomness = 1 + (int) (((float)(chunkCount-1))*rand()/(RAND_MAX+1.0));
2041 for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
2042 const Chunk& cur_chunk = *it;
2043 if(cur_chunk.rank == rank) {
2044 randomness--;
2045 if(randomness == 0) {
2046 // Selection process is over
2047 sender->SetLastPartAsked(cur_chunk.part);
2048 // Remark: this list might be reused up to *count times
2049 chunksList.erase(it);
2050 break; // exit loop for()
2054 } else {
2055 // There is no remaining chunk to download
2056 break; // Exit main loop while()
2060 // Return the number of the blocks
2061 count = newBlockCount;
2062 // Return
2063 return (newBlockCount > 0);
2065 // Maella end
2068 void CPartFile::RemoveBlockFromList(uint64 start,uint64 end)
2070 std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
2071 while (it != m_requestedblocks_list.end()) {
2072 std::list<Requested_Block_Struct*>::iterator it2 = it++;
2074 if ((*it2)->StartOffset <= start && (*it2)->EndOffset >= end) {
2075 m_requestedblocks_list.erase(it2);
2081 void CPartFile::RemoveAllRequestedBlocks(void)
2083 m_requestedblocks_list.clear();
2087 void CPartFile::CompleteFile(bool bIsHashingDone)
2089 if (GetKadFileSearchID()) {
2090 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
2093 theApp->downloadqueue->RemoveLocalServerRequest(this);
2095 AddDebugLogLineN( logPartFile, wxString( wxT("CPartFile::CompleteFile: Hash ") ) + ( bIsHashingDone ? wxT("done") : wxT("not done") ) );
2097 if (!bIsHashingDone) {
2098 SetStatus(PS_COMPLETING);
2099 kBpsDown = 0.0;
2101 CPath partFile = m_partmetfilename.RemoveExt();
2102 CThreadScheduler::AddTask(new CHashingTask(GetFilePath(), partFile, this));
2103 return;
2104 } else {
2105 StopFile();
2106 m_is_A4AF_auto=false;
2107 SetStatus(PS_COMPLETING);
2108 // guess I was wrong about not need to spaw a thread ...
2109 // It is if the temp and incoming dirs are on different
2110 // partitions/drives and the file is large...[oz]
2113 PerformFileComplete();
2117 if (thePrefs::ShowCatTabInfos()) {
2118 Notify_ShowUpdateCatTabTitles();
2120 UpdateDisplayedInfo(true);
2124 void CPartFile::CompleteFileEnded(bool errorOccured, const CPath& newname)
2126 if (errorOccured) {
2127 m_paused = true;
2128 SetStatus(PS_ERROR);
2129 AddLogLineC(CFormat( _("Unexpected error while completing %s. File paused") )% GetFileName() );
2130 } else {
2131 m_fullname = newname;
2133 SetFilePath(m_fullname.GetPath());
2134 SetFileName(m_fullname.GetFullName());
2135 m_lastDateChanged = CPath::GetModificationTime(m_fullname);
2137 SetStatus(PS_COMPLETE);
2138 m_paused = false;
2139 ClearPriority();
2142 // Remove from list of canceled files in case it was canceled once upon a time
2143 if (theApp->canceledfiles->Remove(GetFileHash())) {
2144 theApp->canceledfiles->Save();
2147 // Mark as known (checks if it's already known),
2148 // also updates search files
2149 theApp->knownfiles->SafeAddKFile(this);
2151 // remove the file from the suspended uploads list
2152 theApp->uploadqueue->ResumeUpload(GetFileHash());
2153 theApp->downloadqueue->RemoveFile(this, true);
2154 theApp->sharedfiles->SafeAddKFile(this);
2155 UpdateDisplayedInfo(true);
2157 // republish that file to the ed2k-server to update the 'FT_COMPLETE_SOURCES' counter on the server.
2158 theApp->sharedfiles->RepublishFile(this);
2160 // Ensure that completed shows the correct value
2161 completedsize = GetFileSize();
2163 // clear the blackbox to free up memory
2164 m_CorruptionBlackBox->Free();
2166 AddLogLineC(CFormat( _("Finished downloading: %s") ) % GetFileName() );
2167 #if wxCHECK_VERSION(2,9,0)
2168 #ifndef AMULE_DAEMON
2169 if (thePrefs::ShowNotifications()) {
2170 wxNotificationMessage *notification = new wxNotificationMessage ();
2171 notification->SetTitle("aMule");
2172 notification->SetMessage(CFormat( _("Finished downloading:\n%s") ) % GetFileName() );
2173 notification->SetFlags(wxICON_INFORMATION);
2174 notification->Show(7);
2175 delete notification;
2177 #endif
2178 #endif
2181 theApp->downloadqueue->StartNextFile(this);
2185 void CPartFile::PerformFileComplete()
2187 // add this file to the suspended uploads list
2188 theApp->uploadqueue->SuspendUpload(GetFileHash(), false);
2189 FlushBuffer();
2191 // close permanent handle
2192 if (m_hpartfile.IsOpened()) {
2193 m_hpartfile.Close();
2196 // Schedule task for completion of the file
2197 CThreadScheduler::AddTask(new CCompletionTask(this));
2201 void CPartFile::RemoveAllSources(bool bTryToSwap)
2203 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end();) {
2204 CUpDownClient* cur_src = it++->GetClient();
2205 if (bTryToSwap) {
2206 if (!cur_src->SwapToAnotherFile(true, true, true, NULL)) {
2207 RemoveSource(cur_src,true,false);
2208 // If it was not swapped, it's not on any file anymore, and should die
2210 } else {
2211 RemoveSource(cur_src,true,false);
2215 UpdatePartsInfo();
2217 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
2218 // remove all links A4AF in sources to this file
2219 if(!m_A4AFsrclist.empty()) {
2220 for( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
2221 CUpDownClient* cur_src = it++->GetClient();
2222 if ( cur_src->DeleteFileRequest( this ) ) {
2223 Notify_SourceCtrlRemoveSource(cur_src->ECID(), this);
2226 m_A4AFsrclist.clear();
2228 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
2229 UpdateFileRatingCommentAvail();
2233 void CPartFile::Delete()
2235 AddLogLineN(CFormat(_("Deleting file: %s")) % GetFileName());
2236 // Barry - Need to tell any connected clients to stop sending the file
2237 StopFile(true);
2238 AddDebugLogLineN(logPartFile, wxT("\tStopped"));
2240 #ifdef __DEBUG__
2241 uint16 removed =
2242 #endif
2243 theApp->uploadqueue->SuspendUpload(GetFileHash(), true);
2244 AddDebugLogLineN(logPartFile, CFormat(wxT("\tSuspended upload to %d clients")) % removed);
2245 theApp->sharedfiles->RemoveFile(this);
2246 AddDebugLogLineN(logPartFile, wxT("\tRemoved from shared"));
2247 theApp->downloadqueue->RemoveFile(this);
2248 AddDebugLogLineN(logPartFile, wxT("\tRemoved from download queue"));
2249 Notify_DownloadCtrlRemoveFile(this);
2250 AddDebugLogLineN(logPartFile, wxT("\tRemoved from transferwnd"));
2251 if (theApp->canceledfiles->Add(GetFileHash())) {
2252 theApp->canceledfiles->Save();
2254 AddDebugLogLineN(logPartFile, wxT("\tAdded to canceled file list"));
2255 theApp->searchlist->UpdateSearchFileByHash(GetFileHash()); // Update file in the search dialog if it's still open
2257 if (m_hpartfile.IsOpened()) {
2258 m_hpartfile.Close();
2261 AddDebugLogLineN(logPartFile, wxT("\tClosed"));
2263 // cppcheck-suppress duplicateBranch
2264 if (!CPath::RemoveFile(m_fullname)) {
2265 AddDebugLogLineC(logPartFile, CFormat(wxT("\tFailed to delete '%s'")) % m_fullname);
2266 } else {
2267 AddDebugLogLineN(logPartFile, wxT("\tRemoved .part.met"));
2270 // cppcheck-suppress duplicateBranch
2271 if (!CPath::RemoveFile(m_PartPath)) {
2272 AddDebugLogLineC(logPartFile, CFormat(wxT("Failed to delete '%s'")) % m_PartPath);
2273 } else {
2274 AddDebugLogLineN(logPartFile, wxT("\tRemoved .part"));
2277 CPath BAKName = m_fullname.AppendExt(PARTMET_BAK_EXT);
2278 // cppcheck-suppress duplicateBranch
2279 if (!CPath::RemoveFile(BAKName)) {
2280 AddDebugLogLineC(logPartFile, CFormat(wxT("Failed to delete '%s'")) % BAKName);
2281 } else {
2282 AddDebugLogLineN(logPartFile, wxT("\tRemoved .bak"));
2285 CPath SEEDSName = m_fullname.AppendExt(wxT(".seeds"));
2286 if (SEEDSName.FileExists()) {
2287 // cppcheck-suppress duplicateBranch
2288 if (CPath::RemoveFile(SEEDSName)) {
2289 AddDebugLogLineN(logPartFile, wxT("\tRemoved .seeds"));
2290 } else {
2291 AddDebugLogLineC(logPartFile, CFormat(wxT("Failed to delete '%s'")) % SEEDSName);
2295 AddDebugLogLineN(logPartFile, wxT("Done"));
2297 delete this;
2301 bool CPartFile::HashSinglePart(uint16 partnumber)
2303 if ((GetHashCount() <= partnumber) && (GetPartCount() > 1)) {
2304 AddLogLineC(CFormat( _("WARNING: Unable to hash downloaded part - hashset incomplete for '%s'") )
2305 % GetFileName() );
2306 m_hashsetneeded = true;
2307 return true;
2308 } else if ((GetHashCount() <= partnumber) && GetPartCount() != 1) {
2309 AddLogLineC(CFormat( _("ERROR: Unable to hash downloaded part - hashset incomplete (%s). This should never happen")) % GetFileName() );
2310 m_hashsetneeded = true;
2311 return true;
2312 } else {
2313 CMD4Hash hashresult;
2314 uint64 offset = PARTSIZE * partnumber;
2315 uint32 length = GetPartSize(partnumber);
2316 try {
2317 CreateHashFromFile(m_hpartfile, offset, length, &hashresult, NULL);
2318 } catch (const CIOFailureException& e) {
2319 AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2320 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2321 SetStatus(PS_ERROR);
2322 return false;
2323 } catch (const CEOFException& e) {
2324 AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2325 % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
2326 SetStatus(PS_ERROR);
2327 return false;
2330 if (GetPartCount() > 1) {
2331 if (hashresult != GetPartHash(partnumber)) {
2332 AddDebugLogLineN(logPartFile, CFormat( wxT("%s: Expected hash of part %d: %s")) % GetFileName() % partnumber % GetPartHash(partnumber).Encode() );
2333 AddDebugLogLineN(logPartFile, CFormat( wxT("%s: Actual hash of part %d: %s")) % GetFileName() % partnumber % hashresult.Encode() );
2334 return false;
2335 } else {
2336 return true;
2338 } else {
2339 if (hashresult != m_abyFileHash) {
2340 return false;
2341 } else {
2342 return true;
2349 bool CPartFile::IsCorruptedPart(uint16 partnumber)
2351 return std::find(m_corrupted_list.begin(), m_corrupted_list.end(), partnumber)
2352 != m_corrupted_list.end();
2356 void CPartFile::SetDownPriority(uint8 np, bool bSave, bool bRefresh )
2358 if ( m_iDownPriority != np ) {
2359 m_iDownPriority = np;
2360 if ( bRefresh )
2361 UpdateDisplayedInfo(true);
2362 if ( bSave )
2363 SavePartFile();
2368 void CPartFile::StopFile(bool bCancel)
2370 // Kry - Need to set it here to get into SetStatus(status) correctly
2371 m_stopped = true;
2373 // Barry - Need to tell any connected clients to stop sending the file
2374 PauseFile();
2376 m_LastSearchTimeKad = 0;
2377 m_TotalSearchesKad = 0;
2379 RemoveAllSources(true);
2380 kBpsDown = 0.0;
2381 transferingsrc = 0;
2383 if (!bCancel) {
2384 FlushBuffer();
2387 UpdateDisplayedInfo(true);
2391 void CPartFile::StopPausedFile()
2393 if (!IsStopped()) {
2394 // Once an hour, remove any sources for files which are no longer active downloads
2395 switch (GetStatus()) {
2396 case PS_PAUSED:
2397 case PS_INSUFFICIENT:
2398 case PS_ERROR:
2399 if (time(NULL) - m_iLastPausePurge > (60*60)) {
2400 m_iLastPausePurge = time(NULL);
2401 StopFile();
2403 kBpsDown = 0.0;
2406 // release file handle if unused for some time
2407 m_hpartfile.Release();
2411 void CPartFile::PauseFile(bool bInsufficient)
2413 SetActive(false);
2415 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2416 return;
2419 if (GetKadFileSearchID()) {
2420 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
2421 // If we were in the middle of searching, reset timer so they can resume searching.
2422 m_LastSearchTimeKad = 0;
2425 m_iLastPausePurge = time(NULL);
2427 theApp->downloadqueue->RemoveLocalServerRequest(this);
2429 CPacket packet( OP_CANCELTRANSFER, 0, OP_EDONKEYPROT );
2430 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
2431 CUpDownClient* cur_src = it++->GetClient();
2432 if (cur_src->GetDownloadState() == DS_DOWNLOADING) {
2433 if (!cur_src->GetSentCancelTransfer()) {
2434 theStats::AddUpOverheadOther( packet.GetPacketSize() );
2435 AddDebugLogLineN( logLocalClient, wxT("Local Client: OP_CANCELTRANSFER to ") + cur_src->GetFullIP() );
2436 cur_src->SendPacket( &packet, false, true );
2437 cur_src->SetSentCancelTransfer( true );
2439 cur_src->SetDownloadState(DS_ONQUEUE);
2440 // Allow immediate reconnect on resume
2441 cur_src->ResetLastAskedTime();
2446 m_insufficient = bInsufficient;
2447 m_paused = true;
2450 kBpsDown = 0.0;
2451 transferingsrc = 0;
2453 SetStatus(status);
2457 void CPartFile::ResumeFile()
2459 if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
2460 return;
2463 if ( m_insufficient && !CheckFreeDiskSpace() ) {
2464 // Still not enough free discspace
2465 return;
2468 m_paused = false;
2469 m_stopped = false;
2470 m_insufficient = false;
2472 m_lastsearchtime = 0;
2473 SetStatus(status);
2474 SetActive(theApp->IsConnected());
2476 if (m_gaplist.IsComplete() && (GetStatus() == PS_ERROR)) {
2477 // The file has already been hashed at this point
2478 CompleteFile(true);
2481 UpdateDisplayedInfo(true);
2485 bool CPartFile::CheckFreeDiskSpace( uint64 neededSpace )
2487 uint64 free = CPath::GetFreeSpaceAt(GetFilePath());
2488 if (free == static_cast<uint64>(wxInvalidOffset)) {
2489 // If GetFreeSpaceAt() fails, then the path probably does not exist.
2490 return false;
2493 // The very least acceptable diskspace is a single PART
2494 if ( free < PARTSIZE ) {
2495 // Always fail in this case, since we risk losing data if we try to
2496 // write on a full partition.
2497 return false;
2500 // All other checks are only made if the user has enabled them
2501 if ( thePrefs::IsCheckDiskspaceEnabled() ) {
2502 neededSpace += thePrefs::GetMinFreeDiskSpace();
2504 // Due to the the existance of sparse files, we cannot assume that
2505 // writes within the file doesn't cause new blocks to be allocated.
2506 // Therefore, we have to simply stop writing the moment the limit has
2507 // been exceeded.
2508 return free >= neededSpace;
2511 return true;
2515 void CPartFile::SetLastAnsweredTime()
2517 m_ClientSrcAnswered = ::GetTickCount();
2520 void CPartFile::SetLastAnsweredTimeTimeout()
2522 m_ClientSrcAnswered = 2 * CONNECTION_LATENCY + ::GetTickCount() - SOURCECLIENTREASKS;
2525 CPacket *CPartFile::CreateSrcInfoPacket(const CUpDownClient* forClient, uint8 byRequestedVersion, uint16 nRequestedOptions)
2528 if ( m_SrcList.empty() ) {
2529 return NULL;
2532 if(!IsPartFile()) {
2533 return CKnownFile::CreateSrcInfoPacket(forClient, byRequestedVersion, nRequestedOptions);
2536 if (((forClient->GetRequestFile() != this)
2537 && (forClient->GetUploadFile() != this)) || forClient->GetUploadFileID() != GetFileHash()) {
2538 wxString file1 = _("Unknown");
2539 if (forClient->GetRequestFile() && forClient->GetRequestFile()->GetFileName().IsOk()) {
2540 file1 = forClient->GetRequestFile()->GetFileName().GetPrintable();
2541 } else if (forClient->GetUploadFile() && forClient->GetUploadFile()->GetFileName().IsOk()) {
2542 file1 = forClient->GetUploadFile()->GetFileName().GetPrintable();
2544 wxString file2 = _("Unknown");
2545 if (GetFileName().IsOk()) {
2546 file2 = GetFileName().GetPrintable();
2548 AddDebugLogLineN(logPartFile, wxT("File mismatch on source packet (P) Sending: ") + file1 + wxT(" From: ") + file2);
2549 return NULL;
2552 if ( !(GetStatus() == PS_READY || GetStatus() == PS_EMPTY)) {
2553 return NULL;
2556 const BitVector& reqstatus = forClient->GetPartStatus();
2557 bool KnowNeededParts = !reqstatus.empty();
2558 //wxASSERT(rcvstatus.size() == GetPartCount()); // Obviously!
2559 if (KnowNeededParts && (reqstatus.size() != GetPartCount())) {
2560 // Yuck. Same file but different part count? Seriously fucked up.
2561 // This happens rather often with reqstatus.size() == 0. Don't log then.
2562 if (reqstatus.size()) {
2563 AddDebugLogLineN(logKnownFiles, CFormat(wxT("Impossible situation: different partcounts: %i (client) and %i (file) for %s")) % reqstatus.size() % GetPartCount() % GetFileName());
2565 return NULL;
2568 CMemFile data(1024);
2570 uint8 byUsedVersion;
2571 bool bIsSX2Packet;
2572 if (forClient->SupportsSourceExchange2() && byRequestedVersion > 0){
2573 // the client uses SourceExchange2 and requested the highest version he knows
2574 // and we send the highest version we know, but of course not higher than his request
2575 byUsedVersion = std::min(byRequestedVersion, (uint8)SOURCEEXCHANGE2_VERSION);
2576 bIsSX2Packet = true;
2577 data.WriteUInt8(byUsedVersion);
2579 // we don't support any special SX2 options yet, reserved for later use
2580 if (nRequestedOptions != 0) {
2581 AddDebugLogLineN(logKnownFiles, CFormat(wxT("Client requested unknown options for SourceExchange2: %u")) % nRequestedOptions);
2583 } else {
2584 byUsedVersion = forClient->GetSourceExchange1Version();
2585 bIsSX2Packet = false;
2586 if (forClient->SupportsSourceExchange2()) {
2587 AddDebugLogLineN(logKnownFiles, wxT("Client which announced to support SX2 sent SX1 packet instead"));
2591 uint16 nCount = 0;
2593 data.WriteHash(m_abyFileHash);
2594 data.WriteUInt16(nCount);
2595 bool bNeeded;
2596 for (SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
2597 bNeeded = false;
2598 CUpDownClient* cur_src = it->GetClient();
2600 int state = cur_src->GetDownloadState();
2601 int valid = ( state == DS_DOWNLOADING ) || ( state == DS_ONQUEUE && !cur_src->IsRemoteQueueFull() );
2603 if ( cur_src->HasLowID() || !valid ) {
2604 continue;
2607 // only send source which have needed parts for this client if possible
2608 const BitVector& srcstatus = cur_src->GetPartStatus();
2609 if ( !srcstatus.empty() ) {
2610 //wxASSERT(srcstatus.size() == GetPartCount()); // Obviously!
2611 if (srcstatus.size() != GetPartCount()) {
2612 continue;
2614 if ( KnowNeededParts ) {
2615 // only send sources which have needed parts for this client
2616 for (int x = 0; x < GetPartCount(); ++x) {
2617 if (srcstatus.get(x) && !reqstatus.get(x)) {
2618 bNeeded = true;
2619 break;
2622 } else {
2623 // if we don't know the need parts for this client,
2624 // return any source currently a client sends it's
2625 // file status only after it has at least one complete part
2626 if (srcstatus.size() != GetPartCount()) {
2627 continue;
2629 for (int x = 0; x < GetPartCount(); ++x){
2630 if (srcstatus.get(x)) {
2631 bNeeded = true;
2632 break;
2637 if(bNeeded) {
2638 ++nCount;
2639 uint32 dwID;
2640 if(forClient->GetSourceExchange1Version() > 2) {
2641 dwID = cur_src->GetUserIDHybrid();
2642 } else {
2643 dwID = wxUINT32_SWAP_ALWAYS(cur_src->GetUserIDHybrid());
2645 data.WriteUInt32(dwID);
2646 data.WriteUInt16(cur_src->GetUserPort());
2647 data.WriteUInt32(cur_src->GetServerIP());
2648 data.WriteUInt16(cur_src->GetServerPort());
2650 if (byUsedVersion >= 2) {
2651 data.WriteHash(cur_src->GetUserHash());
2654 if (byUsedVersion >= 4){
2655 // CryptSettings - SourceExchange V4
2656 // 5 Reserved (!)
2657 // 1 CryptLayer Required
2658 // 1 CryptLayer Requested
2659 // 1 CryptLayer Supported
2660 const uint8 uSupportsCryptLayer = cur_src->SupportsCryptLayer() ? 1 : 0;
2661 const uint8 uRequestsCryptLayer = cur_src->RequestsCryptLayer() ? 1 : 0;
2662 const uint8 uRequiresCryptLayer = cur_src->RequiresCryptLayer() ? 1 : 0;
2663 const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
2664 data.WriteUInt8(byCryptOptions);
2667 if (nCount > 500) {
2668 break;
2672 if (!nCount) {
2673 return 0;
2675 data.Seek(bIsSX2Packet ? 17 : 16, wxFromStart);
2676 data.WriteUInt16(nCount);
2678 CPacket* result = new CPacket(data, OP_EMULEPROT, bIsSX2Packet ? OP_ANSWERSOURCES2 : OP_ANSWERSOURCES);
2680 // 16+2+501*(4+2+4+2+16) = 14046 bytes max.
2681 if (result->GetPacketSize() > 354) {
2682 result->PackPacket();
2685 return result;
2688 void CPartFile::AddClientSources(CMemFile* sources, unsigned nSourceFrom, uint8 uClientSXVersion, bool bSourceExchange2, const CUpDownClient* /*pClient*/)
2690 // Kad reviewed
2692 if (m_stopped) {
2693 return;
2696 uint32 nCount = 0;
2697 uint8 uPacketSXVersion = 0;
2698 if (!bSourceExchange2) {
2699 nCount = sources->ReadUInt16();
2701 // Check if the data size matches the 'nCount' for v1 or v2 and eventually correct the source
2702 // exchange version while reading the packet data. Otherwise we could experience a higher
2703 // chance in dealing with wrong source data, userhashs and finally duplicate sources.
2704 uint32 uDataSize = sources->GetLength() - sources->GetPosition();
2706 if ((uint32)(nCount*(4+2+4+2)) == uDataSize) { //Checks if version 1 packet is correct size
2707 if(uClientSXVersion != 1) {
2708 return;
2710 uPacketSXVersion = 1;
2711 } else if ((uint32)(nCount*(4+2+4+2+16)) == uDataSize) { // Checks if version 2&3 packet is correct size
2712 if (uClientSXVersion == 2) {
2713 uPacketSXVersion = 2;
2714 } else if (uClientSXVersion > 2) {
2715 uPacketSXVersion = 3;
2716 } else {
2717 return;
2719 } else if (nCount*(4+2+4+2+16+1) == uDataSize) {
2720 if (uClientSXVersion != 4 ) {
2721 return;
2723 uPacketSXVersion = 4;
2724 } else {
2725 // If v5 inserts additional data (like v2), the above code will correctly filter those packets.
2726 // If v5 appends additional data after <count>(<Sources>)[count], we are in trouble with the
2727 // above code. Though a client which does not understand v5+ should never receive such a packet.
2728 AddDebugLogLineN(logClient, CFormat(wxT("Received invalid source exchange packet (v%u) of data size %u for %s")) % uClientSXVersion % uDataSize % GetFileName());
2729 return;
2731 } else {
2732 // for SX2:
2733 // We only check if the version is known by us and do a quick sanitize check on known version
2734 // other then SX1, the packet will be ignored if any error appears, sicne it can't be a "misunderstanding" anymore
2735 if (uClientSXVersion > SOURCEEXCHANGE2_VERSION || uClientSXVersion == 0 ){
2736 AddDebugLogLineN(logPartFile, CFormat(wxT("Invalid source exchange type version: %i")) % uClientSXVersion);
2737 return;
2740 // all known versions use the first 2 bytes as count and unknown version are already filtered above
2741 nCount = sources->ReadUInt16();
2742 uint32 uDataSize = (uint32)(sources->GetLength() - sources->GetPosition());
2743 bool bError = false;
2744 switch (uClientSXVersion){
2745 case 1:
2746 bError = nCount*(4+2+4+2) != uDataSize;
2747 break;
2748 case 2:
2749 case 3:
2750 bError = nCount*(4+2+4+2+16) != uDataSize;
2751 break;
2752 case 4:
2753 bError = nCount*(4+2+4+2+16+1) != uDataSize;
2754 break;
2755 default:
2756 wxFAIL;
2759 if (bError){
2760 wxFAIL;
2761 AddDebugLogLineN(logPartFile, wxT("Invalid source exchange data size."));
2762 return;
2764 uPacketSXVersion = uClientSXVersion;
2767 for (uint16 i = 0;i != nCount;++i) {
2769 uint32 dwID = sources->ReadUInt32();
2770 uint16 nPort = sources->ReadUInt16();
2771 uint32 dwServerIP = sources->ReadUInt32();
2772 uint16 nServerPort = sources->ReadUInt16();
2774 CMD4Hash userHash;
2775 if (uPacketSXVersion > 1) {
2776 userHash = sources->ReadHash();
2779 uint8 byCryptOptions = 0;
2780 if (uPacketSXVersion >= 4) {
2781 byCryptOptions = sources->ReadUInt8();
2784 //Clients send ID's the the Hyrbid format so highID clients with *.*.*.0 won't be falsely switched to a lowID..
2785 uint32 dwIDED2K;
2786 if (uPacketSXVersion >= 3) {
2787 dwIDED2K = wxUINT32_SWAP_ALWAYS(dwID);
2788 } else {
2789 dwIDED2K = dwID;
2792 // check the HighID(IP) - "Filter LAN IPs" and "IPfilter" the received sources IP addresses
2793 if (!IsLowID(dwID)) {
2794 if (!IsGoodIP(dwIDED2K, thePrefs::FilterLanIPs())) {
2795 // check for 0-IP, localhost and optionally for LAN addresses
2796 AddDebugLogLineN(logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - bad IP")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2797 continue;
2799 if (theApp->ipfilter->IsFiltered(dwIDED2K)) {
2800 AddDebugLogLineN(logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via %s - IPFilter")) % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
2801 continue;
2803 if (theApp->clientlist->IsBannedClient(dwIDED2K)){
2804 continue;
2808 // additionally check for LowID and own IP
2809 if (!CanAddSource(dwID, nPort, dwServerIP, nServerPort, NULL, false)) {
2810 AddDebugLogLineN(logIPFilter, CFormat(wxT("Ignored source (IP=%s) received via source exchange")) % Uint32toStringIP(dwIDED2K));
2811 continue;
2814 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
2815 CUpDownClient* newsource = new CUpDownClient(nPort,dwID,dwServerIP,nServerPort,this, (uPacketSXVersion < 3), true);
2816 if (uPacketSXVersion > 1) {
2817 newsource->SetUserHash(userHash);
2820 if (uPacketSXVersion >= 4) {
2821 newsource->SetConnectOptions(byCryptOptions, true, false);
2824 newsource->SetSourceFrom((ESourceFrom)nSourceFrom);
2825 theApp->downloadqueue->CheckAndAddSource(this,newsource);
2827 } else {
2828 break;
2833 void CPartFile::UpdateAutoDownPriority()
2835 if (!IsAutoDownPriority()) {
2836 return;
2838 if (GetSourceCount() <= theApp->downloadqueue->GetRareFileThreshold()) {
2839 if ( GetDownPriority() != PR_HIGH )
2840 SetDownPriority(PR_HIGH, false, false);
2841 } else if (GetSourceCount() < theApp->downloadqueue->GetCommonFileThreshold()) {
2842 if ( GetDownPriority() != PR_NORMAL )
2843 SetDownPriority(PR_NORMAL, false, false);
2844 } else {
2845 if ( GetDownPriority() != PR_LOW )
2846 SetDownPriority(PR_LOW, false, false);
2850 // making this function return a higher when more sources have the extended
2851 // protocol will force you to ask a larger variety of people for sources
2853 int CPartFile::GetCommonFilePenalty()
2855 //TODO: implement, but never return less than MINCOMMONPENALTY!
2856 return MINCOMMONPENALTY;
2859 /* Barry - Replaces BlockReceived()
2861 Originally this only wrote to disk when a full 180k block
2862 had been received from a client, and only asked for data in
2863 180k blocks.
2865 This meant that on average 90k was lost for every connection
2866 to a client data source. That is a lot of wasted data.
2868 To reduce the lost data, packets are now written to a buffer
2869 and flushed to disk regularly regardless of size downloaded.
2870 This includes compressed packets.
2872 Data is also requested only where gaps are, not in 180k blocks.
2873 The requests will still not exceed 180k, but may be smaller to
2874 fill a gap.
2877 // Kry - transize is 32bits, no packet can be more than that (this is
2878 // compressed size). Even 32bits is too much imho.As for the return size,
2879 // look at the lenData below.
2880 uint32 CPartFile::WriteToBuffer(uint32 transize, uint8_t* data, uint64 start, uint64 end, Requested_Block_Struct *block, const CUpDownClient* client)
2882 // Increment transferred bytes counter for this file
2883 transferred += transize;
2885 // This is needed a few times
2886 // Kry - should not need a uint64 here - no block is larger than
2887 // 2GB even after uncompressed.
2888 uint32 lenData = (uint32) (end - start + 1);
2890 if(lenData > transize) {
2891 m_iGainDueToCompression += lenData-transize;
2894 // Occasionally packets are duplicated, no point writing it twice
2895 if (IsComplete(start, end)) {
2896 AddDebugLogLineN(logPartFile,
2897 CFormat(wxT("File '%s' has already been written from %u to %u"))
2898 % GetFileName() % start % end);
2899 return 0;
2902 // security sanitize check to make sure we do not write anything into an already hashed complete chunk
2903 const uint64 nStartChunk = start / PARTSIZE;
2904 const uint64 nEndChunk = end / PARTSIZE;
2905 if (IsComplete(nStartChunk)) {
2906 AddDebugLogLineN(logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (start): %u-%u; File=%s")) % start % end % GetFileName());
2907 return 0;
2908 } else if (nStartChunk != nEndChunk) {
2909 if (IsComplete(nEndChunk)) {
2910 AddDebugLogLineN(logPartFile, CFormat(wxT("Received data touches already hashed chunk - ignored (end): %u-%u; File=%s")) % start % end % GetFileName());
2911 return 0;
2912 } else {
2913 AddDebugLogLineN(logPartFile, CFormat(wxT("Received data crosses chunk boundaries: %u-%u; File=%s")) % start % end % GetFileName());
2917 // log transferinformation in our "blackbox"
2918 m_CorruptionBlackBox->TransferredData(start, end, client->GetIP());
2920 // Create a new buffered queue entry
2921 PartFileBufferedData *item = new PartFileBufferedData(m_hpartfile, data, start, end, block);
2923 // Add to the queue in the correct position (most likely the end)
2924 bool added = false;
2926 std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
2927 for (; it != m_BufferedData_list.end(); ++it) {
2928 PartFileBufferedData* queueItem = *it;
2930 if (item->end <= queueItem->end) {
2931 if (it != m_BufferedData_list.begin()) {
2932 added = true;
2934 m_BufferedData_list.insert(--it, item);
2937 break;
2941 if (!added) {
2942 m_BufferedData_list.push_front(item);
2945 // Increment buffer size marker
2946 m_nTotalBufferData += lenData;
2948 // Mark this small section of the file as filled
2949 FillGap(item->start, item->end);
2951 // Update the flushed mark on the requested block
2952 // The loop here is unfortunate but necessary to detect deleted blocks.
2954 std::list<Requested_Block_Struct*>::iterator it2 = m_requestedblocks_list.begin();
2955 for (; it2 != m_requestedblocks_list.end(); ++it2) {
2956 if (*it2 == item->block) {
2957 item->block->transferred += lenData;
2961 if (m_gaplist.IsComplete()) {
2962 FlushBuffer();
2965 // Return the length of data written to the buffer
2966 return lenData;
2969 void CPartFile::FlushBuffer(bool fromAICHRecoveryDataAvailable)
2971 m_nLastBufferFlushTime = GetTickCount();
2973 if (m_BufferedData_list.empty()) {
2974 return;
2978 uint32 partCount = GetPartCount();
2979 // Remember which parts need to be checked at the end of the flush
2980 std::vector<bool> changedPart(partCount, false);
2982 // Ensure file is big enough to write data to (the last item will be the furthest from the start)
2983 if (!CheckFreeDiskSpace(m_nTotalBufferData)) {
2984 // Not enough free space to write the last item, bail
2985 AddLogLineC(CFormat( _("WARNING: Not enough free disk-space! Pausing file: %s") ) % GetFileName());
2987 PauseFile( true );
2988 return;
2991 // Loop through queue
2992 while ( !m_BufferedData_list.empty() ) {
2993 // Get top item and remove it from the queue
2994 CScopedPtr<PartFileBufferedData> item(m_BufferedData_list.front());
2995 m_BufferedData_list.pop_front();
2997 // This is needed a few times
2998 wxASSERT((item->end - item->start) < 0xFFFFFFFF);
2999 uint32 lenData = (uint32)(item->end - item->start + 1);
3001 // SLUGFILLER: SafeHash - could be more than one part
3002 for (uint32 curpart = (item->start/PARTSIZE); curpart <= (item->end/PARTSIZE); ++curpart) {
3003 wxASSERT(curpart < partCount);
3004 changedPart[curpart] = true;
3006 // SLUGFILLER: SafeHash
3008 // Go to the correct position in file and write block of data
3009 try {
3010 item->area.FlushAt(m_hpartfile, item->start, lenData);
3011 // Decrease buffer size
3012 m_nTotalBufferData -= lenData;
3013 } catch (const CIOFailureException& e) {
3014 AddDebugLogLineC(logPartFile, wxT("Error while saving part-file: ") + e.what());
3015 SetStatus(PS_ERROR);
3016 // No need to bang your head against it again and again if it has already failed.
3017 DeleteContents(m_BufferedData_list);
3018 m_nTotalBufferData = 0;
3019 return;
3024 // Update last-changed date
3025 m_lastDateChanged = wxDateTime::GetTimeNow();
3027 try {
3028 // Partfile should never be too large
3029 if (m_hpartfile.GetLength() > GetFileSize()) {
3030 // it's "last chance" correction. the real bugfix has to be applied 'somewhere' else
3031 m_hpartfile.SetLength(GetFileSize());
3033 } catch (const CIOFailureException& e) {
3034 AddDebugLogLineC(logPartFile,
3035 CFormat(wxT("Error while truncating part-file (%s): %s"))
3036 % m_PartPath % e.what());
3037 SetStatus(PS_ERROR);
3042 // Check each part of the file
3043 for (uint16 partNumber = 0; partNumber < partCount; ++partNumber) {
3044 if (changedPart[partNumber] == false) {
3045 continue;
3048 uint32 partRange = GetPartSize(partNumber) - 1;
3050 // Is this 9MB part complete
3051 if (IsComplete(partNumber)) {
3052 // Is part corrupt
3053 if (!HashSinglePart(partNumber)) {
3054 AddLogLineC(CFormat(
3055 _("Downloaded part %i is corrupt in file: %s") ) % partNumber % GetFileName() );
3056 AddGap(partNumber);
3057 // add part to corrupted list, if not already there
3058 if (!IsCorruptedPart(partNumber)) {
3059 m_corrupted_list.push_back(partNumber);
3061 // request AICH recovery data
3062 // Don't if called from the AICHRecovery. It's already there and would lead to an infinite recursion.
3063 if (!fromAICHRecoveryDataAvailable) {
3064 RequestAICHRecovery(partNumber);
3066 // Reduce transferred amount by corrupt amount
3067 m_iLostDueToCorruption += (partRange + 1);
3068 } else {
3069 if (!m_hashsetneeded) {
3070 AddDebugLogLineN(logPartFile, CFormat(
3071 wxT("Finished part %u of '%s'")) % partNumber % GetFileName());
3074 // tell the blackbox about the verified data
3075 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3077 // if this part was successfully completed (although ICH is active), remove from corrupted list
3078 EraseFirstValue(m_corrupted_list, partNumber);
3080 if (status == PS_EMPTY) {
3081 if (theApp->IsRunning()) { // may be called during shutdown!
3082 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3083 // Successfully completed part, make it available for sharing
3084 SetStatus(PS_READY);
3085 theApp->sharedfiles->SafeAddKFile(this);
3090 } else if ( IsCorruptedPart(partNumber) && // corrupted part:
3091 (thePrefs::IsICHEnabled() // old ICH: rehash whenever we have new data hoping it will be good now
3092 || fromAICHRecoveryDataAvailable)) {// new AICH: one rehash right before performing it (maybe it's already good)
3093 // Try to recover with minimal loss
3094 if (HashSinglePart(partNumber)) {
3095 ++m_iTotalPacketsSavedDueToICH;
3097 uint64 uMissingInPart = m_gaplist.GetGapSize(partNumber);
3098 FillGap(partNumber);
3099 RemoveBlockFromList(PARTSIZE*partNumber,(PARTSIZE*partNumber + partRange));
3101 // tell the blackbox about the verified data
3102 m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
3104 // remove from corrupted list
3105 EraseFirstValue(m_corrupted_list, partNumber);
3107 AddLogLineC(CFormat( _("ICH: Recovered corrupted part %i for %s -> Saved bytes: %s") )
3108 % partNumber
3109 % GetFileName()
3110 % CastItoXBytes(uMissingInPart));
3112 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3113 if (status == PS_EMPTY) {
3114 // Successfully recovered part, make it available for sharing
3115 SetStatus(PS_READY);
3116 if (theApp->IsRunning()) // may be called during shutdown!
3117 theApp->sharedfiles->SafeAddKFile(this);
3124 // Update met file
3125 SavePartFile();
3127 if (theApp->IsRunning()) { // may be called during shutdown!
3128 // Is this file finished ?
3129 if (m_gaplist.IsComplete()) {
3130 CompleteFile(false);
3136 // read data for upload, return false on error
3137 bool CPartFile::ReadData(CFileArea & area, uint64 offset, uint32 toread)
3139 // Sanity check
3140 if (offset + toread > GetFileSize()) {
3141 AddDebugLogLineN(logPartFile, CFormat(wxT("tried to read %d bytes past eof of %s"))
3142 % (offset + toread - GetFileSize()) % GetFileName());
3143 wxFAIL;
3144 return false;
3147 area.ReadAt(m_hpartfile, offset, toread);
3148 // if it fails it throws (which the caller should catch)
3149 return true;
3153 void CPartFile::UpdateFileRatingCommentAvail()
3155 bool prevComment = m_hasComment;
3156 int prevRating = m_iUserRating;
3158 m_hasComment = false;
3159 m_iUserRating = 0;
3160 int ratingCount = 0;
3162 SourceSet::iterator it = m_SrcList.begin();
3163 for (; it != m_SrcList.end(); ++it) {
3164 CUpDownClient* cur_src = it->GetClient();
3166 if (!cur_src->GetFileComment().IsEmpty()) {
3167 if (thePrefs::IsCommentFiltered(cur_src->GetFileComment())) {
3168 continue;
3170 m_hasComment = true;
3173 uint8 rating = cur_src->GetFileRating();
3174 if (rating) {
3175 wxASSERT(rating <= 5);
3177 ratingCount++;
3178 m_iUserRating += rating;
3182 if (ratingCount) {
3183 m_iUserRating /= ratingCount;
3184 wxASSERT(m_iUserRating > 0 && m_iUserRating <= 5);
3187 if ((prevComment != m_hasComment) || (prevRating != m_iUserRating)) {
3188 UpdateDisplayedInfo();
3193 void CPartFile::SetCategory(uint8 cat)
3195 wxASSERT( cat < theApp->glob_prefs->GetCatCount() );
3197 m_category = cat;
3198 SavePartFile();
3201 bool CPartFile::RemoveSource(CUpDownClient* toremove, bool updatewindow, bool bDoStatsUpdate)
3203 wxASSERT( toremove );
3205 bool result = theApp->downloadqueue->RemoveSource( toremove, updatewindow, bDoStatsUpdate );
3207 // Check if the client should be deleted, but not if the client is already dying
3208 if ( !toremove->GetSocket() && !toremove->HasBeenDeleted() ) {
3209 if ( toremove->Disconnected(wxT("RemoveSource - purged")) ) {
3210 toremove->Safe_Delete();
3214 return result;
3217 void CPartFile::AddDownloadingSource(CUpDownClient* client)
3219 CClientRefList::iterator it =
3220 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), CCLIENTREF(client, wxEmptyString));
3221 if (it == m_downloadingSourcesList.end()) {
3222 m_downloadingSourcesList.push_back(CCLIENTREF(client, wxT("CPartFile::AddDownloadingSource")));
3227 void CPartFile::RemoveDownloadingSource(CUpDownClient* client)
3229 CClientRefList::iterator it =
3230 std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), CCLIENTREF(client, wxEmptyString));
3231 if (it != m_downloadingSourcesList.end()) {
3232 m_downloadingSourcesList.erase(it);
3237 uint64 CPartFile::GetNeededSpace()
3239 try {
3240 uint64 length = m_hpartfile.GetLength();
3242 if (length > GetFileSize()) {
3243 return 0; // Shouldn't happen, but just in case
3246 return GetFileSize() - length;
3247 } catch (const CIOFailureException& e) {
3248 AddDebugLogLineC(logPartFile,
3249 CFormat(wxT("Error while retrieving file-length (%s): %s"))
3250 % m_PartPath % e.what());
3251 SetStatus(PS_ERROR);
3252 return 0;
3256 void CPartFile::SetStatus(uint8 in)
3258 // PAUSED and INSUFFICIENT have extra flag variables m_paused and m_insufficient
3259 // - they are never to be stored in status
3260 wxASSERT( in != PS_PAUSED && in != PS_INSUFFICIENT );
3262 status = in;
3264 if (theApp->IsRunning()) {
3265 UpdateDisplayedInfo( true );
3267 if ( thePrefs::ShowCatTabInfos() ) {
3268 Notify_ShowUpdateCatTabTitles();
3270 Notify_DownloadCtrlSort();
3275 void CPartFile::RequestAICHRecovery(uint16 nPart)
3278 if ( !m_pAICHHashSet->HasValidMasterHash() ||
3279 (m_pAICHHashSet->GetStatus() != AICH_TRUSTED && m_pAICHHashSet->GetStatus() != AICH_VERIFIED)){
3280 AddDebugLogLineN( logAICHRecovery, wxT("Unable to request AICH Recoverydata because we have no trusted Masterhash") );
3281 return;
3283 if (GetPartSize(nPart) <= EMBLOCKSIZE)
3284 return;
3285 if (CAICHHashSet::IsClientRequestPending(this, nPart)){
3286 AddDebugLogLineN( logAICHRecovery, wxT("RequestAICHRecovery: Already a request for this part pending"));
3287 return;
3290 // first check if we have already the recoverydata, no need to rerequest it then
3291 if (m_pAICHHashSet->IsPartDataAvailable(nPart*PARTSIZE)){
3292 AddDebugLogLineN( logAICHRecovery, wxT("Found PartRecoveryData in memory"));
3293 AICHRecoveryDataAvailable(nPart);
3294 return;
3297 wxASSERT( nPart < GetPartCount() );
3298 // find some random client which support AICH to ask for the blocks
3299 // first lets see how many we have at all, we prefer high id very much
3300 uint32 cAICHClients = 0;
3301 uint32 cAICHLowIDClients = 0;
3302 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3303 CUpDownClient* pCurClient = it->GetClient();
3304 if ( pCurClient->IsSupportingAICH() &&
3305 pCurClient->GetReqFileAICHHash() != NULL &&
3306 !pCurClient->IsAICHReqPending()
3307 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3309 if (pCurClient->HasLowID()) {
3310 ++cAICHLowIDClients;
3311 } else {
3312 ++cAICHClients;
3316 if ((cAICHClients | cAICHLowIDClients) == 0){
3317 AddDebugLogLineN( logAICHRecovery, wxT("Unable to request AICH Recoverydata because found no client who supports it and has the same hash as the trusted one"));
3318 return;
3320 uint32 nSeclectedClient;
3321 if (cAICHClients > 0) {
3322 nSeclectedClient = (rand() % cAICHClients) + 1;
3323 } else {
3324 nSeclectedClient = (rand() % cAICHLowIDClients) + 1;
3326 CUpDownClient* pClient = NULL;
3327 for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
3328 CUpDownClient* pCurClient = it->GetClient();
3329 if (pCurClient->IsSupportingAICH() && pCurClient->GetReqFileAICHHash() != NULL && !pCurClient->IsAICHReqPending()
3330 && (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
3332 if (cAICHClients > 0){
3333 if (!pCurClient->HasLowID())
3334 nSeclectedClient--;
3336 else{
3337 wxASSERT( pCurClient->HasLowID());
3338 nSeclectedClient--;
3340 if (nSeclectedClient == 0){
3341 pClient = pCurClient;
3342 break;
3346 if (pClient == NULL){
3347 wxFAIL;
3348 return;
3351 AddDebugLogLineN( logAICHRecovery, CFormat( wxT("Requesting AICH Hash (%s) form client %s") ) % ( cAICHClients ? wxT("HighId") : wxT("LowID") ) % pClient->GetClientFullInfo() );
3352 pClient->SendAICHRequest(this, nPart);
3357 void CPartFile::AICHRecoveryDataAvailable(uint16 nPart)
3359 if (GetPartCount() < nPart){
3360 wxFAIL;
3361 return;
3364 FlushBuffer(true);
3365 uint32 length = GetPartSize(nPart);
3366 // if the part was already ok, it would now be complete
3367 if (IsComplete(nPart)) {
3368 AddDebugLogLineN(logAICHRecovery, CFormat(wxT("Processing AICH Recovery data: The part (%u) is already complete, canceling")) % nPart);
3369 return;
3374 CAICHHashTree* pVerifiedHash = m_pAICHHashSet->m_pHashTree.FindHash(nPart*PARTSIZE, length);
3375 if (pVerifiedHash == NULL || !pVerifiedHash->GetHashValid()){
3376 AddDebugLogLineC( logAICHRecovery, wxT("Processing AICH Recovery data: Unable to get verified hash from hashset (should never happen)") );
3377 wxFAIL;
3378 return;
3380 CAICHHashTree htOurHash(pVerifiedHash->GetNDataSize(), pVerifiedHash->GetIsLeftBranch(), pVerifiedHash->GetNBaseSize());
3381 try {
3382 CreateHashFromFile(m_hpartfile, PARTSIZE * nPart, length, NULL, &htOurHash);
3383 } catch (const CIOFailureException& e) {
3384 AddDebugLogLineC(logAICHRecovery,
3385 CFormat(wxT("IO failure while hashing part-file '%s': %s"))
3386 % m_hpartfile.GetFilePath() % e.what());
3387 SetStatus(PS_ERROR);
3388 return;
3391 if (!htOurHash.GetHashValid()){
3392 AddDebugLogLineN( logAICHRecovery, wxT("Processing AICH Recovery data: Failed to retrieve AICH Hashset of corrupt part") );
3393 wxFAIL;
3394 return;
3397 // now compare the hash we just did, to the verified hash and readd all blocks which are ok
3398 uint32 nRecovered = 0;
3399 for (uint32 pos = 0; pos < length; pos += EMBLOCKSIZE){
3400 const uint32 nBlockSize = min<uint32>(EMBLOCKSIZE, length - pos);
3401 CAICHHashTree* pVerifiedBlock = pVerifiedHash->FindHash(pos, nBlockSize);
3402 CAICHHashTree* pOurBlock = htOurHash.FindHash(pos, nBlockSize);
3403 if ( pVerifiedBlock == NULL || pOurBlock == NULL || !pVerifiedBlock->GetHashValid() || !pOurBlock->GetHashValid()){
3404 wxFAIL;
3405 continue;
3407 if (pOurBlock->GetHash() == pVerifiedBlock->GetHash()){
3408 FillGap(PARTSIZE*nPart+pos, PARTSIZE*nPart + pos + (nBlockSize-1));
3409 RemoveBlockFromList(PARTSIZE*nPart, PARTSIZE*nPart + (nBlockSize-1));
3410 nRecovered += nBlockSize;
3411 // tell the blackbox about the verified data
3412 m_CorruptionBlackBox->VerifiedData(true, nPart, pos, pos + nBlockSize - 1);
3413 } else {
3414 // inform our "blackbox" about the corrupted block which may ban clients who sent it
3415 m_CorruptionBlackBox->VerifiedData(false, nPart, pos, pos + nBlockSize - 1);
3418 m_CorruptionBlackBox->EvaluateData();
3420 // ok now some sanity checks
3421 if (IsComplete(nPart)) {
3422 // this is bad, but it could probably happen under some rare circumstances
3423 // make sure that MD4 agrees to this fact too
3424 if (!HashSinglePart(nPart)) {
3425 AddDebugLogLineN(logAICHRecovery,
3426 CFormat(wxT("Processing AICH Recovery data: The part (%u) got completed while recovering - but MD4 says it corrupt! Setting hashset to error state, deleting part")) % nPart);
3427 // now we are fu... unhappy
3428 m_pAICHHashSet->SetStatus(AICH_ERROR);
3429 AddGap(nPart);
3430 wxFAIL;
3431 return;
3432 } else {
3433 AddDebugLogLineN(logAICHRecovery,
3434 CFormat(wxT("Processing AICH Recovery data: The part (%u) got completed while recovering and MD4 agrees")) % nPart);
3435 if (status == PS_EMPTY && theApp->IsRunning()) {
3436 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
3437 // Successfully recovered part, make it available for sharing
3438 SetStatus(PS_READY);
3439 theApp->sharedfiles->SafeAddKFile(this);
3443 if (theApp->IsRunning()) {
3444 // Is this file finished?
3445 if (m_gaplist.IsComplete()) {
3446 CompleteFile(false);
3450 } // end sanity check
3451 // We did the best we could. If it's still incomplete, then no need to keep
3452 // bashing it with ICH. So remove it from the list of corrupted parts.
3453 EraseFirstValue(m_corrupted_list, nPart);
3454 // Update met file
3455 SavePartFile();
3457 // make sure the user appreciates our great recovering work :P
3458 AddDebugLogLineC( logAICHRecovery, CFormat(
3459 wxT("AICH successfully recovered %s of %s from part %u for %s") )
3460 % CastItoXBytes(nRecovered)
3461 % CastItoXBytes(length)
3462 % nPart
3463 % GetFileName() );
3467 void CPartFile::ClientStateChanged( int oldState, int newState )
3469 if ( oldState == newState )
3470 return;
3472 // If the state is -1, then it's an entirely new item
3473 if ( oldState != -1 ) {
3474 // Was the old state a valid state?
3475 if ( oldState == DS_ONQUEUE || oldState == DS_DOWNLOADING ) {
3476 m_validSources--;
3477 } else {
3478 if ( oldState == DS_CONNECTED /* || oldState == DS_REMOTEQUEUEFULL */ ) {
3479 m_validSources--;
3482 m_notCurrentSources--;
3486 // If the state is -1, then the source is being removed
3487 if ( newState != -1 ) {
3488 // Was the old state a valid state?
3489 if ( newState == DS_ONQUEUE || newState == DS_DOWNLOADING ) {
3490 ++m_validSources;
3491 } else {
3492 if ( newState == DS_CONNECTED /* || newState == DS_REMOTEQUEUEFULL */ ) {
3493 ++m_validSources;
3496 ++m_notCurrentSources;
3502 bool CPartFile::AddSource( CUpDownClient* client )
3504 if (m_SrcList.insert(CCLIENTREF(client, wxT("CPartFile::AddSource"))).second) {
3505 theStats::AddFoundSource();
3506 theStats::AddSourceOrigin(client->GetSourceFrom());
3507 return true;
3508 } else {
3509 return false;
3514 bool CPartFile::DelSource( CUpDownClient* client )
3516 if (m_SrcList.erase(CCLIENTREF(client, wxEmptyString))) {
3517 theStats::RemoveSourceOrigin(client->GetSourceFrom());
3518 theStats::RemoveFoundSource();
3519 return true;
3520 } else {
3521 return false;
3526 void CPartFile::UpdatePartsFrequency( CUpDownClient* client, bool increment )
3528 const BitVector& freq = client->GetPartStatus();
3530 if ( m_SrcpartFrequency.size() != GetPartCount() ) {
3531 m_SrcpartFrequency.clear();
3532 m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
3534 if ( !increment ) {
3535 return;
3539 unsigned int size = freq.size();
3540 if ( size != m_SrcpartFrequency.size() ) {
3541 return;
3544 if ( increment ) {
3545 for ( unsigned int i = 0; i < size; i++ ) {
3546 if ( freq.get(i) ) {
3547 m_SrcpartFrequency[i]++;
3550 } else {
3551 for ( unsigned int i = 0; i < size; i++ ) {
3552 if ( freq.get(i) ) {
3553 m_SrcpartFrequency[i]--;
3559 void CPartFile::GetRatingAndComments(FileRatingList & list) const
3561 list.clear();
3562 // This can be pre-processed, but is it worth the CPU?
3563 CPartFile::SourceSet::const_iterator it = m_SrcList.begin();
3564 for ( ; it != m_SrcList.end(); ++it ) {
3565 CUpDownClient *cur_src = it->GetClient();
3566 if (cur_src->GetFileComment().Length()>0 || cur_src->GetFileRating()>0) {
3567 // AddDebugLogLineN(logPartFile, wxString(wxT("found a comment for ")) << GetFileName());
3568 list.push_back(SFileRating(*cur_src));
3573 #else // CLIENT_GUI
3575 CPartFile::CPartFile(const CEC_PartFile_Tag *tag) : CKnownFile(tag)
3577 Init();
3579 SetFileName(CPath(tag->FileName()));
3580 m_abyFileHash = tag->FileHash();
3581 SetFileSize(tag->SizeFull());
3582 m_gaplist.Init(GetFileSize(), true); // Init empty
3583 m_partmetfilename = CPath(tag->PartMetName());
3584 m_fullname = m_partmetfilename; // We have only the met number, so show it without path in the detail dialog.
3586 m_SrcpartFrequency.insert(m_SrcpartFrequency.end(), GetPartCount(), 0);
3588 // these are only in CLIENT_GUI and not covered by Init()
3589 m_source_count = 0;
3590 m_kbpsDown = 0;
3591 m_iDownPriorityEC = 0;
3592 m_a4af_source_count = 0;
3593 m_isShared = false;
3597 * Remote gui specific code
3599 CPartFile::~CPartFile()
3603 void CPartFile::GetRatingAndComments(FileRatingList & list) const
3605 list = m_FileRatingList;
3608 void CPartFile::SetCategory(uint8 cat)
3610 m_category = cat;
3614 bool CPartFile::AddSource(CUpDownClient* client)
3616 return m_SrcList.insert(CCLIENTREF(client, wxT("CPartFile::AddSource"))).second != 0;
3620 bool CPartFile::DelSource(CUpDownClient* client)
3622 return m_SrcList.erase(CCLIENTREF(client, wxEmptyString)) != 0;
3626 #endif // !CLIENT_GUI
3629 void CPartFile::UpdateDisplayedInfo(bool force)
3631 uint32 curTick = ::GetTickCount();
3633 // Wait 1.5s between each redraw
3634 if (force || curTick-m_lastRefreshedDLDisplay > MINWAIT_BEFORE_DLDISPLAY_WINDOWUPDATE) {
3635 Notify_DownloadCtrlUpdateItem(this);
3636 m_lastRefreshedDLDisplay = curTick;
3641 void CPartFile::Init()
3643 m_lastsearchtime = 0;
3644 lastpurgetime = ::GetTickCount();
3645 m_paused = false;
3646 m_stopped = false;
3647 m_insufficient = false;
3649 status = PS_EMPTY;
3651 transferred = 0;
3652 m_iLastPausePurge = time(NULL);
3654 if(thePrefs::GetNewAutoDown()) {
3655 m_iDownPriority = PR_HIGH;
3656 m_bAutoDownPriority = true;
3657 } else {
3658 m_iDownPriority = PR_NORMAL;
3659 m_bAutoDownPriority = false;
3662 transferingsrc = 0; // new
3664 kBpsDown = 0.0;
3666 m_hashsetneeded = true;
3667 m_count = 0;
3668 percentcompleted = 0;
3669 completedsize=0;
3670 lastseencomplete = 0;
3671 m_availablePartsCount=0;
3672 m_ClientSrcAnswered = 0;
3673 m_LastNoNeededCheck = 0;
3674 m_iRating = 0;
3675 m_nTotalBufferData = 0;
3676 m_nLastBufferFlushTime = 0;
3677 m_bPercentUpdated = false;
3678 m_iGainDueToCompression = 0;
3679 m_iLostDueToCorruption = 0;
3680 m_iTotalPacketsSavedDueToICH = 0;
3681 m_category = 0;
3682 m_lastRefreshedDLDisplay = 0;
3683 m_nDlActiveTime = 0;
3684 m_tActivated = 0;
3685 m_is_A4AF_auto = false;
3686 m_localSrcReqQueued = false;
3687 m_nCompleteSourcesTime = time(NULL);
3688 m_nCompleteSourcesCount = 0;
3689 m_nCompleteSourcesCountLo = 0;
3690 m_nCompleteSourcesCountHi = 0;
3692 m_validSources = 0;
3693 m_notCurrentSources = 0;
3695 // Kad
3696 m_LastSearchTimeKad = 0;
3697 m_TotalSearchesKad = 0;
3699 #ifndef CLIENT_GUI
3700 m_CorruptionBlackBox = new CCorruptionBlackBox();
3701 #endif
3704 wxString CPartFile::getPartfileStatus() const
3707 wxString mybuffer;
3709 if ((status == PS_HASHING) || (status == PS_WAITINGFORHASH)) {
3710 mybuffer=_("Hashing");
3711 } else if (status == PS_ALLOCATING) {
3712 mybuffer = _("Allocating");
3713 } else {
3714 switch (GetStatus()) {
3715 case PS_COMPLETING:
3716 mybuffer=_("Completing");
3717 break;
3718 case PS_COMPLETE:
3719 mybuffer=_("Complete");
3720 break;
3721 case PS_PAUSED:
3722 mybuffer=_("Paused");
3723 break;
3724 case PS_ERROR:
3725 mybuffer=_("Erroneous");
3726 break;
3727 case PS_INSUFFICIENT:
3728 mybuffer = _("Insufficient disk space");
3729 break;
3730 default:
3731 if (GetTransferingSrcCount()>0) {
3732 mybuffer=_("Downloading");
3733 } else {
3734 mybuffer=_("Waiting");
3736 break;
3738 if (m_stopped && (GetStatus()!=PS_COMPLETE)) {
3739 mybuffer=_("Stopped");
3743 return mybuffer;
3746 int CPartFile::getPartfileStatusRang() const
3749 int tempstatus=0;
3750 if (GetTransferingSrcCount()==0) tempstatus=1;
3751 switch (GetStatus()) {
3752 case PS_HASHING:
3753 case PS_WAITINGFORHASH:
3754 tempstatus=3;
3755 break;
3756 case PS_COMPLETING:
3757 tempstatus=4;
3758 break;
3759 case PS_COMPLETE:
3760 tempstatus=5;
3761 break;
3762 case PS_PAUSED:
3763 tempstatus=2;
3764 break;
3765 case PS_ERROR:
3766 tempstatus=6;
3767 break;
3769 return tempstatus;
3773 wxString CPartFile::GetFeedback() const
3775 wxString retval = CKnownFile::GetFeedback();
3776 if (GetStatus() != PS_COMPLETE) {
3777 retval += CFormat(wxT("%s: %s (%.2f%%)\n%s: %u\n"))
3778 % _("Downloaded") % CastItoXBytes(GetCompletedSize()) % GetPercentCompleted() % _("Sources") % GetSourceCount();
3780 return retval + _("Status") + wxT(": ") + getPartfileStatus() + wxT("\n");
3784 sint32 CPartFile::getTimeRemaining() const
3786 if (GetKBpsDown() < 0.001)
3787 return -1;
3788 else
3789 return((GetFileSize()-GetCompletedSize()) / ((int)(GetKBpsDown()*1024.0)));
3792 bool CPartFile::PreviewAvailable()
3794 const uint64 minSizeForPreview = 256 * 1024;
3795 FileType type = GetFiletype(GetFileName());
3797 return (type == ftVideo || type == ftAudio) &&
3798 GetFileSize() >= minSizeForPreview &&
3799 IsComplete(0, minSizeForPreview);
3802 bool CPartFile::CheckShowItemInGivenCat(int inCategory)
3804 // first check if item belongs in this cat in principle
3805 if (inCategory > 0 && inCategory != GetCategory()) {
3806 return false;
3809 // if yes apply filter
3810 bool show = true;
3812 switch (thePrefs::GetAllcatFilter()) {
3813 case acfAllOthers:
3814 show = GetCategory() == 0 || inCategory > 0;
3815 break;
3816 case acfIncomplete:
3817 show = IsPartFile();
3818 break;
3819 case acfCompleted:
3820 show = !IsPartFile();
3821 break;
3822 case acfWaiting:
3823 show =
3824 (GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
3825 GetTransferingSrcCount() == 0;
3826 break;
3827 case acfDownloading:
3828 show =
3829 (GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
3830 GetTransferingSrcCount() > 0;
3831 break;
3832 case acfErroneous:
3833 show = GetStatus() == PS_ERROR;
3834 break;
3835 case acfPaused:
3836 show = GetStatus() == PS_PAUSED && !IsStopped();
3837 break;
3838 case acfStopped:
3839 show = IsStopped();
3840 break;
3841 case acfVideo:
3842 show = GetFiletype(GetFileName()) == ftVideo;
3843 break;
3844 case acfAudio:
3845 show = GetFiletype(GetFileName()) == ftAudio;
3846 break;
3847 case acfArchive:
3848 show = GetFiletype(GetFileName()) == ftArchive;
3849 break;
3850 case acfCDImages:
3851 show = GetFiletype(GetFileName()) == ftCDImage;
3852 break;
3853 case acfPictures:
3854 show = GetFiletype(GetFileName()) == ftPicture;
3855 break;
3856 case acfText:
3857 show = GetFiletype(GetFileName()) == ftText;
3858 break;
3859 case acfActive:
3860 show = !IsStopped() && GetStatus() != PS_PAUSED;
3861 break;
3862 default:
3863 show = true;
3864 break;
3867 return show;
3871 void CPartFile::RemoveCategory(uint8 cat)
3873 if (m_category == cat) {
3874 // Reset the category
3875 m_category = 0;
3876 } else if (m_category > cat) {
3877 // Set to the new position of the original category
3878 m_category--;
3883 void CPartFile::SetActive(bool bActive)
3885 time_t tNow = time(NULL);
3886 if (bActive) {
3887 if (theApp->IsConnected()) {
3888 if (m_tActivated == 0) {
3889 m_tActivated = tNow;
3892 } else {
3893 if (m_tActivated != 0) {
3894 m_nDlActiveTime += tNow - m_tActivated;
3895 m_tActivated = 0;
3901 uint32 CPartFile::GetDlActiveTime() const
3903 uint32 nDlActiveTime = m_nDlActiveTime;
3904 if (m_tActivated != 0) {
3905 nDlActiveTime += time(NULL) - m_tActivated;
3907 return nDlActiveTime;
3911 uint16 CPartFile::GetPartMetNumber() const
3913 long nr;
3914 return m_partmetfilename.RemoveAllExt().GetRaw().ToLong(&nr) ? nr : 0;
3918 void CPartFile::SetHashingProgress(uint16 part) const
3920 m_hashingProgress = part;
3921 Notify_DownloadCtrlUpdateItem(this);
3925 #ifndef CLIENT_GUI
3927 uint8 CPartFile::GetStatus(bool ignorepause) const
3929 if ( (!m_paused && !m_insufficient) ||
3930 status == PS_ERROR ||
3931 status == PS_COMPLETING ||
3932 status == PS_COMPLETE ||
3933 ignorepause) {
3934 return status;
3935 } else if ( m_insufficient ) {
3936 return PS_INSUFFICIENT;
3937 } else {
3938 return PS_PAUSED;
3942 void CPartFile::AddDeadSource(const CUpDownClient* client)
3944 m_deadSources.AddDeadSource( client );
3948 bool CPartFile::IsDeadSource(const CUpDownClient* client)
3950 return m_deadSources.IsDeadSource( client );
3953 void CPartFile::SetFileName(const CPath& fileName)
3955 CKnownFile* pFile = theApp->sharedfiles->GetFileByID(GetFileHash());
3957 bool is_shared = (pFile && pFile == this);
3959 if (is_shared) {
3960 // The file is shared, we must clear the search keywords so we don't
3961 // publish the old name anymore.
3962 theApp->sharedfiles->RemoveKeywords(this);
3965 CKnownFile::SetFileName(fileName);
3967 if (is_shared) {
3968 // And of course, we must advertise the new name if the file is shared.
3969 theApp->sharedfiles->AddKeywords(this);
3972 UpdateDisplayedInfo(true);
3976 uint16 CPartFile::GetMaxSources() const
3978 // This is just like this, while we don't import the private max sources per file
3979 return thePrefs::GetMaxSourcePerFile();
3983 uint16 CPartFile::GetMaxSourcePerFileSoft() const
3985 unsigned int temp = ((unsigned int)GetMaxSources() * 9L) / 10;
3986 if (temp > MAX_SOURCES_FILE_SOFT) {
3987 return MAX_SOURCES_FILE_SOFT;
3989 return temp;
3992 uint16 CPartFile::GetMaxSourcePerFileUDP() const
3994 unsigned int temp = ((unsigned int)GetMaxSources() * 3L) / 4;
3995 if (temp > MAX_SOURCES_FILE_UDP) {
3996 return MAX_SOURCES_FILE_UDP;
3998 return temp;
4001 #define DROP_FACTOR 2
4003 CUpDownClient* CPartFile::GetSlowerDownloadingClient(uint32 speed, CUpDownClient* caller) {
4004 // printf("Start slower source calculation\n");
4005 for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
4006 CUpDownClient* cur_src = it++->GetClient();
4007 if ((cur_src->GetDownloadState() == DS_DOWNLOADING) && (cur_src != caller)) {
4008 uint32 factored_bytes_per_second = static_cast<uint32>(
4009 (cur_src->GetKBpsDown() * 1024) * DROP_FACTOR);
4010 if ( factored_bytes_per_second< speed) {
4011 // printf("Selecting source %p to drop: %d < %d\n", cur_src, factored_bytes_per_second, speed);
4012 // printf("End slower source calculation\n");
4013 return cur_src;
4014 } else {
4015 // printf("Not selecting source %p to drop: %d > %d\n", cur_src, factored_bytes_per_second, speed);
4019 // printf("End slower source calculation\n");
4020 return NULL;
4023 void CPartFile::AllocationFinished()
4025 // see if it can be opened
4026 if (!m_hpartfile.Open(m_PartPath, CFile::read_write)) {
4027 AddLogLineN(CFormat(_("ERROR: Failed to open partfile '%s'")) % GetFullName());
4028 SetStatus(PS_ERROR);
4030 // then close the handle again
4031 m_hpartfile.Release(true);
4034 #endif
4035 // File_checked_for_headers