//
// This file is part of the aMule Project.
//
// Copyright (c) 2003-2026 aMule Team ( https://amule-org.github.io )
// Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
//

#include <wx/wx.h>

#include "PartFile.h"		// Interface declarations.
#include "PartFileWriteThread.h"	// Needed for PB_READY etc.
#include "PartFileHashThread.h"		// Needed for QueueHashCheck
#include "config.h"		// Needed for VERSION
#include <protocol/kad/Constants.h>
#include <protocol/ed2k/Client2Client/TCP.h>
#include <protocol/Protocols.h>
#include <common/DataFileVersion.h>
#include <common/Constants.h>
#include <tags/FileTags.h>

#include <wx/utils.h>
#include <wx/tokenzr.h>		// Needed for wxStringTokenizer

#ifndef AMULE_DAEMON
#include <wx/notifmsg.h>	// Needed for wxNotificationMessage
#endif

#include "KnownFileList.h"	// Needed for CKnownFileList
#include "CanceledFileList.h"
#include "UploadQueue.h"	// Needed for CFileHash
#include "IPFilter.h"		// Needed for CIPFilter
#include "Server.h"		// Needed for CServer
#include "ServerConnect.h"	// Needed for CServerConnect

#ifdef CLIENT_GUI
#include "UpDownClientEC.h"	// Needed for CUpDownClient
#else
#include "updownclient.h"	// Needed for CUpDownClient
#endif

#include "MemFile.h"		// Needed for CMemFile
#include "Preferences.h"	// Needed for CPreferences
#include "DownloadQueue.h"	// Needed for CDownloadQueue
#include "amule.h"		// Needed for theApp
#include "ED2KLink.h"		// Needed for CED2KLink
#include "Packet.h"		// Needed for CTag
#include "SearchList.h"		// Needed for CSearchFile
#include "ClientList.h"		// Needed for clientlist
#include "Statistics.h"		// Needed for theStats
#include "Logger.h"
#include <common/Format.h>	// Needed for CFormat
#include <common/FileFunctions.h>	// Needed for GetLastModificationTime
#include "ThreadTasks.h"	// Needed for CHashingTask/CCompletionTask/CAllocateFileTask
#include "GuiEvents.h"		// Needed for Notify_*
#include "DataToText.h"		// Needed for OriginToText()
#include "PlatformSpecific.h"	// Needed for CreateSparseFile()
#include "FileArea.h"		// Needed for CFileArea
#include "ScopedPtr.h"		// Needed for CScopedArray
#include "CorruptionBlackBox.h"

#include "kademlia/kademlia/Kademlia.h"
#include "kademlia/kademlia/Search.h"


SFileRating::SFileRating(const wxString &u, const wxString &f, sint16 r, const wxString &c)
:
UserName(u),
FileName(f),
Rating(r),
Comment(c)
{
}


#ifndef CLIENT_GUI
SFileRating::SFileRating(const CUpDownClient &client)
:
UserName(client.GetUserName()),
FileName(client.GetClientFilename()),
Rating(client.GetFileRating()),
Comment(client.GetFileComment())
{
}
#endif


// PartFileBufferedData is defined in PartFile.h


typedef std::list<Chunk> ChunkList;


#ifndef CLIENT_GUI

CPartFile::CPartFile()
{
	Init();
}

CPartFile::CPartFile(CSearchFile* searchresult)
{
	Init();

	m_abyFileHash = searchresult->GetFileHash();
	SetFileName(searchresult->GetFileName());
	SetFileSize(searchresult->GetFileSize());

	for (unsigned int i = 0; i < searchresult->m_taglist.size(); ++i){
		const CTag& pTag = searchresult->m_taglist[i];

		bool bTagAdded = false;
		if (pTag.GetNameID() == 0 && !pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
			static const struct {
				wxString	pszName;
				uint8	nType;
			} _aMetaTags[] =
				{
					{ FT_ED2K_MEDIA_ARTIST,  2 },
					{ FT_ED2K_MEDIA_ALBUM,   2 },
					{ FT_ED2K_MEDIA_TITLE,   2 },
					{ FT_ED2K_MEDIA_LENGTH,  2 },
					{ FT_ED2K_MEDIA_BITRATE, 3 },
					{ FT_ED2K_MEDIA_CODEC,   2 }
				};

			for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
				if (	pTag.GetType() == _aMetaTags[t].nType &&
					(pTag.GetName() == _aMetaTags[t].pszName)) {
					// skip string tags with empty string values
					if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
						break;
					}

					// skip "length" tags with "0: 0" values
					if (pTag.GetName() == FT_ED2K_MEDIA_LENGTH) {
						if (pTag.GetStr().IsSameAs("0: 0") ||
							pTag.GetStr().IsSameAs("0:0")) {
							break;
						}
					}

					// skip "bitrate" tags with '0' values
					if ((pTag.GetName() == FT_ED2K_MEDIA_BITRATE) && !pTag.GetInt()) {
						break;
					}

					AddDebugLogLineN( logPartFile,
						"CPartFile::CPartFile(CSearchFile*): added tag " +
						pTag.GetFullInfo() );
					m_taglist.push_back(pTag);
					bTagAdded = true;
					break;
				}
			}
		} else if (pTag.GetNameID() != 0 && pTag.GetName().IsEmpty() && (pTag.IsStr() || pTag.IsInt())) {
			static const struct {
				uint8	nID;
				uint8	nType;
			} _aMetaTags[] =
				{
					{ FT_FILETYPE,		2 },
					{ FT_FILEFORMAT,	2 }
				};
			for (unsigned int t = 0; t < itemsof(_aMetaTags); ++t) {
				if (pTag.GetType() == _aMetaTags[t].nType && pTag.GetNameID() == _aMetaTags[t].nID) {
					// skip string tags with empty string values
					if (pTag.IsStr() && pTag.GetStr().IsEmpty()) {
						break;
					}

					AddDebugLogLineN( logPartFile,
						"CPartFile::CPartFile(CSearchFile*): added tag " +
						pTag.GetFullInfo() );
					m_taglist.push_back(pTag);
					bTagAdded = true;
					break;
				}
			}
		}

		if (!bTagAdded) {
			AddDebugLogLineN( logPartFile,
				"CPartFile::CPartFile(CSearchFile*): ignored tag " +
				pTag.GetFullInfo() );
		}
	}

	CreatePartFile();
}


CPartFile::CPartFile(const CED2KFileLink* fileLink)
{
	Init();

	SetFileName(CPath(fileLink->GetName()));
	SetFileSize(fileLink->GetSize());
	m_abyFileHash = fileLink->GetHashKey();

	CreatePartFile();

	if (fileLink->m_hashset) {
		if (!LoadHashsetFromFile(fileLink->m_hashset, true)) {
			AddDebugLogLineC(logPartFile, "eD2K link contained invalid hashset: " + fileLink->GetLink());
		}
	}
}


CPartFile::~CPartFile()
{
	// Gate Phase 3 below; see m_inDestructor comment in PartFile.h.
	m_inDestructor = true;
	AddDebugLogLineN(logPartFile, CFormat(
		"~CPartFile entered, m_inDestructor set: '%s'") % GetFileName());

	// Wait for any in-flight HashJobs targeting this file to finish.
	// CPartFileHashThread reads m_hpartfile via HashSinglePart; we must
	// not close the file out from under it. m_inDestructor was set above
	// so Phase 3 will not enqueue anything new.
	if (m_pendingHashes > 0) {
		AddDebugLogLineN(logPartFile, CFormat(
			"~CPartFile waiting for %d pending hash job(s) of '%s'")
			% (int)m_pendingHashes % GetFileName());
		while (m_pendingHashes > 0) {
			wxMilliSleep(10);
		}
	}

	// if it's not opened, it was completed or deleted
	if (m_hpartfile.IsOpened()) {
		// FlushBuffer drains buffered writes / harvests Phase 2's
		// PB_WRITTEN items into m_aChangedPart. Phase 3 itself returns
		// early due to m_inDestructor.
		FlushBuffer();

		// Sync-hash any parts still flagged dirty in m_aChangedPart.
		//
		// Why this is needed: in normal operation Phase 3 enqueues
		// dirty parts to CPartFileHashThread asynchronously. But at
		// shutdown the hash thread is torn down before downloadqueue
		// (see CamuleApp::OnExit ordering), so async enqueue isn't
		// available here — and a forced-quit during an active download
		// can leave many parts harvested-but-not-yet-hashed
		// (m_aChangedPart-true but never enqueued because the
		// quiescent guard never opened during the receive burst).
		//
		// Without this, .met would be saved with gaplist marking
		// those parts complete and m_corrupted_list empty, so on
		// next launch they would be treated as implicitly verified
		// (the existing "verified iff IsComplete && !IsCorruptedPart"
		// invariant) — even though they were never hashed.
		//
		// Run the verification synchronously on the main thread.
		// Slow shutdown is acceptable for the rare cancel-mid-download
		// case; the common paths (natural completion, pause/resume,
		// idle drain) keep m_aChangedPart drained throughout the
		// session, so the loop usually has nothing to do.
		if (m_aChangedPart.size() == GetPartCount()) {
			uint16 verified = 0, corrupt = 0;
			for (uint16 i = 0; i < GetPartCount(); ++i) {
				if (!m_aChangedPart[i]) {
					continue;
				}
				m_aChangedPart[i] = false;

				// Only hash parts whose data is actually on disk.
				if (!IsComplete(i)) {
					continue;
				}

				// Lock m_hpartfileMutex against CPartFileWriteThread:
				// Phase 1 of FlushBuffer above may have queued writes
				// that the write thread is still draining concurrently
				// with this sync-hash loop. See m_hpartfileMutex.
				bool ok;
				{
					std::lock_guard<std::mutex> lock(m_hpartfileMutex);
					ok = HashSinglePart(i);
				}
				if (ok) {
					// Part is good. If it was carried in
					// m_corrupted_list from before, drop it so the
					// .met save below records the new clean state.
					if (IsCorruptedPart(i)) {
						EraseFirstValue(m_corrupted_list, i);
					}
					++verified;
				} else {
					// Part is bad. Re-open the gap and record in
					// m_corrupted_list so next launch knows to
					// re-download + retry.
					AddGap(i);
					if (!IsCorruptedPart(i)) {
						m_corrupted_list.push_back(i);
					}
					m_iLostDueToCorruption +=
						(uint64)GetPartSize(i);
					++corrupt;
				}
			}
			if (verified > 0 || corrupt > 0) {
				AddDebugLogLineN(logPartFile, CFormat(
					"~CPartFile sync-hashed %u remaining dirty part(s) "
					"(%u verified, %u corrupt) for '%s'")
					% (verified + corrupt) % verified % corrupt
					% GetFileName());
			}
		}

		m_hpartfile.Close();
		// Update met file (with current directory entry)
		SavePartFile();
	}

	DeleteContents(m_BufferedData_list);
	delete m_CorruptionBlackBox;

	wxASSERT(m_SrcList.empty());
	wxASSERT(m_A4AFsrclist.empty());
}

void CPartFile::CreatePartFile(bool isImporting)
{
	// use lowest free partfilenumber for free file (InterCeptor)
	int i = 0;
	do {
		++i;
		m_partmetfilename = CPath(CFormat("%03i.part.met") % i);
		m_fullname = thePrefs::GetTempDir().JoinPaths(m_partmetfilename);
	} while (m_fullname.FileExists());

	m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());

	m_gaplist.Init(GetFileSize(), true);	// Init empty

	m_PartPath = m_fullname.RemoveExt();
	bool fileCreated;
	if (thePrefs::GetAllocFullFile() || !thePrefs::CreateFilesSparse()) {
		fileCreated = m_hpartfile.Create(m_PartPath, true);
		m_hpartfile.Close();
	} else {
		fileCreated = PlatformSpecific::CreateSparseFile(m_PartPath, GetFileSize());
	}
	if (!fileCreated) {
		AddLogLineN(_("ERROR: Failed to create partfile"));
		SetStatus(PS_ERROR);
	}

	SetFilePath(thePrefs::GetTempDir());

	if (!isImporting && thePrefs::GetAllocFullFile()) {
		SetStatus(PS_ALLOCATING);
		CThreadScheduler::AddTask(new CAllocateFileTask(this, thePrefs::AddNewFilesPaused()));
	} else {
		AllocationFinished();
	}

	m_hashsetneeded = (GetED2KPartHashCount() > 0);

	SavePartFile(true);
	SetActive(theApp->IsConnected());
}


uint8 CPartFile::LoadPartFile(const CPath& in_directory, const CPath& filename, bool from_backup, bool getsizeonly)
{
	bool isnewstyle = false;
	uint8 version,partmettype=PMT_UNKNOWN;

	std::map<uint16, Gap_Struct*> gap_map; // Slugfiller
	transferred = 0;

	m_partmetfilename = filename;
	m_CorruptionBlackBox->SetPartFileInfo(GetFileName().GetPrintable(), m_partmetfilename.RemoveAllExt().GetPrintable());
	m_filePath = in_directory;
	m_fullname = m_filePath.JoinPaths(m_partmetfilename);
	m_PartPath = m_fullname.RemoveExt();

	// readfile data form part.met file
	CPath curMetFilename = m_fullname;
	if (from_backup) {
		curMetFilename = curMetFilename.AppendExt(PARTMET_BAK_EXT);
		AddLogLineN(CFormat( _("Trying to load backup of met-file from %s") )
			% curMetFilename );
	}

	try {
		CFile metFile(curMetFilename, CFile::read);
		if (!metFile.IsOpened()) {
			AddLogLineN(CFormat( _("ERROR: Failed to open part.met file: %s ==> %s") )
				% curMetFilename
				% GetFileName() );

			return false;
		} else if (metFile.GetLength() == 0) {
			AddLogLineN(CFormat( _("ERROR: part.met file is 0 size: %s ==> %s") )
				% m_partmetfilename
				% GetFileName() );

			return false;
		}

		version = metFile.ReadUInt8();
		if (version != PARTFILE_VERSION && version != PARTFILE_SPLITTEDVERSION && version != PARTFILE_VERSION_LARGEFILE){
			metFile.Close();
			//if (version == 83) return ImportShareazaTempFile(...)
			AddLogLineN(CFormat( _("ERROR: Invalid part.met file version: %s ==> %s") )
				% m_partmetfilename
				% GetFileName() );
			return false;
		}

		isnewstyle = (version == PARTFILE_SPLITTEDVERSION);
		partmettype = isnewstyle ? PMT_SPLITTED : PMT_DEFAULTOLD;

		if (version == PARTFILE_VERSION) {// Do we still need this check ?
			uint8 test[4];									// It will fail for certain files.
			metFile.Seek(24, wxFromStart);
			metFile.Read(test,4);

			metFile.Seek(1, wxFromStart);
			if (test[0]==0 && test[1]==0 && test[2]==2 && test[3]==1) {
				isnewstyle=true;	// edonkeys so called "old part style"
				partmettype=PMT_NEWOLD;
			}
		}

		if (isnewstyle) {
			uint32 temp = metFile.ReadUInt32();

			if (temp==0) {	// 0.48 partmets - different again
				LoadHashsetFromFile(&metFile, false);
			} else {
				metFile.Seek(2, wxFromStart);
				LoadDateFromFile(&metFile);
				m_abyFileHash = metFile.ReadHash();
			}

		} else {
			LoadDateFromFile(&metFile);
			LoadHashsetFromFile(&metFile, false);
		}

		uint32 tagcount = metFile.ReadUInt32();

		for (uint32 j = 0; j < tagcount; ++j) {
			CTag newtag(metFile,true);
			if (	!getsizeonly ||
				(getsizeonly &&
					(newtag.GetNameID() == FT_FILESIZE ||
					 newtag.GetNameID() == FT_FILENAME))) {
				switch(newtag.GetNameID()) {
					case FT_FILENAME: {
						if (!GetFileName().IsOk()) {
							// If it's not empty, we already loaded the unicoded one
							SetFileName(CPath(newtag.GetStr()));
						}
						break;
					}
					case FT_LASTSEENCOMPLETE: {
						lastseencomplete = newtag.GetInt();
						break;
					}
					case FT_FILESIZE: {
						SetFileSize(newtag.GetInt());
						break;
					}
					case FT_TRANSFERRED: {
						transferred = newtag.GetInt();
						break;
					}
					case FT_FILETYPE:{
						//#warning needs setfiletype string
						//SetFileType(newtag.GetStr());
						break;
					}
					case FT_CATEGORY: {
						m_category = newtag.GetInt();
						if (m_category > theApp->glob_prefs->GetCatCount() - 1 ) {
							m_category = 0;
						}
						break;
					}
					case FT_OLDDLPRIORITY:
					case FT_DLPRIORITY: {
						if (!isnewstyle){
							m_iDownPriority = newtag.GetInt();
							if( m_iDownPriority == PR_AUTO ){
								m_iDownPriority = PR_HIGH;
								SetAutoDownPriority(true);
							}
							else{
								if (	m_iDownPriority != PR_LOW &&
									m_iDownPriority != PR_NORMAL &&
									m_iDownPriority != PR_HIGH)
									m_iDownPriority = PR_NORMAL;
								SetAutoDownPriority(false);
							}
						}
						break;
					}
					case FT_STATUS: {
						m_paused = (newtag.GetInt() == 1);
						m_stopped = m_paused;
						break;
					}
					case FT_OLDULPRIORITY:
					case FT_ULPRIORITY: {
						if (!isnewstyle){
							SetUpPriority(newtag.GetInt(), false);
							if( GetUpPriority() == PR_AUTO ){
								SetUpPriority(PR_HIGH, false);
								SetAutoUpPriority(true);
							} else {
								SetAutoUpPriority(false);
							}
						}
						break;
					}
					case FT_KADLASTPUBLISHSRC:{
						SetLastPublishTimeKadSrc(newtag.GetInt(), 0);
						if(GetLastPublishTimeKadSrc() > (uint32)time(NULL)+KADEMLIAREPUBLISHTIMES) {
							//There may be a possibility of an older client that saved a random number here.. This will check for that..
							SetLastPublishTimeKadSrc(0,0);
						}
						break;
					}
					case FT_KADLASTPUBLISHNOTES:{
						SetLastPublishTimeKadNotes(newtag.GetInt());
						break;
					}
					// old tags: as long as they are not needed, take the chance to purge them
					case FT_PERMISSIONS:
					case FT_KADLASTPUBLISHKEY:
					case FT_PARTFILENAME:
						break;
					case FT_DL_ACTIVE_TIME:
						if (newtag.IsInt()) {
							m_nDlActiveTime = newtag.GetInt();
						}
						break;
					case FT_CORRUPTEDPARTS: {
						wxASSERT(m_corrupted_list.empty());
						wxString strCorruptedParts(newtag.GetStr());
						wxStringTokenizer tokenizer(strCorruptedParts, ",");
						while ( tokenizer.HasMoreTokens() ) {
							wxString token = tokenizer.GetNextToken();
							unsigned long uPart;
							if (token.ToULong(&uPart)) {
								if (uPart < GetPartCount() && !IsCorruptedPart(uPart)) {
									m_corrupted_list.push_back(uPart);
								}
							}
						}
						break;
					}
					case FT_AICH_HASH:{
						CAICHHash hash;
						bool hashSizeOk =
							hash.DecodeBase32(newtag.GetStr()) == CAICHHash::GetHashSize();
						wxASSERT(hashSizeOk);
						if (hashSizeOk) {
							m_pAICHHashSet->SetMasterHash(hash, AICH_VERIFIED);
							// EC exports GetAICHMasterHash().
							MarkECChanged();
						}
						break;
					}
					case FT_ATTRANSFERRED:{
						statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (uint64)newtag.GetInt());
						break;
					}
					case FT_ATTRANSFERREDHI:{
						statistic.SetAllTimeTransferred(statistic.GetAllTimeTransferred() + (((uint64)newtag.GetInt()) << 32));
						break;
					}
					case FT_ATREQUESTED:{
						statistic.SetAllTimeRequests(newtag.GetInt());
						break;
					}
					case FT_ATACCEPTED:{
						statistic.SetAllTimeAccepts(newtag.GetInt());
						break;
					}
					default: {
						// Start Changes by Slugfiller for better exception handling

						wxCharBuffer tag_ansi_name = newtag.GetName().ToAscii();
						char gap_mark = tag_ansi_name.data() ? tag_ansi_name[0u] : 0;
						if ( newtag.IsInt() && (newtag.GetName().Length() > 1) &&
							((gap_mark == FT_GAPSTART) ||
							 (gap_mark == FT_GAPEND))) {
							Gap_Struct *gap = NULL;
							unsigned long int gapkey;
							if (newtag.GetName().Mid(1).ToULong(&gapkey)) {
								if ( gap_map.find( gapkey ) == gap_map.end() ) {
									gap = new Gap_Struct;
									gap_map[gapkey] = gap;
									gap->start = (uint64)-1;
									gap->end = (uint64)-1;
								} else {
									gap = gap_map[ gapkey ];
								}
								if (gap_mark == FT_GAPSTART) {
									gap->start = newtag.GetInt();
								}
								if (gap_mark == FT_GAPEND) {
									gap->end = newtag.GetInt()-1;
								}
							} else {
								AddDebugLogLineN(logPartFile, "Wrong gap map key while reading met file!");
								wxFAIL;
							}
							// End Changes by Slugfiller for better exception handling
						} else {
							m_taglist.push_back(newtag);
						}
					}
				}
			} else {
				// Nothing. Else, nothing.
			}
		}

		// load the hashsets from the hybridstylepartmet
		if (isnewstyle && !getsizeonly && (metFile.GetPosition()<metFile.GetLength()) ) {
			metFile.Seek(1, wxFromCurrent);

			uint16 parts=GetPartCount();	// assuming we will get all hashsets

			for (uint16 i = 0; i < parts && (metFile.GetPosition()+16<metFile.GetLength()); ++i){
				CMD4Hash cur_hash = metFile.ReadHash();
				m_hashlist.push_back(cur_hash);
			}

			CMD4Hash checkhash;
			if (!m_hashlist.empty()) {
				CreateHashFromHashlist(m_hashlist, &checkhash);
			}
			if (m_abyFileHash != checkhash) {
				m_hashlist.clear();
			}
		}
	} catch (const CInvalidPacket& e) {
		AddLogLineC(CFormat(_("Error: %s (%s) is corrupt (bad tags: %s), unable to load file."))
			% m_partmetfilename
			% GetFileName()
			% e.what());
		return false;
	} catch (const CIOFailureException& e) {
		AddDebugLogLineC(logPartFile, CFormat( "IO failure while loading '%s': %s" )
			% m_partmetfilename
			% e.what() );
		return false;
	} catch (const CEOFException& WXUNUSED(e)) {
		AddLogLineC(CFormat( _("ERROR: %s (%s) is corrupt (wrong tagcount), unable to load file.") )
			% m_partmetfilename
			% GetFileName() );
		AddLogLineC(_("Trying to recover file info..."));

		// Safe file is that who have
		// - FileSize
		if (GetFileSize()) {
			// We have filesize, try other needed info

			// Do we need to check gaps? I think not,
			// because they are checked below. Worst
			// scenario will only mark file as 0 bytes downloaded.

			// -Filename
			if (!GetFileName().IsOk()) {
				// Not critical, let's put a random filename.
				AddLogLineC(_(
					"Recovering no-named file - will try to recover it as RecoveredFile.dat"));
				SetFileName(CPath("RecoveredFile.dat"));
			}

			AddLogLineC(_("Recovered all available file info :D - Trying to use it..."));
		} else {
			AddLogLineC(_("Unable to recover file info :("));
			return false;
		}
	}

	if (getsizeonly) {
		return partmettype;
	}
	// Init Gaplist
	m_gaplist.Init(GetFileSize(), false);	// Init full, then add gaps
	// Now to flush the map into the list (Slugfiller)
	std::map<uint16, Gap_Struct*>::iterator it = gap_map.begin();
	for ( ; it != gap_map.end(); ++it ) {
		Gap_Struct* gap = it->second;
		// SLUGFILLER: SafeHash - revised code, and extra safety
		if (	(gap->start != (uint64)-1) &&
			(gap->end != (uint64)-1) &&
			gap->start <= gap->end &&
			gap->start < GetFileSize()) {
			if (gap->end >= GetFileSize()) {
				gap->end = GetFileSize()-1; // Clipping
			}
			m_gaplist.AddGap(gap->start, gap->end); // All tags accounted for, use safe adding
		}
		delete gap;
		// SLUGFILLER: SafeHash
	}

	//check if this is a backup
	if ( m_fullname.GetExt().MakeLower() == "backup") {
		m_fullname = m_fullname.RemoveExt();
	}

	// open permanent handle
	if ( !m_hpartfile.Open(m_PartPath, CFile::read_write)) {
		AddLogLineN(CFormat( _("Failed to open %s (%s)") )
			% m_fullname
			% GetFileName() );
		return false;
	}

	SetStatus(PS_EMPTY);

	try {
		// SLUGFILLER: SafeHash - final safety, make sure any missing part of the file is gap
		if (m_hpartfile.GetLength() < GetFileSize())
			AddGap(m_hpartfile.GetLength(), GetFileSize()-1);
		// Goes both ways - Partfile should never be too large
		if (m_hpartfile.GetLength() > GetFileSize()) {
			AddDebugLogLineC(logPartFile, CFormat( "Partfile \"%s\" is too large! Truncating %llu bytes." ) % GetFileName() % (m_hpartfile.GetLength() - GetFileSize()));
			m_hpartfile.SetLength(GetFileSize());
		}
		// SLUGFILLER: SafeHash
	} catch (const CIOFailureException& e) {
		AddDebugLogLineC(logPartFile, CFormat( "Error while accessing partfile \"%s\": %s" ) % GetFileName() % e.what());
		SetStatus(PS_ERROR);
	}

	// now close the file again until needed
	m_hpartfile.Release(true);

	// check hashcount, file status etc
	if (GetHashCount() != GetED2KPartHashCount()){
		m_hashsetneeded = true;
		ClearMetDirty();
		ClearStatsDirty();
		m_lastMetSaveTick = ::GetTickCount64();
		return true;
	} else {
		m_hashsetneeded = false;
		for (size_t i = 0; i < m_hashlist.size(); ++i) {
			if (IsComplete(i)) {
				SetStatus(PS_READY);
			}
		}
	}

	if (m_gaplist.IsComplete()) { // is this file complete already?
		CompleteFile(false);
		return true;
	}

	if (!isnewstyle) { // not for importing
		const time_t file_date = CPath::GetModificationTime(m_PartPath);
		if (m_lastDateChanged != file_date) {
			// It's pointless to rehash an empty file, since the case
			// where a user has zero'd a file is handled above ...
			if (m_hpartfile.GetLength()) {
				AddLogLineN(CFormat( _("WARNING: %s might be corrupted (%i)") )
					% m_PartPath
					% (m_lastDateChanged - file_date) );
				// rehash
				SetStatus(PS_WAITING_FOR_HASH);

				CPath partFileName = m_partmetfilename.RemoveExt();
				CThreadScheduler::AddTask(new CHashingTask(m_filePath, partFileName, this));
			}
		}
	}

	UpdateCompletedInfos();
	if (completedsize > transferred) {
		m_iGainDueToCompression = completedsize - transferred;
	} else if (completedsize != transferred) {
		m_iLostDueToCorruption = transferred - completedsize;
	}

	// In-memory state now matches the just-loaded .part.met.  Setters
	// invoked during tag parsing (SetUpPriority, SetAutoUpPriority,
	// SetAutoDownPriority, SetStatus, etc.) may have set m_metDirty;
	// drop it so the first FlushBuffer tick after load does not rewrite
	// the .met with byte-identical content.  Also seed m_lastMetSaveTick
	// from load time so the stats-heartbeat is measured from now.
	ClearMetDirty();
	ClearStatsDirty();
	m_lastMetSaveTick = ::GetTickCount64();

	return true;
}


bool CPartFile::SavePartFile(bool Initial)
{
	switch (status) {
		case PS_WAITING_FOR_HASH:
		case PS_HASHING:
		case PS_COMPLETE:
			return false;
	}

	/* Don't write anything to disk if less than 100 KB of free space is left. */
	sint64 free = CPath::GetFreeSpaceAt(GetFilePath());
	if ((free != wxInvalidOffset) && (free < (100 * 1024))) {
		return false;
	}

	// Atomic-rename save.
	//
	// Old sequence (per partfile, every save tick):
	//   1. CPath::BackupFile(m_fullname, ".backup") -- full content copy
	//      of .part.met into .part.met.backup as an in-flight intermediate.
	//   2. CPath::RemoveFile(m_fullname).
	//   3. Write new .part.met from scratch.
	//   4. CPath::RemoveFile(".backup") on success.
	//   5. CPath::BackupFile(m_fullname, PARTMET_BAK_EXT) -- full content
	//      copy of the freshly-written .part.met into .part.met.bak as the
	//      long-term recovery backup.
	//
	// Three full-size content copies per save plus a delete/create dance on
	// the live .part.met. On a sharer with hundreds of dirty partfiles per
	// timer tick the aggregate runs the main thread continuously through
	// disk I/O (see issue #669) -- write() syscalls are microseconds each,
	// but the loop length itself is the bottleneck.
	//
	// New sequence:
	//   1. Write new content into .part.met.tmp.
	//   2. rename(.part.met, .part.met.bak) -- O(1) metadata op, promotes
	//      the previous .part.met to long-term backup.
	//   3. rename(.part.met.tmp, .part.met) -- atomic install of new
	//      content, POSIX rename guarantees the target is either fully
	//      old-content or fully new-content at every observable moment.
	//
	// One content write plus two metadata renames. ~3x reduction in
	// per-save disk work, and stronger crash safety because the live
	// .part.met is never absent or partial.
	const CPath tmpName = m_fullname.AppendExt(".tmp");
	const CPath bakName = m_fullname.AppendExt(PARTMET_BAK_EXT);

	CFile file;
	try {
		if (!m_PartPath.FileExists()) {
			throw wxString(".part file not found");
		}

		uint32 lsc = lastseencomplete;

		file.Open(tmpName, CFile::write);
		if (!file.IsOpened()) {
			throw wxString("Failed to open part.met.tmp file");
		}

		// version
		file.WriteUInt8(IsLargeFile() ? PARTFILE_VERSION_LARGEFILE : PARTFILE_VERSION);

		file.WriteUInt32(CPath::GetModificationTime(m_PartPath));
		// hash
		file.WriteHash(m_abyFileHash);
		uint16 parts = m_hashlist.size();
		file.WriteUInt16(parts);
		for (int x = 0; x < parts; ++x) {
			file.WriteHash(m_hashlist[x]);
		}
		// tags
		#define FIXED_TAGS 15
		uint32 tagcount = m_taglist.size() + FIXED_TAGS + (m_gaplist.size()*2);
		if (!m_corrupted_list.empty()) {
			++tagcount;
		}

		if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
			++tagcount;
		}

		if (GetLastPublishTimeKadSrc()){
			++tagcount;
		}

		if (GetLastPublishTimeKadNotes()){
			++tagcount;
		}

		if (GetDlActiveTime()){
			++tagcount;
		}

		file.WriteUInt32(tagcount);

		//#warning Kry - Where are lost by corruption and gained by compression?

		// 0 (unicoded part file name)
		// We write it with BOM to keep eMule compatibility. Note that the 'printable' filename is saved,
		// as presently the filename does not represent an actual file.
		CTagString(	FT_FILENAME,	GetFileName().GetPrintable()).WriteTagToFile( &file, utf8strOptBOM );
		CTagString(	FT_FILENAME,	GetFileName().GetPrintable()).WriteTagToFile( &file );                         // 1

		CTagIntSized(	FT_FILESIZE,	GetFileSize(), IsLargeFile() ? 64 : 32).WriteTagToFile( &file );// 2
		CTagIntSized(	FT_TRANSFERRED,	transferred, IsLargeFile() ? 64 : 32).WriteTagToFile( &file );   // 3
		CTagInt32(	FT_STATUS,	(m_paused?1:0)).WriteTagToFile( &file );                        // 4

		if ( IsAutoDownPriority() ) {
			CTagInt32( FT_DLPRIORITY,	(uint8)PR_AUTO	).WriteTagToFile( &file );	// 5
			CTagInt32( FT_OLDDLPRIORITY,	(uint8)PR_AUTO	).WriteTagToFile( &file );	// 6
		} else {
			CTagInt32( FT_DLPRIORITY,	m_iDownPriority	).WriteTagToFile( &file );	// 5
			CTagInt32( FT_OLDDLPRIORITY,	m_iDownPriority	).WriteTagToFile( &file );	// 6
		}

		CTagInt32( FT_LASTSEENCOMPLETE,	lsc			).WriteTagToFile( &file );	// 7

		if ( IsAutoUpPriority() ) {
			CTagInt32( FT_ULPRIORITY,	(uint8)PR_AUTO	).WriteTagToFile( &file );	// 8
			CTagInt32( FT_OLDULPRIORITY,	(uint8)PR_AUTO	).WriteTagToFile( &file );	// 9
		} else {
			CTagInt32( FT_ULPRIORITY,	GetUpPriority() ).WriteTagToFile( &file );	// 8
			CTagInt32( FT_OLDULPRIORITY,	GetUpPriority() ).WriteTagToFile( &file );	// 9
		}

		CTagInt32(FT_CATEGORY,       m_category).WriteTagToFile( &file );                       // 10
		CTagInt32(FT_ATTRANSFERRED,   statistic.GetAllTimeTransferred() & 0xFFFFFFFF).WriteTagToFile( &file );// 11
		CTagInt32(FT_ATTRANSFERREDHI, statistic.GetAllTimeTransferred() >>32).WriteTagToFile( &file );// 12
		CTagInt32(FT_ATREQUESTED,    statistic.GetAllTimeRequests()).WriteTagToFile( &file );	// 13
		CTagInt32(FT_ATACCEPTED,     statistic.GetAllTimeAccepts()).WriteTagToFile( &file );	// 14

		// corrupt part infos
		if (!m_corrupted_list.empty()) {
			wxString strCorruptedParts;
			std::list<uint16>::iterator it = m_corrupted_list.begin();
			for (; it != m_corrupted_list.end(); ++it) {
				uint16 uCorruptedPart = *it;
				if (!strCorruptedParts.IsEmpty()) {
					strCorruptedParts += ",";
				}
				strCorruptedParts += CFormat("%u") % uCorruptedPart;
			}
			wxASSERT( !strCorruptedParts.IsEmpty() );

			CTagString( FT_CORRUPTEDPARTS, strCorruptedParts ).WriteTagToFile( &file); // 11?
		}

		//AICH Filehash
		if (m_pAICHHashSet->HasValidMasterHash() && (m_pAICHHashSet->GetStatus() == AICH_VERIFIED)){
			CTagString aichtag(FT_AICH_HASH, m_pAICHHashSet->GetMasterHash().GetString() );
			aichtag.WriteTagToFile(&file); // 12?
		}

		if (GetLastPublishTimeKadSrc()){
			CTagInt32(FT_KADLASTPUBLISHSRC, GetLastPublishTimeKadSrc()).WriteTagToFile(&file); // 15?
		}

		if (GetLastPublishTimeKadNotes()){
			CTagInt32(FT_KADLASTPUBLISHNOTES, GetLastPublishTimeKadNotes()).WriteTagToFile(&file); // 16?
		}

		if (GetDlActiveTime()){
			CTagInt32(FT_DL_ACTIVE_TIME, GetDlActiveTime()).WriteTagToFile(&file); // 17
		}

		for (uint32 j = 0; j < (uint32)m_taglist.size();++j) {
			m_taglist[j].WriteTagToFile(&file);
		}

		// gaps
		unsigned i_pos = 0;
		for (CGapList::const_iterator it = m_gaplist.begin(); it != m_gaplist.end(); ++it) {
			wxString tagName = CFormat(" %u") % i_pos;

			// gap start = first missing byte but gap ends = first non-missing byte
			// in edonkey but I think its easier to user the real limits
			tagName[0] = FT_GAPSTART;
			CTagIntSized(tagName, it.start(),	IsLargeFile() ? 64 : 32).WriteTagToFile( &file );

			tagName[0] = FT_GAPEND;
			CTagIntSized(tagName, it.end() + 1, IsLargeFile() ? 64 : 32).WriteTagToFile( &file );

			++i_pos;
		}
	} catch (const wxString& error) {
		AddLogLineNS(CFormat( _("ERROR while saving partfile: %s (%s ==> %s)") )
			% error
			% m_partmetfilename
			% GetFileName() );

		file.Close();
		CPath::RemoveFile(tmpName);
		return false;
	} catch (const CIOFailureException& e) {
		AddLogLineCS(_("IO failure while saving partfile: ") + e.what());

		file.Close();
		CPath::RemoveFile(tmpName);
		return false;
	}

	file.Close();

	// Sanity-check the tmp before committing it as the new .part.met.
	// If the write somehow produced a zero-length file (no exception, but
	// disk full, fs quota, etc.) the existing .part.met must stay intact.
	sint64 tmpLength = tmpName.GetFileSize();
	if (tmpLength == wxInvalidOffset || tmpLength == 0) {
		theApp->ShowAlert( CFormat(
			_("Wrote 0/unknown bytes to '%s' -- preserving existing .part.met."))
			% tmpName,
			_("Message"), wxOK);
		CPath::RemoveFile(tmpName);
		return false;
	}

	// Two-rename atomic install.
	//   - The previous .part.met (if any) is promoted to .part.met.bak,
	//     overwriting any older backup. The recovery path in
	//     LoadPartFile(from_backup=true) reads this on next start if the
	//     live .part.met goes missing.
	//   - The freshly-written .part.met.tmp is renamed over the live name,
	//     atomically.
	if (m_fullname.FileExists()) {
		CPath::RenameFile(m_fullname, bakName, true /* overwrite */);
	}
	CPath::RenameFile(tmpName, m_fullname, true /* overwrite */);

	ClearMetDirty();
	ClearStatsDirty();
	m_lastMetSaveTick = ::GetTickCount64();
	return true;
}


void CPartFile::SaveSourceSeeds()
{
	#define MAX_SAVED_SOURCES 10

	// Kry - Sources seeds
	// Based on a Feature request, this saves the last MAX_SAVED_SOURCES
	// sources of the file, giving a 'seed' for the next run.
	// We save the last sources because:
	// 1 - They could be the hardest to get
	// 2 - They will more probably be available
	// However, if we have downloading sources, they have preference because
	// we probably have more credits on them.
	// Anyway, source exchange will get us the rest of the sources
	// This feature is currently used only on rare files (< 20 sources)
	//

	if (GetSourceCount()>20) {
		return;
	}

	CClientRefList source_seeds;
	int n_sources = 0;

	CClientRefList::iterator it = m_downloadingSourcesList.begin();
	for( ; it != m_downloadingSourcesList.end() && n_sources < MAX_SAVED_SOURCES; ++it) {
		if (!it->HasLowID()) {
			source_seeds.push_back(*it);
			++n_sources;
		}
	}

	if (n_sources < MAX_SAVED_SOURCES) {
		// Not enough downloading sources to fill the list, going to sources list
		if (GetSourceCount() > 0) {
			SourceSet::reverse_iterator rit = m_SrcList.rbegin();
			for ( ; ((rit != m_SrcList.rend()) && (n_sources<MAX_SAVED_SOURCES)); ++rit) {
				if (!rit->HasLowID()) {
					source_seeds.push_back(*rit);
					++n_sources;
				}
			}
		}
	}

	// Write the file
	if (!n_sources) {
		return;
	}

	const CPath seedsPath = m_fullname.AppendExt(".seeds");

	CFile file;
	file.Create(seedsPath, true);
	if (!file.IsOpened()) {
		AddLogLineN(CFormat( _("Failed to save part.met.seeds file for %s") )
			% m_fullname);
		return;
	}

	try {
		file.WriteUInt8(0); // v3, to avoid v2 clients choking on it.
		file.WriteUInt8(source_seeds.size());

		CClientRefList::iterator it2 = source_seeds.begin();
		for (; it2 != source_seeds.end(); ++it2) {
			CUpDownClient* cur_src = it2->GetClient();
			file.WriteUInt32(cur_src->GetUserIDHybrid());
			file.WriteUInt16(cur_src->GetUserPort());
			file.WriteHash(cur_src->GetUserHash());
			// CryptSettings - See SourceExchange V4
			const uint8 uSupportsCryptLayer	= cur_src->SupportsCryptLayer() ? 1 : 0;
			const uint8 uRequestsCryptLayer	= cur_src->RequestsCryptLayer() ? 1 : 0;
			const uint8 uRequiresCryptLayer	= cur_src->RequiresCryptLayer() ? 1 : 0;
			const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
			file.WriteUInt8(byCryptOptions);
		}

		/* v2: Added to keep track of too old seeds */
		/* according to https://docs.wxwidgets.org/3.2/classwx_date_time.html#a99263946a9a2ece83421411081c02378
		 * GetTicks() won't work after Jan 19, 2038, and suggest to use GetValue() instead and convert from ms
		 * to seconds.
		 * Since GetValue() returns a wxLongLong which is tagged obsolete, just use time(NULL)*/
		file.WriteUInt32((uint32) time(NULL));

		AddLogLineN(CFormat( wxPLURAL("Saved %i source seed for partfile: %s (%s)", "Saved %i source seeds for partfile: %s (%s)", n_sources) )
			% n_sources
			% m_fullname
			% GetFileName());
	} catch (const CIOFailureException& e) {
		AddDebugLogLineC( logPartFile, CFormat( "Error saving partfile's seeds file (%s - %s): %s" )
				% m_partmetfilename
				% GetFileName()
				% e.what() );

		n_sources = 0;
		file.Close();
		CPath::RemoveFile(seedsPath);
	}
}

void CPartFile::LoadSourceSeeds()
{
	CMemFile sources_data;

	bool valid_sources = false;

	const CPath seedsPath = m_fullname.AppendExt(".seeds");
	if (!seedsPath.FileExists()) {
		return;
	}

	CFile file(seedsPath, CFile::read);
	if (!file.IsOpened()) {
		// Exists but can't be opened. Should not happen. Probably permission problem, try to remove it.
		AddLogLineN(CFormat( _("Can't read seeds file for Partfile %s (%s)") )
			% m_partmetfilename
			% GetFileName() );
		CPath::RemoveFile(seedsPath);
		return;
	}

	bool badSeedsFile = false;
	try {
		uint8 src_count = file.ReadUInt8();

		bool bUseSX2Format = (src_count == 0);

		if (bUseSX2Format) {
			// v3 sources seeds
			src_count = file.ReadUInt8();
		}

		sources_data.WriteUInt16(src_count);

		for (int i = 0; i< src_count; ++i) {
			uint32 dwID = file.ReadUInt32();
			uint16 nPort = file.ReadUInt16();

			sources_data.WriteUInt32(bUseSX2Format ? dwID : wxUINT32_SWAP_ALWAYS(dwID));
			sources_data.WriteUInt16(nPort);
			sources_data.WriteUInt32(0);
			sources_data.WriteUInt16(0);

			if (bUseSX2Format) {
				sources_data.WriteHash(file.ReadHash());
				sources_data.WriteUInt8(file.ReadUInt8());
			}

		}

		if (!file.Eof()) {

			// v2: Added to keep track of too old seeds
			time_t timeFromFile = (time_t)file.ReadUInt32();

			// Time frame is 2 hours. More than enough to compile
			// your new aMule version!.
			if ((timeFromFile + MIN2S(120)) >= (uint32) time(NULL)) {
				valid_sources = true;
			}

		} else {
			// v1 has no time data. We can safely use
			// the sources, next time will be saved.
			valid_sources = true;
		}

		if (valid_sources) {
			sources_data.Seek(0);
			AddClientSources(&sources_data, SF_SOURCE_SEEDS, bUseSX2Format ? 4 : 1, bUseSX2Format);
		}

	} catch (const CSafeIOException& e) {
		AddLogLineN(CFormat( _("Error reading partfile's seeds file (%s - %s): %s") )
				% m_partmetfilename
				% GetFileName()
				% e.what() );
		badSeedsFile = true;
	}

	file.Close();
	if (badSeedsFile) {
		// If we got an exception reading it remove it.
		CPath::RemoveFile(seedsPath);
	}
}

void CPartFile::PartFileHashFinished(CKnownFile* result)
{
	m_lastDateChanged = result->m_lastDateChanged;
	bool errorfound = false;
	if (GetED2KPartHashCount() == 0){
		if (IsComplete(0, GetFileSize()-1)){
			if (result->GetFileHash() != GetFileHash()){
				// cppcheck-suppress zerodiv
				AddLogLineN(CFormat(wxPLURAL(
						"Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
						"Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
						0)
					)
					% 1
					% 0
					% GetFileName()
					% result->GetFileHash().Encode()
					% GetFileHash().Encode() );
				AddGap(0, GetFileSize()-1);
				errorfound = true;
			}
		}
	}
	else{
		for (size_t i = 0; i < m_hashlist.size(); ++i){
			// Kry - trel_ar's completed parts check on rehashing.
			// Very nice feature, if a file is completed but .part.met don't believe it,
			// update it.

			if (!( i < result->GetHashCount() && (result->GetPartHash(i) == GetPartHash(i)))){
				if (IsComplete(i)) {
					CMD4Hash wronghash;
					if ( i < result->GetHashCount() )
						wronghash = result->GetPartHash(i);

					AddLogLineN(CFormat(wxPLURAL(
							"Found corrupted part (%d) in %d part file %s - FileResultHash |%s| FileHash |%s|",
							"Found corrupted part (%d) in %d parts file %s - FileResultHash |%s| FileHash |%s|",
							GetED2KPartHashCount())
						)
						% ( i + 1 )
						% GetED2KPartHashCount()
						% GetFileName()
						% wronghash.Encode()
						% GetPartHash(i).Encode() );

					AddGap(i);
					errorfound = true;
				}
			} else {
				if (!IsComplete(i)){
					AddLogLineN(CFormat( _("Found completed part (%i) in %s") )
						% ( i + 1 )
						% GetFileName() );

					FillGap(i);
					uint64 partStart = i * PARTSIZE;
					uint64 partEnd   = partStart + GetPartSize(i) - 1;
					RemoveBlockFromList(partStart, partEnd);
				}
			}
		}
	}

	if (	!errorfound &&
		result->GetAICHHashset()->GetStatus() == AICH_HASHSETCOMPLETE &&
		status == PS_COMPLETING) {
		delete m_pAICHHashSet;
		m_pAICHHashSet = result->GetAICHHashset();
		result->SetAICHHashset(NULL);
		m_pAICHHashSet->SetOwner(this);
	}
	else if (status == PS_COMPLETING) {
		AddDebugLogLineN(logPartFile,
			CFormat("Failed to store new AICH Hashset for completed file: %s")
				% GetFileName());
	}


	delete result;
	if (!errorfound){
		if (status == PS_COMPLETING){
			CompleteFile(true);
			return;
		}
		else {
			AddLogLineN(CFormat( _("Finished rehashing %s") ) % GetFileName());
		}
	}
	else{
		SetStatus(PS_READY);
		SavePartFile();
		return;
	}
	SetStatus(PS_READY);
	SavePartFile();
	theApp->sharedfiles->SafeAddKFile(this);
}

void CPartFile::AddGap(uint64 start, uint64 end)
{
	m_gaplist.AddGap(start, end);
	MarkMetDirty();
	UpdateDisplayedInfo();
}

void CPartFile::AddGap(uint16 part)
{
	m_gaplist.AddGap(part);
	MarkMetDirty();
	UpdateDisplayedInfo();
}

bool CPartFile::IsAlreadyRequested(uint64 start, uint64 end)
{
	std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
	for (; it != m_requestedblocks_list.end(); ++it) {
		Requested_Block_Struct* cur_block =  *it;

		if ((start <= cur_block->EndOffset) && (end >= cur_block->StartOffset)) {
			return true;
		}
	}
	return false;
}

bool CPartFile::GetNextEmptyBlockInPart(uint16 partNumber, Requested_Block_Struct *result)
{
	// Find start of this part
	uint64 partStart = (PARTSIZE * partNumber);
	uint64 start = partStart;

	// What is the end limit of this block, i.e. can't go outside part (or filesize)
	uint64 partEnd = partStart + GetPartSize(partNumber) - 1;
	// Loop until find a suitable gap and return true, or no more gaps and return false
	CGapList::const_iterator it = m_gaplist.begin();
	while (true) {
		bool noGap = true;
		uint64 gapStart, end;

		// Find the first gap from the start position
		for (; it != m_gaplist.end(); ++it) {
			gapStart = it.start();
			end = it.end();

			// Want gaps that overlap start<->partEnd
			if (gapStart <= partEnd && end >= start) {
				noGap = false;
				break;
			} else if (gapStart > partEnd) {
				break;
			}
		}

		// If no gaps after start, exit
		if (noGap) {
			return false;
		}
		// Update start position if gap starts after current pos
		if (start < gapStart) {
			start = gapStart;
		}
		// Find end, keeping within the max block size and the part limit
		uint64 blockLimit = partStart + (BLOCKSIZE * (((start - partStart) / BLOCKSIZE) + 1)) - 1;
		if (end > blockLimit) {
			end = blockLimit;
		}
		if (end > partEnd) {
			end = partEnd;
		}
		// If this gap has not already been requested, we have found a valid entry
		if (!IsAlreadyRequested(start, end)) {
			// Was this block to be returned
			if (result != NULL) {
				result->StartOffset = start;
				result->EndOffset = end;
				md4cpy(result->FileID, GetFileHash().GetHash());
				result->transferred = 0;
			}
			return true;
		} else {
			// Reposition to end of that gap
			start = end + 1;
		}
		// If tried all gaps then break out of the loop
		if (end == partEnd) {
			break;
		}
	}
	// No suitable gap found
	return false;
}


void CPartFile::FillGap(uint64 start, uint64 end)
{
	m_gaplist.FillGap(start, end);
	MarkMetDirty();
	UpdateCompletedInfos();
	UpdateDisplayedInfo();
}

void CPartFile::FillGap(uint16 part)
{
	m_gaplist.FillGap(part);
	MarkMetDirty();
	UpdateCompletedInfos();
	UpdateDisplayedInfo();
}


void CPartFile::UpdateCompletedInfos()
{
	uint64 allgaps = m_gaplist.GetGapSize();

	percentcompleted = (1.0 - (double)allgaps/GetFileSize()) * 100.0;
	completedsize = GetFileSize() - allgaps;
}


void CPartFile::WritePartStatus(CMemFile* file)
{
	uint16 parts = GetED2KPartCount();
	file->WriteUInt16(parts);
	uint16 done = 0;
	while (done != parts){
		uint8 towrite = 0;
		for (uint32 i = 0;i != 8;++i) {
			if (IsComplete(done)) {
				towrite |= (1<<i);
			}
			++done;
			if (done == parts) {
				break;
			}
		}
		file->WriteUInt8(towrite);
	}
}

void CPartFile::WriteCompleteSourcesCount(CMemFile* file)
{
	file->WriteUInt16(m_nCompleteSourcesCount);
}

uint32 CPartFile::Process(uint8 m_icounter)
{
	// Partfiles have ~20 EC-exported fields that change frequently and
	// independently (status, speed, transfer counters, source counts, gap
	// list, hashed-part count, …). Per-field hooks have diminishing
	// returns when the file is actively transferring anyway, so use a
	// coarse mark here — Process() runs once per second per partfile and
	// the file genuinely has new state every tick during active download.
	MarkECChanged();

	uint16 old_trans;
	uint64 dwCurTick = ::GetTickCount64();

	// Flush on buffer-full, time-limit, or pending hash drain.
	// HasPendingHashWork() bypass drains Phase 3 at Process()-tick
	// rate (~100 ms) instead of every 60 s when the file is idle.
	if (	(m_nTotalBufferData > thePrefs::GetFileBufferSize()) ||
		(dwCurTick > (m_nLastBufferFlushTime + BUFFER_TIME_LIMIT)) ||
		HasPendingHashWork()) {
		FlushBuffer();
	}


	// check if we want new sources from server --> MOVED for 16.40 version
	old_trans=transferingsrc;
	transferingsrc = 0;
	kBpsDown = 0.0;

	if (m_icounter < 10) {
		// Update only downloading sources.
		CClientRefList::iterator it = m_downloadingSourcesList.begin();
		for( ; it != m_downloadingSourcesList.end(); ) {
			CUpDownClient *cur_src = it++->GetClient();
			if(cur_src->GetDownloadState() == DS_DOWNLOADING) {
				++transferingsrc;
				kBpsDown += cur_src->TickDownloadAndMeasure();
			}
		}
	} else {
		// Update all sources (including downloading sources)
		for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
			CUpDownClient* cur_src = it++->GetClient();
			switch (cur_src->GetDownloadState()) {
				case DS_DOWNLOADING: {
					++transferingsrc;
					kBpsDown += cur_src->TickDownloadAndMeasure();
					break;
				}
				case DS_BANNED: {
					break;
				}
				case DS_ERROR: {
					break;
				}
				case DS_LOWTOLOWIP: {
					if (cur_src->HasLowID() && !theApp->CanDoCallback(cur_src->GetServerIP(), cur_src->GetServerPort())) {
						// If we are almost maxed on sources,
						// slowly remove these client to see
						// if we can find a better source.
						if (((dwCurTick - lastpurgetime) > 30000) &&
							(GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8))) {
							RemoveSource(cur_src);
							lastpurgetime = dwCurTick;
							break;
						}
					} else {
						cur_src->SetDownloadState(DS_ONQUEUE);
					}

					break;
				}
				case DS_NONEEDEDPARTS: {
					// we try to purge noneeded source, even without reaching the limit
					if((dwCurTick - lastpurgetime) > 40000) {
						if(!cur_src->SwapToAnotherFile(false , false, false , NULL)) {
							//however we only delete them if reaching the limit
							if (GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) {
								RemoveSource(cur_src);
								lastpurgetime = dwCurTick;
								break; //Johnny-B - nothing more to do here (good eye!)
							}
						} else {
							lastpurgetime = dwCurTick;
							break;
						}
					}
					// doubled reasktime for no needed parts - save connections and traffic
					if (	!((!cur_src->GetLastAskedTime()) ||
						 (dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME*2)) {
						break;
					}
					// Recheck this client to see if still NNP..
					// Set to DS_NONE so that we force a TCP reask next time..
					cur_src->SetDownloadState(DS_NONE);

					break;
				}
				case DS_ONQUEUE: {
					if( cur_src->IsRemoteQueueFull()) {
						if(	((dwCurTick - lastpurgetime) > 60000) &&
							(GetSourceCount() >= (thePrefs::GetMaxSourcePerFile()*.8 )) ) {
							RemoveSource( cur_src );
							lastpurgetime = dwCurTick;
							break; //Johnny-B - nothing more to do here (good eye!)
						}
					}

					// Give up to 1 min for UDP to respond..
					// If we are within on min on TCP, do not try..
					if (	theApp->IsConnected() &&
						(	(!cur_src->GetLastAskedTime()) ||
							(dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME-20000)) {
						cur_src->UDPReaskForDownload();
					}

					// No break here, since the next case takes care of asking for downloads.
				}
				/* fall through */
				case DS_CONNECTING:
				case DS_TOOMANYCONNS:
				case DS_NONE:
				case DS_WAITCALLBACK:
				case DS_WAITCALLBACKKAD:	{
					if (	theApp->IsConnected() &&
						(	(!cur_src->GetLastAskedTime()) ||
							(dwCurTick - cur_src->GetLastAskedTime()) > FILEREASKTIME)) {
						if (!cur_src->AskForDownload()) {
							// I left this break here just as a reminder
							// just in case re rearange things..
							break;
						}
					}
					break;
				}
			}
		}

		/* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
		if (IsA4AFAuto() && ((!m_LastNoNeededCheck) || (dwCurTick - m_LastNoNeededCheck > 900000))) {
			m_LastNoNeededCheck = dwCurTick;
			for ( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
				CUpDownClient *cur_source = it++->GetClient();
				uint8 download_state=cur_source->GetDownloadState();
				if( download_state != DS_DOWNLOADING
				&& cur_source->GetRequestFile()
				&& ((!cur_source->GetRequestFile()->IsA4AFAuto()) || download_state == DS_NONEEDEDPARTS))
				{
					cur_source->SwapToAnotherFile(false, false, false, this);
				}
			}
		}
		/* eMule 0.30c implementation, i give it a try (Creteil) END ... */

		// swap No needed partfiles if possible

		if (((old_trans==0) && (transferingsrc>0)) || ((old_trans>0) && (transferingsrc==0))) {
			SetStatus(status);
		}

		// Kad source search
		if( GetMaxSourcePerFileUDP() > GetSourceCount()){
			//Once we can handle lowID users in Kad, we remove the second IsConnected
			if (theApp->downloadqueue->DoKademliaFileRequest() && (Kademlia::CKademlia::GetTotalFile() < KADEMLIATOTALFILE) && (dwCurTick > m_LastSearchTimeKad) &&  Kademlia::CKademlia::IsConnected() && theApp->IsConnected() && !IsStopped()){
				//Kademlia
				theApp->downloadqueue->SetLastKademliaFileRequest();

				if (GetKadFileSearchID()) {
					/*	This will never happen anyway. We're talking a
						1h timespan and searches are at max 45secs */
					Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
				}

				Kademlia::CUInt128 kadFileID(GetFileHash().GetHash());
				Kademlia::CSearch* pSearch = Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::FILE, true, kadFileID);
				AddDebugLogLineN(logKadSearch, CFormat("Preparing a Kad Search for '%s'") % GetFileName());
				if (pSearch) {
					AddDebugLogLineN(logKadSearch, CFormat("Kad lookup started for '%s'") % GetFileName());
					if(m_TotalSearchesKad < 7) {
						m_TotalSearchesKad++;
					}
					m_LastSearchTimeKad = dwCurTick + (KADEMLIAREASKTIME*m_TotalSearchesKad);
					SetKadFileSearchID(pSearch->GetSearchID());
				}
			}
		} else {
			if(GetKadFileSearchID()) {
				Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
			}
		}

		// check if we want new sources from server
		if (	!m_localSrcReqQueued &&
			(	(!m_lastsearchtime) ||
				(dwCurTick - m_lastsearchtime) > SERVERREASKTIME) &&
			theApp->IsConnectedED2K() &&
			thePrefs::GetMaxSourcePerFileSoft() > GetSourceCount() &&
			!m_stopped ) {
			m_localSrcReqQueued = true;
			theApp->downloadqueue->SendLocalSrcRequest(this);
		}

		// calculate datarate, set limit etc.
	}

	++m_count;

	// Kry - does the 3 / 30 difference produce too much flickering or CPU?
	if (m_count >= 30) {
		m_count = 0;
		UpdateAutoDownPriority();
		UpdateDisplayedInfo();
		if(m_bPercentUpdated == false) {
			UpdateCompletedInfos();
		}
		m_bPercentUpdated = false;
	}

	// release file handle if unused for some time
	m_hpartfile.Release();

	return (uint32)(kBpsDown*1024.0);
}

bool CPartFile::CanAddSource(uint32 userid, uint16 port, uint32 serverip, uint16 serverport, uint8* pdebug_lowiddropped, bool ed2kID)
{

	//The incoming ID could have the userid in the Hybrid format..
	uint32 hybridID = 0;
	if( ed2kID ) {
		if (IsLowID(userid)) {
			hybridID = userid;
		} else {
			hybridID = wxUINT32_SWAP_ALWAYS(userid);
		}
	} else {
		hybridID = userid;
		if (!IsLowID(userid)) {
			userid = wxUINT32_SWAP_ALWAYS(userid);
		}
	}

	// A LowID of zero has no callback semantics — server LowID
	// assignments start at 1, so an OP_CALLBACKREQUEST aimed at LowID
	// 0 can't be routed to anything. Source-exchange and server source
	// lists skip the IsGoodIP filter for LowID ids, so such entries
	// otherwise sail through, become a CUpDownClient, and burn ~60 s
	// of idle-socket time inside listensocket's pool before
	// CClientTCPSocket::CheckTimeOut cleans them up (#786).
	//
	// HighID 0 (IP 0.0.0.0) is intentionally left to the upstream
	// IsGoodIP filter that already gates both callers; this guard is
	// scoped narrowly to the LowID case.
	if (IsLowID(hybridID) && hybridID == 0) {
		return false;
	}

	// MOD Note: Do not change this part - Merkur
	if (theApp->IsConnectedED2K()) {
		if(::IsLowID(theApp->GetED2KID())) {
			if(theApp->GetED2KID() == userid && theApp->serverconnect->GetCurrentServer()->GetIP() == serverip && theApp->serverconnect->GetCurrentServer()->GetPort() == serverport ) {
				return false;
			}
			if(theApp->GetPublicIP() == userid) {
				return false;
			}
		} else {
			if(theApp->GetED2KID() == userid && thePrefs::GetPort() == port) {
				return false;
			}
		}
	}

	if (Kademlia::CKademlia::IsConnected()) {
		if(!Kademlia::CKademlia::IsFirewalled()) {
			if(Kademlia::CKademlia::GetIPAddress() == hybridID && thePrefs::GetPort() == port) {
				return false;
			}
		}
	}

	//This allows *.*.*.0 clients to not be removed if Ed2kID == false
	if ( IsLowID(hybridID) && theApp->IsFirewalled()) {
		if (pdebug_lowiddropped) {
			(*pdebug_lowiddropped)++;
		}
		return false;
	}
	// MOD Note - end
	return true;
}

void CPartFile::AddSources(CMemFile& sources,uint32 serverip, uint16 serverport, unsigned origin, bool bWithObfuscationAndHash)
{
	uint8 count = sources.ReadUInt8();
	uint8 debug_lowiddropped = 0;
	uint8 debug_possiblesources = 0;
	CMD4Hash achUserHash;

	if (m_stopped) {
		// since we may received multiple search source UDP results we have to "consume" all data of that packet
		AddDebugLogLineN(logPartFile, "Trying to add sources for a stopped file");
		sources.Seek(count*(4+2), wxFromCurrent);
		return;
	}

	for (int i = 0;i != count;++i) {
		uint32 userid = sources.ReadUInt32();
		uint16 port   = sources.ReadUInt16();

		uint8 byCryptOptions = 0;
		if (bWithObfuscationAndHash){
			byCryptOptions = sources.ReadUInt8();
			if ((byCryptOptions & 0x80) > 0) {
				achUserHash = sources.ReadHash();
			}

			if ((thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x01/*supported*/) > 0 && (byCryptOptions & 0x80) == 0)
				|| (thePrefs::IsClientCryptLayerSupported() && (byCryptOptions & 0x02/*requested*/) > 0 && (byCryptOptions & 0x80) == 0)) {
				AddDebugLogLineN(logPartFile, CFormat("Server didn't provide UserHash for source %u, even if it was expected to (or local obfuscationsettings changed during serverconnect") % userid);
			} else if (!thePrefs::IsClientCryptLayerRequested() && (byCryptOptions & 0x02/*requested*/) == 0 && (byCryptOptions & 0x80) != 0) {
				AddDebugLogLineN(logPartFile, CFormat("Server provided UserHash for source %u, even if it wasn't expected to (or local obfuscationsettings changed during serverconnect") % userid);
			}
		}


		// "Filter LAN IPs" and "IPfilter" the received sources IP addresses
		if (!IsLowID(userid)) {
			// check for 0-IP, localhost and optionally for LAN addresses
			if ( !IsGoodIP(userid, thePrefs::FilterLanIPs()) ) {
				continue;
			}
			if (theApp->ipfilter->IsFiltered(userid)) {
				continue;
			}
		}

		if (!CanAddSource(userid, port, serverip, serverport, &debug_lowiddropped)) {
			continue;
		}

		if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
			++debug_possiblesources;
			CUpDownClient* newsource = new CUpDownClient(port,userid,serverip,serverport,this, true, true);

			newsource->SetSourceFrom((ESourceFrom)origin);
			newsource->SetConnectOptions(byCryptOptions, true, false);

			if ((byCryptOptions & 0x80) != 0) {
				newsource->SetUserHash(achUserHash);
			}

			theApp->downloadqueue->CheckAndAddSource(this,newsource);
		} else {
			AddDebugLogLineN(logPartFile, "Consuming a packet because of max sources reached");
			// Since we may receive multiple search source UDP results we have to "consume" all data of that packet
			// This '+1' is added because 'i' counts from 0.
			sources.Seek((count-(i+1))*(4+2), wxFromCurrent);
			if (GetKadFileSearchID()) {
				Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
			}
			break;
		}
	}
}

void CPartFile::UpdatePartsInfo()
{
	if( !IsPartFile() ) {
		CKnownFile::UpdatePartsInfo();
		return;
	}

	// Cache part count
	uint16 partcount = GetPartCount();
	bool flag = (time(NULL) - m_nCompleteSourcesTime > 0);

	// Ensure the frequency-list is ready
	if ( m_SrcpartFrequency.size() != GetPartCount() ) {
		m_SrcpartFrequency.clear();
		m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);
	}

	// Find number of available parts
	uint16 availablecounter = 0;
	for ( uint16 i = 0; i < partcount; ++i ) {
		if ( m_SrcpartFrequency[i] )
			++availablecounter;
	}

	if ( ( availablecounter == partcount ) && ( m_availablePartsCount < partcount ) ) {
		lastseencomplete = time(NULL);
		MarkMetDirty();
	}

	m_availablePartsCount = availablecounter;

	if ( flag ) {
		ArrayOfUInts16 count;

		count.reserve(GetSourceCount());

		for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
			CUpDownClient* client = it->GetClient();
			if ( !client->GetUpPartStatus().empty() && client->GetUpPartCount() == partcount ) {
				count.push_back(client->GetUpCompleteSourcesCount());
			}
		}

		m_nCompleteSourcesCount = m_nCompleteSourcesCountLo = m_nCompleteSourcesCountHi = 0;

		for (uint16 i = 0; i < partcount; ++i)	{
			if( !i )	{
				m_nCompleteSourcesCount = m_SrcpartFrequency[i];
			}
			else if( m_nCompleteSourcesCount > m_SrcpartFrequency[i]) {
				m_nCompleteSourcesCount = m_SrcpartFrequency[i];
			}
		}
		count.push_back(m_nCompleteSourcesCount);

		int32 n = count.size();
		if (n > 0) {
			std::sort(count.begin(), count.end(), std::less<uint16>());

			// calculate range
			int32 i= n >> 1;		// (n / 2)
			int32 j= (n * 3) >> 2;	// (n * 3) / 4
			int32 k= (n * 7) >> 3;	// (n * 7) / 8

			//When still a part file, adjust your guesses by 20% to what you see..


			if (n < 5) {
				//Not many sources, so just use what you see..
				// welcome to 'plain stupid code'
				// m_nCompleteSourcesCount;
				m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
				m_nCompleteSourcesCountHi= m_nCompleteSourcesCount;
			} else if (n < 20) {
				// For low guess and normal guess count
				//	 If we see more sources then the guessed low and normal, use what we see.
				//	 If we see less sources then the guessed low, adjust network accounts for 80%,
				//  we account for 20% with what we see and make sure we are still above the normal.
				// For high guess
				//  Adjust 80% network and 20% what we see.
				if ( count[i] < m_nCompleteSourcesCount ) {
					m_nCompleteSourcesCountLo = m_nCompleteSourcesCount;
				} else {
					m_nCompleteSourcesCountLo =
						(uint16)((float)(count[i]*.8) +
							 (float)(m_nCompleteSourcesCount*.2));
				}
				m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
				m_nCompleteSourcesCountHi =
					(uint16)((float)(count[j]*.8) +
						 (float)(m_nCompleteSourcesCount*.2));
				if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
					m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
				}
			} else {
				// Many sources
				// ------------
				// For low guess
				//	 Use what we see.
				// For normal guess
				//	 Adjust network accounts for 80%, we account for 20% with what
				//  we see and make sure we are still above the low.
				// For high guess
				//  Adjust network accounts for 80%, we account for 20% with what
				//  we see and make sure we are still above the normal.

				m_nCompleteSourcesCountLo= m_nCompleteSourcesCount;
				m_nCompleteSourcesCount= (uint16)((float)(count[j]*.8)+(float)(m_nCompleteSourcesCount*.2));
				if( m_nCompleteSourcesCount < m_nCompleteSourcesCountLo ) {
					m_nCompleteSourcesCount = m_nCompleteSourcesCountLo;
				}
				m_nCompleteSourcesCountHi= (uint16)((float)(count[k]*.8)+(float)(m_nCompleteSourcesCount*.2));
				if( m_nCompleteSourcesCountHi < m_nCompleteSourcesCount ) {
					m_nCompleteSourcesCountHi = m_nCompleteSourcesCount;
				}
			}
		}
		m_nCompleteSourcesTime = time(NULL) + (60);
	}
	UpdateDisplayedInfo();
}

// [Maella -Enhanced Chunk Selection- (based on jicxicmic)]
bool CPartFile::GetNextRequestedBlock(CUpDownClient* sender,
							std::vector<Requested_Block_Struct*>& toadd, uint16& count)
{

	// The purpose of this function is to return a list of blocks (~180KB) to
	// download. To avoid a prematurely stop of the downloading, all blocks that
	// are requested from the same source must be located within the same
	// chunk (=> part ~9MB).
	//
	// The selection of the chunk to download is one of the CRITICAL parts of the
	// edonkey network. The selection algorithm must insure the best spreading
	// of files.
	//
	// The selection is based on 4 criteria:
	//  1.  Frequency of the chunk (availability), very rare chunks must be downloaded
	//      as quickly as possible to become a new available source.
	//  2.  Parts used for preview (first + last chunk), preview or check a
	//      file (e.g. movie, mp3)
	//  3.  Request state (downloading in process), try to ask each source for another
	//      chunk. Spread the requests between all sources.
	//  4.  Completion (shortest-to-complete), partially retrieved chunks should be
	//      completed before starting to download other one.
	//
	// The frequency criterion defines three zones: very rare (<10%), rare (<50%)
	// and common (>30%). Inside each zone, the criteria have a specific weight, used
	// to calculate the priority of chunks. The chunk(s) with the highest
	// priority (highest=0, lowest=0xffff) is/are selected first.
	//
	//          very rare   (preview)       rare                      common
	//    0% <---- +0 pt ----> 10% <----- +10000 pt -----> 50% <---- +20000 pt ----> 100%
	// 1.  <------- frequency: +25*frequency pt ----------->
	// 2.  <- preview: +1 pt --><-------------- preview: set to 10000 pt ------------->
	// 3.                       <------ request: download in progress +20000 pt ------>
	// 4a. <- completion: 0% +100, 25% +75 .. 100% +0 pt --><-- !req => completion --->
	// 4b.                                                  <--- req => !completion -->
	//
	// Unrolled, the priority scale is:
	//
	// 0..xxxx       unrequested and requested very rare chunks
	// 10000..1xxxx  unrequested rare chunks + unrequested preview chunks
	// 20000..2xxxx  unrequested common chunks (priority to the most complete)
	// 30000..3xxxx  requested rare chunks + requested preview chunks
	// 40000..4xxxx  requested common chunks (priority to the least complete)
	//
	// This algorithm usually selects first the rarest chunk(s). However, partially
	// complete chunk(s) that is/are close to completion may overtake the priority
	// (priority inversion).
	// For the common chunks, the algorithm tries to spread the download between
	// the sources
	//

	// Check input parameters
	if ( sender->GetPartStatus().empty() ) {
		return false;
	}
	// Define and create the list of the chunks to download
	const uint16 partCount = GetPartCount();
	ChunkList chunksList;

	// Main loop
	uint16 newBlockCount = 0;
	while(newBlockCount != count) {
		// Create a request block structure if a chunk has been previously selected
		if(sender->GetLastPartAsked() != 0xffff) {
			Requested_Block_Struct* pBlock = new Requested_Block_Struct;
			if(GetNextEmptyBlockInPart(sender->GetLastPartAsked(), pBlock) == true) {
				// Keep a track of all pending requested blocks
				m_requestedblocks_list.push_back(pBlock);
				// Update list of blocks to return
				toadd.push_back(pBlock);
				newBlockCount++;
				// Skip end of loop (=> CPU load)
				continue;
			} else {
				// All blocks for this chunk have been already requested
				delete pBlock;
				// => Try to select another chunk
				sender->SetLastPartAsked(0xffff);
			}
		}

		// Check if a new chunk must be selected (e.g. download starting, previous chunk complete)
		if(sender->GetLastPartAsked() == 0xffff) {
			// Quantify all chunks (create list of chunks to download)
			// This is done only one time and only if it is necessary (=> CPU load)
			if(chunksList.empty()) {
				// Identify the locally missing part(s) that this source has
				for(uint16 i=0; i < partCount; ++i) {
					if(sender->IsPartAvailable(i) == true && GetNextEmptyBlockInPart(i, NULL) == true) {
						// Create a new entry for this chunk and add it to the list
						Chunk newEntry;
						newEntry.part = i;
						newEntry.frequency = m_SrcpartFrequency[i];
						chunksList.push_back(newEntry);
					}
				}

				// Check if any blocks(s) could be downloaded
				if(chunksList.empty()) {
					break; // Exit main loop while()
				}

				// Define the bounds of the three zones (very rare, rare)
				// more depending on available sources
				uint8 modif=10;
				if (GetSourceCount()>800) {
					modif=2;
				} else if (GetSourceCount()>200) {
					modif=5;
				}
				uint16 limit= modif*GetSourceCount()/ 100;
				if (limit==0) {
					limit=1;
				}
				const uint16 veryRareBound = limit;
				const uint16 rareBound = 2*limit;

				// Cache Preview state (Criterion 2)
				FileType type = GetFiletype(GetFileName());
				const bool isPreviewEnable =
					thePrefs::GetPreviewPrio() &&
					(type == ftArchive || type == ftVideo);

				// Collect and calculate criteria for all chunks
				for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
					Chunk& cur_chunk = *it;

					// Offsets of chunk
					const uint64 uStart = cur_chunk.part * PARTSIZE;
					const uint64 uEnd   = uStart + GetPartSize(cur_chunk.part) - 1;
					// Criterion 2. Parts used for preview
					// Remark: - We need to download the first part and the last part(s).
					//        - When the last part is very small, it's necessary to
					//          download the two last parts.
					bool critPreview = false;
					if(isPreviewEnable == true) {
						if(cur_chunk.part == 0) {
							critPreview = true; // First chunk
						} else if(cur_chunk.part == partCount-1) {
							critPreview = true; // Last chunk
						} else if(cur_chunk.part == partCount-2) {
							// Last chunk - 1 (only if last chunk is too small)
							const uint32 sizeOfLastChunk = GetFileSize() - uEnd;
							if(sizeOfLastChunk < PARTSIZE/3) {
								critPreview = true; // Last chunk - 1
							}
						}
					}

					// Criterion 3. Request state (downloading in process from other source(s))
					// => CPU load
					const bool critRequested =
						cur_chunk.frequency > veryRareBound &&
						IsAlreadyRequested(uStart, uEnd);

					// Criterion 4. Completion
					// PARTSIZE instead of GetPartSize() favours the last chunk - but that may be intentional
					uint32 partSize = PARTSIZE - m_gaplist.GetGapSize(cur_chunk.part);
					const uint16 critCompletion = (uint16)(partSize/(PARTSIZE/100)); // in [%]

					// Calculate priority with all criteria
					if(cur_chunk.frequency <= veryRareBound) {
						// 0..xxxx unrequested + requested very rare chunks
						cur_chunk.rank = (25 * cur_chunk.frequency) + // Criterion 1
						((critPreview == true) ? 0 : 1) + // Criterion 2
						(100 - critCompletion); // Criterion 4
					} else if(critPreview == true) {
						// 10000..10100  unrequested preview chunks
						// 30000..30100  requested preview chunks
						cur_chunk.rank = ((critRequested == false) ? 10000 : 30000) + // Criterion 3
						(100 - critCompletion); // Criterion 4
					} else if(cur_chunk.frequency <= rareBound) {
						// 10101..1xxxx  unrequested rare chunks
						// 30101..3xxxx  requested rare chunks
						cur_chunk.rank = (25 * cur_chunk.frequency) +                 // Criterion 1
						((critRequested == false) ? 10101 : 30101) + // Criterion 3
						(100 - critCompletion); // Criterion 4
					} else {
						// common chunk
						if(critRequested == false) { // Criterion 3
							// 20000..2xxxx  unrequested common chunks
							cur_chunk.rank = 20000 + // Criterion 3
							(100 - critCompletion); // Criterion 4
						} else {
							// 40000..4xxxx  requested common chunks
							// Remark: The weight of the completion criterion is inversed
							//         to spead the requests over the completing chunks.
							//         Without this, the chunk closest to completion will
							//         received every new sources.
							cur_chunk.rank = 40000 + // Criterion 3
							(critCompletion); // Criterion 4
						}
					}
				}
			}

			// Select the next chunk to download
			if(!chunksList.empty()) {
				// Find and count the chunk(s) with the highest priority
				uint16 chunkCount = 0; // Number of found chunks with same priority
				uint16 rank = 0xffff; // Highest priority found

				// Collect and calculate criteria for all chunks
				for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
					const Chunk& cur_chunk = *it;
					if(cur_chunk.rank < rank) {
						chunkCount = 1;
						rank = cur_chunk.rank;
					} else if(cur_chunk.rank == rank) {
						++chunkCount;
					}
				}

				// Use a random access to avoid that everybody tries to download the
				// same chunks at the same time (=> spread the selected chunk among clients)
				uint16 randomness = 1 + (int) (((float)(chunkCount-1))*rand()/(RAND_MAX+1.0));

				for (ChunkList::iterator it = chunksList.begin(); it != chunksList.end(); ++it) {
					const Chunk& cur_chunk = *it;
					if(cur_chunk.rank == rank) {
						randomness--;
						if(randomness == 0) {
							// Selection process is over
							sender->SetLastPartAsked(cur_chunk.part);
							// Remark: this list might be reused up to *count times
							chunksList.erase(it);
							break; // exit loop for()
						}
					}
				}
			} else {
				// There is no remaining chunk to download
				break; // Exit main loop while()
			}
		}
	}
	// Return the number of the blocks
	count = newBlockCount;
	// Return
	return (newBlockCount > 0);
}
// Maella end


void  CPartFile::RemoveBlockFromList(uint64 start,uint64 end)
{
	std::list<Requested_Block_Struct*>::iterator it = m_requestedblocks_list.begin();
	while (it != m_requestedblocks_list.end()) {
		std::list<Requested_Block_Struct*>::iterator it2 = it++;

		if ((*it2)->StartOffset <= start && (*it2)->EndOffset >= end) {
			m_requestedblocks_list.erase(it2);
		}
	}
}


void CPartFile::RemoveAllRequestedBlocks(void)
{
	m_requestedblocks_list.clear();
}


void CPartFile::CompleteFile(bool bIsHashingDone)
{
	if (GetKadFileSearchID()) {
		Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), false);
	}

	theApp->downloadqueue->RemoveLocalServerRequest(this);

	AddDebugLogLineN( logPartFile, wxString( "CPartFile::CompleteFile: Hash " ) + ( bIsHashingDone ? "done" : "not done" ) );

	if (!bIsHashingDone) {
		SetStatus(PS_COMPLETING);
		kBpsDown = 0.0;

		CPath partFile = m_partmetfilename.RemoveExt();
		CThreadScheduler::AddTask(new CHashingTask(GetFilePath(), partFile, this));
		return;
	} else {
		StopFile();
		m_is_A4AF_auto=false;
		SetStatus(PS_COMPLETING);
		// guess I was wrong about not need to spawn a thread ...
		// It is if the temp and incoming dirs are on different
		// partitions/drives and the file is large...[oz]
		//

		PerformFileComplete();


	}
	if (thePrefs::ShowCatTabInfos()) {
		Notify_ShowUpdateCatTabTitles();
	}
	UpdateDisplayedInfo(true);
}


void CPartFile::CompleteFileEnded(bool errorOccured, const CPath& newname)
{
	if (errorOccured) {
		m_paused = true;
		SetStatus(PS_ERROR);
		AddLogLineC(CFormat( _("Unexpected error while completing %s. File paused") )% GetFileName() );
	} else {
		m_fullname = newname;

		SetFilePath(m_fullname.GetPath());
		SetFileName(m_fullname.GetFullName());
		m_lastDateChanged = CPath::GetModificationTime(m_fullname);

		SetStatus(PS_COMPLETE);
		m_paused = false;
		ClearPriority();


		// Remove from list of canceled files in case it was canceled once upon a time
		if (theApp->canceledfiles->Remove(GetFileHash())) {
			theApp->canceledfiles->Save();
		}

		// Mark as known (checks if it's already known),
		// also updates search files
		theApp->knownfiles->SafeAddKFile(this);

		// remove the file from the suspended uploads list
		theApp->uploadqueue->ResumeUpload(GetFileHash());
		theApp->downloadqueue->RemoveFile(this, true);
		theApp->sharedfiles->SafeAddKFile(this);
		UpdateDisplayedInfo(true);

		// republish that file to the ed2k-server to update the 'FT_COMPLETE_SOURCES' counter on the server.
		theApp->sharedfiles->RepublishFile(this);

		// Ensure that completed shows the correct value
		completedsize = GetFileSize();

		// clear the blackbox to free up memory
		m_CorruptionBlackBox->Free();

		AddLogLineC(CFormat( _("Finished downloading: %s") ) % GetFileName() );
#ifndef AMULE_DAEMON
		if (thePrefs::ShowNotifications()) {
			wxNotificationMessage *notification = new wxNotificationMessage ();
			notification->SetTitle("aMule");
			notification->SetMessage(CFormat( _("Finished downloading:\n%s") ) % GetFileName() );
			notification->SetFlags(wxICON_INFORMATION);
			notification->Show(7);
			delete notification;
		}
#endif
	}

	theApp->downloadqueue->StartNextFile(this);
}


void CPartFile::PerformFileComplete()
{
	// add this file to the suspended uploads list
	theApp->uploadqueue->SuspendUpload(GetFileHash(), false);
	FlushBuffer();

	// close permanent handle
	if (m_hpartfile.IsOpened()) {
		m_hpartfile.Close();
	}

	// Schedule task for completion of the file
	CThreadScheduler::AddTask(new CCompletionTask(this));
}


void  CPartFile::RemoveAllSources(bool bTryToSwap)
{
	for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end();) {
		CUpDownClient* cur_src = it++->GetClient();
		if (bTryToSwap) {
			if (!cur_src->SwapToAnotherFile(true, true, true, NULL)) {
				RemoveSource(cur_src,true,false);
				// If it was not swapped, it's not on any file anymore, and should die
			}
		} else {
			RemoveSource(cur_src,true,false);
		}
	}

	UpdatePartsInfo();

	/* eMule 0.30c implementation, i give it a try (Creteil) BEGIN ... */
	// remove all links A4AF in sources to this file
	if(!m_A4AFsrclist.empty()) {
		for( SourceSet::iterator it = m_A4AFsrclist.begin(); it != m_A4AFsrclist.end(); ) {
			CUpDownClient* cur_src = it++->GetClient();
			if ( cur_src->DeleteFileRequest( this ) ) {
				Notify_SourceCtrlRemoveSource(cur_src->ECID(), this);
			}
		}
		m_A4AFsrclist.clear();
	}
	/* eMule 0.30c implementation, i give it a try (Creteil) END ... */
	UpdateFileRatingCommentAvail();
}


void CPartFile::Delete()
{
	AddLogLineN(CFormat(_("Deleting file: %s")) % GetFileName());
	// Notify every subscriber that holds a raw CKnownFile* / CPartFile*
	// to this object — list ctrls, comment dialogs, file-detail dialog,
	// AICH static request list, write/hash threads, and on amulegui
	// the CUpDownClient::m_uploadingfile / m_reqfile fields. Subscribers
	// strip their references using pointer-value comparison only (this
	// object is still live when the notify fires; by the time main-
	// thread queued subscribers run, this object may already have been
	// freed). See MuleNotify::KnownFileBeingDestroyed (GuiEvents.cpp).
	Notify_KnownFileBeingDestroyed(this);
	// Barry - Need to tell any connected clients to stop sending the file
	StopFile(true);
	AddDebugLogLineN(logPartFile, "\tStopped");

#ifdef __DEBUG__
	uint16 removed =
#endif
		theApp->uploadqueue->SuspendUpload(GetFileHash(), true);
	AddDebugLogLineN(logPartFile, CFormat("\tSuspended upload to %d clients") % removed);
	theApp->sharedfiles->RemoveFile(this);
	AddDebugLogLineN(logPartFile, "\tRemoved from shared");
	theApp->downloadqueue->RemoveFile(this);
	AddDebugLogLineN(logPartFile, "\tRemoved from download queue");
	Notify_DownloadCtrlRemoveFile(this);
	AddDebugLogLineN(logPartFile, "\tRemoved from transferwnd");
	if (theApp->canceledfiles->Add(GetFileHash())) {
		theApp->canceledfiles->Save();
	}
	AddDebugLogLineN(logPartFile, "\tAdded to canceled file list");
	theApp->searchlist->UpdateSearchFileByHash(GetFileHash());	// Update file in the search dialog if it's still open

	if (m_hpartfile.IsOpened()) {
		m_hpartfile.Close();
	}

	AddDebugLogLineN(logPartFile, "\tClosed");

	// cppcheck-suppress duplicateBranch
	if (!CPath::RemoveFile(m_fullname)) {
		AddDebugLogLineC(logPartFile, CFormat("\tFailed to delete '%s'") % m_fullname);
	} else {
		AddDebugLogLineN(logPartFile, "\tRemoved .part.met");
	}

	// cppcheck-suppress duplicateBranch
	if (!CPath::RemoveFile(m_PartPath)) {
		AddDebugLogLineC(logPartFile, CFormat("Failed to delete '%s'") % m_PartPath);
	} else {
		AddDebugLogLineN(logPartFile, "\tRemoved .part");
	}

	CPath BAKName = m_fullname.AppendExt(PARTMET_BAK_EXT);
	// cppcheck-suppress duplicateBranch
	if (!CPath::RemoveFile(BAKName)) {
		AddDebugLogLineC(logPartFile, CFormat("Failed to delete '%s'") % BAKName);
	} else {
		AddDebugLogLineN(logPartFile, "\tRemoved .bak");
	}

	CPath SEEDSName = m_fullname.AppendExt(".seeds");
	if (SEEDSName.FileExists()) {
		// cppcheck-suppress duplicateBranch
		if (CPath::RemoveFile(SEEDSName)) {
			AddDebugLogLineN(logPartFile, "\tRemoved .seeds");
		} else {
			AddDebugLogLineC(logPartFile, CFormat("Failed to delete '%s'") % SEEDSName);
		}
	}

	AddDebugLogLineN(logPartFile, "Done");

	delete this;
}


bool CPartFile::HashSinglePart(uint16 partnumber)
{
	if ((GetHashCount() <= partnumber) && (GetPartCount() > 1)) {
		AddLogLineC(CFormat( _("WARNING: Unable to hash downloaded part - hashset incomplete for '%s'") )
				% GetFileName() );
		m_hashsetneeded = true;
		return true;
	} else if ((GetHashCount() <= partnumber) && GetPartCount() != 1) {
		AddLogLineC(CFormat( _("ERROR: Unable to hash downloaded part - hashset incomplete (%s). This should never happen")) % GetFileName() );
		m_hashsetneeded = true;
		return true;
	} else {
		CMD4Hash hashresult;
		uint64 offset = PARTSIZE * partnumber;
		uint32 length = GetPartSize(partnumber);

		// Drift defense: gap-tracking advances on receive, but bytes
		// only hit disk via the buffered write path; a write failure
		// (ENOSPC, EIO, transient I/O error) followed by a SavePartFile
		// and an unclean shutdown leaves the .met persisted with the
		// optimistic gap state, while the partfile is shorter than
		// the gap-list claims. Reading past EOF would throw and trip
		// PS_ERROR, requiring manual recovery (truncate -s <fullsize>).
		// Detect the divergence pre-read and reopen the gap so the
		// missing bytes are re-fetched cleanly. Also covers external
		// truncation, partial backup restore, and FS corruption.
		const uint64 partfileLen = m_hpartfile.GetLength();
		if (partfileLen < offset + length) {
			AddLogLineC(CFormat(_(
				"Part %u of '%s' marked complete but partfile is shorter than expected "
				"(have %llu bytes, need %llu); reopening gap for re-download."))
				% partnumber % GetFileName()
				% (unsigned long long)partfileLen
				% (unsigned long long)(offset + length));
			AddGap(offset, offset + length - 1);
			return false;
		}

		try {
			CreateHashFromFile(m_hpartfile, offset, length, &hashresult, NULL);
		} catch (const CIOFailureException& e) {
			AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
				% partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
			SetStatus(PS_ERROR);
			return false;
		} catch (const CEOFException& e) {
			AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s"))
				% partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what());
			SetStatus(PS_ERROR);
			return false;
		}

		if (GetPartCount() > 1) {
			if (hashresult != GetPartHash(partnumber)) {
				AddDebugLogLineN(logPartFile, CFormat( "%s: Expected hash of part %d: %s") % GetFileName() % partnumber % GetPartHash(partnumber).Encode() );
				AddDebugLogLineN(logPartFile, CFormat( "%s: Actual   hash of part %d: %s") % GetFileName() % partnumber % hashresult.Encode() );
				return false;
			} else {
				return true;
			}
		} else {
			if (hashresult != m_abyFileHash) {
				return false;
			} else {
				return true;
			}
		}
	}

}

bool CPartFile::IsCorruptedPart(uint16 partnumber)
{
	return std::find(m_corrupted_list.begin(), m_corrupted_list.end(), partnumber)
		!= m_corrupted_list.end();
}


void CPartFile::SetDownPriority(uint8 np, bool bSave, bool bRefresh )
{
	if ( m_iDownPriority != np ) {
		m_iDownPriority = np;
		MarkMetDirty();
		// EC exports the priority value via EC_TAG_PARTFILE_PRIO.
		MarkECChanged();
		if ( bRefresh )
			UpdateDisplayedInfo(true);
		if ( bSave )
			SavePartFile();
	}
}


bool CPartFile::HasPendingHashWork() const
{
	if (m_iWrites > 0) {
		return false;
	}
	for (bool dirty : m_aChangedPart) {
		if (dirty) {
			return true;
		}
	}
	// Completion-pending: gaplist is closed and the file is in active
	// state but writes haven't all been harvested yet — keep firing
	// FlushBuffer at Process tick rate so the trailing CompleteFile
	// check runs once the worker thread drains. Without this, a slow
	// worker that completes the gap-closing write after the last
	// FlushBuffer would leave us waiting for BUFFER_TIME_LIMIT (60 s).
	if (m_gaplist.IsComplete()
		&& (status == PS_EMPTY || status == PS_READY)
		&& !m_BufferedData_list.empty()) {
		return true;
	}
	return false;
}


void CPartFile::StopFile(bool bCancel)
{
	// Kry - Need to set it here to get into SetStatus(status) correctly
	m_stopped = true;
	// EC exports IsStopped() via EC_TAG_PARTFILE_STOPPED. Process() is
	// gated to PS_READY/PS_EMPTY (see DownloadQueue::Process), so a
	// stopped partfile no longer auto-marks each tick — the user action
	// must mark explicitly or amulegui/amuleweb never see the new state.
	MarkECChanged();

	// Barry - Need to tell any connected clients to stop sending the file
	PauseFile();

	m_LastSearchTimeKad = 0;
	m_TotalSearchesKad = 0;

	RemoveAllSources(true);
	kBpsDown = 0.0;
	transferingsrc = 0;

	if (!bCancel) {
		FlushBuffer();
	}

	UpdateDisplayedInfo(true);
}


void CPartFile::StopPausedFile()
{
	if (!IsStopped()) {
		// Once an hour, remove any sources for files which are no longer active downloads
		switch (GetStatus()) {
			case PS_PAUSED:
			case PS_INSUFFICIENT:
			case PS_ERROR:
				if (time(NULL) - m_iLastPausePurge > (60*60)) {
					m_iLastPausePurge = time(NULL);
					StopFile();
				}
				kBpsDown = 0.0;
		}
	}
	// release file handle if unused for some time
	m_hpartfile.Release();
}


void CPartFile::PauseFile(bool bInsufficient)
{
	SetActive(false);

	if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
		return;
	}

	if (GetKadFileSearchID()) {
		Kademlia::CSearchManager::StopSearch(GetKadFileSearchID(), true);
		// If we were in the middle of searching, reset timer so they can resume searching.
		m_LastSearchTimeKad = 0;
	}

	m_iLastPausePurge = time(NULL);

	theApp->downloadqueue->RemoveLocalServerRequest(this);

	CPacket packet( OP_CANCELTRANSFER, 0, OP_EDONKEYPROT );
	for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
		CUpDownClient* cur_src = it++->GetClient();
		if (cur_src->GetDownloadState() == DS_DOWNLOADING) {
			if (!cur_src->GetSentCancelTransfer()) {
				theStats::AddUpOverheadOther( packet.GetPacketSize() );
				AddDebugLogLineN( logLocalClient, "Local Client: OP_CANCELTRANSFER to " + cur_src->GetFullIP() );
				cur_src->SendPacket( &packet, false, true );
				cur_src->SetSentCancelTransfer( true );
			}
			cur_src->SetDownloadState(DS_ONQUEUE);
			// Allow immediate reconnect on resume
			cur_src->ResetLastAskedTime();
		}
	}


	m_insufficient = bInsufficient;
	if (!m_paused) {
		MarkMetDirty();
		// EC exports IsStopped() / GetStatus(); see StopFile comment.
		MarkECChanged();
	}
	m_paused = true;


	kBpsDown = 0.0;
	transferingsrc = 0;

	SetStatus(status);
}


void CPartFile::ResumeFile()
{
	if ( status == PS_COMPLETE || status == PS_COMPLETING ) {
		return;
	}

	if ( m_insufficient && !CheckFreeDiskSpace() ) {
		// Still not enough free discspace
		return;
	}

	if (m_paused) {
		MarkMetDirty();
		// EC exports IsStopped() / GetStatus(); Process() resumes
		// auto-marking after we leave the paused state but the
		// transition itself needs to be visible immediately.
		MarkECChanged();
	}
	m_paused = false;
	m_stopped = false;
	m_insufficient = false;

	m_lastsearchtime = 0;
	SetStatus(status);
	SetActive(theApp->IsConnected());

	if (m_gaplist.IsComplete() && (GetStatus() == PS_ERROR)) {
		// The file has already been hashed at this point
		CompleteFile(true);
	}

	UpdateDisplayedInfo(true);
}


bool CPartFile::CheckFreeDiskSpace( uint64 neededSpace )
{
	uint64 free = CPath::GetFreeSpaceAt(GetFilePath());
	if (free == static_cast<uint64>(wxInvalidOffset)) {
		// If GetFreeSpaceAt() fails, then the path probably does not exist.
		return false;
	}

	// The very least acceptable diskspace is a single PART
	if ( free < PARTSIZE ) {
		// Always fail in this case, since we risk losing data if we try to
		// write on a full partition.
		return false;
	}

	// All other checks are only made if the user has enabled them
	if ( thePrefs::IsCheckDiskspaceEnabled() ) {
		neededSpace += thePrefs::GetMinFreeDiskSpace();

		// Due to the the existence of sparse files, we cannot assume that
		// writes within the file doesn't cause new blocks to be allocated.
		// Therefore, we have to simply stop writing the moment the limit has
		// been exceeded.
		return free >= neededSpace;
	}

	return true;
}


void CPartFile::SetLastAnsweredTime()
{
	m_ClientSrcAnswered = ::GetTickCount64();
}

void CPartFile::SetLastAnsweredTimeTimeout()
{
	m_ClientSrcAnswered = 2 * CONNECTION_LATENCY + ::GetTickCount64() - SOURCECLIENTREASKS;
}

CPacket *CPartFile::CreateSrcInfoPacket(const CUpDownClient* forClient, uint8 byRequestedVersion, uint16 nRequestedOptions)
{

	if ( m_SrcList.empty() ) {
		return NULL;
	}

	if(!IsPartFile())  {
		return CKnownFile::CreateSrcInfoPacket(forClient, byRequestedVersion, nRequestedOptions);
	}

	if (((forClient->GetRequestFile() != this)
		&& (forClient->GetUploadFile() != this)) || forClient->GetUploadFileID() != GetFileHash()) {
		wxString file1 = _("Unknown");
		if (forClient->GetRequestFile() && forClient->GetRequestFile()->GetFileName().IsOk()) {
			file1 = forClient->GetRequestFile()->GetFileName().GetPrintable();
		} else if (forClient->GetUploadFile() && forClient->GetUploadFile()->GetFileName().IsOk()) {
			file1 = forClient->GetUploadFile()->GetFileName().GetPrintable();
		}
		wxString file2 = _("Unknown");
		if (GetFileName().IsOk()) {
			file2 = GetFileName().GetPrintable();
		}
		AddDebugLogLineN(logPartFile, "File mismatch on source packet (P) Sending: " + file1 + "  From: " + file2);
		return NULL;
	}

	if ( !(GetStatus() == PS_READY || GetStatus() == PS_EMPTY)) {
		return NULL;
	}

	const BitVector& reqstatus = forClient->GetPartStatus();
	bool KnowNeededParts = !reqstatus.empty();
	//wxASSERT(rcvstatus.size() == GetPartCount()); // Obviously!
	if (KnowNeededParts && (reqstatus.size() != GetPartCount())) {
		// Yuck. Same file but different part count? Seriously fucked up.
		// This happens rather often with reqstatus.size() == 0. Don't log then.
		if (reqstatus.size()) {
			AddDebugLogLineN(logKnownFiles, CFormat("Impossible situation: different partcounts: %i (client) and %i (file) for %s") % reqstatus.size() % GetPartCount() % GetFileName());
		}
		return NULL;
	}

	CMemFile data(1024);

	uint8 byUsedVersion;
	bool bIsSX2Packet;
	if (forClient->SupportsSourceExchange2() && byRequestedVersion > 0){
		// the client uses SourceExchange2 and requested the highest version he knows
		// and we send the highest version we know, but of course not higher than his request
		byUsedVersion = std::min(byRequestedVersion, (uint8)SOURCEEXCHANGE2_VERSION);
		bIsSX2Packet = true;
		data.WriteUInt8(byUsedVersion);

		// we don't support any special SX2 options yet, reserved for later use
		if (nRequestedOptions != 0) {
			AddDebugLogLineN(logKnownFiles, CFormat("Client requested unknown options for SourceExchange2: %u") % nRequestedOptions);
		}
	} else {
		byUsedVersion = forClient->GetSourceExchange1Version();
		bIsSX2Packet = false;
		if (forClient->SupportsSourceExchange2()) {
			AddDebugLogLineN(logKnownFiles, "Client which announced to support SX2 sent SX1 packet instead");
		}
	}

	uint16 nCount = 0;

	data.WriteHash(m_abyFileHash);
	data.WriteUInt16(nCount);
	bool bNeeded;
	for (SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it ) {
		bNeeded = false;
		CUpDownClient* cur_src = it->GetClient();

		int state = cur_src->GetDownloadState();
		int valid = ( state == DS_DOWNLOADING ) || ( state == DS_ONQUEUE && !cur_src->IsRemoteQueueFull() );

		if ( cur_src->HasLowID() || !valid ) {
			continue;
		}

		// only send source which have needed parts for this client if possible
		const BitVector& srcstatus = cur_src->GetPartStatus();
		if ( !srcstatus.empty() ) {
			//wxASSERT(srcstatus.size() == GetPartCount()); // Obviously!
			if (srcstatus.size() != GetPartCount()) {
				continue;
			}
			if ( KnowNeededParts ) {
				// only send sources which have needed parts for this client
				for (int x = 0; x < GetPartCount(); ++x) {
					if (srcstatus.get(x) && !reqstatus.get(x)) {
						bNeeded = true;
						break;
					}
				}
			} else {
				// if we don't know the need parts for this client,
				// return any source currently a client sends it's
				// file status only after it has at least one complete part
				if (srcstatus.size() != GetPartCount()) {
					continue;
				}
				for (int x = 0; x < GetPartCount(); ++x){
					if (srcstatus.get(x)) {
						bNeeded = true;
						break;
					}
				}
			}
		}
		if(bNeeded) {
			++nCount;
			uint32 dwID;
			if(forClient->GetSourceExchange1Version() > 2) {
				dwID = cur_src->GetUserIDHybrid();
			} else {
				dwID = wxUINT32_SWAP_ALWAYS(cur_src->GetUserIDHybrid());
			}
			data.WriteUInt32(dwID);
			data.WriteUInt16(cur_src->GetUserPort());
			data.WriteUInt32(cur_src->GetServerIP());
			data.WriteUInt16(cur_src->GetServerPort());

			if (byUsedVersion >= 2) {
			    data.WriteHash(cur_src->GetUserHash());
			}

			if (byUsedVersion >= 4){
				// CryptSettings - SourceExchange V4
				// 5 Reserved (!)
				// 1 CryptLayer Required
				// 1 CryptLayer Requested
				// 1 CryptLayer Supported
				const uint8 uSupportsCryptLayer	= cur_src->SupportsCryptLayer() ? 1 : 0;
				const uint8 uRequestsCryptLayer	= cur_src->RequestsCryptLayer() ? 1 : 0;
				const uint8 uRequiresCryptLayer	= cur_src->RequiresCryptLayer() ? 1 : 0;
				const uint8 byCryptOptions = (uRequiresCryptLayer << 2) | (uRequestsCryptLayer << 1) | (uSupportsCryptLayer << 0);
				data.WriteUInt8(byCryptOptions);
			}

			if (nCount > 500) {
				break;
			}
		}
	}
	if (!nCount) {
		return 0;
	}
	data.Seek(bIsSX2Packet ? 17 : 16, wxFromStart);
	data.WriteUInt16(nCount);

	CPacket* result = new CPacket(data, OP_EMULEPROT, bIsSX2Packet ? OP_ANSWERSOURCES2 : OP_ANSWERSOURCES);

	// 16+2+501*(4+2+4+2+16) = 14046 bytes max.
	if (result->GetPacketSize() > 354) {
		result->PackPacket();
	}

	return result;
}

void CPartFile::AddClientSources(CMemFile* sources, unsigned nSourceFrom, uint8 uClientSXVersion, bool bSourceExchange2, const CUpDownClient* /*pClient*/)
{
	// Kad reviewed

	if (m_stopped) {
		return;
	}

	uint32 nCount = 0;
	uint8 uPacketSXVersion = 0;
	if (!bSourceExchange2) {
		nCount = sources->ReadUInt16();

		// Check if the data size matches the 'nCount' for v1 or v2 and eventually correct the source
		// exchange version while reading the packet data. Otherwise we could experience a higher
		// chance in dealing with wrong source data, userhashs and finally duplicate sources.
		uint32 uDataSize = sources->GetLength() - sources->GetPosition();

		if ((uint32)(nCount*(4+2+4+2)) == uDataSize) { //Checks if version 1 packet is correct size
			if(uClientSXVersion != 1) {
				return;
			}
			uPacketSXVersion = 1;
		} else if ((uint32)(nCount*(4+2+4+2+16)) == uDataSize) { // Checks if version 2&3 packet is correct size
			if (uClientSXVersion == 2) {
				uPacketSXVersion = 2;
			} else if (uClientSXVersion > 2) {
				uPacketSXVersion = 3;
			} else {
				return;
			}
		} else if (nCount*(4+2+4+2+16+1) == uDataSize) {
			if (uClientSXVersion != 4 ) {
				return;
			}
			uPacketSXVersion = 4;
		} else {
			// If v5 inserts additional data (like v2), the above code will correctly filter those packets.
			// If v5 appends additional data after <count>(<Sources>)[count], we are in trouble with the
			// above code. Though a client which does not understand v5+ should never receive such a packet.
			AddDebugLogLineN(logClient, CFormat("Received invalid source exchange packet (v%u) of data size %u for %s") % uClientSXVersion % uDataSize % GetFileName());
			return;
		}
	} else {
		// for SX2:
		// We only check if the version is known by us and do a quick sanitize check on known version
		// other then SX1, the packet will be ignored if any error appears, since it can't be a "misunderstanding" anymore
		if (uClientSXVersion > SOURCEEXCHANGE2_VERSION || uClientSXVersion == 0 ){
			AddDebugLogLineN(logPartFile, CFormat("Invalid source exchange type version: %i") % uClientSXVersion);
			return;
		}

		// all known versions use the first 2 bytes as count and unknown version are already filtered above
		nCount = sources->ReadUInt16();
		uint32 uDataSize = (uint32)(sources->GetLength() - sources->GetPosition());
		bool bError = false;
		switch (uClientSXVersion){
			case 1:
				bError = nCount*(4+2+4+2) != uDataSize;
				break;
			case 2:
			case 3:
				bError = nCount*(4+2+4+2+16) != uDataSize;
				break;
			case 4:
				bError = nCount*(4+2+4+2+16+1) != uDataSize;
				break;
			default:
				wxFAIL;
		}

		if (bError){
			wxFAIL;
			AddDebugLogLineN(logPartFile, "Invalid source exchange data size.");
			return;
		}
		uPacketSXVersion = uClientSXVersion;
	}

	for (uint16 i = 0;i != nCount;++i) {

		uint32 dwID = sources->ReadUInt32();
		uint16 nPort = sources->ReadUInt16();
		uint32 dwServerIP = sources->ReadUInt32();
		uint16 nServerPort = sources->ReadUInt16();

		CMD4Hash userHash;
		if (uPacketSXVersion > 1) {
			userHash = sources->ReadHash();
		}

		uint8 byCryptOptions = 0;
		if (uPacketSXVersion >= 4) {
			byCryptOptions = sources->ReadUInt8();
		}

		//Clients send ID's the the Hyrbid format so highID clients with *.*.*.0 won't be falsely switched to a lowID..
		uint32 dwIDED2K;
		if (uPacketSXVersion >= 3) {
			dwIDED2K = wxUINT32_SWAP_ALWAYS(dwID);
		} else {
			dwIDED2K = dwID;
		}

		// check the HighID(IP) - "Filter LAN IPs" and "IPfilter" the received sources IP addresses
		if (!IsLowID(dwID)) {
			if (!IsGoodIP(dwIDED2K, thePrefs::FilterLanIPs())) {
				// check for 0-IP, localhost and optionally for LAN addresses
				AddDebugLogLineN(logIPFilter, CFormat("Ignored source (IP=%s) received via %s - bad IP") % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
				continue;
			}
			if (theApp->ipfilter->IsFiltered(dwIDED2K)) {
				AddDebugLogLineN(logIPFilter, CFormat("Ignored source (IP=%s) received via %s - IPFilter") % Uint32toStringIP(dwIDED2K) % OriginToText(nSourceFrom));
				continue;
			}
			if (theApp->clientlist->IsBannedClient(dwIDED2K)){
				continue;
			}
		}

		// additionally check for LowID and own IP
		if (!CanAddSource(dwID, nPort, dwServerIP, nServerPort, NULL, false)) {
			AddDebugLogLineN(logIPFilter, CFormat("Ignored source (IP=%s) received via source exchange") % Uint32toStringIP(dwIDED2K));
			continue;
		}

		if(thePrefs::GetMaxSourcePerFile() > GetSourceCount()) {
			CUpDownClient* newsource = new CUpDownClient(nPort,dwID,dwServerIP,nServerPort,this, (uPacketSXVersion < 3), true);
			if (uPacketSXVersion > 1) {
				newsource->SetUserHash(userHash);
			}

			if (uPacketSXVersion >= 4) {
				newsource->SetConnectOptions(byCryptOptions, true, false);
			}

			newsource->SetSourceFrom((ESourceFrom)nSourceFrom);
			theApp->downloadqueue->CheckAndAddSource(this,newsource);

	} else {
			break;
		}
	}
}

void CPartFile::UpdateAutoDownPriority()
{
	if (!IsAutoDownPriority()) {
		return;
	}
	if (GetSourceCount() <= theApp->downloadqueue->GetRareFileThreshold()) {
		if ( GetDownPriority() != PR_HIGH )
			SetDownPriority(PR_HIGH, false, false);
	} else if (GetSourceCount() < theApp->downloadqueue->GetCommonFileThreshold()) {
		if ( GetDownPriority() != PR_NORMAL )
			SetDownPriority(PR_NORMAL, false, false);
	} else {
		if ( GetDownPriority() != PR_LOW )
			SetDownPriority(PR_LOW, false, false);
	}
}

// making this function return a higher when more sources have the extended
// protocol will force you to ask a larger variety of people for sources

int CPartFile::GetCommonFilePenalty()
{
	//TODO: implement, but never return less than MINCOMMONPENALTY!
	return MINCOMMONPENALTY;
}

/* Barry - Replaces BlockReceived()

	Originally this only wrote to disk when a full 180k block
	had been received from a client, and only asked for data in
	180k blocks.

	This meant that on average 90k was lost for every connection
	to a client data source. That is a lot of wasted data.

	To reduce the lost data, packets are now written to a buffer
	and flushed to disk regularly regardless of size downloaded.
	This includes compressed packets.

	Data is also requested only where gaps are, not in 180k blocks.
	The requests will still not exceed 180k, but may be smaller to
	fill a gap.
*/

// Kry - transize is 32bits, no packet can be more than that (this is
// compressed size). Even 32bits is too much imho.As for the return size,
// look at the lenData below.
uint32 CPartFile::WriteToBuffer(uint32 transize, uint8_t* data, uint64 start, uint64 end, Requested_Block_Struct *block, const CUpDownClient* client)
{
	// Increment transferred bytes counter for this file
	transferred += transize;

	// This is needed a few times
	// Kry - should not need a uint64 here - no block is larger than
	// 2GB even after uncompressed.
	uint32 lenData = (uint32) (end - start + 1);

	if(lenData > transize) {
		m_iGainDueToCompression += lenData-transize;
	}

	// Occasionally packets are duplicated, no point writing it twice
	if (IsComplete(start, end)) {
		AddDebugLogLineN(logPartFile,
			CFormat("File '%s' has already been written from %u to %u")
				% GetFileName() % start % end);
		return 0;
	}

	// security sanitize check to make sure we do not write anything into an already hashed complete chunk
	const uint64 nStartChunk = start / PARTSIZE;
	const uint64 nEndChunk = end / PARTSIZE;
	if (IsComplete(nStartChunk)) {
		AddDebugLogLineN(logPartFile, CFormat("Received data touches already hashed chunk - ignored (start): %u-%u; File=%s") % start % end % GetFileName());
		return 0;
	} else if (nStartChunk != nEndChunk) {
		if (IsComplete(nEndChunk)) {
			AddDebugLogLineN(logPartFile, CFormat("Received data touches already hashed chunk - ignored (end): %u-%u; File=%s") % start % end % GetFileName());
			return 0;
		} else {
			AddDebugLogLineN(logPartFile, CFormat("Received data crosses chunk boundaries: %u-%u; File=%s") % start % end % GetFileName());
		}
	}

	// log transferinformation in our "blackbox"
	m_CorruptionBlackBox->TransferredData(start, end, client->GetIP());

	// Stamp for FlushBuffer's Phase 3 quiescent guard, and for the
	// download-list "Last Reception" column. The latter must stamp on
	// real data arrival, not on every periodic FlushBuffer call, or
	// idle/paused/stalled files will all read "now" and the column
	// stops being useful for spotting hopeless downloads.
	m_nLastBlockReceivedTick = GetTickCount64();
	m_lastDateChanged = wxDateTime::GetTimeNow();

	// Create a new buffered queue entry
	PartFileBufferedData *item = new PartFileBufferedData(m_hpartfile, data, start, end, block);

	// Add to the queue in the correct position (most likely the end)
	bool added = false;

	std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
	for (; it != m_BufferedData_list.end(); ++it) {
		PartFileBufferedData* queueItem = *it;

		if (item->end <= queueItem->end) {
			if (it != m_BufferedData_list.begin()) {
				added = true;

				m_BufferedData_list.insert(--it, item);
			}

			break;
		}
	}

	if (!added) {
		m_BufferedData_list.push_front(item);
	}

	// Increment buffer size marker
	m_nTotalBufferData += lenData;

	// Mark this small section of the file as filled
	FillGap(item->start, item->end);

	// Update the flushed mark on the requested block
	// The loop here is unfortunate but necessary to detect deleted blocks.

	std::list<Requested_Block_Struct*>::iterator it2 = m_requestedblocks_list.begin();
	for (; it2 != m_requestedblocks_list.end(); ++it2) {
		if (*it2 == item->block) {
			item->block->transferred += lenData;
		}
	}

	if (m_gaplist.IsComplete()) {
		FlushBuffer();
	}

	// Return the length of data written to the buffer
	return lenData;
}

void CPartFile::FlushBuffer(bool fromAICHRecoveryDataAvailable)
{
	m_nLastBufferFlushTime = GetTickCount64();

	uint32 partCount = GetPartCount();
	// Persistent tracking of parts needing hash verification (eMule ref: m_aChangedPart).
	// Survives across FlushBuffer() calls so parts written by the background thread
	// get hashed once all writes are complete.
	if (m_aChangedPart.size() != partCount) {
		m_aChangedPart.resize(partCount, false);
	}

	// No empty-buffer early-return: Phase 3 below still needs to run
	// when m_aChangedPart has dirty entries left from earlier writes
	// (e.g. paused/idle download). Phase 1+2 are no-ops on empty list.
	if (!m_BufferedData_list.empty()) {
		// Ensure file is big enough to write data to (the last item will be the furthest from the start)
		if (!CheckFreeDiskSpace(m_nTotalBufferData)) {
			// Not enough free space to write the last item, bail
			AddLogLineC(CFormat( _("WARNING: Not enough free disk-space! Pausing file: %s") ) % GetFileName());

			PauseFile( true );
			return;
		}
	}

	// eMule ref: PartFile.cpp:4102-4127 — Phase 1: queue PB_READY items to the write thread
	CPartFileWriteThread* pThread = theApp->partFileWriteThread;
	if (pThread && pThread->IsRunning()) {
		for (std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
			 it != m_BufferedData_list.end(); ++it)
		{
			PartFileBufferedData* item = *it;
			if (item->flushed == PB_READY) {
				item->flushed = PB_PENDING;
				++m_iWrites;
				pThread->QueueWrite(this, item);
			}
		}
	}

	// eMule ref: PartFile.cpp:4129-4145 — Phase 2: harvest completed writes
	for (std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
		 it != m_BufferedData_list.end(); )
	{
		PartFileBufferedData* item = *it;

		switch (item->flushed) {
		case PB_READY:
			// Write thread not running — fall back to synchronous write
			{
				wxASSERT((item->end - item->start) < 0xFFFFFFFF);
				uint32 lenData = (uint32)(item->end - item->start + 1);
				for (uint32 curpart = (item->start/PARTSIZE); curpart <= (item->end/PARTSIZE); ++curpart) {
					wxASSERT(curpart < partCount);
					m_aChangedPart[curpart] = true;
				}
				try {
					{
						// Defensive lock vs hash thread; see m_hpartfileMutex.
						std::lock_guard<std::mutex> lock(m_hpartfileMutex);
						item->area.FlushAt(m_hpartfile, item->start, lenData);
					}
					m_nTotalBufferData -= lenData;
				} catch (const CIOFailureException& e) {
					AddDebugLogLineC(logPartFile, "Error while saving part-file: " + e.what());
					SetStatus(PS_ERROR);
					DeleteContents(m_BufferedData_list);
					m_nTotalBufferData = 0;
					m_iWrites = 0;
					return;
				}
				delete item;
				it = m_BufferedData_list.erase(it);
			}
			continue;

		case PB_PENDING:
			// Still in flight on the write thread — skip
			++it;
			continue;

		case PB_ERROR:
			// Write thread reported error — retry next time
			item->flushed = PB_READY;
			AddDebugLogLineC(logPartFile, "Write thread reported error, will retry");
			++it;
			continue;

		case PB_WRITTEN:
			// Successfully written by the write thread — harvest
			{
				uint32 lenData = (uint32)(item->end - item->start + 1);
				for (uint32 curpart = (item->start/PARTSIZE); curpart <= (item->end/PARTSIZE); ++curpart) {
					wxASSERT(curpart < partCount);
					m_aChangedPart[curpart] = true;
				}
				m_nTotalBufferData -= lenData;
				// m_iWrites already decremented by the write thread
				delete item;
				it = m_BufferedData_list.erase(it);
			}
			continue;
		}
	}


	try {
		// Partfile should never be too large. IsOpened() guard: StopPausedFile() Release()s
		// the fd, and StopFile() reaches here via PauseFile -- GetLength() asserts on a closed fd.
		if (m_hpartfile.IsOpened() && m_hpartfile.GetLength() > GetFileSize()) {
			// it's "last chance" correction. the real bugfix has to be applied 'somewhere' else
			m_hpartfile.SetLength(GetFileSize());
		}
	} catch (const CIOFailureException& e) {
		AddDebugLogLineC(logPartFile,
			CFormat("Error while truncating part-file (%s): %s")
				% m_PartPath % e.what());
		SetStatus(PS_ERROR);
	}



	// Destructor / shutdown gate (see m_inDestructor in PartFile.h).
	// Hoisted above the m_iWrites check below so the first-share early
	// path also respects it.
	if (m_inDestructor || !theApp || !theApp->IsRunning()) {
		return;
	}

	// First-share early path (#761). SafeAddKFile fires from
	// OnAsyncHashComplete and is gated on status == PS_EMPTY -- so it
	// only ever runs once per partfile lifetime, when the first part
	// is verified. The bulk-drain gates below (m_iWrites > 0 and the
	// 1 s quiescent window) sit engaged across an entire active
	// download, which means a continuously-downloading file never
	// gets that first verification and stays invisible in "Shared
	// files" until the user pauses or it completes. Bypass both gates
	// to enqueue exactly one safe candidate.
	//
	// Safety: m_aChangedPart[N] && IsComplete(N) means Phase 2 has
	// already observed at least one PB_WRITTEN for N and the gaplist
	// has no holes in N. But FillGap runs at queue time in
	// WriteToBuffer (line 3160), so a queued PB_PENDING/PB_READY for
	// the same part can still exist -- hashing then would read
	// pre-write bytes. Scan m_BufferedData_list for overlap to skip
	// any such part.
	if (status == PS_EMPTY
		&& GetHashCount() == GetED2KPartHashCount()
		&& !m_hashsetneeded
		&& m_pendingHashes == 0
		&& !m_gaplist.IsComplete()) {
		for (uint32 partNumber = 0; partNumber < partCount; ++partNumber) {
			if (!m_aChangedPart[partNumber]) {
				continue;
			}
			if (!IsComplete(partNumber)) {
				continue;
			}
			const uint64 partStart = (uint64)partNumber * PARTSIZE;
			const uint64 partEnd = std::min<uint64>(
				partStart + PARTSIZE - 1, GetFileSize() - 1);
			bool pendingWrite = false;
			for (const PartFileBufferedData* item : m_BufferedData_list) {
				if (item->flushed == PB_WRITTEN) {
					continue;
				}
				if (item->start > partEnd || item->end < partStart) {
					continue;
				}
				pendingWrite = true;
				break;
			}
			if (pendingWrite) {
				continue;
			}
			m_aChangedPart[partNumber] = false;
			++m_pendingHashes;
			theApp->partFileHashThread->QueueHashCheck(this, partNumber,
				fromAICHRecoveryDataAvailable);
			AddDebugLogLineN(logPartFile, CFormat(
				"First-share: enqueued part %u of '%s' for hash") % partNumber % GetFileName());
			break;
		}
	}

	// Check each part of the file.
	// Skip hash verification if writes are still in flight — data isn't on disk yet.
	// Hashing will run on the next FlushBuffer() call once all writes complete.
	if (m_iWrites > 0) {
		return;
	}

	// Skip Phase 3 at gaplist completion: CCompletionTask re-reads
	// the file for the ED2K root + AICH tree, subsuming Phase 3's
	// per-part MD4. Running both is duplicate work and (for fresh
	// downloads where every part is dirty) a 30-60 s main-thread
	// freeze.
	if (m_gaplist.IsComplete()) {
		const uint32 dirty = (uint32)std::count(
			m_aChangedPart.begin(), m_aChangedPart.end(), true);
		if (dirty > 0) {
			AddDebugLogLineN(logPartFile, CFormat(
				"Phase 3 skipped at gaplist completion: cleared %u dirty parts of '%s'")
				% dirty % GetFileName());
		}
		std::fill(m_aChangedPart.begin(), m_aChangedPart.end(), false);
	} else {
		// Quiescent guard: defer per-part hashing until 1 s of no new
		// blocks, so the synchronous read+MD4 doesn't fire mid-burst.
		const uint64 kHashQuiescentMs = 1000;
		const uint64 nowTick = GetTickCount64();
		if (m_nLastBlockReceivedTick != 0 &&
			(nowTick - m_nLastBlockReceivedTick) < kHashQuiescentMs) {
			return;
		}

		// Async enqueue: hand each dirty part to CPartFileHashThread,
		// which runs HashSinglePart on its own thread and posts a
		// CPartFileHashResultEvent back to CamuleApp's main-thread
		// handler — OnAsyncHashComplete then runs the AICH-recovery /
		// SafeAddKFile branches below.  Main-thread cost here is just
		// queue inserts (microseconds per part), so even a 3000-part
		// dirty list enqueues in milliseconds with zero hashing freeze.
		//
		// PR #454 rejected an async-hash thread because read-for-hash
		// competed with CPartFileWriteThread's writes during active
		// download.  The quiescent guard above (1 s no receives)
		// guarantees writes have drained before we enqueue, so the
		// contention case can't arise.
		uint32 enqueued = 0;
		for (uint32 partNumber = 0; partNumber < partCount; ++partNumber) {
			if (!m_aChangedPart[partNumber]) {
				continue;
			}
			m_aChangedPart[partNumber] = false;

			// Mirror the synchronous verify loop at line 286: a write
			// that touches a part marks it dirty, but the part may still
			// have gaps. Hashing an incomplete part would read past the
			// highest written offset and EOF. Future writes to this part
			// will re-set the dirty bit; once gap-complete, the next
			// pass enqueues it.
			if (!IsComplete(partNumber)) {
				continue;
			}
			++m_pendingHashes;
			theApp->partFileHashThread->QueueHashCheck(this, partNumber,
				fromAICHRecoveryDataAvailable);
			++enqueued;
		}
		if (enqueued > 0) {
			AddDebugLogLineN(logPartFile, CFormat(
				"Phase 3 enqueued %u parts to hash thread for '%s'")
				% enqueued % GetFileName());
		}
	}  // close gaplistComplete else-branch (async enqueue)

	// Update met file -- only when something has actually changed since
	// the last save.  Two tiers:
	//   1. Hard state (gap list, status, priority, category, etc.) sets
	//      m_metDirty.  Saved at the next FlushBuffer tick (~60 s).
	//   2. Soft stats (AllTimeRequests / AllTimeAccepts /
	//      AllTimeTransferred) set m_statsDirty only.  Saved at the
	//      STATS_HEARTBEAT_MS cadence so a popular sharer does not
	//      rewrite its .met on every served chunk.  The heartbeat is
	//      measured from the last successful save, so a regular dirty
	//      save automatically resets it.
	// If neither bit is dirty, no save fires.  An idle seeder with no
	// activity at all writes nothing until shutdown.
	const uint64 STATS_HEARTBEAT_MS = 10 * 60 * 1000;
	if (IsMetDirty() ||
		(IsStatsDirty() &&
		 (::GetTickCount64() - m_lastMetSaveTick > STATS_HEARTBEAT_MS))) {
		SavePartFile();
	}

	// Final PB_WRITTEN harvest. The Phase 2 loop above can race with
	// CPartFileWriteThread::Entry(), which decrements m_iWrites and
	// then sets PB_WRITTEN: an item Phase 2 saw as PB_PENDING (and
	// left in the list) can transition to PB_WRITTEN before the
	// trailing check below runs. Without this sweep, the gap-closing
	// write at file completion would leave m_BufferedData_list
	// non-empty, fail the trailing check, and the download would stick
	// at 99.9% until the next BUFFER_TIME_LIMIT (60 s) tick.
	for (std::list<PartFileBufferedData*>::iterator it = m_BufferedData_list.begin();
		 it != m_BufferedData_list.end(); )
	{
		PartFileBufferedData* item = *it;
		if (item->flushed == PB_WRITTEN) {
			uint32 lenData = (uint32)(item->end - item->start + 1);
			for (uint32 curpart = (item->start/PARTSIZE);
				 curpart <= (item->end/PARTSIZE); ++curpart) {
				wxASSERT(curpart < partCount);
				m_aChangedPart[curpart] = true;
			}
			m_nTotalBufferData -= lenData;
			delete item;
			it = m_BufferedData_list.erase(it);
		} else {
			++it;
		}
	}

	if (theApp->IsRunning()) { // may be called during shutdown!
		// CompleteFile is not idempotent — each call kicks off a fresh
		// CHashingTask. Must guard:
		//   - status PS_EMPTY or PS_READY: skip if already
		//     PS_COMPLETING / PS_HASHING / PS_WAITING_FOR_HASH / PS_ERROR
		//     (otherwise StopFile→FlushBuffer and PerformFileComplete→
		//     FlushBuffer in the post-CompleteFile path would re-fire
		//     and spawn duplicate hash tasks). Both PS_EMPTY and
		//     PS_READY are normal in-flight states: PS_READY is set on
		//     load (LoadPartFile, line ~757) when any part is already
		//     complete, and in OnAsyncHashComplete after a successful
		//     part hash. So a resumed download is PS_READY from the
		//     moment .met is loaded — gating on PS_EMPTY alone made
		//     CompleteFile fail to fire on every resume-then-finish.
		//   - m_pendingHashes <= 0: don't kick off CCompletionTask while
		//     CPartFileHashThread is still reading m_hpartfile via
		//     HashSinglePart — PerformFileComplete closes the file and
		//     the worker would fault. The last OnAsyncHashComplete (when
		//     pendingHashes drops to 0) re-runs this same check and
		//     fires CompleteFile then.
		if ((status == PS_EMPTY || status == PS_READY)
			&& m_gaplist.IsComplete()
			&& m_iWrites <= 0
			&& m_BufferedData_list.empty()
			&& m_pendingHashes <= 0) {
			CompleteFile(false);
		}
	}
}


// Receives a HashSinglePart result from CPartFileHashThread (via the
// CPartFileHashResultEvent dispatched on CamuleApp).  Runs the same
// success/failure / AICH-recovery logic the original synchronous Phase 3
// did, just with the boolean result already computed by the worker.
void CPartFile::OnAsyncHashComplete(uint16 partNumber, bool ok,
	bool fromAICHRecoveryDataAvailable)
{
	if (m_inDestructor || !theApp || !theApp->IsRunning()) {
		return;
	}
	if (partNumber >= GetPartCount()) {
		return;
	}

	const uint32 partRange = GetPartSize(partNumber) - 1;

	// Track whether this part's outcome changes persistent state.
	// Only those branches need a SavePartFile flush; the success path
	// for an already-complete part touches in-memory bookkeeping only
	// (VerifiedData, m_corrupted_list, status), which is fine to
	// persist on the next FlushBuffer-driven SavePartFile.  Avoids
	// hammering the .met file on the main thread once per part —
	// that's what made the GUI freeze on async-hash drain.
	bool stateChanged = false;

	if (IsComplete(partNumber)) {
		if (!ok) {
			AddLogLineC(CFormat(
				_("Downloaded part %i is corrupt in file: %s") )
				% partNumber % GetFileName());
			AddGap(partNumber);
			if (!IsCorruptedPart(partNumber)) {
				m_corrupted_list.push_back(partNumber);
			}
			// request AICH recovery data; skip if we're already inside
			// an AICH recovery to avoid infinite recursion.
			if (!fromAICHRecoveryDataAvailable) {
				RequestAICHRecovery(partNumber);
			}
			m_iLostDueToCorruption += (partRange + 1);
			stateChanged = true;
		} else {
			if (!m_hashsetneeded) {
				AddDebugLogLineN(logPartFile, CFormat(
					"Finished part %u of '%s'") % partNumber % GetFileName());
			}

			m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);

			// If this part was carried as corrupted in the .met from a
			// prior session, removing it from m_corrupted_list IS a
			// persistent state change — without a save, next launch
			// would still flag it as needing recovery.
			if (IsCorruptedPart(partNumber)) {
				EraseFirstValue(m_corrupted_list, partNumber);
				stateChanged = true;
			}

			if (status == PS_EMPTY) {
				if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
					SetStatus(PS_READY);
					theApp->sharedfiles->SafeAddKFile(this);
					stateChanged = true;
				}
			}
		}
	} else if (IsCorruptedPart(partNumber) &&
		(thePrefs::IsICHEnabled() || fromAICHRecoveryDataAvailable)) {
		if (ok) {
			++m_iTotalPacketsSavedDueToICH;
			uint64 uMissingInPart = m_gaplist.GetGapSize(partNumber);
			FillGap(partNumber);
			RemoveBlockFromList(PARTSIZE * partNumber,
				(PARTSIZE * partNumber + partRange));
			m_CorruptionBlackBox->VerifiedData(true, partNumber, 0, partRange);
			EraseFirstValue(m_corrupted_list, partNumber);

			AddLogLineC(CFormat(
				_("ICH: Recovered corrupted part %i for %s -> Saved bytes: %s") )
				% partNumber % GetFileName()
				% CastItoXBytes(uMissingInPart));

			if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
				if (status == PS_EMPTY) {
					SetStatus(PS_READY);
					theApp->sharedfiles->SafeAddKFile(this);
				}
			}
			stateChanged = true;
		}
	}

	if (stateChanged) {
		SavePartFile();
	}

	// Now that this part's hash has been processed, the file may be
	// ready for completion. Mirrors the trailing CompleteFile check in
	// FlushBuffer — accept both PS_EMPTY and PS_READY (resumed
	// downloads are PS_READY from load), but still skip PS_COMPLETING
	// so a duplicate CompleteFile / CHashingTask isn't spawned.
	if ((status == PS_EMPTY || status == PS_READY)
		&& m_gaplist.IsComplete()
		&& m_iWrites <= 0
		&& m_BufferedData_list.empty()
		&& m_pendingHashes <= 0) {
		CompleteFile(false);
	}
}


// read data for upload, return false on error
bool CPartFile::ReadData(CFileArea & area, uint64 offset, uint32 toread)
{
	// Sanity check
	if (offset + toread > GetFileSize()) {
		AddDebugLogLineN(logPartFile, CFormat("tried to read %d bytes past eof of %s")
			% (offset + toread - GetFileSize()) % GetFileName());
		wxFAIL;
		return false;
	}

	// Lock m_hpartfileMutex against the write/hash threads. ReadAt
	// is Seek+Read on the underlying CFileAutoClose; CFile's internal
	// recursive_mutex (#576) serialises the individual syscalls but
	// not the Seek+Read composition. CPartFileWriteThread::Entry holds
	// m_hpartfileMutex around its Seek+Write -- without the matching
	// lock here, the upload reader (CUploadDiskIOThread::Entry calls
	// this from a worker thread) can interleave its Seek with a write
	// thread's Seek+Write, leaving fd_pos at the upload's seek target.
	// The write thread's Write then lands at the wrong offset, and
	// the read returns bytes from the post-write position. See #586.
	std::lock_guard<std::mutex> lock(m_hpartfileMutex);
	area.ReadAt(m_hpartfile, offset, toread);
	// if it fails it throws (which the caller should catch)
	return true;
}


void CPartFile::UpdateFileRatingCommentAvail()
{
	bool prevComment = m_hasComment;
	int prevRating = m_iUserRating;

	m_hasComment = false;
	m_iUserRating = 0;
	int ratingCount = 0;

	SourceSet::iterator it = m_SrcList.begin();
	for (; it != m_SrcList.end(); ++it) {
		CUpDownClient* cur_src = it->GetClient();

		if (!cur_src->GetFileComment().IsEmpty()) {
			if (thePrefs::IsCommentFiltered(cur_src->GetFileComment())) {
				continue;
			}
			m_hasComment = true;
		}

		uint8 rating = cur_src->GetFileRating();
		if (rating) {
			wxASSERT(rating <= 5);

			ratingCount++;
			m_iUserRating += rating;
		}
	}

	if (ratingCount) {
		m_iUserRating /= ratingCount;
		wxASSERT(m_iUserRating > 0 && m_iUserRating <= 5);
	}

	if ((prevComment != m_hasComment) || (prevRating != m_iUserRating)) {
		UpdateDisplayedInfo();
		// EC exports EC_TAG_PARTFILE_COMMENTS (rating + comments tag
		// tree). Sources can deliver new comments at any time via
		// OP_MESSAGE; without a mark here amulegui would only refresh
		// when something else on the file changes.
		MarkECChanged();
	}
}


void CPartFile::SetCategory(uint8 cat)
{
	wxASSERT( cat < theApp->glob_prefs->GetCatCount() );

	if (m_category != cat) {
		MarkMetDirty();
		// EC exports the category via EC_TAG_PARTFILE_CAT.
		MarkECChanged();
	}
	m_category = cat;
	SavePartFile();
}

bool CPartFile::RemoveSource(CUpDownClient* toremove, bool updatewindow, bool bDoStatsUpdate)
{
	wxASSERT( toremove );

	bool result = theApp->downloadqueue->RemoveSource( toremove, updatewindow, bDoStatsUpdate );

	// Check if the client should be deleted, but not if the client is already dying
	if ( !toremove->GetSocket() && !toremove->HasBeenDeleted() ) {
		if ( toremove->Disconnected("RemoveSource - purged") ) {
			toremove->Safe_Delete();
		}
	}

	return result;
}

void CPartFile::AddDownloadingSource(CUpDownClient* client)
{
	CClientRefList::iterator it =
		std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), CCLIENTREF(client, ""));
	if (it == m_downloadingSourcesList.end()) {
		m_downloadingSourcesList.push_back(CCLIENTREF(client, "CPartFile::AddDownloadingSource"));
	}
}


void CPartFile::RemoveDownloadingSource(CUpDownClient* client)
{
	CClientRefList::iterator it =
		std::find(m_downloadingSourcesList.begin(), m_downloadingSourcesList.end(), CCLIENTREF(client, ""));
	if (it != m_downloadingSourcesList.end()) {
		m_downloadingSourcesList.erase(it);
	}
}


uint64 CPartFile::GetNeededSpace()
{
	try {
		uint64 length = m_hpartfile.GetLength();

		if (length > GetFileSize()) {
			return 0;	// Shouldn't happen, but just in case
		}

		return GetFileSize() - length;
	} catch (const CIOFailureException& e) {
		AddDebugLogLineC(logPartFile,
			CFormat("Error while retrieving file-length (%s): %s")
				% m_PartPath % e.what());
		SetStatus(PS_ERROR);
		return 0;
	}
}

void CPartFile::SetStatus(uint8 in)
{
	// PAUSED and INSUFFICIENT have extra flag variables m_paused and m_insufficient
	// - they are never to be stored in status
	wxASSERT( in != PS_PAUSED && in != PS_INSUFFICIENT );

	if (status != in) {
		MarkMetDirty();
	}
	status = in;

	if (theApp->IsRunning()) {
		UpdateDisplayedInfo( true );

		if ( thePrefs::ShowCatTabInfos() ) {
			Notify_ShowUpdateCatTabTitles();
		}
		Notify_DownloadCtrlSort();
	}
}


void CPartFile::RequestAICHRecovery(uint16 nPart)
{

	if (	!m_pAICHHashSet->HasValidMasterHash() ||
		(m_pAICHHashSet->GetStatus() != AICH_TRUSTED && m_pAICHHashSet->GetStatus() != AICH_VERIFIED)){
		AddDebugLogLineN( logAICHRecovery, "Unable to request AICH Recoverydata because we have no trusted Masterhash" );
		return;
	}
	if (GetPartSize(nPart) <= EMBLOCKSIZE)
		return;
	if (CAICHHashSet::IsClientRequestPending(this, nPart)){
		AddDebugLogLineN( logAICHRecovery, "RequestAICHRecovery: Already a request for this part pending");
		return;
	}

	// first check if we have already the recoverydata, no need to rerequest it then
	if (m_pAICHHashSet->IsPartDataAvailable(nPart*PARTSIZE)){
		AddDebugLogLineN( logAICHRecovery, "Found PartRecoveryData in memory");
		AICHRecoveryDataAvailable(nPart);
		return;
	}

	wxASSERT( nPart < GetPartCount() );
	// find some random client which support AICH to ask for the blocks
	// first lets see how many we have at all, we prefer high id very much
	uint32 cAICHClients = 0;
	uint32 cAICHLowIDClients = 0;
	for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
		CUpDownClient* pCurClient = it->GetClient();
		if (	pCurClient->IsSupportingAICH() &&
			pCurClient->GetReqFileAICHHash() != NULL &&
			!pCurClient->IsAICHReqPending()
			&& (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
		{
			if (pCurClient->HasLowID()) {
				++cAICHLowIDClients;
			} else {
				++cAICHClients;
			}
		}
	}
	if ((cAICHClients | cAICHLowIDClients) == 0){
		AddDebugLogLineN( logAICHRecovery, "Unable to request AICH Recoverydata because found no client who supports it and has the same hash as the trusted one");
		return;
	}
	uint32 nSeclectedClient;
	if (cAICHClients > 0) {
		nSeclectedClient = (rand() % cAICHClients) + 1;
	} else {
		nSeclectedClient = (rand() % cAICHLowIDClients) + 1;
	}
	CUpDownClient* pClient = NULL;
	for ( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ++it) {
		CUpDownClient* pCurClient = it->GetClient();
		if (pCurClient->IsSupportingAICH() && pCurClient->GetReqFileAICHHash() != NULL && !pCurClient->IsAICHReqPending()
			&& (*pCurClient->GetReqFileAICHHash()) == m_pAICHHashSet->GetMasterHash())
		{
			if (cAICHClients > 0){
				if (!pCurClient->HasLowID())
					nSeclectedClient--;
			}
			else{
				wxASSERT( pCurClient->HasLowID());
				nSeclectedClient--;
			}
			if (nSeclectedClient == 0){
				pClient = pCurClient;
				break;
			}
		}
	}
	if (pClient == NULL){
		wxFAIL;
		return;
	}

	AddDebugLogLineN( logAICHRecovery, CFormat( "Requesting AICH Hash (%s) form client %s" ) % ( cAICHClients ? "HighId" : "LowID" ) % pClient->GetClientFullInfo() );
	pClient->SendAICHRequest(this, nPart);

}


void CPartFile::AICHRecoveryDataAvailable(uint16 nPart)
{
	if (GetPartCount() < nPart){
		wxFAIL;
		return;
	}

	FlushBuffer(true);
	uint32 length = GetPartSize(nPart);
	// if the part was already ok, it would now be complete
	if (IsComplete(nPart)) {
		AddDebugLogLineN(logAICHRecovery, CFormat("Processing AICH Recovery data: The part (%u) is already complete, canceling") % nPart);
		return;
	}



	CAICHHashTree* pVerifiedHash = m_pAICHHashSet->m_pHashTree.FindHash(nPart*PARTSIZE, length);
	if (pVerifiedHash == NULL || !pVerifiedHash->GetHashValid()){
		AddDebugLogLineC( logAICHRecovery, "Processing AICH Recovery data: Unable to get verified hash from hashset (should never happen)" );
		wxFAIL;
		return;
	}
	CAICHHashTree htOurHash(pVerifiedHash->GetNDataSize(), pVerifiedHash->GetIsLeftBranch(), pVerifiedHash->GetNBaseSize());
	try {
		// Lock m_hpartfileMutex against the write/hash/upload threads.
		// CreateHashFromFile is a sequence of Seek+Read inside the
		// CFileAutoClose wrapper, and CFile's recursive_mutex (#576)
		// serialises individual syscalls but not the composition.
		// Without the matching lock here, the AICH-recovery read can
		// interleave with a concurrent Seek+Write from
		// CPartFileWriteThread, fd_pos lands in the wrong place, the
		// recovery hash is computed over the wrong bytes -- and the
		// recovered part gets accepted as good even though it is not.
		// See #586.
		std::lock_guard<std::mutex> lock(m_hpartfileMutex);
		CreateHashFromFile(m_hpartfile, PARTSIZE * nPart, length, NULL, &htOurHash);
	} catch (const CIOFailureException& e) {
		AddDebugLogLineC(logAICHRecovery,
			CFormat("IO failure while hashing part-file '%s': %s")
				% m_hpartfile.GetFilePath() % e.what());
		SetStatus(PS_ERROR);
		return;
	}

	if (!htOurHash.GetHashValid()){
		AddDebugLogLineN( logAICHRecovery, "Processing AICH Recovery data: Failed to retrieve AICH Hashset of corrupt part" );
		wxFAIL;
		return;
	}

	// now compare the hash we just did, to the verified hash and readd all blocks which are ok
	uint32 nRecovered = 0;
	for (uint32 pos = 0; pos < length; pos += EMBLOCKSIZE){
		const uint32 nBlockSize = min<uint32>(EMBLOCKSIZE, length - pos);
		CAICHHashTree* pVerifiedBlock = pVerifiedHash->FindHash(pos, nBlockSize);
		CAICHHashTree* pOurBlock = htOurHash.FindHash(pos, nBlockSize);
		if ( pVerifiedBlock == NULL || pOurBlock == NULL || !pVerifiedBlock->GetHashValid() || !pOurBlock->GetHashValid()){
			wxFAIL;
			continue;
		}
		if (pOurBlock->GetHash() == pVerifiedBlock->GetHash()){
			FillGap(PARTSIZE*nPart+pos, PARTSIZE*nPart + pos + (nBlockSize-1));
			RemoveBlockFromList(PARTSIZE*nPart, PARTSIZE*nPart + (nBlockSize-1));
			nRecovered += nBlockSize;
			// tell the blackbox about the verified data
			m_CorruptionBlackBox->VerifiedData(true, nPart, pos, pos + nBlockSize - 1);
		} else {
			// inform our "blackbox" about the corrupted block which may ban clients who sent it
			m_CorruptionBlackBox->VerifiedData(false, nPart, pos, pos + nBlockSize - 1);
		}
	}
	m_CorruptionBlackBox->EvaluateData();

	// ok now some sanity checks
	if (IsComplete(nPart)) {
		// this is bad, but it could probably happen under some rare circumstances
		// make sure that MD4 agrees to this fact too
		if (!HashSinglePart(nPart)) {
			AddDebugLogLineN(logAICHRecovery,
				CFormat("Processing AICH Recovery data: The part (%u) got completed while recovering - but MD4 says it corrupt! Setting hashset to error state, deleting part") % nPart);
			// now we are fu... unhappy
			m_pAICHHashSet->SetStatus(AICH_ERROR);
			AddGap(nPart);
			wxFAIL;
			return;
		} else {
			AddDebugLogLineN(logAICHRecovery,
				CFormat("Processing AICH Recovery data: The part (%u) got completed while recovering and MD4 agrees") % nPart);
			if (status == PS_EMPTY && theApp->IsRunning()) {
				if (GetHashCount() == GetED2KPartHashCount() && !m_hashsetneeded) {
					// Successfully recovered part, make it available for sharing
					SetStatus(PS_READY);
					theApp->sharedfiles->SafeAddKFile(this);
				}
			}

			if (theApp->IsRunning()) {
				// Is this file finished?
				if (m_gaplist.IsComplete()) {
					CompleteFile(false);
				}
			}
		}
	} // end sanity check
	// We did the best we could. If it's still incomplete, then no need to keep
	// bashing it with ICH. So remove it from the list of corrupted parts.
	EraseFirstValue(m_corrupted_list, nPart);
	// Update met file
	SavePartFile();

	// make sure the user appreciates our great recovering work :P
	AddDebugLogLineC( logAICHRecovery, CFormat(
		"AICH successfully recovered %s of %s from part %u for %s" )
		% CastItoXBytes(nRecovered)
		% CastItoXBytes(length)
		% nPart
		% GetFileName() );
}


void CPartFile::ClientStateChanged( int oldState, int newState )
{
	if ( oldState == newState )
		return;

	// If the state is -1, then it's an entirely new item
	if ( oldState != -1 ) {
		// Was the old state a valid state?
		if ( oldState == DS_ONQUEUE || oldState == DS_DOWNLOADING ) {
			m_validSources--;
		} else {
			if ( oldState == DS_CONNECTED /* || oldState == DS_REMOTEQUEUEFULL  */ ) {
				m_validSources--;
			}

			m_notCurrentSources--;
		}
	}

	// If the state is -1, then the source is being removed
	if ( newState != -1 ) {
		// Was the old state a valid state?
		if ( newState == DS_ONQUEUE || newState == DS_DOWNLOADING ) {
			++m_validSources;
		} else {
			if ( newState == DS_CONNECTED /* || newState == DS_REMOTEQUEUEFULL  */ ) {
				++m_validSources;
			}

			++m_notCurrentSources;
		}
	}
}


bool CPartFile::AddSource( CUpDownClient* client )
{
	if (m_SrcList.insert(CCLIENTREF(client, "CPartFile::AddSource")).second) {
		theStats::AddFoundSource();
		theStats::AddSourceOrigin(client->GetSourceFrom());
		return true;
	} else {
		return false;
	}
}


bool CPartFile::DelSource( CUpDownClient* client )
{
	if (m_SrcList.erase(CCLIENTREF(client, ""))) {
		theStats::RemoveSourceOrigin(client->GetSourceFrom());
		theStats::RemoveFoundSource();
		return true;
	} else {
		return false;
	}
}


void CPartFile::UpdatePartsFrequency( CUpDownClient* client, bool increment )
{
	const BitVector& freq = client->GetPartStatus();

	if ( m_SrcpartFrequency.size() != GetPartCount() ) {
		m_SrcpartFrequency.clear();
		m_SrcpartFrequency.insert(m_SrcpartFrequency.begin(), GetPartCount(), 0);

		if ( !increment ) {
			return;
		}
	}

	unsigned int size = freq.size();
	if ( size != m_SrcpartFrequency.size() ) {
		return;
	}

	if ( increment ) {
		for ( unsigned int i = 0; i < size; i++ ) {
			if ( freq.get(i) ) {
				m_SrcpartFrequency[i]++;
			}
		}
	} else {
		for ( unsigned int i = 0; i < size; i++ ) {
			if ( freq.get(i) ) {
				m_SrcpartFrequency[i]--;
			}
		}
	}
}

void CPartFile::GetRatingAndComments(FileRatingList & list) const
{
	list.clear();
	// This can be pre-processed, but is it worth the CPU?
	CPartFile::SourceSet::const_iterator it = m_SrcList.begin();
	for ( ; it != m_SrcList.end(); ++it ) {
		CUpDownClient *cur_src = it->GetClient();
		if (cur_src->GetFileComment().Length()>0 || cur_src->GetFileRating()>0) {
			// AddDebugLogLineN(logPartFile, wxString("found a comment for ") << GetFileName());
			list.push_back(SFileRating(*cur_src));
		}
	}
}

#else   // CLIENT_GUI

CPartFile::CPartFile(const CEC_PartFile_Tag *tag) : CKnownFile(tag)
{
	Init();

	SetFileName(CPath(tag->FileName()));
	m_abyFileHash = tag->FileHash();
	SetFileSize(tag->SizeFull());
	m_gaplist.Init(GetFileSize(), true);	// Init empty
	m_partmetfilename = CPath(tag->PartMetName());
	m_fullname = m_partmetfilename;		// We have only the met number, so show it without path in the detail dialog.

	m_SrcpartFrequency.insert(m_SrcpartFrequency.end(), GetPartCount(), 0);

	// these are only in CLIENT_GUI and not covered by Init()
	m_source_count = 0;
	m_kbpsDown = 0;
	m_iDownPriorityEC = 0;
	m_a4af_source_count = 0;
	m_isShared = false;
}

/*
 * Remote gui specific code
 */
CPartFile::~CPartFile()
{
}

void CPartFile::GetRatingAndComments(FileRatingList & list) const
{
	list = m_FileRatingList;
}

void CPartFile::SetCategory(uint8 cat)
{
	m_category = cat;
}


bool CPartFile::AddSource(CUpDownClient* client)
{
	return m_SrcList.insert(CCLIENTREF(client, "CPartFile::AddSource")).second != 0;
}


bool CPartFile::DelSource(CUpDownClient* client)
{
	return m_SrcList.erase(CCLIENTREF(client, "")) != 0;
}


#endif // !CLIENT_GUI


void CPartFile::UpdateDisplayedInfo(bool force)
{
	uint64 curTick = ::GetTickCount64();

	// Wait 1.5s between each redraw
	if (force || curTick-m_lastRefreshedDLDisplay > MINWAIT_BEFORE_DLDISPLAY_WINDOWUPDATE) {
		Notify_DownloadCtrlUpdateItem(this);
		m_lastRefreshedDLDisplay = curTick;
	}
}


void CPartFile::Init()
{
	m_lastsearchtime = 0;
	lastpurgetime = ::GetTickCount64();
	m_paused = false;
	m_stopped = false;
	m_insufficient = false;

	status = PS_EMPTY;

	transferred = 0;
	m_iLastPausePurge = time(NULL);

	if(thePrefs::GetNewAutoDown()) {
		m_iDownPriority = PR_HIGH;
		m_bAutoDownPriority = true;
	} else {
		m_iDownPriority = PR_NORMAL;
		m_bAutoDownPriority = false;
	}

	transferingsrc = 0; // new

	kBpsDown = 0.0;

	m_hashsetneeded = true;
	m_count = 0;
	percentcompleted = 0;
	completedsize=0;
	lastseencomplete = 0;
	m_availablePartsCount=0;
	m_ClientSrcAnswered = 0;
	m_LastNoNeededCheck = 0;
	m_iRating = 0;
	m_nTotalBufferData = 0;
	m_nLastBufferFlushTime = 0;
	m_iWrites = 0;
	m_bPercentUpdated = false;
	m_iGainDueToCompression = 0;
	m_iLostDueToCorruption = 0;
	m_iTotalPacketsSavedDueToICH = 0;
	m_category = 0;
	m_lastRefreshedDLDisplay = 0;
	m_nDlActiveTime = 0;
	m_tActivated = 0;
	m_is_A4AF_auto = false;
	m_localSrcReqQueued = false;
	m_nCompleteSourcesTime = time(NULL);
	m_nCompleteSourcesCount = 0;
	m_nCompleteSourcesCountLo = 0;
	m_nCompleteSourcesCountHi = 0;

	m_validSources = 0;
	m_notCurrentSources = 0;

	// Kad
	m_LastSearchTimeKad = 0;
	m_TotalSearchesKad = 0;

#ifndef CLIENT_GUI
	m_CorruptionBlackBox = new CCorruptionBlackBox();
#endif
}

wxString CPartFile::getPartfileStatus() const
{

	wxString mybuffer;

	if ((status == PS_HASHING) || (status == PS_WAITING_FOR_HASH)) {
		mybuffer=_("Hashing");
	} else if (status == PS_ALLOCATING) {
		mybuffer = _("Allocating");
	} else {
		switch (GetStatus()) {
			case PS_COMPLETING:
				mybuffer=_("Completing");
				break;
			case PS_COMPLETE:
				mybuffer=_("Complete");
				break;
			case PS_PAUSED:
				mybuffer=_("Paused");
				break;
			case PS_ERROR:
				mybuffer=_("Erroneous");
				break;
			case PS_INSUFFICIENT:
				mybuffer = _("Insufficient disk space");
				break;
			default:
				if (GetTransferingSrcCount()>0) {
					mybuffer=_("Downloading");
				}	else {
					mybuffer=_("Waiting");
				}
				break;
		}
		if (m_stopped && (GetStatus()!=PS_COMPLETE)) {
			mybuffer=_("Stopped");
		}
	}

	return mybuffer;
}

int CPartFile::getPartfileStatusRang() const
{

	int tempstatus=0;
	if (GetTransferingSrcCount()==0) tempstatus=1;
	switch (GetStatus()) {
		case PS_HASHING:
		case PS_WAITING_FOR_HASH:
			tempstatus=3;
			break;
		case PS_COMPLETING:
			tempstatus=4;
			break;
		case PS_COMPLETE:
			tempstatus=5;
			break;
		case PS_PAUSED:
			tempstatus=2;
			break;
		case PS_ERROR:
			tempstatus=6;
			break;
	}
	return tempstatus;
}


wxString CPartFile::GetFeedback() const
{
	wxString retval = CKnownFile::GetFeedback();
	if (GetStatus() != PS_COMPLETE) {
		retval += CFormat("%s: %s (%.2f%%)\n%s: %u\n")
			% _("Downloaded") % CastItoXBytes(GetCompletedSize()) % GetPercentCompleted() % _("Sources") % GetSourceCount();
	}
	return retval + _("Status") + ": " + getPartfileStatus() + "\n";
}


sint32 CPartFile::getTimeRemaining() const
{
	if (GetKBpsDown() < 0.001)
		return -1;
	else
		return((GetFileSize()-GetCompletedSize()) / ((int)(GetKBpsDown()*1024.0)));
}

bool CPartFile::PreviewAvailable()
{
	const uint64 minSizeForPreview = 256 * 1024;
	FileType type = GetFiletype(GetFileName());

	return (type == ftVideo || type == ftAudio) &&
	       GetFileSize() >= minSizeForPreview &&
	       IsComplete(0, minSizeForPreview);
}

bool CPartFile::CheckShowItemInGivenCat(int inCategory)
{
	// first check if item belongs in this cat in principle
	if (inCategory > 0 && inCategory != GetCategory()) {
		return false;
	}

	// if yes apply filter
	bool show = true;

	switch (thePrefs::GetAllcatFilter()) {
		case acfAllOthers:
			show = GetCategory() == 0 || inCategory > 0;
			break;
		case acfIncomplete:
			show = IsPartFile();
			break;
		case acfCompleted:
			show = !IsPartFile();
			break;
		case acfWaiting:
			show =
				(GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
				GetTransferingSrcCount() == 0;
			break;
		case acfDownloading:
			show =
				(GetStatus() == PS_READY || GetStatus() == PS_EMPTY) &&
				GetTransferingSrcCount() > 0;
			break;
		case acfErroneous:
			show = GetStatus() == PS_ERROR;
			break;
		case acfPaused:
			show = GetStatus() == PS_PAUSED && !IsStopped();
			break;
		case acfStopped:
			show = IsStopped();
			break;
		case acfVideo:
			show = GetFiletype(GetFileName()) == ftVideo;
			break;
		case acfAudio:
			show = GetFiletype(GetFileName()) == ftAudio;
			break;
		case acfArchive:
			show = GetFiletype(GetFileName()) == ftArchive;
			break;
		case acfCDImages:
			show = GetFiletype(GetFileName()) == ftCDImage;
			break;
		case acfPictures:
			show = GetFiletype(GetFileName()) == ftPicture;
			break;
		case acfText:
			show = GetFiletype(GetFileName()) == ftText;
			break;
		case acfActive:
			show = !IsStopped() && GetStatus() != PS_PAUSED;
			break;
		default:
			show = true;
			break;
	}

	return show;
}


void CPartFile::RemoveCategory(uint8 cat)
{
	if (m_category == cat) {
		// Reset the category
		m_category = 0;
	} else if (m_category > cat) {
		// Set to the new position of the original category
		m_category--;
	}
}


void CPartFile::SetActive(bool bActive)
{
	time_t tNow = time(NULL);
	if (bActive) {
		if (theApp->IsConnected()) {
			if (m_tActivated == 0) {
				m_tActivated = tNow;
			}
		}
	} else {
		if (m_tActivated != 0) {
			m_nDlActiveTime += tNow - m_tActivated;
			m_tActivated = 0;
		}
	}
}


uint32 CPartFile::GetDlActiveTime() const
{
	uint32 nDlActiveTime = m_nDlActiveTime;
	if (m_tActivated != 0) {
		nDlActiveTime += time(NULL) - m_tActivated;
	}
	return nDlActiveTime;
}


uint16 CPartFile::GetPartMetNumber() const
{
	long nr;
	return m_partmetfilename.RemoveAllExt().GetRaw().ToLong(&nr) ? nr : 0;
}


void CPartFile::SetHashingProgress(uint16 part) const
{
	if (m_hashingProgress != part) {
		m_hashingProgress = part;
		// EC exports the hashed-part count via
		// EC_TAG_PARTFILE_HASHED_PART_COUNT. Hashing runs outside
		// Process()'s PS_READY/PS_EMPTY gate, so the per-tick mark
		// doesn't fire — explicit mark here keeps amulegui's
		// progress bar live during hashing.
		const_cast<CPartFile*>(this)->MarkECChanged();
	}
	Notify_DownloadCtrlUpdateItem(this);
}


#ifndef CLIENT_GUI

uint8 CPartFile::GetStatus(bool ignorepause) const
{
	if (	(!m_paused && !m_insufficient) ||
		status == PS_ERROR ||
		status == PS_COMPLETING ||
		status == PS_COMPLETE ||
		ignorepause) {
		return status;
	} else if ( m_insufficient ) {
		return PS_INSUFFICIENT;
	} else {
		return PS_PAUSED;
	}
}

void CPartFile::AddDeadSource(const CUpDownClient* client)
{
	m_deadSources.AddDeadSource( client );
}


bool CPartFile::IsDeadSource(const CUpDownClient* client)
{
	return m_deadSources.IsDeadSource( client );
}

const wxString& CPartFile::GetCachedPartMetBasename() const
{
	if (m_cachedPartMetBasename.IsEmpty()) {
		// One-shot evaluate: the partmet filename (e.g. "012.part.met")
		// is set when the partfile is created and is stable for life,
		// so cache after first use. The wxString cast collapses the
		// CFormat round-trip that the EC tag constructor used to do
		// per request.
		m_cachedPartMetBasename = m_partmetfilename.RemoveExt().GetPrintable();
	}
	return m_cachedPartMetBasename;
}

void CPartFile::SetFileName(const CPath& fileName)
{
	CKnownFile* pFile = theApp->sharedfiles->GetFileByID(GetFileHash());

	bool is_shared = (pFile && pFile == this);

	if (is_shared) {
		// The file is shared, we must clear the search keywords so we don't
		// publish the old name anymore.
		theApp->sharedfiles->RemoveKeywords(this);
	}

	if (GetFileName() != fileName) {
		MarkMetDirty();
	}
	CKnownFile::SetFileName(fileName);

	if (is_shared) {
		// And of course, we must advertise the new name if the file is shared.
		theApp->sharedfiles->AddKeywords(this);
	}

	UpdateDisplayedInfo(true);
}


uint16 CPartFile::GetMaxSources() const
{
	// This is just like this, while we don't import the private max sources per file
	return thePrefs::GetMaxSourcePerFile();
}


uint16 CPartFile::GetMaxSourcePerFileSoft() const
{
	unsigned int temp = ((unsigned int)GetMaxSources() * 9L) / 10;
	if (temp > MAX_SOURCES_FILE_SOFT) {
		return MAX_SOURCES_FILE_SOFT;
	}
	return temp;
}

uint16 CPartFile::GetMaxSourcePerFileUDP() const
{
	unsigned int temp = ((unsigned int)GetMaxSources() * 3L) / 4;
	if (temp > MAX_SOURCES_FILE_UDP) {
		return MAX_SOURCES_FILE_UDP;
	}
	return temp;
}

#define DROP_FACTOR 2

CUpDownClient* CPartFile::GetSlowerDownloadingClient(uint32 speed, CUpDownClient* caller) {
//	printf("Start slower source calculation\n");
	for( SourceSet::iterator it = m_SrcList.begin(); it != m_SrcList.end(); ) {
		CUpDownClient* cur_src = it++->GetClient();
		if ((cur_src->GetDownloadState() == DS_DOWNLOADING) && (cur_src != caller)) {
			uint32 factored_bytes_per_second = static_cast<uint32>(
				(cur_src->GetKBpsDown() * 1024) * DROP_FACTOR);
			if ( factored_bytes_per_second< speed) {
//				printf("Selecting source %p to drop: %d < %d\n", cur_src, factored_bytes_per_second, speed);
//				printf("End slower source calculation\n");
				return cur_src;
			} else {
//				printf("Not selecting source %p to drop: %d > %d\n", cur_src, factored_bytes_per_second, speed);
			}
		}
	}
//	printf("End slower source calculation\n");
	return NULL;
}

void CPartFile::AllocationFinished()
{
	// see if it can be opened
	if (!m_hpartfile.Open(m_PartPath, CFile::read_write)) {
		AddLogLineN(CFormat(_("ERROR: Failed to open partfile '%s'")) % GetFullName());
		SetStatus(PS_ERROR);
	}
	// then close the handle again
	m_hpartfile.Release(true);
}

#endif
// File_checked_for_headers
