2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002 Merkur ( devs@emule-project.net / http://www.emule-project.net )
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
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
28 #include "PartFile.h" // Interface declarations.
31 #include "config.h" // Needed for VERSION
34 #include <protocol/kad/Constants.h>
35 #include <protocol/ed2k/Client2Client/TCP.h>
36 #include <protocol/Protocols.h>
37 #include <common/DataFileVersion.h>
38 #include <common/Constants.h>
39 #include <tags/FileTags.h>
42 #include <wx/tokenzr.h> // Needed for wxStringTokenizer
44 #include "KnownFileList.h" // Needed for CKnownFileList
45 #include "UploadQueue.h" // Needed for CFileHash
46 #include "IPFilter.h" // Needed for CIPFilter
47 #include "Server.h" // Needed for CServer
48 #include "ServerConnect.h" // Needed for CServerConnect
49 #include "updownclient.h" // Needed for CUpDownClient
50 #include "MemFile.h" // Needed for CMemFile
51 #include "Preferences.h" // Needed for CPreferences
52 #include "DownloadQueue.h" // Needed for CDownloadQueue
53 #include "amule.h" // Needed for theApp
54 #include "ED2KLink.h" // Needed for CED2KLink
55 #include "Packet.h" // Needed for CTag
56 #include "SearchList.h" // Needed for CSearchFile
57 #include "ClientList.h" // Needed for clientlist
58 #include "Statistics.h" // Needed for theStats
60 #include <common/Format.h> // Needed for CFormat
61 #include <common/FileFunctions.h> // Needed for GetLastModificationTime
62 #include "ThreadTasks.h" // Needed for CHashingTask/CCompletionTask/CAllocateFileTask
63 #include "GuiEvents.h" // Needed for Notify_*
64 #include "DataToText.h" // Needed for OriginToText()
65 #include "PlatformSpecific.h" // Needed for CreateSparseFile()
67 #include "kademlia/kademlia/Kademlia.h"
68 #include "kademlia/kademlia/Search.h"
71 SFileRating::SFileRating(const wxString
&u
, const wxString
&f
, sint16 r
, const wxString
&c
)
81 SFileRating::SFileRating(const SFileRating
&fr
)
83 UserName(fr
.UserName
),
84 FileName(fr
.FileName
),
91 SFileRating::SFileRating(const CUpDownClient
&client
)
93 UserName(client
.GetUserName()),
94 FileName(client
.GetClientFilename()),
95 Rating(client
.GetFileRating()),
96 Comment(client
.GetFileComment())
101 SFileRating::~SFileRating()
106 typedef std::list
<Chunk
> ChunkList
;
111 CPartFile::CPartFile()
116 CPartFile::CPartFile(CSearchFile
* searchresult
)
120 m_abyFileHash
= searchresult
->GetFileHash();
121 SetFileName(searchresult
->GetFileName());
122 SetFileSize(searchresult
->GetFileSize());
124 for (unsigned int i
= 0; i
< searchresult
->m_taglist
.size(); ++i
){
125 const CTag
& pTag
= searchresult
->m_taglist
[i
];
127 bool bTagAdded
= false;
128 if (pTag
.GetNameID() == 0 && !pTag
.GetName().IsEmpty() && (pTag
.IsStr() || pTag
.IsInt())) {
129 static const struct {
134 { wxT(FT_ED2K_MEDIA_ARTIST
), 2 },
135 { wxT(FT_ED2K_MEDIA_ALBUM
), 2 },
136 { wxT(FT_ED2K_MEDIA_TITLE
), 2 },
137 { wxT(FT_ED2K_MEDIA_LENGTH
), 2 },
138 { wxT(FT_ED2K_MEDIA_BITRATE
), 3 },
139 { wxT(FT_ED2K_MEDIA_CODEC
), 2 }
142 for (unsigned int t
= 0; t
< itemsof(_aMetaTags
); ++t
) {
143 if ( pTag
.GetType() == _aMetaTags
[t
].nType
&&
144 (pTag
.GetName() == _aMetaTags
[t
].pszName
)) {
145 // skip string tags with empty string values
146 if (pTag
.IsStr() && pTag
.GetStr().IsEmpty()) {
150 // skip "length" tags with "0: 0" values
151 if (pTag
.GetName() == wxT(FT_ED2K_MEDIA_LENGTH
)) {
152 if (pTag
.GetStr().IsSameAs(wxT("0: 0")) ||
153 pTag
.GetStr().IsSameAs(wxT("0:0"))) {
158 // skip "bitrate" tags with '0' values
159 if ((pTag
.GetName() == wxT(FT_ED2K_MEDIA_BITRATE
)) && !pTag
.GetInt()) {
163 AddDebugLogLineM( false, logPartFile
,
164 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
165 pTag
.GetFullInfo() );
166 m_taglist
.push_back(pTag
);
171 } else if (pTag
.GetNameID() != 0 && pTag
.GetName().IsEmpty() && (pTag
.IsStr() || pTag
.IsInt())) {
172 static const struct {
180 for (unsigned int t
= 0; t
< itemsof(_aMetaTags
); ++t
) {
181 if (pTag
.GetType() == _aMetaTags
[t
].nType
&& pTag
.GetNameID() == _aMetaTags
[t
].nID
) {
182 // skip string tags with empty string values
183 if (pTag
.IsStr() && pTag
.GetStr().IsEmpty()) {
187 AddDebugLogLineM( false, logPartFile
,
188 wxT("CPartFile::CPartFile(CSearchFile*): added tag ") +
189 pTag
.GetFullInfo() );
190 m_taglist
.push_back(pTag
);
198 AddDebugLogLineM( false, logPartFile
,
199 wxT("CPartFile::CPartFile(CSearchFile*): ignored tag ") +
200 pTag
.GetFullInfo() );
208 CPartFile::CPartFile(const CED2KFileLink
* fileLink
)
212 SetFileName(CPath(fileLink
->GetName()));
213 SetFileSize(fileLink
->GetSize());
214 m_abyFileHash
= fileLink
->GetHashKey();
218 if (fileLink
->m_hashset
) {
219 if (!LoadHashsetFromFile(fileLink
->m_hashset
, true)) {
220 AddDebugLogLineM(true, logPartFile
, wxT("eD2K link contained invalid hashset: ") + fileLink
->GetLink());
226 CPartFile::~CPartFile()
229 // Barry - Ensure all buffered data is written
232 // eMule had same problem with lseek error ... and override with a simple
233 // check for INVALID_HANDLE_VALUE (that, btw, does not exist on linux)
234 // So we just guess is < 0 on error and > 2 if ok (0 stdin, 1 stdout, 2 stderr)
235 // But, where does this wrong handle comes from?
237 if (m_hpartfile
.IsOpened() && (m_hpartfile
.fd() > 2)) {
241 if (m_hpartfile
.IsOpened() && (m_hpartfile
.fd() > 2)) {
243 // Update met file (with current directory entry)
247 DeleteContents(m_gaplist
);
249 std::list
<PartFileBufferedData
*>::iterator it
= m_BufferedData_list
.begin();
250 for (; it
!= m_BufferedData_list
.end(); ++it
) {
251 PartFileBufferedData
* item
= *it
;
257 wxASSERT(m_SrcList
.empty());
258 wxASSERT(m_A4AFsrclist
.empty());
261 void CPartFile::CreatePartFile()
264 // use lowest free partfilenumber for free file (InterCeptor)
268 m_partmetfilename
= CPath(wxString::Format(wxT("%03i.part.met"), i
));
269 m_fullname
= thePrefs::GetTempDir().JoinPaths(m_partmetfilename
);
270 } while (m_fullname
.FileExists());
272 wxString strPartName
= m_partmetfilename
.RemoveExt().GetRaw();
273 m_taglist
.push_back(CTagString(FT_PARTFILENAME
, strPartName
));
275 Gap_Struct
* gap
= new Gap_Struct
;
277 gap
->end
= GetFileSize() - 1;
279 m_gaplist
.push_back(gap
);
281 CPath partPath
= m_fullname
.RemoveExt();
283 if (thePrefs::GetAllocFullFile()) {
284 fileCreated
= m_hpartfile
.Create(partPath
.GetRaw(), true);
287 fileCreated
= PlatformSpecific::CreateSparseFile(partPath
, GetFileSize());
290 AddLogLineM(false,_("ERROR: Failed to create partfile)"));
291 SetPartFileStatus(PS_ERROR
);
294 SetFilePath(thePrefs::GetTempDir());
296 if (thePrefs::GetAllocFullFile()) {
297 SetPartFileStatus(PS_ALLOCATING
);
298 CThreadScheduler::AddTask(new CAllocateFileTask(this, thePrefs::AddNewFilesPaused()));
300 AllocationFinished();
303 m_hashsetneeded
= (GetED2KPartHashCount() > 0);
306 SetActive(theApp
->IsConnected());
311 uint8
CPartFile::LoadPartFile(const CPath
& in_directory
, const CPath
& filename
, bool from_backup
, bool getsizeonly
)
313 bool isnewstyle
= false;
314 uint8 version
,partmettype
=PMT_UNKNOWN
;
316 std::map
<uint16
, Gap_Struct
*> gap_map
; // Slugfiller
319 m_partmetfilename
= filename
;
320 m_filePath
= in_directory
;
321 m_fullname
= m_filePath
.JoinPaths(m_partmetfilename
);
323 // readfile data form part.met file
324 CPath curMetFilename
= m_fullname
;
326 curMetFilename
= curMetFilename
.AppendExt(PARTMET_BAK_EXT
);
327 AddLogLineM(false, CFormat( _("Trying to load backup of met-file from %s") )
332 CFile
metFile(curMetFilename
, CFile::read
);
333 if (!metFile
.IsOpened()) {
334 AddLogLineM(false, CFormat( _("Error: Failed to open part.met file: %s ==> %s") )
339 } else if (metFile
.GetLength() == 0) {
340 AddLogLineM(false, CFormat( _("Error: part.met file is 0 size: %s ==> %s") )
347 version
= metFile
.ReadUInt8();
348 if (version
!= PARTFILE_VERSION
&& version
!= PARTFILE_SPLITTEDVERSION
&& version
!= PARTFILE_VERSION_LARGEFILE
){
350 //if (version == 83) return ImportShareazaTempFile(...)
351 AddLogLineM(false, CFormat( _("Error: Invalid part.met fileversion: %s ==> %s") )
357 isnewstyle
= (version
== PARTFILE_SPLITTEDVERSION
);
358 partmettype
= isnewstyle
? PMT_SPLITTED
: PMT_DEFAULTOLD
;
362 metFile
.Seek(24, wxFromStart
);
363 metFile
.Read(test
,4);
365 metFile
.Seek(1, wxFromStart
);
366 if (test
[0]==0 && test
[1]==0 && test
[2]==2 && test
[3]==1) {
367 isnewstyle
=true; // edonkeys so called "old part style"
368 partmettype
=PMT_NEWOLD
;
373 uint32 temp
= metFile
.ReadUInt32();
375 if (temp
==0) { // 0.48 partmets - different again
376 LoadHashsetFromFile(&metFile
, false);
378 metFile
.Seek(2, wxFromStart
);
379 LoadDateFromFile(&metFile
);
380 m_abyFileHash
= metFile
.ReadHash();
384 LoadDateFromFile(&metFile
);
385 LoadHashsetFromFile(&metFile
, false);
388 uint32 tagcount
= metFile
.ReadUInt32();
390 for (uint32 j
= 0; j
< tagcount
; ++j
) {
391 CTag
newtag(metFile
,true);
394 (newtag
.GetNameID() == FT_FILESIZE
||
395 newtag
.GetNameID() == FT_FILENAME
))) {
396 switch(newtag
.GetNameID()) {
398 if (!GetFileName().IsOk()) {
399 // If it's not empty, we already loaded the unicoded one
400 SetFileName(CPath(newtag
.GetStr()));
404 case FT_LASTSEENCOMPLETE
: {
405 lastseencomplete
= newtag
.GetInt();
409 SetFileSize(newtag
.GetInt());
412 case FT_TRANSFERRED
: {
413 transferred
= newtag
.GetInt();
417 //#warning needs setfiletype string
418 //SetFileType(newtag.GetStr());
422 m_category
= newtag
.GetInt();
423 if (m_category
> theApp
->glob_prefs
->GetCatCount() - 1 ) {
428 case FT_OLDDLPRIORITY
:
429 case FT_DLPRIORITY
: {
431 m_iDownPriority
= newtag
.GetInt();
432 if( m_iDownPriority
== PR_AUTO
){
433 m_iDownPriority
= PR_HIGH
;
434 SetAutoDownPriority(true);
437 if ( m_iDownPriority
!= PR_LOW
&&
438 m_iDownPriority
!= PR_NORMAL
&&
439 m_iDownPriority
!= PR_HIGH
)
440 m_iDownPriority
= PR_NORMAL
;
441 SetAutoDownPriority(false);
447 m_paused
= (newtag
.GetInt() == 1);
448 m_stopped
= m_paused
;
451 case FT_OLDULPRIORITY
:
452 case FT_ULPRIORITY
: {
454 SetUpPriority(newtag
.GetInt(), false);
455 if( GetUpPriority() == PR_AUTO
){
456 SetUpPriority(PR_HIGH
, false);
457 SetAutoUpPriority(true);
459 SetAutoUpPriority(false);
464 case FT_KADLASTPUBLISHSRC
:{
465 SetLastPublishTimeKadSrc(newtag
.GetInt(), 0);
466 if(GetLastPublishTimeKadSrc() > (uint32
)time(NULL
)+KADEMLIAREPUBLISHTIMES
) {
467 //There may be a posibility of an older client that saved a random number here.. This will check for that..
468 SetLastPublishTimeKadSrc(0,0);
472 case FT_KADLASTPUBLISHNOTES
:{
473 SetLastPublishTimeKadNotes(newtag
.GetInt());
476 // old tags: as long as they are not needed, take the chance to purge them
478 case FT_KADLASTPUBLISHKEY
:
480 case FT_DL_ACTIVE_TIME
:
481 if (newtag
.IsInt()) {
482 m_nDlActiveTime
= newtag
.GetInt();
485 case FT_CORRUPTEDPARTS
: {
486 wxASSERT(m_corrupted_list
.empty());
487 wxString
strCorruptedParts(newtag
.GetStr());
488 wxStringTokenizer
tokenizer(strCorruptedParts
, wxT(","));
489 while ( tokenizer
.HasMoreTokens() ) {
490 wxString token
= tokenizer
.GetNextToken();
492 if (token
.ToULong(&uPart
)) {
493 if (uPart
< GetPartCount() && !IsCorruptedPart(uPart
)) {
494 m_corrupted_list
.push_back(uPart
);
503 hash
.DecodeBase32(newtag
.GetStr()) == CAICHHash::GetHashSize();
504 wxASSERT(hashSizeOk
);
506 m_pAICHHashSet
->SetMasterHash(hash
, AICH_VERIFIED
);
510 case FT_ATTRANSFERRED
:{
511 statistic
.SetAllTimeTransferred(statistic
.GetAllTimeTransferred() + (uint64
)newtag
.GetInt());
514 case FT_ATTRANSFERREDHI
:{
515 statistic
.SetAllTimeTransferred(statistic
.GetAllTimeTransferred() + (((uint64
)newtag
.GetInt()) << 32));
518 case FT_ATREQUESTED
:{
519 statistic
.SetAllTimeRequests(newtag
.GetInt());
523 statistic
.SetAllTimeAccepts(newtag
.GetInt());
527 // Start Changes by Slugfiller for better exception handling
529 wxCharBuffer tag_ansi_name
= newtag
.GetName().ToAscii();
530 char gap_mark
= tag_ansi_name
? tag_ansi_name
[0u] : 0;
531 if ( newtag
.IsInt() && (newtag
.GetName().Length() > 1) &&
532 ((gap_mark
== FT_GAPSTART
) ||
533 (gap_mark
== FT_GAPEND
))) {
534 Gap_Struct
*gap
= NULL
;
535 unsigned long int gapkey
;
536 if (newtag
.GetName().Mid(1).ToULong(&gapkey
)) {
537 if ( gap_map
.find( gapkey
) == gap_map
.end() ) {
538 gap
= new Gap_Struct
;
539 gap_map
[gapkey
] = gap
;
540 gap
->start
= (uint64
)-1;
541 gap
->end
= (uint64
)-1;
543 gap
= gap_map
[ gapkey
];
545 if (gap_mark
== FT_GAPSTART
) {
546 gap
->start
= newtag
.GetInt();
548 if (gap_mark
== FT_GAPEND
) {
549 gap
->end
= newtag
.GetInt()-1;
552 printf("Wrong gap map key while reading met file!\n");
555 // End Changes by Slugfiller for better exception handling
557 m_taglist
.push_back(newtag
);
562 // Nothing. Else, nothing.
566 // load the hashsets from the hybridstylepartmet
567 if (isnewstyle
&& !getsizeonly
&& (metFile
.GetPosition()<metFile
.GetLength()) ) {
568 metFile
.Seek(1, wxFromCurrent
);
570 uint16 parts
=GetPartCount(); // assuming we will get all hashsets
572 for (uint16 i
= 0; i
< parts
&& (metFile
.GetPosition()+16<metFile
.GetLength()); ++i
){
573 CMD4Hash cur_hash
= metFile
.ReadHash();
574 m_hashlist
.push_back(cur_hash
);
578 if (!m_hashlist
.empty()) {
579 CreateHashFromHashlist(m_hashlist
, &checkhash
);
582 if (m_abyFileHash
== checkhash
) {
589 } catch (const CInvalidPacket
& e
) {
590 AddLogLineM(true, CFormat(wxT("Error: %s (%s) is corrupt (bad tags: %s), unable to load file."))
595 } catch (const CIOFailureException
& e
) {
596 AddDebugLogLineM(true, logPartFile
, CFormat( wxT("IO failure while loading '%s': %s") )
600 } catch (const CEOFException
& WXUNUSED(e
)) {
601 AddLogLineM(true, CFormat( _("Error: %s (%s) is corrupt (wrong tagcount), unable to load file.") )
604 AddLogLineM(true, _("Trying to recover file info..."));
606 // Safe file is that who have
609 // We have filesize, try other needed info
611 // Do we need to check gaps? I think not,
612 // because they are checked below. Worst
613 // scenario will only mark file as 0 bytes downloaded.
616 if (!GetFileName().IsOk()) {
617 // Not critical, let's put a random filename.
619 "Recovering no-named file - will try to recover it as RecoveredFile.dat"));
620 SetFileName(CPath(wxT("RecoveredFile.dat")));
624 _("Recovered all available file info :D - Trying to use it..."));
626 AddLogLineM(true, _("Unable to recover file info :("));
634 // Now to flush the map into the list (Slugfiller)
635 std::map
<uint16
, Gap_Struct
*>::iterator it
= gap_map
.begin();
636 for ( ; it
!= gap_map
.end(); ++it
) {
637 Gap_Struct
* gap
= it
->second
;
638 // SLUGFILLER: SafeHash - revised code, and extra safety
639 if ( (gap
->start
!= (uint64
)-1) &&
640 (gap
->end
!= (uint64
)-1) &&
641 gap
->start
<= gap
->end
&&
642 gap
->start
< GetFileSize()) {
643 if (gap
->end
>= GetFileSize()) {
644 gap
->end
= GetFileSize()-1; // Clipping
646 AddGap(gap
->start
, gap
->end
); // All tags accounted for, use safe adding
649 // SLUGFILLER: SafeHash
652 //check if this is a backup
653 if ( m_fullname
.GetExt().MakeLower() == wxT("backup" )) {
654 m_fullname
= m_fullname
.RemoveExt();
657 // open permanent handle
658 CPath partFilePath
= m_fullname
.RemoveExt();
659 if ( !m_hpartfile
.Open(partFilePath
, CFile::read_write
)) {
660 AddLogLineM(false, CFormat( _("Failed to open %s (%s)") )
666 SetPartFileStatus(PS_EMPTY
);
669 // SLUGFILLER: SafeHash - final safety, make sure any missing part of the file is gap
670 if (m_hpartfile
.GetLength() < GetFileSize())
671 AddGap(m_hpartfile
.GetLength(), GetFileSize()-1);
672 // Goes both ways - Partfile should never be too large
673 if (m_hpartfile
.GetLength() > GetFileSize()) {
674 AddDebugLogLineM( true, logPartFile
, CFormat( wxT("Partfile \"%s\" is too large! Truncating %llu bytes.") ) % GetFileName() % (m_hpartfile
.GetLength() - GetFileSize()));
675 m_hpartfile
.SetLength(GetFileSize());
677 // SLUGFILLER: SafeHash
678 } catch (const CIOFailureException
& e
) {
679 AddDebugLogLineM( true, logPartFile
, CFormat( wxT("Error while accessing partfile \"%s\": %s") ) % GetFileName() % e
.what());
680 SetPartFileStatus(PS_ERROR
);
684 // check hashcount, file status etc
685 if (GetHashCount() != GetED2KPartHashCount()){
686 m_hashsetneeded
= true;
689 m_hashsetneeded
= false;
690 for (size_t i
= 0; i
< m_hashlist
.size(); ++i
) {
691 if (IsComplete(i
*PARTSIZE
,((i
+1)*PARTSIZE
)-1)) {
692 SetPartFileStatus(PS_READY
);
697 if (m_gaplist
.empty()) { // is this file complete already?
702 if (!isnewstyle
) { // not for importing
703 const time_t file_date
= CPath::GetModificationTime(partFilePath
);
704 if (m_lastDateChanged
!= file_date
) {
705 // It's pointless to rehash an empty file, since the case
706 // where a user has zero'd a file is handled above ...
707 if (m_hpartfile
.GetLength()) {
708 AddLogLineM(false, CFormat( _("Warning: %s might be corrupted (%i)") )
710 % (m_lastDateChanged
- file_date
) );
712 SetPartFileStatus(PS_WAITINGFORHASH
);
714 CPath partFileName
= m_partmetfilename
.RemoveExt();
715 CThreadScheduler::AddTask(new CHashingTask(m_filePath
, partFileName
, this));
720 UpdateCompletedInfos();
721 if (completedsize
> transferred
) {
722 m_iGainDueToCompression
= completedsize
- transferred
;
723 } else if (completedsize
!= transferred
) {
724 m_iLostDueToCorruption
= transferred
- completedsize
;
731 bool CPartFile::SavePartFile(bool Initial
)
734 case PS_WAITINGFORHASH
:
740 /* Don't write anything to disk if less than 100 KB of free space is left. */
741 sint64 free
= CPath::GetFreeSpaceAt(GetFilePath());
742 if ((free
!= wxInvalidOffset
) && (free
< (100 * 1024))) {
748 if (!m_fullname
.RemoveExt().FileExists()) {
749 throw wxString(wxT(".part file not found"));
752 uint32 lsc
= lastseencomplete
;
755 CPath::BackupFile(m_fullname
, wxT(".backup"));
756 CPath::RemoveFile(m_fullname
);
759 file
.Open(m_fullname
, CFile::write
);
760 if (!file
.IsOpened()) {
761 throw wxString(wxT("Failed to open part.met file"));
765 file
.WriteUInt8(IsLargeFile() ? PARTFILE_VERSION_LARGEFILE
: PARTFILE_VERSION
);
767 file
.WriteUInt32(CPath::GetModificationTime(m_fullname
.RemoveExt()));
769 file
.WriteHash(m_abyFileHash
);
770 uint16 parts
= m_hashlist
.size();
771 file
.WriteUInt16(parts
);
772 for (int x
= 0; x
< parts
; ++x
) {
773 file
.WriteHash(m_hashlist
[x
]);
776 #define FIXED_TAGS 15
777 uint32 tagcount
= m_taglist
.size() + FIXED_TAGS
+ (m_gaplist
.size()*2);
778 if (!m_corrupted_list
.empty()) {
782 if (m_pAICHHashSet
->HasValidMasterHash() && (m_pAICHHashSet
->GetStatus() == AICH_VERIFIED
)){
786 if (GetLastPublishTimeKadSrc()){
790 if (GetLastPublishTimeKadNotes()){
794 if (GetDlActiveTime()){
798 file
.WriteUInt32(tagcount
);
800 //#warning Kry - Where are lost by coruption and gained by compression?
802 // 0 (unicoded part file name)
803 // We write it with BOM to keep eMule compatibility. Note that the 'printable' filename is saved,
804 // as presently the filename does not represent an actual file.
805 CTagString( FT_FILENAME
, GetFileName().GetPrintable()).WriteTagToFile( &file
, utf8strOptBOM
);
806 CTagString( FT_FILENAME
, GetFileName().GetPrintable()).WriteTagToFile( &file
); // 1
808 CTagIntSized( FT_FILESIZE
, GetFileSize(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file
);// 2
809 CTagIntSized( FT_TRANSFERRED
, transferred
, IsLargeFile() ? 64 : 32).WriteTagToFile( &file
); // 3
810 CTagInt32( FT_STATUS
, (m_paused
?1:0)).WriteTagToFile( &file
); // 4
812 if ( IsAutoDownPriority() ) {
813 CTagInt32( FT_DLPRIORITY
, (uint8
)PR_AUTO
).WriteTagToFile( &file
); // 5
814 CTagInt32( FT_OLDDLPRIORITY
, (uint8
)PR_AUTO
).WriteTagToFile( &file
); // 6
816 CTagInt32( FT_DLPRIORITY
, m_iDownPriority
).WriteTagToFile( &file
); // 5
817 CTagInt32( FT_OLDDLPRIORITY
, m_iDownPriority
).WriteTagToFile( &file
); // 6
820 CTagInt32( FT_LASTSEENCOMPLETE
, lsc
).WriteTagToFile( &file
); // 7
822 if ( IsAutoUpPriority() ) {
823 CTagInt32( FT_ULPRIORITY
, (uint8
)PR_AUTO
).WriteTagToFile( &file
); // 8
824 CTagInt32( FT_OLDULPRIORITY
, (uint8
)PR_AUTO
).WriteTagToFile( &file
); // 9
826 CTagInt32( FT_ULPRIORITY
, GetUpPriority() ).WriteTagToFile( &file
); // 8
827 CTagInt32( FT_OLDULPRIORITY
, GetUpPriority() ).WriteTagToFile( &file
); // 9
830 CTagInt32(FT_CATEGORY
, m_category
).WriteTagToFile( &file
); // 10
831 CTagInt32(FT_ATTRANSFERRED
, statistic
.GetAllTimeTransferred() & 0xFFFFFFFF).WriteTagToFile( &file
);// 11
832 CTagInt32(FT_ATTRANSFERREDHI
, statistic
.GetAllTimeTransferred() >>32).WriteTagToFile( &file
);// 12
833 CTagInt32(FT_ATREQUESTED
, statistic
.GetAllTimeRequests()).WriteTagToFile( &file
); // 13
834 CTagInt32(FT_ATACCEPTED
, statistic
.GetAllTimeAccepts()).WriteTagToFile( &file
); // 14
836 // currupt part infos
837 if (!m_corrupted_list
.empty()) {
838 wxString strCorruptedParts
;
839 std::list
<uint16
>::iterator it
= m_corrupted_list
.begin();
840 for (; it
!= m_corrupted_list
.end(); ++it
) {
841 uint16 uCorruptedPart
= *it
;
842 if (!strCorruptedParts
.IsEmpty()) {
843 strCorruptedParts
+= wxT(",");
845 strCorruptedParts
+= wxString::Format(wxT("%u"), (unsigned)uCorruptedPart
);
847 wxASSERT( !strCorruptedParts
.IsEmpty() );
849 CTagString( FT_CORRUPTEDPARTS
, strCorruptedParts
).WriteTagToFile( &file
); // 11?
853 if (m_pAICHHashSet
->HasValidMasterHash() && (m_pAICHHashSet
->GetStatus() == AICH_VERIFIED
)){
854 CTagString
aichtag(FT_AICH_HASH
, m_pAICHHashSet
->GetMasterHash().GetString() );
855 aichtag
.WriteTagToFile(&file
); // 12?
858 if (GetLastPublishTimeKadSrc()){
859 CTagInt32(FT_KADLASTPUBLISHSRC
, GetLastPublishTimeKadSrc()).WriteTagToFile(&file
); // 15?
862 if (GetLastPublishTimeKadNotes()){
863 CTagInt32(FT_KADLASTPUBLISHNOTES
, GetLastPublishTimeKadNotes()).WriteTagToFile(&file
); // 16?
866 if (GetDlActiveTime()){
867 CTagInt32(FT_DL_ACTIVE_TIME
, GetDlActiveTime()).WriteTagToFile(&file
); // 17
870 for (uint32 j
= 0; j
< (uint32
)m_taglist
.size();++j
) {
871 m_taglist
[j
].WriteTagToFile(&file
);
876 std::list
<Gap_Struct
*>::iterator it
= m_gaplist
.begin();
877 for (; it
!= m_gaplist
.end(); ++it
) {
878 wxString tagName
= wxString::Format(wxT(" %u"), i_pos
);
880 // gap start = first missing byte but gap ends = first non-missing byte
881 // in edonkey but I think its easier to user the real limits
882 tagName
[0] = FT_GAPSTART
;
883 CTagIntSized(tagName
, (*it
)->start
, IsLargeFile() ? 64 : 32).WriteTagToFile( &file
);
885 tagName
[0] = FT_GAPEND
;
886 CTagIntSized(tagName
, ((*it
)->end
+ 1), IsLargeFile() ? 64 : 32).WriteTagToFile( &file
);
890 } catch (const wxString
& error
) {
891 AddLogLineM(false, CFormat( _("ERROR while saving partfile: %s (%s ==> %s)") )
896 wxString err
= CFormat( _("ERROR while saving partfile: %s (%s ==> %s)") )
901 printf("%s\n", (const char*)unicode2char(err
));
904 } catch (const CIOFailureException
& e
) {
905 AddDebugLogLineM(true, logPartFile
, wxT("IO failure while saving partfile: ") + e
.what());
906 printf("IO failure while saving partfile: %s\n", (const char*)unicode2char(e
.what()));
914 CPath::RemoveFile(m_fullname
.AppendExt(wxT(".backup")));
917 sint64 metLength
= m_fullname
.GetFileSize();
918 if (metLength
== wxInvalidOffset
) {
919 theApp
->ShowAlert( CFormat( _("Could not retrieve length of '%s' - using %s file.") )
924 CPath::CloneFile(m_fullname
.AppendExt(PARTMET_BAK_EXT
), m_fullname
, true);
925 } else if (metLength
== 0) {
926 // Don't backup if it's 0 size but raise a warning!!!
927 theApp
->ShowAlert( CFormat( _("'%s' is 0 size somehow - using %s file.") )
932 CPath::CloneFile(m_fullname
.AppendExt(PARTMET_BAK_EXT
), m_fullname
, true);
934 // no error, just backup
935 CPath::BackupFile(m_fullname
, PARTMET_BAK_EXT
);
942 void CPartFile::SaveSourceSeeds()
944 #define MAX_SAVED_SOURCES 10
946 // Kry - Sources seeds
947 // Based on a Feature request, this saves the last MAX_SAVED_SOURCES
948 // sources of the file, giving a 'seed' for the next run.
949 // We save the last sources because:
950 // 1 - They could be the hardest to get
951 // 2 - They will more probably be available
952 // However, if we have downloading sources, they have preference because
953 // we probably have more credits on them.
954 // Anyway, source exchange will get us the rest of the sources
955 // This feature is currently used only on rare files (< 20 sources)
958 if (GetSourceCount()>20) {
962 CClientPtrList source_seeds
;
965 CClientPtrList::iterator it
= m_downloadingSourcesList
.begin();
966 for( ; it
!= m_downloadingSourcesList
.end() && n_sources
< MAX_SAVED_SOURCES
; ++it
) {
967 CUpDownClient
*cur_src
= *it
;
968 if (!cur_src
->HasLowID()) {
969 source_seeds
.push_back(cur_src
);
974 if (n_sources
< MAX_SAVED_SOURCES
) {
975 // Not enough downloading sources to fill the list, going to sources list
976 if (GetSourceCount() > 0) {
977 SourceSet::reverse_iterator rit
= m_SrcList
.rbegin();
978 for ( ; ((rit
!= m_SrcList
.rend()) && (n_sources
<MAX_SAVED_SOURCES
)); ++rit
) {
979 CUpDownClient
* cur_src
= *rit
;
980 if (!cur_src
->HasLowID()) {
981 source_seeds
.push_back(cur_src
);
993 const CPath seedsPath
= m_fullname
.AppendExt(wxT(".seeds"));
996 file
.Create(seedsPath
, true);
997 if (!file
.IsOpened()) {
998 AddLogLineM(false, CFormat( _("Failed to save part.met.seeds file for %s") )
1004 file
.WriteUInt8(0); // v3, to avoid v2 clients choking on it.
1005 file
.WriteUInt8(source_seeds
.size());
1007 CClientPtrList::iterator it2
= source_seeds
.begin();
1008 for (; it2
!= source_seeds
.end(); ++it2
) {
1009 CUpDownClient
* cur_src
= *it2
;
1010 file
.WriteUInt32(cur_src
->GetUserIDHybrid());
1011 file
.WriteUInt16(cur_src
->GetUserPort());
1012 file
.WriteHash(cur_src
->GetUserHash());
1013 // CryptSettings - See SourceExchange V4
1014 const uint8 uSupportsCryptLayer
= cur_src
->SupportsCryptLayer() ? 1 : 0;
1015 const uint8 uRequestsCryptLayer
= cur_src
->RequestsCryptLayer() ? 1 : 0;
1016 const uint8 uRequiresCryptLayer
= cur_src
->RequiresCryptLayer() ? 1 : 0;
1017 const uint8 byCryptOptions
= (uRequiresCryptLayer
<< 2) | (uRequestsCryptLayer
<< 1) | (uSupportsCryptLayer
<< 0);
1018 file
.WriteUInt8(byCryptOptions
);
1021 /* v2: Added to keep track of too old seeds */
1022 file
.WriteUInt32(wxDateTime::Now().GetTicks());
1024 AddLogLineM(false, CFormat( wxPLURAL("Saved %i source seed for partfile: %s (%s)", "Saved %i source seeds for partfile: %s (%s)", n_sources
) )
1028 } catch (const CIOFailureException
& e
) {
1029 AddDebugLogLineM(true, logPartFile
, CFormat( wxT("Error saving partfile's seeds file (%s - %s): %s") )
1036 CPath::RemoveFile(seedsPath
);
1040 void CPartFile::LoadSourceSeeds()
1042 CMemFile sources_data
;
1044 bool valid_sources
= false;
1046 const CPath seedsPath
= m_fullname
.AppendExt(wxT(".seeds"));
1047 if (!seedsPath
.FileExists()) {
1051 CFile
file(seedsPath
, CFile::read
);
1052 if (!file
.IsOpened()) {
1053 AddLogLineM(false, CFormat( _("Partfile %s (%s) has no seeds file") )
1061 if (file
.GetLength() <= 1) {
1062 AddLogLineM(false, CFormat( _("Partfile %s (%s) has a void seeds file") )
1068 uint8 src_count
= file
.ReadUInt8();
1070 bool bUseSX2Format
= (src_count
== 0);
1072 if (bUseSX2Format
) {
1074 src_count
= file
.ReadUInt8();
1077 sources_data
.WriteUInt16(src_count
);
1079 for (int i
= 0; i
< src_count
; ++i
) {
1080 uint32 dwID
= file
.ReadUInt32();
1081 uint16 nPort
= file
.ReadUInt16();
1083 sources_data
.WriteUInt32(bUseSX2Format
? dwID
: wxUINT32_SWAP_ALWAYS(dwID
));
1084 sources_data
.WriteUInt16(nPort
);
1085 sources_data
.WriteUInt32(0);
1086 sources_data
.WriteUInt16(0);
1088 if (bUseSX2Format
) {
1089 sources_data
.WriteHash(file
.ReadHash());
1090 sources_data
.WriteUInt8(file
.ReadUInt8());
1097 // v2: Added to keep track of too old seeds
1098 time_t time
= (time_t)file
.ReadUInt32();
1100 // Time frame is 2 hours. More than enough to compile
1101 // your new aMule version!.
1102 if ((time
+ MIN2S(120)) >= wxDateTime::Now().GetTicks()) {
1103 valid_sources
= true;
1107 // v1 has no time data. We can safely use
1108 // the sources, next time will be saved.
1109 valid_sources
= true;
1112 if (valid_sources
) {
1113 sources_data
.Seek(0);
1114 AddClientSources(&sources_data
, SF_SOURCE_SEEDS
, bUseSX2Format
? 4 : 1, bUseSX2Format
);
1117 } catch (const CSafeIOException
& e
) {
1118 AddLogLineM(false, CFormat( _("Error reading partfile's seeds file (%s - %s): %s") )
1127 void CPartFile::PartFileHashFinished(CKnownFile
* result
)
1129 m_lastDateChanged
= result
->m_lastDateChanged
;
1130 bool errorfound
= false;
1131 if (GetED2KPartHashCount() == 0){
1132 if (IsComplete(0, GetFileSize()-1)){
1133 if (result
->GetFileHash() != GetFileHash()){
1136 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1137 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1143 % result
->GetFileHash().Encode()
1144 % GetFileHash().Encode() );
1145 AddGap(0, GetFileSize()-1);
1151 for (size_t i
= 0; i
< m_hashlist
.size(); ++i
){
1152 // Kry - trel_ar's completed parts check on rehashing.
1153 // Very nice feature, if a file is completed but .part.met don't believe it,
1156 if (!( i
< result
->GetHashCount() && (result
->GetPartHash(i
) == GetPartHash(i
)))){
1157 if (IsComplete(i
*PARTSIZE
,((i
+1)*PARTSIZE
)-1)) {
1159 if ( i
< result
->GetHashCount() )
1160 wronghash
= result
->GetPartHash(i
);
1164 "Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
1165 "Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
1166 GetED2KPartHashCount())
1169 % GetED2KPartHashCount()
1171 % wronghash
.Encode()
1172 % GetPartHash(i
).Encode() );
1175 ((uint64
)(((i
+1)*PARTSIZE
)-1) >= GetFileSize()) ?
1176 GetFileSize()-1 : ((i
+1)*PARTSIZE
)-1);
1180 if (!IsComplete(i
*PARTSIZE
,((i
+1)*PARTSIZE
)-1)){
1181 AddLogLineM(false, CFormat( _("Found completed part (%i) in %s") )
1186 ((uint64
)(((i
+1)*PARTSIZE
)-1) >= GetFileSize()) ?
1187 GetFileSize()-1 : ((i
+1)*PARTSIZE
)-1);
1188 RemoveBlockFromList(i
*PARTSIZE
,
1189 ((uint64
)(((i
+1)*PARTSIZE
)-1) >= GetFileSize()) ?
1190 GetFileSize()-1 : ((i
+1)*PARTSIZE
)-1);
1197 result
->GetAICHHashset()->GetStatus() == AICH_HASHSETCOMPLETE
&&
1198 status
== PS_COMPLETING
) {
1199 delete m_pAICHHashSet
;
1200 m_pAICHHashSet
= result
->GetAICHHashset();
1201 result
->SetAICHHashset(NULL
);
1202 m_pAICHHashSet
->SetOwner(this);
1204 else if (status
== PS_COMPLETING
) {
1205 AddDebugLogLineM(false, logPartFile
,
1206 CFormat(wxT("Failed to store new AICH Hashset for completed file: %s"))
1213 if (status
== PS_COMPLETING
){
1218 AddLogLineM(false, CFormat( _("Finished rehashing %s") ) % GetFileName());
1222 SetStatus(PS_READY
);
1226 SetStatus(PS_READY
);
1228 theApp
->sharedfiles
->SafeAddKFile(this);
1231 void CPartFile::AddGap(uint64 start
, uint64 end
)
1233 std::list
<Gap_Struct
*>::iterator it
= m_gaplist
.begin();
1234 while (it
!= m_gaplist
.end()) {
1235 std::list
<Gap_Struct
*>::iterator it2
= it
++;
1236 Gap_Struct
* cur_gap
= *it2
;
1238 if (cur_gap
->start
>= start
&& cur_gap
->end
<= end
) {
1239 // this gap is inside the new gap - delete
1240 m_gaplist
.erase(it2
);
1243 } else if (cur_gap
->start
>= start
&& cur_gap
->start
<= end
+ 1) {
1244 // head of this gap is in the new gap, or this gap is
1245 // directly behind the new gap - extend limit and delete
1247 m_gaplist
.erase(it2
);
1250 } else if (cur_gap
->end
<= end
&& cur_gap
->end
>= start
- 1) {
1251 // tail of this gap is in the new gap, or this gap is
1252 // directly before the new gap - extend limit and delete
1253 start
= cur_gap
->start
;
1254 m_gaplist
.erase(it2
);
1257 } else if (start
>= cur_gap
->start
&& end
<= cur_gap
->end
){
1258 // new gap is already inside this gap - return
1260 // now all cases of overlap are ruled out
1261 } else if (cur_gap
->start
> start
) {
1262 // this gap is the first behind the new gap -> insert before it
1268 Gap_Struct
* new_gap
= new Gap_Struct
;
1269 new_gap
->start
= start
;
1271 m_gaplist
.insert(it
, new_gap
);
1272 UpdateDisplayedInfo();
1275 bool CPartFile::IsAlreadyRequested(uint64 start
, uint64 end
)
1277 std::list
<Requested_Block_Struct
*>::iterator it
= m_requestedblocks_list
.begin();
1278 for (; it
!= m_requestedblocks_list
.end(); ++it
) {
1279 Requested_Block_Struct
* cur_block
= *it
;
1281 if ((start
<= cur_block
->EndOffset
) && (end
>= cur_block
->StartOffset
)) {
1288 bool CPartFile::GetNextEmptyBlockInPart(uint16 partNumber
, Requested_Block_Struct
*result
)
1290 Gap_Struct
*firstGap
;
1291 Gap_Struct
*currentGap
;
1295 // Find start of this part
1296 uint64 partStart
= (PARTSIZE
* partNumber
);
1297 uint64 start
= partStart
;
1299 // What is the end limit of this block, i.e. can't go outside part (or filesize)
1300 uint64 partEnd
= (PARTSIZE
* (partNumber
+ 1)) - 1;
1301 if (partEnd
>= GetFileSize()) {
1302 partEnd
= GetFileSize() - 1;
1304 // Loop until find a suitable gap and return true, or no more gaps and return false
1308 // Find the first gap from the start position
1309 std::list
<Gap_Struct
*>::iterator it
= m_gaplist
.begin();
1310 for (; it
!= m_gaplist
.end(); ++it
) {
1313 // Want gaps that overlap start<->partEnd
1314 if ((currentGap
->start
<= partEnd
) && (currentGap
->end
>= start
)) {
1315 // Is this the first gap?
1316 if ((firstGap
== NULL
) || (currentGap
->start
< firstGap
->start
)) {
1317 firstGap
= currentGap
;
1322 // If no gaps after start, exit
1323 if (firstGap
== NULL
) {
1326 // Update start position if gap starts after current pos
1327 if (start
< firstGap
->start
) {
1328 start
= firstGap
->start
;
1330 // If this is not within part, exit
1331 if (start
> partEnd
) {
1334 // Find end, keeping within the max block size and the part limit
1335 end
= firstGap
->end
;
1336 blockLimit
= partStart
+ (BLOCKSIZE
* (((start
- partStart
) / BLOCKSIZE
) + 1)) - 1;
1337 if (end
> blockLimit
) {
1340 if (end
> partEnd
) {
1343 // If this gap has not already been requested, we have found a valid entry
1344 if (!IsAlreadyRequested(start
, end
)) {
1345 // Was this block to be returned
1346 if (result
!= NULL
) {
1347 result
->StartOffset
= start
;
1348 result
->EndOffset
= end
;
1349 md4cpy(result
->FileID
, GetFileHash().GetHash());
1350 result
->transferred
= 0;
1354 // Reposition to end of that gap
1357 // If tried all gaps then break out of the loop
1358 if (end
== partEnd
) {
1362 // No suitable gap found
1367 void CPartFile::FillGap(uint64 start
, uint64 end
)
1369 std::list
<Gap_Struct
*>::iterator it
= m_gaplist
.begin();
1370 while (it
!= m_gaplist
.end()) {
1371 std::list
<Gap_Struct
*>::iterator it2
= it
++;
1372 Gap_Struct
* cur_gap
= *it2
;
1374 if (cur_gap
->start
>= start
&& cur_gap
->end
<= end
) {
1375 // our part fills this gap completly
1376 m_gaplist
.erase(it2
);
1379 } else if (cur_gap
->start
>= start
&& cur_gap
->start
<= end
) {
1380 // a part of this gap is in the part - set limit
1381 cur_gap
->start
= end
+1;
1382 } else if (cur_gap
->end
<= end
&& cur_gap
->end
>= start
) {
1383 // a part of this gap is in the part - set limit
1384 cur_gap
->end
= start
-1;
1385 } else if (start
>= cur_gap
->start
&& end
<= cur_gap
->end
) {
1386 uint64 buffer
= cur_gap
->end
;
1387 cur_gap
->end
= start
-1;
1388 cur_gap
= new Gap_Struct
;
1389 cur_gap
->start
= end
+1;
1390 cur_gap
->end
= buffer
;
1391 m_gaplist
.insert(++it2
, cur_gap
);
1395 UpdateCompletedInfos();
1396 UpdateDisplayedInfo();
1400 void CPartFile::UpdateCompletedInfos()
1404 std::list
<Gap_Struct
*>::iterator it
= m_gaplist
.begin();
1405 for (; it
!= m_gaplist
.end(); ) {
1406 std::list
<Gap_Struct
*>::iterator it2
= it
++;
1407 Gap_Struct
* cur_gap
= *it2
;
1409 if ((cur_gap
->end
> GetFileSize()) || (cur_gap
->start
>= GetFileSize())) {
1410 m_gaplist
.erase(it2
);
1412 allgaps
+= cur_gap
->end
- cur_gap
->start
+ 1;
1416 if ((!m_gaplist
.empty()) || (!m_requestedblocks_list
.empty())) {
1417 percentcompleted
= (1.0f
-(double)allgaps
/GetFileSize()) * 100;
1418 completedsize
= GetFileSize() - allgaps
;
1420 percentcompleted
= 100;
1421 completedsize
= GetFileSize();
1426 void CPartFile::WritePartStatus(CMemFile
* file
)
1428 uint16 parts
= GetED2KPartCount();
1429 file
->WriteUInt16(parts
);
1431 while (done
!= parts
){
1433 for (uint32 i
= 0;i
!= 8;++i
) {
1434 if (IsComplete(done
*PARTSIZE
,((done
+1)*PARTSIZE
)-1)) {
1438 if (done
== parts
) {
1442 file
->WriteUInt8(towrite
);
1446 void CPartFile::WriteCompleteSourcesCount(CMemFile
* file
)
1448 file
->WriteUInt16(m_nCompleteSourcesCount
);
1451 uint32
CPartFile::Process(uint32 reducedownload
/*in percent*/,uint8 m_icounter
)
1454 uint32 dwCurTick
= ::GetTickCount();
1456 // If buffer size exceeds limit, or if not written within time limit, flush data
1457 if ( (m_nTotalBufferData
> thePrefs::GetFileBufferSize()) ||
1458 (dwCurTick
> (m_nLastBufferFlushTime
+ BUFFER_TIME_LIMIT
))) {
1459 // Avoid flushing while copying preview file
1460 if (!m_bPreviewing
) {
1466 // check if we want new sources from server --> MOVED for 16.40 version
1467 old_trans
=transferingsrc
;
1471 if (m_icounter
< 10) {
1472 // Update only downloading sources.
1473 CClientPtrList::iterator it
= m_downloadingSourcesList
.begin();
1474 for( ; it
!= m_downloadingSourcesList
.end(); ) {
1475 CUpDownClient
*cur_src
= *it
++;
1476 if(cur_src
->GetDownloadState() == DS_DOWNLOADING
) {
1478 kBpsDown
+= cur_src
->SetDownloadLimit(reducedownload
);
1482 // Update all sources (including downloading sources)
1483 for ( SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end(); ) {
1484 CUpDownClient
* cur_src
= *it
++;
1485 switch (cur_src
->GetDownloadState()) {
1486 case DS_DOWNLOADING
: {
1488 kBpsDown
+= cur_src
->SetDownloadLimit(reducedownload
);
1497 case DS_LOWTOLOWIP
: {
1498 if ( cur_src
->HasLowID() && !theApp
->DoCallback( cur_src
) ) {
1499 // If we are almost maxed on sources,
1500 // slowly remove these client to see
1501 // if we can find a better source.
1502 if( ((dwCurTick
- lastpurgetime
) > 30000) &&
1503 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8))) {
1504 RemoveSource( cur_src
);
1505 lastpurgetime
= dwCurTick
;
1509 cur_src
->SetDownloadState(DS_ONQUEUE
);
1514 case DS_NONEEDEDPARTS
: {
1515 // we try to purge noneeded source, even without reaching the limit
1516 if((dwCurTick
- lastpurgetime
) > 40000) {
1517 if(!cur_src
->SwapToAnotherFile(false , false, false , NULL
)) {
1518 //however we only delete them if reaching the limit
1519 if (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) {
1520 RemoveSource(cur_src
);
1521 lastpurgetime
= dwCurTick
;
1522 break; //Johnny-B - nothing more to do here (good eye!)
1525 lastpurgetime
= dwCurTick
;
1529 // doubled reasktime for no needed parts - save connections and traffic
1530 if ( !((!cur_src
->GetLastAskedTime()) ||
1531 (dwCurTick
- cur_src
->GetLastAskedTime()) > FILEREASKTIME
*2)) {
1534 // Recheck this client to see if still NNP..
1535 // Set to DS_NONE so that we force a TCP reask next time..
1536 cur_src
->SetDownloadState(DS_NONE
);
1541 if( cur_src
->IsRemoteQueueFull()) {
1542 if( ((dwCurTick
- lastpurgetime
) > 60000) &&
1543 (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) ) {
1544 RemoveSource( cur_src
);
1545 lastpurgetime
= dwCurTick
;
1546 break; //Johnny-B - nothing more to do here (good eye!)
1550 // Give up to 1 min for UDP to respond..
1551 // If we are within on min on TCP, do not try..
1552 if ( theApp
->IsConnected() &&
1553 ( (!cur_src
->GetLastAskedTime()) ||
1554 (dwCurTick
- cur_src
->GetLastAskedTime()) > FILEREASKTIME
-20000)) {
1555 cur_src
->UDPReaskForDownload();
1558 // No break here, since the next case takes care of asking for downloads.
1561 case DS_TOOMANYCONNS
:
1563 case DS_WAITCALLBACK
:
1564 case DS_WAITCALLBACKKAD
: {
1565 if ( theApp
->IsConnected() &&
1566 ( (!cur_src
->GetLastAskedTime()) ||
1567 (dwCurTick
- cur_src
->GetLastAskedTime()) > FILEREASKTIME
)) {
1568 if (!cur_src
->AskForDownload()) {
1569 // I left this break here just as a reminder
1570 // just in case re rearange things..
1579 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
1580 if (IsA4AFAuto() && ((!m_LastNoNeededCheck
) || (dwCurTick
- m_LastNoNeededCheck
> 900000))) {
1581 m_LastNoNeededCheck
= dwCurTick
;
1582 for ( SourceSet::iterator it
= m_A4AFsrclist
.begin(); it
!= m_A4AFsrclist
.end(); ) {
1583 CUpDownClient
*cur_source
= *it
++;
1584 uint8 download_state
=cur_source
->GetDownloadState();
1585 if( download_state
!= DS_DOWNLOADING
1586 && cur_source
->GetRequestFile()
1587 && ((!cur_source
->GetRequestFile()->IsA4AFAuto()) || download_state
== DS_NONEEDEDPARTS
))
1589 cur_source
->SwapToAnotherFile(false, false, false, this);
1593 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
1595 // swap No needed partfiles if possible
1597 if (((old_trans
==0) && (transferingsrc
>0)) || ((old_trans
>0) && (transferingsrc
==0))) {
1598 SetPartFileStatus(status
);
1601 // Kad source search
1602 if( GetMaxSourcePerFileUDP() > GetSourceCount()){
1603 //Once we can handle lowID users in Kad, we remove the second IsConnected
1604 if (theApp
->downloadqueue
->DoKademliaFileRequest() && (Kademlia::CKademlia::GetTotalFile() < KADEMLIATOTALFILE
) && (dwCurTick
> m_LastSearchTimeKad
) && Kademlia::CKademlia::IsConnected() && theApp
->IsConnected() && !IsStopped()){
1606 theApp
->downloadqueue
->SetLastKademliaFileRequest();
1608 if (GetKadFileSearchID()) {
1609 /* This will never happen anyway. We're talking a
1610 1h timespan and searches are at max 45secs */
1611 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1614 Kademlia::CUInt128
kadFileID(GetFileHash().GetHash());
1615 Kademlia::CSearch
* pSearch
= Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FILE, true, kadFileID
);
1616 AddDebugLogLineM(false, logKadSearch
, CFormat(wxT("Preparing a Kad Search for '%s'")) % GetFileName());
1618 AddDebugLogLineM(false, logKadSearch
, CFormat(wxT("Kad lookup started for '%s'")) % GetFileName());
1619 if(m_TotalSearchesKad
< 7) {
1620 m_TotalSearchesKad
++;
1622 m_LastSearchTimeKad
= dwCurTick
+ (KADEMLIAREASKTIME
*m_TotalSearchesKad
);
1623 SetKadFileSearchID(pSearch
->GetSearchID());
1627 if(GetKadFileSearchID()) {
1628 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
1632 // check if we want new sources from server
1633 if ( !m_localSrcReqQueued
&&
1634 ( (!m_lastsearchtime
) ||
1635 (dwCurTick
- m_lastsearchtime
) > SERVERREASKTIME
) &&
1636 theApp
->IsConnectedED2K() &&
1637 thePrefs::GetMaxSourcePerFileSoft() > GetSourceCount() &&
1639 m_localSrcReqQueued
= true;
1640 theApp
->downloadqueue
->SendLocalSrcRequest(this);
1643 // calculate datarate, set limit etc.
1648 // Kry - does the 3 / 30 difference produce too much flickering or CPU?
1649 if (m_count
>= 30) {
1651 UpdateAutoDownPriority();
1652 UpdateDisplayedInfo();
1653 if(m_bPercentUpdated
== false) {
1654 UpdateCompletedInfos();
1656 m_bPercentUpdated
= false;
1657 if (thePrefs::ShowCatTabInfos()) {
1658 Notify_ShowUpdateCatTabTitles();
1662 return (uint32
)(kBpsDown
*1024.0);
1665 bool CPartFile::CanAddSource(uint32 userid
, uint16 port
, uint32 serverip
, uint16 serverport
, uint8
* pdebug_lowiddropped
, bool ed2kID
)
1668 //The incoming ID could have the userid in the Hybrid format..
1669 uint32 hybridID
= 0;
1671 if (IsLowID(userid
)) {
1674 hybridID
= wxUINT32_SWAP_ALWAYS(userid
);
1678 if (!IsLowID(userid
)) {
1679 userid
= wxUINT32_SWAP_ALWAYS(userid
);
1683 // MOD Note: Do not change this part - Merkur
1684 if (theApp
->IsConnectedED2K()) {
1685 if(::IsLowID(theApp
->GetED2KID())) {
1686 if(theApp
->GetED2KID() == userid
&& theApp
->serverconnect
->GetCurrentServer()->GetIP() == serverip
&& theApp
->serverconnect
->GetCurrentServer()->GetPort() == serverport
) {
1689 if(theApp
->GetPublicIP() == userid
) {
1693 if(theApp
->GetED2KID() == userid
&& thePrefs::GetPort() == port
) {
1699 if (Kademlia::CKademlia::IsConnected()) {
1700 if(!Kademlia::CKademlia::IsFirewalled()) {
1701 if(Kademlia::CKademlia::GetIPAddress() == hybridID
&& thePrefs::GetPort() == port
) {
1707 //This allows *.*.*.0 clients to not be removed if Ed2kID == false
1708 if ( IsLowID(hybridID
) && theApp
->IsFirewalled()) {
1709 if (pdebug_lowiddropped
) {
1710 (*pdebug_lowiddropped
)++;
1718 void CPartFile::AddSources(CMemFile
& sources
,uint32 serverip
, uint16 serverport
, unsigned origin
, bool bWithObfuscationAndHash
)
1720 uint8 count
= sources
.ReadUInt8();
1721 uint8 debug_lowiddropped
= 0;
1722 uint8 debug_possiblesources
= 0;
1723 CMD4Hash achUserHash
;
1726 // since we may received multiple search source UDP results we have to "consume" all data of that packet
1727 AddDebugLogLineM(false, logPartFile
, wxT("Trying to add sources for a stopped file"));
1728 sources
.Seek(count
*(4+2), wxFromCurrent
);
1732 for (int i
= 0;i
!= count
;++i
) {
1733 uint32 userid
= sources
.ReadUInt32();
1734 uint16 port
= sources
.ReadUInt16();
1736 uint8 byCryptOptions
= 0;
1737 if (bWithObfuscationAndHash
){
1738 byCryptOptions
= sources
.ReadUInt8();
1739 if ((byCryptOptions
& 0x80) > 0) {
1740 achUserHash
= sources
.ReadHash();
1743 if ((thePrefs::IsClientCryptLayerRequested() && (byCryptOptions
& 0x01/*supported*/) > 0 && (byCryptOptions
& 0x80) == 0)
1744 || (thePrefs::IsClientCryptLayerSupported() && (byCryptOptions
& 0x02/*requested*/) > 0 && (byCryptOptions
& 0x80) == 0)) {
1745 AddDebugLogLineM(false, logPartFile
, wxString::Format(wxT("Server didn't provide UserHash for source %u, even if it was expected to (or local obfuscationsettings changed during serverconnect"), userid
));
1746 } else if (!thePrefs::IsClientCryptLayerRequested() && (byCryptOptions
& 0x02/*requested*/) == 0 && (byCryptOptions
& 0x80) != 0) {
1747 AddDebugLogLineM(false, logPartFile
, wxString::Format(wxT("Server provided UserHash for source %u, even if it wasn't expected to (or local obfuscationsettings changed during serverconnect"), userid
));
1752 // "Filter LAN IPs" and "IPfilter" the received sources IP addresses
1753 if (!IsLowID(userid
)) {
1754 // check for 0-IP, localhost and optionally for LAN addresses
1755 if ( !IsGoodIP(userid
, thePrefs::FilterLanIPs()) ) {
1758 if (theApp
->ipfilter
->IsFiltered(userid
)) {
1763 if (!CanAddSource(userid
, port
, serverip
, serverport
, &debug_lowiddropped
)) {
1767 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
1768 ++debug_possiblesources
;
1769 CUpDownClient
* newsource
= new CUpDownClient(port
,userid
,serverip
,serverport
,this, true, true);
1771 newsource
->SetSourceFrom((ESourceFrom
)origin
);
1772 newsource
->SetConnectOptions(byCryptOptions
, true, false);
1774 if ((byCryptOptions
& 0x80) != 0) {
1775 newsource
->SetUserHash(achUserHash
);
1778 theApp
->downloadqueue
->CheckAndAddSource(this,newsource
);
1780 AddDebugLogLineM(false, logPartFile
, wxT("Consuming a packet because of max sources reached"));
1781 // Since we may receive multiple search source UDP results we have to "consume" all data of that packet
1782 // This '+1' is added because 'i' counts from 0.
1783 sources
.Seek((count
-(i
+1))*(4+2), wxFromCurrent
);
1784 if (GetKadFileSearchID()) {
1785 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
1792 void CPartFile::UpdatePartsInfo()
1794 if( !IsPartFile() ) {
1795 CKnownFile::UpdatePartsInfo();
1800 uint16 partcount
= GetPartCount();
1801 bool flag
= (time(NULL
) - m_nCompleteSourcesTime
> 0);
1803 // Ensure the frequency-list is ready
1804 if ( m_SrcpartFrequency
.size() != GetPartCount() ) {
1805 m_SrcpartFrequency
.clear();
1806 m_SrcpartFrequency
.insert(m_SrcpartFrequency
.begin(), GetPartCount(), 0);
1809 // Find number of available parts
1810 uint16 availablecounter
= 0;
1811 for ( uint16 i
= 0; i
< partcount
; ++i
) {
1812 if ( m_SrcpartFrequency
[i
] )
1816 if ( ( availablecounter
== partcount
) && ( m_availablePartsCount
< partcount
) ) {
1817 lastseencomplete
= time(NULL
);
1820 m_availablePartsCount
= availablecounter
;
1823 ArrayOfUInts16 count
;
1825 count
.reserve(GetSourceCount());
1827 for ( SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end(); ++it
) {
1828 if ( !(*it
)->GetUpPartStatus().empty() && (*it
)->GetUpPartCount() == partcount
) {
1829 count
.push_back((*it
)->GetUpCompleteSourcesCount());
1833 m_nCompleteSourcesCount
= m_nCompleteSourcesCountLo
= m_nCompleteSourcesCountHi
= 0;
1835 for (uint16 i
= 0; i
< partcount
; ++i
) {
1837 m_nCompleteSourcesCount
= m_SrcpartFrequency
[i
];
1839 else if( m_nCompleteSourcesCount
> m_SrcpartFrequency
[i
]) {
1840 m_nCompleteSourcesCount
= m_SrcpartFrequency
[i
];
1843 count
.push_back(m_nCompleteSourcesCount
);
1845 int32 n
= count
.size();
1847 std::sort(count
.begin(), count
.end(), std::less
<uint16
>());
1850 int32 i
= n
>> 1; // (n / 2)
1851 int32 j
= (n
* 3) >> 2; // (n * 3) / 4
1852 int32 k
= (n
* 7) >> 3; // (n * 7) / 8
1854 //When still a part file, adjust your guesses by 20% to what you see..
1858 //Not many sources, so just use what you see..
1859 // welcome to 'plain stupid code'
1860 // m_nCompleteSourcesCount;
1861 m_nCompleteSourcesCountLo
= m_nCompleteSourcesCount
;
1862 m_nCompleteSourcesCountHi
= m_nCompleteSourcesCount
;
1863 } else if (n
< 20) {
1864 // For low guess and normal guess count
1865 // If we see more sources then the guessed low and normal, use what we see.
1866 // If we see less sources then the guessed low, adjust network accounts for 80%,
1867 // we account for 20% with what we see and make sure we are still above the normal.
1869 // Adjust 80% network and 20% what we see.
1870 if ( count
[i
] < m_nCompleteSourcesCount
) {
1871 m_nCompleteSourcesCountLo
= m_nCompleteSourcesCount
;
1873 m_nCompleteSourcesCountLo
=
1874 (uint16
)((float)(count
[i
]*.8) +
1875 (float)(m_nCompleteSourcesCount
*.2));
1877 m_nCompleteSourcesCount
= m_nCompleteSourcesCountLo
;
1878 m_nCompleteSourcesCountHi
=
1879 (uint16
)((float)(count
[j
]*.8) +
1880 (float)(m_nCompleteSourcesCount
*.2));
1881 if( m_nCompleteSourcesCountHi
< m_nCompleteSourcesCount
) {
1882 m_nCompleteSourcesCountHi
= m_nCompleteSourcesCount
;
1890 // Adjust network accounts for 80%, we account for 20% with what
1891 // we see and make sure we are still above the low.
1893 // Adjust network accounts for 80%, we account for 20% with what
1894 // we see and make sure we are still above the normal.
1896 m_nCompleteSourcesCountLo
= m_nCompleteSourcesCount
;
1897 m_nCompleteSourcesCount
= (uint16
)((float)(count
[j
]*.8)+(float)(m_nCompleteSourcesCount
*.2));
1898 if( m_nCompleteSourcesCount
< m_nCompleteSourcesCountLo
) {
1899 m_nCompleteSourcesCount
= m_nCompleteSourcesCountLo
;
1901 m_nCompleteSourcesCountHi
= (uint16
)((float)(count
[k
]*.8)+(float)(m_nCompleteSourcesCount
*.2));
1902 if( m_nCompleteSourcesCountHi
< m_nCompleteSourcesCount
) {
1903 m_nCompleteSourcesCountHi
= m_nCompleteSourcesCount
;
1907 m_nCompleteSourcesTime
= time(NULL
) + (60);
1909 UpdateDisplayedInfo();
1912 // Kry - Updated to 0.42e + bugfix
1913 // [Maella -Enhanced Chunk Selection- (based on jicxicmic)]
1914 bool CPartFile::GetNextRequestedBlock(CUpDownClient
* sender
, Requested_Block_Struct
** newblocks
, uint16
* count
)
1917 // The purpose of this function is to return a list of blocks (~180KB) to
1918 // download. To avoid a prematurely stop of the downloading, all blocks that
1919 // are requested from the same source must be located within the same
1920 // chunk (=> part ~9MB).
1922 // The selection of the chunk to download is one of the CRITICAL parts of the
1923 // edonkey network. The selection algorithm must insure the best spreading
1926 // The selection is based on 4 criteria:
1927 // 1. Frequency of the chunk (availability), very rare chunks must be downloaded
1928 // as quickly as possible to become a new available source.
1929 // 2. Parts used for preview (first + last chunk), preview or check a
1930 // file (e.g. movie, mp3)
1931 // 3. Request state (downloading in process), try to ask each source for another
1932 // chunk. Spread the requests between all sources.
1933 // 4. Completion (shortest-to-complete), partially retrieved chunks should be
1934 // completed before starting to download other one.
1936 // The frequency criterion defines three zones: very rare (<10%), rare (<50%)
1937 // and common (>30%). Inside each zone, the criteria have a specific weight, used
1938 // to calculate the priority of chunks. The chunk(s) with the highest
1939 // priority (highest=0, lowest=0xffff) is/are selected first.
1941 // very rare (preview) rare common
1942 // 0% <---- +0 pt ----> 10% <----- +10000 pt -----> 50% <---- +20000 pt ----> 100%
1943 // 1. <------- frequency: +25*frequency pt ----------->
1944 // 2. <- preview: +1 pt --><-------------- preview: set to 10000 pt ------------->
1945 // 3. <------ request: download in progress +20000 pt ------>
1946 // 4a. <- completion: 0% +100, 25% +75 .. 100% +0 pt --><-- !req => completion --->
1947 // 4b. <--- req => !completion -->
1949 // Unrolled, the priority scale is:
1951 // 0..xxxx unrequested and requested very rare chunks
1952 // 10000..1xxxx unrequested rare chunks + unrequested preview chunks
1953 // 20000..2xxxx unrequested common chunks (priority to the most complete)
1954 // 30000..3xxxx requested rare chunks + requested preview chunks
1955 // 40000..4xxxx requested common chunks (priority to the least complete)
1957 // This algorithm usually selects first the rarest chunk(s). However, partially
1958 // complete chunk(s) that is/are close to completion may overtake the priority
1959 // (priority inversion).
1960 // For the common chuncks, the algorithm tries to spread the dowload between
1964 // Check input parameters
1968 if ( sender
->GetPartStatus().empty() ) {
1971 // Define and create the list of the chunks to download
1972 const uint16 partCount
= GetPartCount();
1973 ChunkList chunksList
;
1976 uint16 newBlockCount
= 0;
1977 while(newBlockCount
!= *count
) {
1978 // Create a request block stucture if a chunk has been previously selected
1979 if(sender
->GetLastPartAsked() != 0xffff) {
1980 Requested_Block_Struct
* pBlock
= new Requested_Block_Struct
;
1981 if(GetNextEmptyBlockInPart(sender
->GetLastPartAsked(), pBlock
) == true) {
1982 // Keep a track of all pending requested blocks
1983 m_requestedblocks_list
.push_back(pBlock
);
1984 // Update list of blocks to return
1985 newblocks
[newBlockCount
++] = pBlock
;
1986 // Skip end of loop (=> CPU load)
1989 // All blocks for this chunk have been already requested
1991 // => Try to select another chunk
1992 sender
->SetLastPartAsked(0xffff);
1996 // Check if a new chunk must be selected (e.g. download starting, previous chunk complete)
1997 if(sender
->GetLastPartAsked() == 0xffff) {
1998 // Quantify all chunks (create list of chunks to download)
1999 // This is done only one time and only if it is necessary (=> CPU load)
2000 if(chunksList
.empty()) {
2001 // Indentify the locally missing part(s) that this source has
2002 for(uint16 i
=0; i
< partCount
; ++i
) {
2003 if(sender
->IsPartAvailable(i
) == true && GetNextEmptyBlockInPart(i
, NULL
) == true) {
2004 // Create a new entry for this chunk and add it to the list
2007 newEntry
.frequency
= m_SrcpartFrequency
[i
];
2008 chunksList
.push_back(newEntry
);
2012 // Check if any bloks(s) could be downloaded
2013 if(chunksList
.empty()) {
2014 break; // Exit main loop while()
2017 // Define the bounds of the three zones (very rare, rare)
2018 // more depending on available sources
2020 if (GetSourceCount()>800) {
2022 } else if (GetSourceCount()>200) {
2025 uint16 limit
= modif
*GetSourceCount()/ 100;
2029 const uint16 veryRareBound
= limit
;
2030 const uint16 rareBound
= 2*limit
;
2032 // Cache Preview state (Criterion 2)
2033 FileType type
= GetFiletype(GetFileName());
2034 const bool isPreviewEnable
=
2035 thePrefs::GetPreviewPrio() &&
2036 (type
== ftArchive
|| type
== ftVideo
);
2038 // Collect and calculate criteria for all chunks
2039 for (ChunkList::iterator it
= chunksList
.begin(); it
!= chunksList
.end(); ++it
) {
2040 Chunk
& cur_chunk
= *it
;
2043 const uint64 uStart
= cur_chunk
.part
* PARTSIZE
;
2045 ((GetFileSize() - 1) < (uStart
+ PARTSIZE
- 1)) ?
2046 (GetFileSize() - 1) : (uStart
+ PARTSIZE
- 1);
2047 // Criterion 2. Parts used for preview
2048 // Remark: - We need to download the first part and the last part(s).
2049 // - When the last part is very small, it's necessary to
2050 // download the two last parts.
2051 bool critPreview
= false;
2052 if(isPreviewEnable
== true) {
2053 if(cur_chunk
.part
== 0) {
2054 critPreview
= true; // First chunk
2055 } else if(cur_chunk
.part
== partCount
-1) {
2056 critPreview
= true; // Last chunk
2057 } else if(cur_chunk
.part
== partCount
-2) {
2058 // Last chunk - 1 (only if last chunk is too small)
2059 const uint32 sizeOfLastChunk
= GetFileSize() - uEnd
;
2060 if(sizeOfLastChunk
< PARTSIZE
/3) {
2061 critPreview
= true; // Last chunk - 1
2066 // Criterion 3. Request state (downloading in process from other source(s))
2068 const bool critRequested
=
2069 cur_chunk
.frequency
> veryRareBound
&&
2070 IsAlreadyRequested(uStart
, uEnd
);
2072 // Criterion 4. Completion
2073 uint64 partSize
= PARTSIZE
;
2075 std::list
<Gap_Struct
*>::iterator it2
= m_gaplist
.begin();
2076 for (; it2
!= m_gaplist
.end(); ++it2
) {
2077 const Gap_Struct
* cur_gap
= *it2
;
2078 // Check if Gap is into the limit
2079 if(cur_gap
->start
< uStart
) {
2080 if(cur_gap
->end
> uStart
&& cur_gap
->end
< uEnd
) {
2081 partSize
-= cur_gap
->end
- uStart
+ 1;
2082 } else if(cur_gap
->end
>= uEnd
) {
2084 break; // exit loop for()
2086 } else if(cur_gap
->start
<= uEnd
) {
2087 if(cur_gap
->end
< uEnd
) {
2088 partSize
-= cur_gap
->end
- cur_gap
->start
+ 1;
2090 partSize
-= uEnd
- cur_gap
->start
+ 1;
2094 const uint16 critCompletion
= (uint16
)(partSize
/(PARTSIZE
/100)); // in [%]
2096 // Calculate priority with all criteria
2097 if(cur_chunk
.frequency
<= veryRareBound
) {
2098 // 0..xxxx unrequested + requested very rare chunks
2099 cur_chunk
.rank
= (25 * cur_chunk
.frequency
) + // Criterion 1
2100 ((critPreview
== true) ? 0 : 1) + // Criterion 2
2101 (100 - critCompletion
); // Criterion 4
2102 } else if(critPreview
== true) {
2103 // 10000..10100 unrequested preview chunks
2104 // 30000..30100 requested preview chunks
2105 cur_chunk
.rank
= ((critRequested
== false) ? 10000 : 30000) + // Criterion 3
2106 (100 - critCompletion
); // Criterion 4
2107 } else if(cur_chunk
.frequency
<= rareBound
) {
2108 // 10101..1xxxx unrequested rare chunks
2109 // 30101..3xxxx requested rare chunks
2110 cur_chunk
.rank
= (25 * cur_chunk
.frequency
) + // Criterion 1
2111 ((critRequested
== false) ? 10101 : 30101) + // Criterion 3
2112 (100 - critCompletion
); // Criterion 4
2115 if(critRequested
== false) { // Criterion 3
2116 // 20000..2xxxx unrequested common chunks
2117 cur_chunk
.rank
= 20000 + // Criterion 3
2118 (100 - critCompletion
); // Criterion 4
2120 // 40000..4xxxx requested common chunks
2121 // Remark: The weight of the completion criterion is inversed
2122 // to spead the requests over the completing chunks.
2123 // Without this, the chunk closest to completion will
2124 // received every new sources.
2125 cur_chunk
.rank
= 40000 + // Criterion 3
2126 (critCompletion
); // Criterion 4
2132 // Select the next chunk to download
2133 if(!chunksList
.empty()) {
2134 // Find and count the chunck(s) with the highest priority
2135 uint16 chunkCount
= 0; // Number of found chunks with same priority
2136 uint16 rank
= 0xffff; // Highest priority found
2138 // Collect and calculate criteria for all chunks
2139 for (ChunkList::iterator it
= chunksList
.begin(); it
!= chunksList
.end(); ++it
) {
2140 const Chunk
& cur_chunk
= *it
;
2141 if(cur_chunk
.rank
< rank
) {
2143 rank
= cur_chunk
.rank
;
2144 } else if(cur_chunk
.rank
== rank
) {
2149 // Use a random access to avoid that everybody tries to download the
2150 // same chunks at the same time (=> spread the selected chunk among clients)
2151 uint16 randomness
= 1 + (int) (((float)(chunkCount
-1))*rand()/(RAND_MAX
+1.0));
2153 for (ChunkList::iterator it
= chunksList
.begin(); it
!= chunksList
.end(); ++it
) {
2154 const Chunk
& cur_chunk
= *it
;
2155 if(cur_chunk
.rank
== rank
) {
2157 if(randomness
== 0) {
2158 // Selection process is over
2159 sender
->SetLastPartAsked(cur_chunk
.part
);
2160 // Remark: this list might be reused up to *count times
2161 chunksList
.erase(it
);
2162 break; // exit loop for()
2167 // There is no remaining chunk to download
2168 break; // Exit main loop while()
2172 // Return the number of the blocks
2173 *count
= newBlockCount
;
2175 return (newBlockCount
> 0);
2181 void CPartFile::RemoveBlockFromList(uint64 start
,uint64 end
)
2183 std::list
<Requested_Block_Struct
*>::iterator it
= m_requestedblocks_list
.begin();
2184 while (it
!= m_requestedblocks_list
.end()) {
2185 std::list
<Requested_Block_Struct
*>::iterator it2
= it
++;
2187 if ((*it2
)->StartOffset
<= start
&& (*it2
)->EndOffset
>= end
) {
2188 m_requestedblocks_list
.erase(it2
);
2194 void CPartFile::RemoveAllRequestedBlocks(void)
2196 m_requestedblocks_list
.clear();
2200 void CPartFile::CompleteFile(bool bIsHashingDone
)
2202 if (GetKadFileSearchID()) {
2203 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
2206 theApp
->downloadqueue
->RemoveLocalServerRequest(this);
2208 AddDebugLogLineM( false, logPartFile
, wxString( wxT("CPartFile::CompleteFile: Hash ") ) + ( bIsHashingDone
? wxT("done") : wxT("not done") ) );
2210 if (!bIsHashingDone
) {
2211 SetPartFileStatus(PS_COMPLETING
);
2214 CPath partFile
= m_partmetfilename
.RemoveExt();
2215 CThreadScheduler::AddTask(new CHashingTask(GetFilePath(), partFile
, this));
2219 m_is_A4AF_auto
=false;
2220 SetPartFileStatus(PS_COMPLETING
);
2221 // guess I was wrong about not need to spaw a thread ...
2222 // It is if the temp and incoming dirs are on different
2223 // partitions/drives and the file is large...[oz]
2226 PerformFileComplete();
2230 if (thePrefs::ShowCatTabInfos()) {
2231 Notify_ShowUpdateCatTabTitles();
2233 UpdateDisplayedInfo(true);
2237 void CPartFile::CompleteFileEnded(bool errorOccured
, const CPath
& newname
)
2241 SetPartFileStatus(PS_ERROR
);
2242 AddLogLineM(true, CFormat( _("Unexpected file error while completing %s. File paused") )% GetFileName() );
2244 m_fullname
= newname
;
2246 SetFilePath(m_fullname
.GetPath());
2247 SetFileName(m_fullname
.GetFullName());
2249 SetPartFileStatus(PS_COMPLETE
);
2253 // TODO: What the f*** if it is already known?
2254 theApp
->knownfiles
->SafeAddKFile(this);
2256 // remove the file from the suspended uploads list
2257 theApp
->uploadqueue
->ResumeUpload(GetFileHash());
2258 theApp
->downloadqueue
->RemoveFile(this);
2259 theApp
->sharedfiles
->SafeAddKFile(this);
2260 UpdateDisplayedInfo(true);
2262 // republish that file to the ed2k-server to update the 'FT_COMPLETE_SOURCES' counter on the server.
2263 theApp
->sharedfiles
->RepublishFile(this);
2265 // Ensure that completed shows the correct value
2266 completedsize
= GetFileSize();
2268 AddLogLineM(true, CFormat( _("Finished downloading: %s") ) % GetFileName() );
2271 theApp
->downloadqueue
->StartNextFile(this);
2275 void CPartFile::PerformFileComplete()
2277 // add this file to the suspended uploads list
2278 theApp
->uploadqueue
->SuspendUpload(GetFileHash());
2281 // close permanent handle
2282 if (m_hpartfile
.IsOpened()) {
2283 m_hpartfile
.Close();
2286 // Schedule task for completion of the file
2287 CThreadScheduler::AddTask(new CCompletionTask(this));
2291 void CPartFile::RemoveAllSources(bool bTryToSwap
)
2293 for( SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end();) {
2294 CUpDownClient
* cur_src
= *it
++;
2296 if (!cur_src
->SwapToAnotherFile(true, true, true, NULL
)) {
2297 RemoveSource(cur_src
,true,false);
2298 // If it was not swapped, it's not on any file anymore, and should die
2301 RemoveSource(cur_src
,true,false);
2307 /* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
2308 // remove all links A4AF in sources to this file
2309 if(!m_A4AFsrclist
.empty()) {
2310 for( SourceSet::iterator it
= m_A4AFsrclist
.begin(); it
!= m_A4AFsrclist
.end(); ) {
2311 CUpDownClient
* cur_src
= *it
++;
2312 if ( cur_src
->DeleteFileRequest( this ) ) {
2313 Notify_DownloadCtrlRemoveSource(cur_src
, this);
2316 m_A4AFsrclist
.clear();
2318 /* eMule 0.30c implementation, i give it a try (Creteil) END ... */
2319 UpdateFileRatingCommentAvail();
2323 void CPartFile::Delete()
2325 AddLogLineM(false, CFormat(_("Deleting file: %s")) % GetFileName());
2326 // Barry - Need to tell any connected clients to stop sending the file
2328 AddDebugLogLineM(false, logPartFile
, wxT("\tStopped"));
2330 theApp
->sharedfiles
->RemoveFile(this);
2331 AddDebugLogLineM(false, logPartFile
, wxT("\tRemoved from shared"));
2332 theApp
->downloadqueue
->RemoveFile(this);
2333 AddDebugLogLineM(false, logPartFile
, wxT("\tRemoved from download queue"));
2334 Notify_DownloadCtrlRemoveFile(this);
2335 AddDebugLogLineM(false, logPartFile
, wxT("\tRemoved transferwnd"));
2338 // eMule had same problem with lseek error ... and override with a simple
2339 // check for INVALID_HANDLE_VALUE (that, btw, does not exist on linux)
2340 // So we just guess is < 0 on error and > 2 if ok (0 stdin, 1 stdout, 2 stderr)
2341 if (m_hpartfile
.fd() > 2) { // 0 stdin, 1 stdout, 2 stderr
2342 m_hpartfile
.Close();
2345 AddDebugLogLineM(false, logPartFile
, wxT("\tClosed"));
2347 if (!CPath::RemoveFile(m_fullname
)) {
2348 AddDebugLogLineM(true, logPartFile
, CFormat(wxT("\tFailed to delete '%s'")) % m_fullname
);
2350 AddDebugLogLineM(false, logPartFile
, wxT("\tRemoved .part.met"));
2353 CPath partFile
= m_fullname
.RemoveExt();
2354 if (!CPath::RemoveFile(partFile
)) {
2355 AddDebugLogLineM(true, logPartFile
, CFormat(wxT("Failed to delete '%s'")) % partFile
);
2357 AddDebugLogLineM(false, logPartFile
, wxT("\tRemoved .part"));
2360 CPath BAKName
= m_fullname
.AppendExt(PARTMET_BAK_EXT
);
2361 if (!CPath::RemoveFile(BAKName
)) {
2362 AddDebugLogLineM(true, logPartFile
, CFormat(wxT("Failed to delete '%s'")) % BAKName
);
2364 AddDebugLogLineM(false, logPartFile
, wxT("\tRemoved .BAK"));
2367 CPath SEEDSName
= m_fullname
.AppendExt(wxT(".seeds"));
2368 if (SEEDSName
.FileExists()) {
2369 if (CPath::RemoveFile(SEEDSName
)) {
2370 AddDebugLogLineM(false, logPartFile
, wxT("\tRemoved .seeds"));
2372 AddDebugLogLineM(true, logPartFile
, CFormat(wxT("Failed to delete '%s'")) % SEEDSName
);
2376 AddDebugLogLineM(false, logPartFile
, wxT("Done"));
2382 bool CPartFile::HashSinglePart(uint16 partnumber
)
2384 if ((GetHashCount() <= partnumber
) && (GetPartCount() > 1)) {
2386 CFormat( _("Warning: Unable to hash downloaded part - hashset incomplete for '%s'") )
2388 m_hashsetneeded
= true;
2390 } else if ((GetHashCount() <= partnumber
) && GetPartCount() != 1) {
2391 AddLogLineM(true, CFormat( _("Error: Unable to hash downloaded part - hashset incomplete (%s). This should never happen")) % GetFileName() );
2392 m_hashsetneeded
= true;
2395 CMD4Hash hashresult
;
2396 uint64 length
= PARTSIZE
;
2397 const uint64 offset
= length
* partnumber
;
2399 m_hpartfile
.Seek(offset
, wxFromStart
);
2400 if (offset
+ PARTSIZE
> m_hpartfile
.GetLength()) {
2401 length
= m_hpartfile
.GetLength() - offset
;
2402 wxASSERT( length
<= PARTSIZE
);
2404 CreateHashFromFile(&m_hpartfile
, length
, &hashresult
, NULL
);
2405 } catch (const CIOFailureException
& e
) {
2406 AddLogLineM(true, CFormat( wxT("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2407 % partnumber
% length
% (offset
+length
) % GetFileName() % GetFileSize() % e
.what());
2408 SetPartFileStatus(PS_ERROR
);
2410 } catch (const CEOFException
& e
) {
2411 AddLogLineM(true, CFormat( wxT("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
2412 % partnumber
% length
% (offset
+length
) % GetFileName() % GetFileSize() % e
.what());
2416 if (GetPartCount() > 1) {
2417 if (hashresult
!= GetPartHash(partnumber
)) {
2418 AddDebugLogLineM(false, logPartFile
, CFormat( wxT("%s: Expected part-hash: %s")) % GetFileName() % GetPartHash(partnumber
).Encode() );
2419 AddDebugLogLineM(false, logPartFile
, CFormat( wxT("%s: Actual part-hash: %s")) % GetFileName() % hashresult
.Encode() );
2425 if (hashresult
!= m_abyFileHash
) {
2435 bool CPartFile::IsCorruptedPart(uint16 partnumber
)
2437 return std::find(m_corrupted_list
.begin(), m_corrupted_list
.end(), partnumber
)
2438 != m_corrupted_list
.end();
2442 void CPartFile::SetDownPriority(uint8 np
, bool bSave
, bool bRefresh
)
2444 if ( m_iDownPriority
!= np
) {
2445 m_iDownPriority
= np
;
2447 UpdateDisplayedInfo(true);
2454 void CPartFile::StopFile(bool bCancel
)
2456 // Kry - Need to set it here to get into SetPartFileStatus(status) correctly
2459 // Barry - Need to tell any connected clients to stop sending the file
2462 m_LastSearchTimeKad
= 0;
2463 m_TotalSearchesKad
= 0;
2465 RemoveAllSources(true);
2468 memset(m_anStates
,0,sizeof(m_anStates
));
2474 UpdateDisplayedInfo(true);
2478 void CPartFile::StopPausedFile()
2481 // Once an hour, remove any sources for files which are no longer active downloads
2482 switch (GetStatus()) {
2484 case PS_INSUFFICIENT
:
2486 if (time(NULL
) - m_iLastPausePurge
> (60*60)) {
2487 m_iLastPausePurge
= time(NULL
);
2495 void CPartFile::PauseFile(bool bInsufficient
)
2499 if ( status
== PS_COMPLETE
|| status
== PS_COMPLETING
) {
2503 if (GetKadFileSearchID()) {
2504 Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
2505 // If we were in the middle of searching, reset timer so they can resume searching.
2506 m_LastSearchTimeKad
= 0;
2509 m_iLastPausePurge
= time(NULL
);
2511 theApp
->downloadqueue
->RemoveLocalServerRequest(this);
2513 CPacket
packet( OP_CANCELTRANSFER
, 0, OP_EDONKEYPROT
);
2514 for( SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end(); ) {
2515 CUpDownClient
* cur_src
= *it
++;
2516 if (cur_src
->GetDownloadState() == DS_DOWNLOADING
) {
2517 if (!cur_src
->GetSentCancelTransfer()) {
2518 theStats::AddUpOverheadOther( packet
.GetPacketSize() );
2519 AddDebugLogLineM( false, logLocalClient
, wxT("Local Client: OP_CANCELTRANSFER to ") + cur_src
->GetFullIP() );
2520 cur_src
->SendPacket( &packet
, false, true );
2521 cur_src
->SetSentCancelTransfer( true );
2523 cur_src
->SetDownloadState(DS_ONQUEUE
);
2528 m_insufficient
= bInsufficient
;
2534 m_anStates
[DS_DOWNLOADING
] = 0;
2540 void CPartFile::ResumeFile()
2542 if ( status
== PS_COMPLETE
|| status
== PS_COMPLETING
) {
2546 if ( m_insufficient
&& !CheckFreeDiskSpace() ) {
2547 // Still not enough free discspace
2553 m_insufficient
= false;
2555 m_lastsearchtime
= 0;
2557 SetActive(theApp
->IsConnected());
2559 if (m_gaplist
.empty() && (GetStatus() == PS_ERROR
)) {
2560 // The file has already been hashed at this point
2564 UpdateDisplayedInfo(true);
2568 bool CPartFile::CheckFreeDiskSpace( uint32 neededSpace
)
2570 uint64 free
= CPath::GetFreeSpaceAt(GetFilePath());
2571 if (free
== static_cast<uint64
>(wxInvalidOffset
)) {
2572 // If GetFreeSpaceAt() fails, then the path probably does not exist.
2576 // The very least acceptable diskspace is a single PART
2577 if ( free
< PARTSIZE
) {
2578 // Always fail in this case, since we risk losing data if we try to
2579 // write on a full partition.
2583 // All other checks are only made if the user has enabled them
2584 if ( thePrefs::IsCheckDiskspaceEnabled() ) {
2585 neededSpace
+= thePrefs::GetMinFreeDiskSpace();
2587 // Due to the the existance of sparse files, we cannot assume that
2588 // writes within the file doesn't cause new blocks to be allocated.
2589 // Therefore, we have to simply stop writing the moment the limit has
2591 return free
>= neededSpace
;
2598 void CPartFile::SetLastAnsweredTime()
2600 m_ClientSrcAnswered
= ::GetTickCount();
2603 void CPartFile::SetLastAnsweredTimeTimeout()
2605 m_ClientSrcAnswered
= 2 * CONNECTION_LATENCY
+ ::GetTickCount() - SOURCECLIENTREASKS
;
2608 CPacket
*CPartFile::CreateSrcInfoPacket(const CUpDownClient
* forClient
, uint8 byRequestedVersion
, uint16 nRequestedOptions
)
2611 if ( m_SrcList
.empty() ) {
2616 return CKnownFile::CreateSrcInfoPacket(forClient
, byRequestedVersion
, nRequestedOptions
);
2619 if (((forClient
->GetRequestFile() != this)
2620 && (forClient
->GetUploadFile() != this)) || forClient
->GetUploadFileID() != GetFileHash()) {
2621 wxString file1
= _("Unknown");
2622 if (forClient
->GetRequestFile() && forClient
->GetRequestFile()->GetFileName().IsOk()) {
2623 file1
= forClient
->GetRequestFile()->GetFileName().GetPrintable();
2624 } else if (forClient
->GetUploadFile() && forClient
->GetUploadFile()->GetFileName().IsOk()) {
2625 file1
= forClient
->GetUploadFile()->GetFileName().GetPrintable();
2627 wxString file2
= _("Unknown");
2628 if (GetFileName().IsOk()) {
2629 file2
= GetFileName().GetPrintable();
2631 AddDebugLogLineM(false, logPartFile
, wxT("File mismatch on source packet (P) Sending: ") + file1
+ wxT(" From: ") + file2
);
2635 if ( !(GetStatus() == PS_READY
|| GetStatus() == PS_EMPTY
)) {
2639 const BitVector
& reqstatus
= forClient
->GetPartStatus();
2640 bool KnowNeededParts
= !reqstatus
.empty();
2641 //wxASSERT(rcvstatus.size() == GetPartCount()); // Obviously!
2642 if (reqstatus
.size() != GetPartCount()) {
2643 // Yuck. Same file but different part count? Seriously fucked up.
2644 AddDebugLogLineM(false, logPartFile
, wxString::Format(wxT("Impossible situation: different partcounts for the same part file: %i (client) and %i (file)"),reqstatus
.size(),GetPartCount()));
2648 CMemFile
data(1024);
2650 uint8 byUsedVersion
;
2652 if (forClient
->SupportsSourceExchange2() && byRequestedVersion
> 0){
2653 // the client uses SourceExchange2 and requested the highest version he knows
2654 // and we send the highest version we know, but of course not higher than his request
2655 byUsedVersion
= std::min(byRequestedVersion
, (uint8
)SOURCEEXCHANGE2_VERSION
);
2656 bIsSX2Packet
= true;
2657 data
.WriteUInt8(byUsedVersion
);
2659 // we don't support any special SX2 options yet, reserved for later use
2660 if (nRequestedOptions
!= 0) {
2661 AddDebugLogLineM(false, logKnownFiles
, CFormat(wxT("Client requested unknown options for SourceExchange2: %u")) % nRequestedOptions
);
2664 byUsedVersion
= forClient
->GetSourceExchange1Version();
2665 bIsSX2Packet
= false;
2666 if (forClient
->SupportsSourceExchange2()) {
2667 AddDebugLogLineM(false, logKnownFiles
, wxT("Client which announced to support SX2 sent SX1 packet instead"));
2673 data
.WriteHash(m_abyFileHash
);
2674 data
.WriteUInt16(nCount
);
2676 for (SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end(); ++it
) {
2678 CUpDownClient
* cur_src
= *it
;
2680 int state
= cur_src
->GetDownloadState();
2681 int valid
= ( state
== DS_DOWNLOADING
) || ( state
== DS_ONQUEUE
&& !cur_src
->IsRemoteQueueFull() );
2683 if ( cur_src
->HasLowID() || !valid
) {
2687 // only send source which have needed parts for this client if possible
2688 const BitVector
& srcstatus
= cur_src
->GetPartStatus();
2689 if ( !srcstatus
.empty() ) {
2690 //wxASSERT(srcstatus.size() == GetPartCount()); // Obviously!
2691 if (srcstatus
.size() != GetPartCount()) {
2694 if ( KnowNeededParts
) {
2695 // only send sources which have needed parts for this client
2696 for (int x
= 0; x
< GetPartCount(); ++x
) {
2697 if (srcstatus
[x
] && !reqstatus
[x
]) {
2703 // if we don't know the need parts for this client,
2704 // return any source currently a client sends it's
2705 // file status only after it has at least one complete part
2706 if (srcstatus
.size() != GetPartCount()) {
2709 for (int x
= 0; x
< GetPartCount(); ++x
){
2720 if(forClient
->GetSourceExchange1Version() > 2) {
2721 dwID
= cur_src
->GetUserIDHybrid();
2723 dwID
= wxUINT32_SWAP_ALWAYS(cur_src
->GetUserIDHybrid());
2725 data
.WriteUInt32(dwID
);
2726 data
.WriteUInt16(cur_src
->GetUserPort());
2727 data
.WriteUInt32(cur_src
->GetServerIP());
2728 data
.WriteUInt16(cur_src
->GetServerPort());
2730 if (byUsedVersion
>= 2) {
2731 data
.WriteHash(cur_src
->GetUserHash());
2734 if (byUsedVersion
>= 4){
2735 // CryptSettings - SourceExchange V4
2737 // 1 CryptLayer Required
2738 // 1 CryptLayer Requested
2739 // 1 CryptLayer Supported
2740 const uint8 uSupportsCryptLayer
= cur_src
->SupportsCryptLayer() ? 1 : 0;
2741 const uint8 uRequestsCryptLayer
= cur_src
->RequestsCryptLayer() ? 1 : 0;
2742 const uint8 uRequiresCryptLayer
= cur_src
->RequiresCryptLayer() ? 1 : 0;
2743 const uint8 byCryptOptions
= (uRequiresCryptLayer
<< 2) | (uRequestsCryptLayer
<< 1) | (uSupportsCryptLayer
<< 0);
2744 data
.WriteUInt8(byCryptOptions
);
2755 data
.Seek(bIsSX2Packet
? 17 : 16, wxFromStart
);
2756 data
.WriteUInt16(nCount
);
2758 CPacket
* result
= new CPacket(data
, OP_EMULEPROT
, bIsSX2Packet
? OP_ANSWERSOURCES2
: OP_ANSWERSOURCES
);
2760 // 16+2+501*(4+2+4+2+16) = 14046 bytes max.
2761 if (result
->GetPacketSize() > 354) {
2762 result
->PackPacket();
2768 void CPartFile::AddClientSources(CMemFile
* sources
, unsigned nSourceFrom
, uint8 uClientSXVersion
, bool bSourceExchange2
, const CUpDownClient
* /*pClient*/)
2777 uint8 uPacketSXVersion
= 0;
2778 if (!bSourceExchange2
) {
2779 nCount
= sources
->ReadUInt16();
2781 // Check if the data size matches the 'nCount' for v1 or v2 and eventually correct the source
2782 // exchange version while reading the packet data. Otherwise we could experience a higher
2783 // chance in dealing with wrong source data, userhashs and finally duplicate sources.
2784 uint32 uDataSize
= sources
->GetLength() - sources
->GetPosition();
2786 if ((uint32
)(nCount
*(4+2+4+2)) == uDataSize
) { //Checks if version 1 packet is correct size
2787 if(uClientSXVersion
!= 1) {
2790 uPacketSXVersion
= 1;
2791 } else if ((uint32
)(nCount
*(4+2+4+2+16)) == uDataSize
) { // Checks if version 2&3 packet is correct size
2792 if (uClientSXVersion
== 2) {
2793 uPacketSXVersion
= 2;
2794 } else if (uClientSXVersion
> 2) {
2795 uPacketSXVersion
= 3;
2799 } else if (nCount
*(4+2+4+2+16+1) == uDataSize
) {
2800 if (uClientSXVersion
!= 4 ) {
2803 uPacketSXVersion
= 4;
2805 // If v5 inserts additional data (like v2), the above code will correctly filter those packets.
2806 // If v5 appends additional data after <count>(<Sources>)[count], we are in trouble with the
2807 // above code. Though a client which does not understand v5+ should never receive such a packet.
2808 AddDebugLogLineM(false, logClient
, CFormat(wxT("Received invalid source exchange packet (v%u) of data size %u for %s")) % uClientSXVersion
% uDataSize
% GetFileName());
2813 // We only check if the version is known by us and do a quick sanitize check on known version
2814 // other then SX1, the packet will be ignored if any error appears, sicne it can't be a "misunderstanding" anymore
2815 if (uClientSXVersion
> SOURCEEXCHANGE2_VERSION
|| uClientSXVersion
== 0 ){
2816 AddDebugLogLineM(false, logPartFile
, CFormat(wxT("Invalid source exchange type version: %i")) % uClientSXVersion
);
2820 // all known versions use the first 2 bytes as count and unknown version are already filtered above
2821 nCount
= sources
->ReadUInt16();
2822 uint32 uDataSize
= (uint32
)(sources
->GetLength() - sources
->GetPosition());
2823 bool bError
= false;
2824 switch (uClientSXVersion
){
2826 bError
= nCount
*(4+2+4+2) != uDataSize
;
2830 bError
= nCount
*(4+2+4+2+16) != uDataSize
;
2833 bError
= nCount
*(4+2+4+2+16+1) != uDataSize
;
2841 AddDebugLogLineM(false, logPartFile
, wxT("Invalid source exchange data size."));
2844 uPacketSXVersion
= uClientSXVersion
;
2847 for (uint16 i
= 0;i
!= nCount
;++i
) {
2849 uint32 dwID
= sources
->ReadUInt32();
2850 uint16 nPort
= sources
->ReadUInt16();
2851 uint32 dwServerIP
= sources
->ReadUInt32();
2852 uint16 nServerPort
= sources
->ReadUInt16();
2855 if (uPacketSXVersion
> 1) {
2856 userHash
= sources
->ReadHash();
2859 uint8 byCryptOptions
= 0;
2860 if (uPacketSXVersion
>= 4) {
2861 byCryptOptions
= sources
->ReadUInt8();
2864 //Clients send ID's the the Hyrbid format so highID clients with *.*.*.0 won't be falsely switched to a lowID..
2866 if (uPacketSXVersion
>= 3) {
2867 dwIDED2K
= wxUINT32_SWAP_ALWAYS(dwID
);
2872 // check the HighID(IP) - "Filter LAN IPs" and "IPfilter" the received sources IP addresses
2873 if (!IsLowID(dwID
)) {
2874 if (!IsGoodIP(dwIDED2K
, thePrefs::FilterLanIPs())) {
2875 // check for 0-IP, localhost and optionally for LAN addresses
2876 AddDebugLogLineM(false, logIPFilter
, CFormat(wxT("Ignored source (IP=%s) received via %s - bad IP")) % Uint32toStringIP(dwIDED2K
) % OriginToText(nSourceFrom
));
2879 if (theApp
->ipfilter
->IsFiltered(dwIDED2K
)) {
2880 AddDebugLogLineM(false, logIPFilter
, CFormat(wxT("Ignored source (IP=%s) received via %s - IPFilter")) % Uint32toStringIP(dwIDED2K
) % OriginToText(nSourceFrom
));
2883 if (theApp
->clientlist
->IsBannedClient(dwIDED2K
)){
2888 // additionally check for LowID and own IP
2889 if (!CanAddSource(dwID
, nPort
, dwServerIP
, nServerPort
, NULL
, false)) {
2890 AddDebugLogLineM(false, logIPFilter
, CFormat(wxT("Ignored source (IP=%s) received via source exchange")) % Uint32toStringIP(dwIDED2K
));
2894 if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
2895 CUpDownClient
* newsource
= new CUpDownClient(nPort
,dwID
,dwServerIP
,nServerPort
,this, (uPacketSXVersion
< 3), true);
2896 if (uPacketSXVersion
> 1) {
2897 newsource
->SetUserHash(userHash
);
2900 if (uPacketSXVersion
>= 4) {
2901 newsource
->SetConnectOptions(byCryptOptions
, true, false);
2904 newsource
->SetSourceFrom((ESourceFrom
)nSourceFrom
);
2905 theApp
->downloadqueue
->CheckAndAddSource(this,newsource
);
2913 void CPartFile::UpdateAutoDownPriority()
2915 if (!IsAutoDownPriority()) {
2918 if (GetSourceCount() <= RARE_FILE
) {
2919 if ( GetDownPriority() != PR_HIGH
)
2920 SetDownPriority(PR_HIGH
, false, false);
2921 } else if (GetSourceCount() < 100) {
2922 if ( GetDownPriority() != PR_NORMAL
)
2923 SetDownPriority(PR_NORMAL
, false, false);
2925 if ( GetDownPriority() != PR_LOW
)
2926 SetDownPriority(PR_LOW
, false, false);
2930 // making this function return a higher when more sources have the extended
2931 // protocol will force you to ask a larger variety of people for sources
2933 int CPartFile::GetCommonFilePenalty()
2935 //TODO: implement, but never return less than MINCOMMONPENALTY!
2936 return MINCOMMONPENALTY
;
2939 /* Barry - Replaces BlockReceived()
2941 Originally this only wrote to disk when a full 180k block
2942 had been received from a client, and only asked for data in
2945 This meant that on average 90k was lost for every connection
2946 to a client data source. That is a lot of wasted data.
2948 To reduce the lost data, packets are now written to a buffer
2949 and flushed to disk regularly regardless of size downloaded.
2950 This includes compressed packets.
2952 Data is also requested only where gaps are, not in 180k blocks.
2953 The requests will still not exceed 180k, but may be smaller to
2957 // Kry - transize is 32bits, no packet can be more than that (this is
2958 // compressed size). Even 32bits is too much imho.As for the return size,
2959 // look at the lenData below.
2960 uint32
CPartFile::WriteToBuffer(uint32 transize
, byte
* data
, uint64 start
, uint64 end
, Requested_Block_Struct
*block
)
2962 // Increment transferred bytes counter for this file
2963 transferred
+= transize
;
2965 // This is needed a few times
2966 // Kry - should not need a uint64 here - no block is larger than
2967 // 2GB even after uncompressed.
2968 uint32 lenData
= (uint32
) (end
- start
+ 1);
2970 if(lenData
> transize
) {
2971 m_iGainDueToCompression
+= lenData
-transize
;
2974 // Occasionally packets are duplicated, no point writing it twice
2975 if (IsComplete(start
, end
)) {
2976 AddDebugLogLineM(false, logPartFile
,
2977 CFormat(wxT("File '%s' has already been written from %u to %u"))
2978 % GetFileName() % start
% end
);
2982 // Create copy of data as new buffer
2983 byte
*buffer
= new byte
[lenData
];
2984 memcpy(buffer
, data
, lenData
);
2986 // Create a new buffered queue entry
2987 PartFileBufferedData
*item
= new PartFileBufferedData
;
2988 item
->data
= buffer
;
2989 item
->start
= start
;
2991 item
->block
= block
;
2993 // Add to the queue in the correct position (most likely the end)
2996 std::list
<PartFileBufferedData
*>::iterator it
= m_BufferedData_list
.begin();
2997 for (; it
!= m_BufferedData_list
.end(); ++it
) {
2998 PartFileBufferedData
* queueItem
= *it
;
3000 if (item
->end
<= queueItem
->end
) {
3001 if (it
!= m_BufferedData_list
.begin()) {
3004 m_BufferedData_list
.insert(--it
, item
);
3012 m_BufferedData_list
.push_front(item
);
3015 // Increment buffer size marker
3016 m_nTotalBufferData
+= lenData
;
3018 // Mark this small section of the file as filled
3019 FillGap(item
->start
, item
->end
);
3021 // Update the flushed mark on the requested block
3022 // The loop here is unfortunate but necessary to detect deleted blocks.
3024 std::list
<Requested_Block_Struct
*>::iterator it2
= m_requestedblocks_list
.begin();
3025 for (; it2
!= m_requestedblocks_list
.end(); ++it2
) {
3026 if (*it2
== item
->block
) {
3027 item
->block
->transferred
+= lenData
;
3031 if (m_gaplist
.empty()) {
3035 // Return the length of data written to the buffer
3039 void CPartFile::FlushBuffer(bool /*forcewait*/, bool bForceICH
, bool bNoAICH
)
3041 m_nLastBufferFlushTime
= GetTickCount();
3043 if (m_BufferedData_list
.empty()) {
3048 uint32 partCount
= GetPartCount();
3049 std::vector
<bool> changedPart(partCount
);
3051 // Remember which parts need to be checked at the end of the flush
3052 for ( uint32 i
= 0; i
< partCount
; ++i
) {
3053 changedPart
[ i
] = false;
3057 // Ensure file is big enough to write data to (the last item will be the furthest from the start)
3060 std::list
<PartFileBufferedData
*>::iterator it
= m_BufferedData_list
.begin();
3061 for (; it
!= m_BufferedData_list
.end(); ++it
) {
3062 PartFileBufferedData
* item
= *it
;
3063 wxASSERT((item
->end
- item
->start
) < 0xFFFFFFFF);
3064 newData
+= (uint32
) (item
->end
- item
->start
+ 1);
3067 if ( !CheckFreeDiskSpace( newData
) ) {
3068 // Not enough free space to write the last item, bail
3069 AddLogLineM(true, CFormat( _("WARNING: Not enough free disk-space! Pausing file: %s") ) % GetFileName());
3075 // Loop through queue
3076 while ( !m_BufferedData_list
.empty() ) {
3077 // Get top item and remove it from the queue
3078 PartFileBufferedData
* item
= m_BufferedData_list
.front();
3079 m_BufferedData_list
.pop_front();
3081 // This is needed a few times
3082 wxASSERT((item
->end
- item
->start
) < 0xFFFFFFFF);
3083 uint32 lenData
= (uint32
)(item
->end
- item
->start
+ 1);
3085 // SLUGFILLER: SafeHash - could be more than one part
3086 for (uint32 curpart
= (item
->start
/PARTSIZE
); curpart
<= (item
->end
/PARTSIZE
); ++curpart
) {
3087 wxASSERT(curpart
< partCount
);
3088 changedPart
[curpart
] = true;
3090 // SLUGFILLER: SafeHash
3092 // Go to the correct position in file and write block of data
3094 m_hpartfile
.Seek(item
->start
);
3095 m_hpartfile
.Write(item
->data
, lenData
);
3096 } catch (const CIOFailureException
& e
) {
3097 AddDebugLogLineM(true, logPartFile
, wxT("Error while saving part-file: ") + e
.what());
3098 SetPartFileStatus(PS_ERROR
);
3101 // Decrease buffer size
3102 m_nTotalBufferData
-= lenData
;
3104 // Release memory used by this item
3105 delete [] item
->data
;
3110 // Update last-changed date
3111 m_lastDateChanged
= wxDateTime::GetTimeNow();
3114 // Partfile should never be too large
3115 if (m_hpartfile
.GetLength() > GetFileSize()) {
3116 // it's "last chance" correction. the real bugfix has to be applied 'somewhere' else
3117 m_hpartfile
.SetLength(GetFileSize());
3119 } catch (const CIOFailureException
& e
) {
3120 AddDebugLogLineM(true, logPartFile
,
3121 CFormat(wxT("Error while truncating part-file (%s): %s"))
3122 % m_fullname
.RemoveExt() % e
.what());
3123 SetPartFileStatus(PS_ERROR
);
3128 // Check each part of the file
3129 uint32 partRange
= 0;
3131 uint64 curLength
= m_hpartfile
.GetLength();
3133 partRange
= (uint32
)((curLength
% PARTSIZE
> 0) ? ((curLength
% PARTSIZE
) - 1) : (PARTSIZE
- 1));
3134 } catch (const CIOFailureException
& e
) {
3135 AddDebugLogLineM(true, logPartFile
,
3136 CFormat(wxT("Error while accessing part-file (%s): %s"))
3137 % m_fullname
.RemoveExt() % e
.what());
3138 SetPartFileStatus(PS_ERROR
);
3141 wxASSERT(partRange
);
3142 for (int partNumber
= partCount
-1; partRange
&& partNumber
>= 0; partNumber
--) {
3143 if (changedPart
[partNumber
] == false) {
3144 // Any parts other than last must be full size
3145 partRange
= PARTSIZE
- 1;
3149 // Is this 9MB part complete
3150 if (IsComplete(PARTSIZE
* partNumber
, (PARTSIZE
* (partNumber
+ 1)) - 1)) {
3152 if (!HashSinglePart(partNumber
)) {
3153 AddLogLineM(true, CFormat(
3154 _("Downloaded part %i is corrupt in file: %s") ) % partNumber
% GetFileName() );
3155 AddGap(PARTSIZE
*partNumber
, (PARTSIZE
*partNumber
+ partRange
));
3156 // add part to corrupted list, if not already there
3157 if (!IsCorruptedPart(partNumber
)) {
3158 m_corrupted_list
.push_back(partNumber
);
3160 // request AICH recovery data
3162 RequestAICHRecovery((uint16
)partNumber
);
3164 // Reduce transferred amount by corrupt amount
3165 m_iLostDueToCorruption
+= (partRange
+ 1);
3167 if (!m_hashsetneeded
) {
3168 AddDebugLogLineM(false, logPartFile
, CFormat(
3169 wxT("Finished part %u of '%s'")) % partNumber
% GetFileName());
3172 // if this part was successfully completed (although ICH is active), remove from corrupted list
3173 EraseFirstValue(m_corrupted_list
, partNumber
);
3175 if (status
== PS_EMPTY
) {
3176 if (theApp
->IsRunning()) { // may be called during shutdown!
3177 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded
) {
3178 // Successfully completed part, make it available for sharing
3179 SetStatus(PS_READY
);
3180 theApp
->sharedfiles
->SafeAddKFile(this);
3185 } else if ( IsCorruptedPart(partNumber
) && (thePrefs::IsICHEnabled() || bForceICH
)) {
3186 // Try to recover with minimal loss
3187 if (HashSinglePart(partNumber
)) {
3188 ++m_iTotalPacketsSavedDueToICH
;
3190 uint64 uMissingInPart
= GetTotalGapSizeInPart(partNumber
);
3191 FillGap(PARTSIZE
*partNumber
,(PARTSIZE
*partNumber
+partRange
));
3192 RemoveBlockFromList(PARTSIZE
*partNumber
,(PARTSIZE
*partNumber
+ partRange
));
3194 // remove from corrupted list
3195 EraseFirstValue(m_corrupted_list
, partNumber
);
3197 AddLogLineM(true, CFormat( _("ICH: Recovered corrupted part %i for %s -> Saved bytes: %s") )
3200 % CastItoXBytes(uMissingInPart
));
3202 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded
) {
3203 if (status
== PS_EMPTY
) {
3204 // Successfully recovered part, make it available for sharing
3205 SetStatus(PS_READY
);
3206 if (theApp
->IsRunning()) // may be called during shutdown!
3207 theApp
->sharedfiles
->SafeAddKFile(this);
3212 // Any parts other than last must be full size
3213 partRange
= PARTSIZE
- 1;
3219 if (theApp
->IsRunning()) { // may be called during shutdown!
3220 // Is this file finished ?
3221 if (m_gaplist
.empty()) {
3222 CompleteFile(false);
3228 void CPartFile::UpdateFileRatingCommentAvail()
3230 bool prevComment
= m_hasComment
;
3231 int prevRating
= m_iUserRating
;
3233 m_hasComment
= false;
3235 int ratingCount
= 0;
3237 SourceSet::iterator it
= m_SrcList
.begin();
3238 for (; it
!= m_SrcList
.end(); ++it
) {
3239 CUpDownClient
* cur_src
= *it
;
3241 if (!cur_src
->GetFileComment().IsEmpty()) {
3242 if (thePrefs::IsCommentFiltered(cur_src
->GetFileComment())) {
3245 m_hasComment
= true;
3248 uint8 rating
= cur_src
->GetFileRating();
3250 wxASSERT(rating
<= 5);
3253 m_iUserRating
+= rating
;
3258 m_iUserRating
/= ratingCount
;
3259 wxASSERT(m_iUserRating
> 0 && m_iUserRating
<= 5);
3262 if ((prevComment
!= m_hasComment
) || (prevRating
!= m_iUserRating
)) {
3263 UpdateDisplayedInfo();
3268 void CPartFile::SetCategory(uint8 cat
)
3270 wxASSERT( cat
< theApp
->glob_prefs
->GetCatCount() );
3276 bool CPartFile::RemoveSource(CUpDownClient
* toremove
, bool updatewindow
, bool bDoStatsUpdate
)
3278 wxASSERT( toremove
);
3280 bool result
= theApp
->downloadqueue
->RemoveSource( toremove
, updatewindow
, bDoStatsUpdate
);
3282 // Check if the client should be deleted, but not if the client is already dying
3283 if ( !toremove
->GetSocket() && !toremove
->HasBeenDeleted() ) {
3284 if ( toremove
->Disconnected(wxT("RemoveSource - purged")) ) {
3285 toremove
->Safe_Delete();
3292 void CPartFile::AddDownloadingSource(CUpDownClient
* client
)
3294 CClientPtrList::iterator it
=
3295 std::find(m_downloadingSourcesList
.begin(), m_downloadingSourcesList
.end(), client
);
3296 if (it
== m_downloadingSourcesList
.end()) {
3297 m_downloadingSourcesList
.push_back(client
);
3302 void CPartFile::RemoveDownloadingSource(CUpDownClient
* client
)
3304 CClientPtrList::iterator it
=
3305 std::find(m_downloadingSourcesList
.begin(), m_downloadingSourcesList
.end(), client
);
3306 if (it
!= m_downloadingSourcesList
.end()) {
3307 m_downloadingSourcesList
.erase(it
);
3312 void CPartFile::SetPartFileStatus(uint8 newstatus
)
3316 if (thePrefs::GetAllcatType()) {
3317 Notify_DownloadCtrlUpdateItem(this);
3320 Notify_DownloadCtrlSort();
3324 uint64
CPartFile::GetNeededSpace()
3327 uint64 length
= m_hpartfile
.GetLength();
3329 if (length
> GetFileSize()) {
3330 return 0; // Shouldn't happen, but just in case
3333 return GetFileSize() - length
;
3334 } catch (const CIOFailureException
& e
) {
3335 AddDebugLogLineM(true, logPartFile
,
3336 CFormat(wxT("Error while retrieving file-length (%s): %s"))
3337 % m_fullname
.RemoveExt() % e
.what());
3338 SetPartFileStatus(PS_ERROR
);
3343 void CPartFile::SetStatus(uint8 in
)
3345 wxASSERT( in
!= PS_PAUSED
&& in
!= PS_INSUFFICIENT
);
3349 if (theApp
->IsRunning()) {
3350 UpdateDisplayedInfo( true );
3352 if ( thePrefs::ShowCatTabInfos() ) {
3353 Notify_ShowUpdateCatTabTitles();
3359 uint64
CPartFile::GetTotalGapSizeInRange(uint64 uRangeStart
, uint64 uRangeEnd
) const
3361 uint64 uTotalGapSize
= 0;
3363 if (uRangeEnd
>= GetFileSize()) {
3364 uRangeEnd
= GetFileSize() - 1;
3367 std::list
<Gap_Struct
*>::const_iterator it
= m_gaplist
.begin();
3368 for (; it
!= m_gaplist
.end(); ++it
) {
3369 const Gap_Struct
* pGap
= *it
;
3371 if (pGap
->start
< uRangeStart
&& pGap
->end
> uRangeEnd
) {
3372 uTotalGapSize
+= uRangeEnd
- uRangeStart
+ 1;
3376 if (pGap
->start
>= uRangeStart
&& pGap
->start
<= uRangeEnd
) {
3377 uint64 uEnd
= (pGap
->end
> uRangeEnd
) ? uRangeEnd
: pGap
->end
;
3378 uTotalGapSize
+= uEnd
- pGap
->start
+ 1;
3379 } else if (pGap
->end
>= uRangeStart
&& pGap
->end
<= uRangeEnd
) {
3380 uTotalGapSize
+= pGap
->end
- uRangeStart
+ 1;
3384 wxASSERT( uTotalGapSize
<= uRangeEnd
- uRangeStart
+ 1 );
3386 return uTotalGapSize
;
3389 uint64
CPartFile::GetTotalGapSizeInPart(uint32 uPart
) const
3391 uint64 uRangeStart
= uPart
* PARTSIZE
;
3392 uint64 uRangeEnd
= uRangeStart
+ PARTSIZE
- 1;
3393 if (uRangeEnd
>= GetFileSize()) {
3394 uRangeEnd
= GetFileSize();
3396 return GetTotalGapSizeInRange(uRangeStart
, uRangeEnd
);
3400 void CPartFile::RequestAICHRecovery(uint16 nPart
)
3403 if ( !m_pAICHHashSet
->HasValidMasterHash() ||
3404 (m_pAICHHashSet
->GetStatus() != AICH_TRUSTED
&& m_pAICHHashSet
->GetStatus() != AICH_VERIFIED
)){
3405 AddDebugLogLineM( false, logAICHRecovery
, wxT("Unable to request AICH Recoverydata because we have no trusted Masterhash") );
3408 if (GetFileSize() <= EMBLOCKSIZE
|| GetFileSize() - PARTSIZE
*nPart
<= EMBLOCKSIZE
)
3410 if (CAICHHashSet::IsClientRequestPending(this, nPart
)){
3411 AddDebugLogLineM( false, logAICHRecovery
, wxT("RequestAICHRecovery: Already a request for this part pending"));
3415 // first check if we have already the recoverydata, no need to rerequest it then
3416 if (m_pAICHHashSet
->IsPartDataAvailable(nPart
*PARTSIZE
)){
3417 AddDebugLogLineM( false, logAICHRecovery
, wxT("Found PartRecoveryData in memory"));
3418 AICHRecoveryDataAvailable(nPart
);
3422 wxASSERT( nPart
< GetPartCount() );
3423 // find some random client which support AICH to ask for the blocks
3424 // first lets see how many we have at all, we prefer high id very much
3425 uint32 cAICHClients
= 0;
3426 uint32 cAICHLowIDClients
= 0;
3427 for ( SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end(); ++it
) {
3428 CUpDownClient
* pCurClient
= *(it
);
3429 if ( pCurClient
->IsSupportingAICH() &&
3430 pCurClient
->GetReqFileAICHHash() != NULL
&&
3431 !pCurClient
->IsAICHReqPending()
3432 && (*pCurClient
->GetReqFileAICHHash()) == m_pAICHHashSet
->GetMasterHash())
3434 if (pCurClient
->HasLowID()) {
3435 ++cAICHLowIDClients
;
3441 if ((cAICHClients
| cAICHLowIDClients
) == 0){
3442 AddDebugLogLineM( false, logAICHRecovery
, wxT("Unable to request AICH Recoverydata because found no client who supports it and has the same hash as the trusted one"));
3445 uint32 nSeclectedClient
;
3446 if (cAICHClients
> 0) {
3447 nSeclectedClient
= (rand() % cAICHClients
) + 1;
3449 nSeclectedClient
= (rand() % cAICHLowIDClients
) + 1;
3451 CUpDownClient
* pClient
= NULL
;
3452 for ( SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end(); ++it
) {
3453 CUpDownClient
* pCurClient
= *(it
);
3454 if (pCurClient
->IsSupportingAICH() && pCurClient
->GetReqFileAICHHash() != NULL
&& !pCurClient
->IsAICHReqPending()
3455 && (*pCurClient
->GetReqFileAICHHash()) == m_pAICHHashSet
->GetMasterHash())
3457 if (cAICHClients
> 0){
3458 if (!pCurClient
->HasLowID())
3462 wxASSERT( pCurClient
->HasLowID());
3465 if (nSeclectedClient
== 0){
3466 pClient
= pCurClient
;
3471 if (pClient
== NULL
){
3476 AddDebugLogLineM( false, logAICHRecovery
, CFormat( wxT("Requesting AICH Hash (%s) form client %s") ) % ( cAICHClients
? wxT("HighId") : wxT("LowID") ) % pClient
->GetClientFullInfo() );
3477 pClient
->SendAICHRequest(this, nPart
);
3482 void CPartFile::AICHRecoveryDataAvailable(uint16 nPart
)
3484 if (GetPartCount() < nPart
){
3489 FlushBuffer(true, true, true);
3491 uint64 length
= PARTSIZE
;
3494 if ((unsigned)(PARTSIZE
* (nPart
+ 1)) > m_hpartfile
.GetLength()){
3495 length
= (m_hpartfile
.GetLength() - (PARTSIZE
* nPart
));
3496 wxASSERT( length
<= PARTSIZE
);
3498 } catch (const CIOFailureException
& e
) {
3499 AddDebugLogLineM(true, logPartFile
,
3500 CFormat(wxT("Error while retrieving file-length (%s): %s"))
3501 % m_fullname
.RemoveExt() % e
.what());
3502 SetPartFileStatus(PS_ERROR
);
3506 // if the part was already ok, it would now be complete
3507 if (IsComplete(nPart
*PARTSIZE
, ((nPart
*PARTSIZE
)+length
)-1)){
3508 AddDebugLogLineM( false, logAICHRecovery
,
3509 wxString::Format( wxT("Processing AICH Recovery data: The part (%u) is already complete, canceling"), nPart
) );
3515 CAICHHashTree
* pVerifiedHash
= m_pAICHHashSet
->m_pHashTree
.FindHash(nPart
*PARTSIZE
, length
);
3516 if (pVerifiedHash
== NULL
|| !pVerifiedHash
->GetHashValid()){
3517 AddDebugLogLineM( true, logAICHRecovery
, wxT("Processing AICH Recovery data: Unable to get verified hash from hashset (should never happen)") );
3521 CAICHHashTree
htOurHash(pVerifiedHash
->GetNDataSize(), pVerifiedHash
->GetIsLeftBranch(), pVerifiedHash
->GetNBaseSize());
3523 m_hpartfile
.Seek(PARTSIZE
* nPart
,wxFromStart
);
3524 CreateHashFromFile(&m_hpartfile
,length
, NULL
, &htOurHash
);
3525 } catch (const CIOFailureException
& e
) {
3526 AddDebugLogLineM(true, logAICHRecovery
,
3527 CFormat(wxT("IO failure while hashing part-file '%s': %s"))
3528 % m_hpartfile
.GetFilePath() % e
.what());
3529 SetPartFileStatus(PS_ERROR
);
3533 if (!htOurHash
.GetHashValid()){
3534 AddDebugLogLineM( false, logAICHRecovery
, wxT("Processing AICH Recovery data: Failed to retrieve AICH Hashset of corrupt part") );
3539 // now compare the hash we just did, to the verified hash and readd all blocks which are ok
3540 uint32 nRecovered
= 0;
3541 for (uint32 pos
= 0; pos
< length
; pos
+= EMBLOCKSIZE
){
3542 const uint32 nBlockSize
= min
<uint32
>(EMBLOCKSIZE
, length
- pos
);
3543 CAICHHashTree
* pVerifiedBlock
= pVerifiedHash
->FindHash(pos
, nBlockSize
);
3544 CAICHHashTree
* pOurBlock
= htOurHash
.FindHash(pos
, nBlockSize
);
3545 if ( pVerifiedBlock
== NULL
|| pOurBlock
== NULL
|| !pVerifiedBlock
->GetHashValid() || !pOurBlock
->GetHashValid()){
3549 if (pOurBlock
->GetHash() == pVerifiedBlock
->GetHash()){
3550 FillGap(PARTSIZE
*nPart
+pos
, PARTSIZE
*nPart
+ pos
+ (nBlockSize
-1));
3551 RemoveBlockFromList(PARTSIZE
*nPart
, PARTSIZE
*nPart
+ (nBlockSize
-1));
3552 nRecovered
+= nBlockSize
;
3556 // ok now some sanity checks
3557 if (IsComplete(nPart
*PARTSIZE
, ((nPart
*PARTSIZE
)+length
)-1)){
3558 // this is a bad, but it could probably happen under some rare circumstances
3559 // make sure that MD4 agrres to this fact too
3560 if (!HashSinglePart(nPart
)){
3561 AddDebugLogLineM( false, logAICHRecovery
,
3562 wxString::Format(wxT("Processing AICH Recovery data: The part (%u) got completed while recovering - but MD4 says it corrupt! Setting hashset to error state, deleting part"), nPart
));
3563 // now we are fu... unhappy
3564 m_pAICHHashSet
->SetStatus(AICH_ERROR
);
3565 AddGap(PARTSIZE
*nPart
, ((nPart
*PARTSIZE
)+length
)-1);
3570 AddDebugLogLineM( false, logAICHRecovery
, wxString::Format(
3571 wxT("Processing AICH Recovery data: The part (%u) got completed while recovering and MD4 agrees"), nPart
) );
3572 // alrighty not so bad
3573 EraseFirstValue(m_corrupted_list
, nPart
);
3574 if (status
== PS_EMPTY
&& theApp
->IsRunning()){
3575 if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded
){
3576 // Successfully recovered part, make it available for sharing
3577 SetStatus(PS_READY
);
3578 theApp
->sharedfiles
->SafeAddKFile(this);
3582 if (theApp
->IsRunning()){
3583 // Is this file finished?
3584 if (m_gaplist
.empty()) {
3585 CompleteFile(false);
3589 } // end sanity check
3593 // make sure the user appreciates our great recovering work :P
3594 AddDebugLogLineM( true, logAICHRecovery
, CFormat(
3595 wxT("AICH successfully recovered %s of %s from part %u for %s") )
3596 % CastItoXBytes(nRecovered
)
3597 % CastItoXBytes(length
)
3603 void CPartFile::ClientStateChanged( int oldState
, int newState
)
3605 if ( oldState
== newState
)
3608 // If the state is -1, then it's an entirely new item
3609 if ( oldState
!= -1 ) {
3610 // Was the old state a valid state?
3611 if ( oldState
== DS_ONQUEUE
|| oldState
== DS_DOWNLOADING
) {
3614 if ( oldState
== DS_CONNECTED
/* || oldState == DS_REMOTEQUEUEFULL */ ) {
3618 m_notCurrentSources
--;
3622 // If the state is -1, then the source is being removed
3623 if ( newState
!= -1 ) {
3624 // Was the old state a valid state?
3625 if ( newState
== DS_ONQUEUE
|| newState
== DS_DOWNLOADING
) {
3628 if ( newState
== DS_CONNECTED
/* || newState == DS_REMOTEQUEUEFULL */ ) {
3632 ++m_notCurrentSources
;
3638 bool CPartFile::AddSource( CUpDownClient
* client
)
3640 if (m_SrcList
.insert( client
).second
) {
3641 theStats::AddFoundSource();
3642 theStats::AddSourceOrigin(client
->GetSourceFrom());
3650 bool CPartFile::DelSource( CUpDownClient
* client
)
3652 if (m_SrcList
.erase( client
)) {
3653 theStats::RemoveSourceOrigin(client
->GetSourceFrom());
3654 theStats::RemoveFoundSource();
3662 void CPartFile::UpdatePartsFrequency( CUpDownClient
* client
, bool increment
)
3664 const BitVector
& freq
= client
->GetPartStatus();
3666 if ( m_SrcpartFrequency
.size() != GetPartCount() ) {
3667 m_SrcpartFrequency
.clear();
3668 m_SrcpartFrequency
.insert(m_SrcpartFrequency
.begin(), GetPartCount(), 0);
3675 unsigned int size
= freq
.size();
3676 if ( size
!= m_SrcpartFrequency
.size() ) {
3681 for ( unsigned int i
= 0; i
< size
; i
++ ) {
3683 m_SrcpartFrequency
[i
]++;
3687 for ( unsigned int i
= 0; i
< size
; i
++ ) {
3689 m_SrcpartFrequency
[i
]--;
3695 const FileRatingList
&CPartFile::GetRatingAndComments()
3697 m_FileRatingList
.clear();
3698 // This can be pre-processed, but is it worth the CPU?
3699 CPartFile::SourceSet::iterator it
= m_SrcList
.begin();
3700 for ( ; it
!= m_SrcList
.end(); ++it
) {
3701 CUpDownClient
*cur_src
= *it
;
3702 if (cur_src
->GetFileComment().Length()>0 || cur_src
->GetFileRating()>0) {
3703 // AddDebugLogLineM(false, logPartFile, wxString(wxT("found a comment for ")) << GetFileName());
3704 m_FileRatingList
.push_back(SFileRating(*cur_src
));
3708 return m_FileRatingList
;
3713 CPartFile::CPartFile(CEC_PartFile_Tag
*tag
)
3717 SetFileName(CPath(tag
->FileName()));
3718 m_abyFileHash
= tag
->ID();
3719 SetFileSize(tag
->SizeFull());
3720 m_partmetfilename
= CPath(tag
->PartMetName());
3721 transferred
= tag
->SizeXfer();
3722 percentcompleted
= (100.0*completedsize
) / GetFileSize();
3723 completedsize
= tag
->SizeDone();
3725 m_category
= tag
->FileCat();
3727 m_iPartCount
= ((uint64
)GetFileSize() + (PARTSIZE
- 1)) / PARTSIZE
;
3728 m_SrcpartFrequency
.insert(m_SrcpartFrequency
.end(), m_iPartCount
, 0);
3729 m_iDownPriority
= tag
->Prio();
3730 if ( m_iDownPriority
>= 10 ) {
3731 m_iDownPriority
-= 10;
3732 m_bAutoDownPriority
= true;
3734 m_bAutoDownPriority
= false;
3740 m_a4af_source_count
= 0;
3744 * Remote gui specific code
3746 CPartFile::~CPartFile()
3750 const FileRatingList
&CPartFile::GetRatingAndComments()
3752 return m_FileRatingList
;
3754 #endif // !CLIENT_GUI
3757 void CPartFile::UpdateDisplayedInfo(bool force
)
3759 uint32 curTick
= ::GetTickCount();
3760 m_CommentUpdated
= true;
3762 // Wait 1.5s between each redraw
3763 if(force
|| curTick
-m_lastRefreshedDLDisplay
> MINWAIT_BEFORE_DLDISPLAY_WINDOWUPDATE
) {
3764 Notify_DownloadCtrlUpdateItem(this);
3765 m_lastRefreshedDLDisplay
= curTick
;
3771 void CPartFile::Init()
3773 m_showSources
= false;
3774 m_lastsearchtime
= 0;
3775 lastpurgetime
= ::GetTickCount();
3778 m_insufficient
= false;
3783 m_iLastPausePurge
= time(NULL
);
3785 if(thePrefs::GetNewAutoDown()) {
3786 m_iDownPriority
= PR_HIGH
;
3787 m_bAutoDownPriority
= true;
3789 m_iDownPriority
= PR_NORMAL
;
3790 m_bAutoDownPriority
= false;
3793 memset(m_anStates
,0,sizeof(m_anStates
));
3795 transferingsrc
= 0; // new
3799 m_CommentUpdated
= false;
3800 m_hashsetneeded
= true;
3802 percentcompleted
= 0;
3804 m_bPreviewing
= false;
3805 lastseencomplete
= 0;
3806 m_availablePartsCount
=0;
3807 m_ClientSrcAnswered
= 0;
3808 m_LastNoNeededCheck
= 0;
3810 m_nTotalBufferData
= 0;
3811 m_nLastBufferFlushTime
= 0;
3812 m_bPercentUpdated
= false;
3813 m_bRecoveringArchive
= false;
3814 m_iGainDueToCompression
= 0;
3815 m_iLostDueToCorruption
= 0;
3816 m_iTotalPacketsSavedDueToICH
= 0;
3818 m_lastRefreshedDLDisplay
= 0;
3819 m_nDlActiveTime
= 0;
3821 m_is_A4AF_auto
= false;
3822 m_localSrcReqQueued
= false;
3823 m_nCompleteSourcesTime
= time(NULL
);
3824 m_nCompleteSourcesCount
= 0;
3825 m_nCompleteSourcesCountLo
= 0;
3826 m_nCompleteSourcesCountHi
= 0;
3829 m_notCurrentSources
= 0;
3832 m_LastSearchTimeKad
= 0;
3833 m_TotalSearchesKad
= 0;
3837 wxString
CPartFile::getPartfileStatus() const
3842 if ((status
== PS_HASHING
) || (status
== PS_WAITINGFORHASH
)) {
3843 mybuffer
=_("Hashing");
3844 } else if (status
== PS_ALLOCATING
) {
3845 mybuffer
= _("Allocating");
3847 switch (GetStatus()) {
3849 mybuffer
=_("Completing");
3852 mybuffer
=_("Complete");
3855 mybuffer
=_("Paused");
3858 mybuffer
=_("Erroneous");
3860 case PS_INSUFFICIENT
:
3861 mybuffer
= _("Insufficient Diskspace");
3864 if (GetTransferingSrcCount()>0) {
3865 mybuffer
=_("Downloading");
3867 mybuffer
=_("Waiting");
3871 if (m_stopped
&& (GetStatus()!=PS_COMPLETE
)) {
3872 mybuffer
=_("Stopped");
3879 int CPartFile::getPartfileStatusRang() const
3883 if (GetTransferingSrcCount()==0) tempstatus
=1;
3884 switch (GetStatus()) {
3886 case PS_WAITINGFORHASH
:
3906 wxString
CPartFile::GetFeedback() const
3908 wxString retval
= CKnownFile::GetFeedback();
3909 if (GetStatus() != PS_COMPLETE
) {
3910 retval
+= wxString(_("Downloaded")) + wxT(": ") + CastItoXBytes(GetCompletedSize()) + wxString::Format(wxT(" (%.2f%%)\n"), GetPercentCompleted())
3911 + _("Sources") + CFormat(wxT(": %u\n")) % GetSourceCount();
3913 return retval
+ _("Status") + wxT(": ") + getPartfileStatus() + wxT("\n");
3917 sint32
CPartFile::getTimeRemaining() const
3919 if (GetKBpsDown() < 0.001)
3922 return((GetFileSize()-GetCompletedSize()) / ((int)(GetKBpsDown()*1024.0)));
3925 bool CPartFile::PreviewAvailable()
3927 FileType type
= GetFiletype(GetFileName());
3929 return (((type
== ftVideo
) || (type
== ftAudio
)) && IsComplete(0, 256*1024));
3932 bool CPartFile::CheckShowItemInGivenCat(int inCategory
)
3934 // easy normal cases
3936 bool IsNotFiltered
= true;
3938 IsInCat
= ((inCategory
==0) || (inCategory
>0 && inCategory
==GetCategory()));
3940 switch (thePrefs::GetAllcatType()) {
3942 IsNotFiltered
= GetCategory() == 0 || inCategory
> 0;
3945 IsNotFiltered
= IsPartFile();
3948 IsNotFiltered
= !IsPartFile();
3952 (GetStatus() == PS_READY
|| GetStatus() == PS_EMPTY
) &&
3953 GetTransferingSrcCount() == 0;
3957 (GetStatus() == PS_READY
|| GetStatus()==PS_EMPTY
) &&
3958 GetTransferingSrcCount() > 0;
3961 IsNotFiltered
= GetStatus() == PS_ERROR
;
3964 IsNotFiltered
= GetStatus() == PS_PAUSED
&& !IsStopped();
3967 IsNotFiltered
= IsStopped();
3970 IsNotFiltered
= GetFiletype(GetFileName()) == ftVideo
;
3973 IsNotFiltered
= GetFiletype(GetFileName()) == ftAudio
;
3976 IsNotFiltered
= GetFiletype(GetFileName()) == ftArchive
;
3979 IsNotFiltered
= GetFiletype(GetFileName()) == ftCDImage
;
3982 IsNotFiltered
= GetFiletype(GetFileName()) == ftPicture
;
3985 IsNotFiltered
= GetFiletype(GetFileName()) == ftText
;
3988 IsNotFiltered
= !IsStopped() && GetStatus() != PS_PAUSED
;
3992 return IsNotFiltered
&& IsInCat
;
3995 bool CPartFile::IsComplete(uint64 start
, uint64 end
)
3997 if (end
>= GetFileSize()) {
3998 end
= GetFileSize()-1;
4001 std::list
<Gap_Struct
*>::iterator it
= m_gaplist
.begin();
4002 for (; it
!= m_gaplist
.end(); ++it
) {
4003 Gap_Struct
* cur_gap
= *it
;
4004 if ((cur_gap
->start
>= start
&& cur_gap
->end
<= end
)||(cur_gap
->start
>= start
4005 && cur_gap
->start
<= end
)||(cur_gap
->end
<= end
&& cur_gap
->end
>= start
)
4006 ||(start
>= cur_gap
->start
&& end
<= cur_gap
->end
)) {
4014 void CPartFile::SetActive(bool bActive
)
4016 time_t tNow
= time(NULL
);
4018 if (theApp
->IsConnected()) {
4019 if (m_tActivated
== 0) {
4020 m_tActivated
= tNow
;
4024 if (m_tActivated
!= 0) {
4025 m_nDlActiveTime
+= tNow
- m_tActivated
;
4032 uint32
CPartFile::GetDlActiveTime() const
4034 uint32 nDlActiveTime
= m_nDlActiveTime
;
4035 if (m_tActivated
!= 0) {
4036 nDlActiveTime
+= time(NULL
) - m_tActivated
;
4038 return nDlActiveTime
;
4044 uint8
CPartFile::GetStatus(bool ignorepause
) const
4046 if ( (!m_paused
&& !m_insufficient
) ||
4047 status
== PS_ERROR
||
4048 status
== PS_COMPLETING
||
4049 status
== PS_COMPLETE
||
4052 } else if ( m_insufficient
) {
4053 return PS_INSUFFICIENT
;
4059 void CPartFile::AddDeadSource(const CUpDownClient
* client
)
4061 m_deadSources
.AddDeadSource( client
);
4065 bool CPartFile::IsDeadSource(const CUpDownClient
* client
)
4067 return m_deadSources
.IsDeadSource( client
);
4070 void CPartFile::SetFileName(const CPath
& fileName
)
4072 CKnownFile
* pFile
= theApp
->sharedfiles
->GetFileByID(GetFileHash());
4074 bool is_shared
= (pFile
&& pFile
== this);
4077 // The file is shared, we must clear the search keywords so we don't
4078 // publish the old name anymore.
4079 theApp
->sharedfiles
->RemoveKeywords(this);
4082 CKnownFile::SetFileName(fileName
);
4085 // And of course, we must advertise the new name if the file is shared.
4086 theApp
->sharedfiles
->AddKeywords(this);
4089 UpdateDisplayedInfo(true);
4093 uint16
CPartFile::GetMaxSources() const
4095 // This is just like this, while we don't import the private max sources per file
4096 return thePrefs::GetMaxSourcePerFile();
4100 uint16
CPartFile::GetMaxSourcePerFileSoft() const
4102 unsigned int temp
= ((unsigned int)GetMaxSources() * 9L) / 10;
4103 if (temp
> MAX_SOURCES_FILE_SOFT
) {
4104 return MAX_SOURCES_FILE_SOFT
;
4109 uint16
CPartFile::GetMaxSourcePerFileUDP() const
4111 unsigned int temp
= ((unsigned int)GetMaxSources() * 3L) / 4;
4112 if (temp
> MAX_SOURCES_FILE_UDP
) {
4113 return MAX_SOURCES_FILE_UDP
;
4118 #define DROP_FACTOR 2
4120 CUpDownClient
* CPartFile::GetSlowerDownloadingClient(uint32 speed
, CUpDownClient
* caller
) {
4121 // printf("Start slower source calculation\n");
4122 for( SourceSet::iterator it
= m_SrcList
.begin(); it
!= m_SrcList
.end(); ) {
4123 CUpDownClient
* cur_src
= *it
++;
4124 if ((cur_src
->GetDownloadState() == DS_DOWNLOADING
) && (cur_src
!= caller
)) {
4125 uint32 factored_bytes_per_second
= static_cast<uint32
>(
4126 (cur_src
->GetKBpsDown() * 1024) * DROP_FACTOR
);
4127 if ( factored_bytes_per_second
< speed
) {
4128 // printf("Selecting source %p to drop: %d < %d\n", cur_src, factored_bytes_per_second, speed);
4129 // printf("End slower source calculation\n");
4132 // printf("Not selecting source %p to drop: %d > %d\n", cur_src, factored_bytes_per_second, speed);
4136 // printf("End slower source calculation\n");
4140 void CPartFile::AllocationFinished()
4142 if (!m_hpartfile
.Open(GetFullName().RemoveExt(), CFile::read_write
)) {
4143 AddLogLineM(false, CFormat(_("ERROR: Failed to open partfile '%s'")) % GetFullName());
4144 SetPartFileStatus(PS_ERROR
);
4149 // File_checked_for_headers