/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision$ * $Date$ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #ifndef WIN32 #include #include #endif #include #include "nzbget.h" #include "QueueCoordinator.h" #include "Options.h" #include "ServerPool.h" #include "ArticleDownloader.h" #include "DiskState.h" #include "Util.h" #include "Decoder.h" extern Options* g_pOptions; extern ServerPool* g_pServerPool; extern DiskState* g_pDiskState; bool QueueCoordinator::CoordinatorDownloadQueue::EditEntry( int ID, EEditAction eAction, int iOffset, const char* szText) { return m_pOwner->m_QueueEditor.EditEntry(&m_pOwner->m_DownloadQueue, ID, eAction, iOffset, szText); } bool QueueCoordinator::CoordinatorDownloadQueue::EditList( IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, EEditAction eAction, int iOffset, const char* szText) { return m_pOwner->m_QueueEditor.EditList(&m_pOwner->m_DownloadQueue, pIDList, pNameList, eMatchMode, eAction, iOffset, szText); } void QueueCoordinator::CoordinatorDownloadQueue::Save() { if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(this); } } QueueCoordinator::QueueCoordinator() { debug("Creating QueueCoordinator"); m_bHasMoreJobs = true; ResetSpeedStat(); m_iAllBytes = 0; m_tStartServer = 0; m_tStartDownload = 0; m_tPausedFrom = 0; m_bStandBy = true; m_iServerConfigGeneration = 0; g_pLog->RegisterDebuggable(this); m_DownloadQueue.m_pOwner = this; CoordinatorDownloadQueue::Init(&m_DownloadQueue); YDecoder::Init(); } QueueCoordinator::~QueueCoordinator() { debug("Destroying QueueCoordinator"); // Cleanup g_pLog->UnregisterDebuggable(this); debug("Deleting ArticleDownloaders"); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { delete *it; } m_ActiveDownloads.clear(); CoordinatorDownloadQueue::Final(); YDecoder::Final(); debug("QueueCoordinator destroyed"); } void QueueCoordinator::Run() { debug("Entering QueueCoordinator-loop"); if (g_pOptions->GetServerMode()) { g_pDiskState->LoadStats(g_pServerPool->GetServers()); // currently there are no any stats but we need to save current server list into diskstate g_pDiskState->SaveStats(g_pServerPool->GetServers()); } if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pDiskState->DownloadQueueExists()) { if (g_pOptions->GetReloadQueue()) { g_pDiskState->LoadDownloadQueue(&m_DownloadQueue); } else { g_pDiskState->DiscardDownloadQueue(); } } g_pDiskState->CleanupTempDir(&m_DownloadQueue); CoordinatorDownloadQueue::Loaded(); AdjustDownloadsLimit(); m_tStartServer = time(NULL); m_tLastCheck = m_tStartServer; bool bWasStandBy = true; bool bArticeDownloadsRunning = false; int iResetCounter = 0; while (!IsStopped()) { bool bDownloadsChecked = false; if (!g_pOptions->GetPauseDownload()) { NNTPConnection* pConnection = g_pServerPool->GetConnection(0, NULL, NULL); if (pConnection) { // start download for next article FileInfo* pFileInfo; ArticleInfo* pArticleInfo; bool bFreeConnection = false; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); bool bHasMoreArticles = GetNextArticle(pDownloadQueue, pFileInfo, pArticleInfo); bArticeDownloadsRunning = !m_ActiveDownloads.empty(); bDownloadsChecked = true; m_bHasMoreJobs = bHasMoreArticles || bArticeDownloadsRunning; if (bHasMoreArticles && !IsStopped() && (int)m_ActiveDownloads.size() < m_iDownloadsLimit && (!g_pOptions->GetTempPauseDownload() || pFileInfo->GetExtraPriority())) { StartArticleDownload(pFileInfo, pArticleInfo, pConnection); bArticeDownloadsRunning = true; } else { bFreeConnection = true; } DownloadQueue::Unlock(); if (bFreeConnection) { g_pServerPool->FreeConnection(pConnection, false); } } } if (!bDownloadsChecked) { DownloadQueue::Lock(); bArticeDownloadsRunning = !m_ActiveDownloads.empty(); DownloadQueue::Unlock(); } bool bStandBy = !bArticeDownloadsRunning; if (bStandBy != bWasStandBy) { EnterLeaveStandBy(bStandBy); bWasStandBy = bStandBy; } // sleep longer in StandBy int iSleepInterval = bStandBy ? 100 : 5; usleep(iSleepInterval * 1000); if (!bStandBy) { AddSpeedReading(0); } iResetCounter += iSleepInterval; if (iResetCounter >= 1000) { // this code should not be called too often, once per second is OK g_pServerPool->CloseUnusedConnections(); ResetHangingDownloads(); iResetCounter = 0; AdjustStartTime(); AdjustDownloadsLimit(); } } // waiting for downloads debug("QueueCoordinator: waiting for Downloads to complete"); bool completed = false; while (!completed) { DownloadQueue::Lock(); completed = m_ActiveDownloads.size() == 0; DownloadQueue::Unlock(); usleep(100 * 1000); ResetHangingDownloads(); } debug("QueueCoordinator: Downloads are completed"); debug("Exiting QueueCoordinator-loop"); } /* * Compute maximum number of allowed download threads **/ void QueueCoordinator::AdjustDownloadsLimit() { if (m_iServerConfigGeneration == g_pServerPool->GetGeneration()) { return; } // two extra threads for completing files (when connections are not needed) int iDownloadsLimit = 2; // allow one thread per 0-level (main) and 1-level (backup) server connection for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pNewsServer = *it; if ((pNewsServer->GetNormLevel() == 0 || pNewsServer->GetNormLevel() == 1) && pNewsServer->GetActive()) { iDownloadsLimit += pNewsServer->GetMaxConnections(); } } m_iDownloadsLimit = iDownloadsLimit; } void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, NZBInfo* pUrlInfo, bool bAddFirst) { debug("Adding NZBFile to queue"); NZBInfo* pNZBInfo = pNZBFile->GetNZBInfo(); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); DownloadQueue::Aspect foundAspect = { DownloadQueue::eaNzbFound, pDownloadQueue, pNZBInfo, NULL }; pDownloadQueue->Notify(&foundAspect); NZBInfo::EDeleteStatus eDeleteStatus = pNZBInfo->GetDeleteStatus(); if (eDeleteStatus != NZBInfo::dsNone) { bool bAllPaused = !pNZBInfo->GetFileList()->empty(); for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; bAllPaused &= pFileInfo->GetPaused(); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo); } } pNZBInfo->SetDeletePaused(bAllPaused); } if (eDeleteStatus != NZBInfo::dsManual) { // NZBInfo will be added either to queue or to history as duplicate // and therefore can be detached from NZBFile. pNZBFile->DetachNZBInfo(); } if (eDeleteStatus == NZBInfo::dsNone) { if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce) { CheckDupeFileInfos(pNZBInfo); } if (pUrlInfo) { // insert at the URL position for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pPosNzbInfo = *it; if (pPosNzbInfo == pUrlInfo) { pDownloadQueue->GetQueue()->insert(it, pNZBInfo); break; } } } else if (bAddFirst) { pDownloadQueue->GetQueue()->push_front(pNZBInfo); } else { pDownloadQueue->GetQueue()->push_back(pNZBInfo); } } if (pUrlInfo) { pDownloadQueue->GetQueue()->Remove(pUrlInfo); delete pUrlInfo; } char szNZBName[1024]; strncpy(szNZBName, pNZBInfo->GetName(), sizeof(szNZBName)-1); if (eDeleteStatus != NZBInfo::dsManual) { DownloadQueue::Aspect addedAspect = { DownloadQueue::eaNzbAdded, pDownloadQueue, pNZBInfo, NULL }; pDownloadQueue->Notify(&addedAspect); } pDownloadQueue->Save(); DownloadQueue::Unlock(); if (eDeleteStatus == NZBInfo::dsNone) { info("Collection %s added to queue", szNZBName); } } void QueueCoordinator::CheckDupeFileInfos(NZBInfo* pNZBInfo) { debug("CheckDupeFileInfos"); if (!g_pOptions->GetDupeCheck() || pNZBInfo->GetDupeMode() == dmForce) { return; } FileList dupeList(true); int index1 = 0; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { index1++; FileInfo* pFileInfo = *it; bool dupe = false; int index2 = 0; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { index2++; FileInfo* pFileInfo2 = *it2; if (pFileInfo != pFileInfo2 && !strcmp(pFileInfo->GetFilename(), pFileInfo2->GetFilename()) && (pFileInfo->GetSize() < pFileInfo2->GetSize() || (pFileInfo->GetSize() == pFileInfo2->GetSize() && index2 < index1))) { warn("File \"%s\" appears twice in collection, adding only the biggest file", pFileInfo->GetFilename()); dupe = true; break; } } if (dupe) { dupeList.push_back(pFileInfo); continue; } } for (FileList::iterator it = dupeList.begin(); it != dupeList.end(); it++) { FileInfo* pFileInfo = *it; StatFileInfo(pFileInfo, false); pNZBInfo->GetFileList()->Remove(pFileInfo); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo); } } } /* * NOTE: see note to "AddSpeedReading" */ int QueueCoordinator::CalcCurrentDownloadSpeed() { if (m_bStandBy) { return 0; } int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE; if (iTimeDiff == 0) { return 0; } return m_iSpeedTotalBytes / iTimeDiff; } void QueueCoordinator::AddSpeedReading(int iBytes) { time_t tCurTime = time(NULL); int iNowSlot = (int)tCurTime / SPEEDMETER_SLOTSIZE; if (g_pOptions->GetAccurateRate()) { #ifdef HAVE_SPINLOCK m_spinlockSpeed.Lock(); #else m_mutexSpeed.Lock(); #endif } while (iNowSlot > m_iSpeedTime[m_iSpeedBytesIndex]) { //record bytes in next slot m_iSpeedBytesIndex++; if (m_iSpeedBytesIndex >= SPEEDMETER_SLOTS) { m_iSpeedBytesIndex = 0; } //Adjust counters with outgoing information. m_iSpeedTotalBytes -= m_iSpeedBytes[m_iSpeedBytesIndex]; //Note we should really use the start time of the next slot //but its easier to just use the outgoing slot time. This //will result in a small error. m_iSpeedStartTime = m_iSpeedTime[m_iSpeedBytesIndex]; //Now reset. m_iSpeedBytes[m_iSpeedBytesIndex] = 0; m_iSpeedTime[m_iSpeedBytesIndex] = iNowSlot; } // Once per second recalculate summary field "m_iSpeedTotalBytes" to recover from possible synchronisation errors if (tCurTime > m_tSpeedCorrection) { int iSpeedTotalBytes = 0; for (int i = 0; i < SPEEDMETER_SLOTS; i++) { iSpeedTotalBytes += m_iSpeedBytes[i]; } m_iSpeedTotalBytes = iSpeedTotalBytes; m_tSpeedCorrection = tCurTime; } if (m_iSpeedTotalBytes == 0) { m_iSpeedStartTime = iNowSlot; } m_iSpeedBytes[m_iSpeedBytesIndex] += iBytes; m_iSpeedTotalBytes += iBytes; m_iAllBytes += iBytes; if (g_pOptions->GetAccurateRate()) { #ifdef HAVE_SPINLOCK m_spinlockSpeed.Unlock(); #else m_mutexSpeed.Unlock(); #endif } } void QueueCoordinator::ResetSpeedStat() { time_t tCurTime = time(NULL); m_iSpeedStartTime = (int)tCurTime / SPEEDMETER_SLOTSIZE; for (int i = 0; i < SPEEDMETER_SLOTS; i++) { m_iSpeedBytes[i] = 0; m_iSpeedTime[i] = m_iSpeedStartTime; } m_iSpeedBytesIndex = 0; m_iSpeedTotalBytes = 0; m_tSpeedCorrection = tCurTime; } // TODO: refactor: move to DownloadQueue (QueueCoordinator::CalcRemainingSize()) long long QueueCoordinator::CalcRemainingSize() { long long lRemainingSize = 0; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (!pFileInfo->GetPaused() && !pFileInfo->GetDeleted()) { lRemainingSize += pFileInfo->GetRemainingSize(); } } } DownloadQueue::Unlock(); return lRemainingSize; } void QueueCoordinator::Stop() { Thread::Stop(); debug("Stopping ArticleDownloads"); DownloadQueue::Lock(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { (*it)->Stop(); } DownloadQueue::Unlock(); debug("ArticleDownloads are notified"); } /* * Returns next article for download. */ bool QueueCoordinator::GetNextArticle(DownloadQueue* pDownloadQueue, FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo) { // find an unpaused file with the highest priority, then take the next article from the file. // if the file doesn't have any articles left for download, we store that fact and search again, // ignoring all files which were previously marked as not having any articles. // special case: if the file has ExtraPriority-flag set, it has the highest priority and the // Paused-flag is ignored. //debug("QueueCoordinator::GetNextArticle()"); bool bOK = false; // pCheckedFiles stores bool* pCheckedFiles = NULL; time_t tCurDate = time(NULL); while (!bOK) { pFileInfo = NULL; int iNum = 0; int iFileNum = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo1 = *it2; if ((!pCheckedFiles || !pCheckedFiles[iNum]) && !pFileInfo1->GetPaused() && !pFileInfo1->GetDeleted() && (g_pOptions->GetPropagationDelay() == 0 || (int)pFileInfo1->GetTime() < (int)tCurDate - g_pOptions->GetPropagationDelay()) && (!pFileInfo || (pFileInfo1->GetExtraPriority() == pFileInfo->GetExtraPriority() && pFileInfo1->GetNZBInfo()->GetPriority() > pFileInfo->GetNZBInfo()->GetPriority()) || (pFileInfo1->GetExtraPriority() > pFileInfo->GetExtraPriority()))) { pFileInfo = pFileInfo1; iFileNum = iNum; } iNum++; } } if (!pFileInfo) { // there are no more files for download break; } if (pFileInfo->GetArticles()->empty() && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->LoadArticles(pFileInfo); } // check if the file has any articles left for download for (FileInfo::Articles::iterator at = pFileInfo->GetArticles()->begin(); at != pFileInfo->GetArticles()->end(); at++) { pArticleInfo = *at; if (pArticleInfo->GetStatus() == 0) { bOK = true; break; } } if (!bOK) { // the file doesn't have any articles left for download, we mark the file as such if (!pCheckedFiles) { int iTotalFileCount = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; iTotalFileCount += pNZBInfo->GetFileList()->size(); } int iArrSize = sizeof(bool) * iTotalFileCount; pCheckedFiles = (bool*)malloc(iArrSize); memset(pCheckedFiles, false, iArrSize); } pCheckedFiles[iFileNum] = true; } } free(pCheckedFiles); return bOK; } void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection) { debug("Starting new ArticleDownloader"); ArticleDownloader* pArticleDownloader = new ArticleDownloader(); pArticleDownloader->SetAutoDestroy(true); pArticleDownloader->Attach(this); pArticleDownloader->SetFileInfo(pFileInfo); pArticleDownloader->SetArticleInfo(pArticleInfo); pArticleDownloader->SetConnection(pConnection); char szInfoName[1024]; snprintf(szInfoName, 1024, "%s%c%s [%i/%i]", pFileInfo->GetNZBInfo()->GetName(), (int)PATH_SEPARATOR, pFileInfo->GetFilename(), pArticleInfo->GetPartNumber(), (int)pFileInfo->GetArticles()->size()); szInfoName[1024-1] = '\0'; pArticleDownloader->SetInfoName(szInfoName); pArticleInfo->SetStatus(ArticleInfo::aiRunning); pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() + 1); pFileInfo->GetNZBInfo()->SetActiveDownloads(pFileInfo->GetNZBInfo()->GetActiveDownloads() + 1); m_ActiveDownloads.push_back(pArticleDownloader); pArticleDownloader->Start(); } void QueueCoordinator::Update(Subject* Caller, void* Aspect) { debug("Notification from ArticleDownloader received"); ArticleDownloader* pArticleDownloader = (ArticleDownloader*) Caller; if ((pArticleDownloader->GetStatus() == ArticleDownloader::adFinished) || (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed) || (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry)) { ArticleCompleted(pArticleDownloader); } } void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader) { debug("Article downloaded"); FileInfo* pFileInfo = pArticleDownloader->GetFileInfo(); NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo(); ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo(); bool bRetry = false; bool fileCompleted = false; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); if (pArticleDownloader->GetStatus() == ArticleDownloader::adFinished) { pArticleInfo->SetStatus(ArticleInfo::aiFinished); pFileInfo->SetSuccessSize(pFileInfo->GetSuccessSize() + pArticleInfo->GetSize()); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() + pArticleInfo->GetSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0)); pFileInfo->SetSuccessArticles(pFileInfo->GetSuccessArticles() + 1); } else if (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed) { pArticleInfo->SetStatus(ArticleInfo::aiFailed); pFileInfo->SetFailedSize(pFileInfo->GetFailedSize() + pArticleInfo->GetSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() + pArticleInfo->GetSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0)); pFileInfo->SetFailedArticles(pFileInfo->GetFailedArticles() + 1); } else if (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry) { pArticleInfo->SetStatus(ArticleInfo::aiUndefined); bRetry = true; } if (!bRetry) { pFileInfo->SetRemainingSize(pFileInfo->GetRemainingSize() - pArticleInfo->GetSize()); pNZBInfo->SetRemainingSize(pNZBInfo->GetRemainingSize() - pArticleInfo->GetSize()); if (pFileInfo->GetPaused()) { pNZBInfo->SetPausedSize(pNZBInfo->GetPausedSize() - pArticleInfo->GetSize()); } pFileInfo->SetCompletedArticles(pFileInfo->GetCompletedArticles() + 1); fileCompleted = (int)pFileInfo->GetArticles()->size() == pFileInfo->GetCompletedArticles(); pNZBInfo->GetServerStats()->Add(pArticleDownloader->GetServerStats()); } if (!pFileInfo->GetFilenameConfirmed() && pArticleDownloader->GetStatus() == ArticleDownloader::adFinished && pArticleDownloader->GetArticleFilename()) { pFileInfo->SetFilename(pArticleDownloader->GetArticleFilename()); pFileInfo->SetFilenameConfirmed(true); if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce && !pNZBInfo->GetManyDupeFiles() && Util::FileExists(pNZBInfo->GetDestDir(), pFileInfo->GetFilename())) { warn("File \"%s\" seems to be duplicate, cancelling download and deleting file from queue", pFileInfo->GetFilename()); fileCompleted = false; pFileInfo->SetAutoDeleted(true); DeleteQueueEntry(pDownloadQueue, pFileInfo); } } bool deleteFileObj = false; if (fileCompleted && !IsStopped() && !pFileInfo->GetDeleted()) { // all jobs done DownloadQueue::Unlock(); pArticleDownloader->CompleteFileParts(); pDownloadQueue = DownloadQueue::Lock(); deleteFileObj = true; } CheckHealth(pDownloadQueue, pFileInfo); bool hasOtherDownloaders = false; for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pDownloader = *it; if (pDownloader != pArticleDownloader && pDownloader->GetFileInfo() == pFileInfo) { hasOtherDownloaders = true; break; } } deleteFileObj |= pFileInfo->GetDeleted() && !hasOtherDownloaders; // remove downloader from downloader list m_ActiveDownloads.erase(std::find(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), pArticleDownloader)); pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() - 1); pNZBInfo->SetActiveDownloads(pNZBInfo->GetActiveDownloads() - 1); if (deleteFileObj) { DeleteFileInfo(pDownloadQueue, pFileInfo, fileCompleted); pDownloadQueue->Save(); } DownloadQueue::Unlock(); } void QueueCoordinator::StatFileInfo(FileInfo* pFileInfo, bool bCompleted) { NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo(); if (bCompleted || pNZBInfo->GetDeleting()) { pNZBInfo->SetSuccessSize(pNZBInfo->GetSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() + pFileInfo->GetFailedSize()); pNZBInfo->SetFailedArticles(pNZBInfo->GetFailedArticles() + pFileInfo->GetFailedArticles() + pFileInfo->GetMissedArticles()); pNZBInfo->SetSuccessArticles(pNZBInfo->GetSuccessArticles() + pFileInfo->GetSuccessArticles()); if (pFileInfo->GetParFile()) { pNZBInfo->SetParSuccessSize(pNZBInfo->GetParSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() + pFileInfo->GetFailedSize()); } } else if (!pNZBInfo->GetDeleting() && !pNZBInfo->GetParCleanup()) { // file deleted but not the whole nzb and not par-cleanup pNZBInfo->SetFileCount(pNZBInfo->GetFileCount() - 1); pNZBInfo->SetSize(pNZBInfo->GetSize() - pFileInfo->GetSize()); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetTotalArticles(pNZBInfo->GetTotalArticles() - pFileInfo->GetTotalArticles()); if (pFileInfo->GetParFile()) { pNZBInfo->SetParSize(pNZBInfo->GetParSize() - pFileInfo->GetSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); } pNZBInfo->SetRemainingSize(pNZBInfo->GetRemainingSize() - pFileInfo->GetRemainingSize()); if (pFileInfo->GetPaused()) { pNZBInfo->SetPausedSize(pNZBInfo->GetPausedSize() - pFileInfo->GetRemainingSize()); } } if (pFileInfo->GetParFile()) { pNZBInfo->SetRemainingParCount(pNZBInfo->GetRemainingParCount() - 1); } if (pFileInfo->GetPaused()) { pNZBInfo->SetPausedFileCount(pNZBInfo->GetPausedFileCount() - 1); } } void QueueCoordinator::DeleteFileInfo(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, bool bCompleted) { bool fileDeleted = pFileInfo->GetDeleted(); pFileInfo->SetDeleted(true); StatFileInfo(pFileInfo, bCompleted); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo); } if (!bCompleted) { DiscardDiskFile(pFileInfo); } NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo(); DownloadQueue::Aspect aspect = { bCompleted && !fileDeleted ? DownloadQueue::eaFileCompleted : DownloadQueue::eaFileDeleted, pDownloadQueue, pNZBInfo, pFileInfo }; pDownloadQueue->Notify(&aspect); // nzb-file could be deleted from queue in "Notify", check if it is still in queue. if (std::find(pDownloadQueue->GetQueue()->begin(), pDownloadQueue->GetQueue()->end(), pNZBInfo) != pDownloadQueue->GetQueue()->end()) { pNZBInfo->GetFileList()->Remove(pFileInfo); delete pFileInfo; } } void QueueCoordinator::DiscardDiskFile(FileInfo* pFileInfo) { // deleting temporary files if (!g_pOptions->GetDirectWrite() || g_pOptions->GetContinuePartial()) { for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; if (pa->GetResultFilename()) { remove(pa->GetResultFilename()); } } } if (g_pOptions->GetDirectWrite() && pFileInfo->GetOutputFilename()) { remove(pFileInfo->GetOutputFilename()); } } void QueueCoordinator::CheckHealth(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo) { if (g_pOptions->GetHealthCheck() == Options::hcNone || pFileInfo->GetNZBInfo()->GetHealthPaused() || pFileInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth || pFileInfo->GetNZBInfo()->CalcHealth() >= pFileInfo->GetNZBInfo()->CalcCriticalHealth(true)) { return; } if (g_pOptions->GetHealthCheck() == Options::hcPause) { warn("Pausing %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth(true) / 10.0); pFileInfo->GetNZBInfo()->SetHealthPaused(true); pDownloadQueue->EditEntry(pFileInfo->GetNZBInfo()->GetID(), DownloadQueue::eaGroupPause, 0, NULL); } else if (g_pOptions->GetHealthCheck() == Options::hcDelete) { warn("Cancelling download and deleting %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth(true) / 10.0); pFileInfo->GetNZBInfo()->SetDeleteStatus(NZBInfo::dsHealth); pDownloadQueue->EditEntry(pFileInfo->GetNZBInfo()->GetID(), DownloadQueue::eaGroupDelete, 0, NULL); } } void QueueCoordinator::LogDebugInfo() { debug(" SpeedMeter"); debug(" ----------"); float fSpeed = (float)(CalcCurrentDownloadSpeed() / 1024.0); int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE; debug(" Speed: %f", fSpeed); debug(" SpeedStartTime: %i", m_iSpeedStartTime); debug(" SpeedTotalBytes: %i", m_iSpeedTotalBytes); debug(" SpeedBytesIndex: %i", m_iSpeedBytesIndex); debug(" AllBytes: %i", m_iAllBytes); debug(" Time: %i", (int)time(NULL)); debug(" TimeDiff: %i", iTimeDiff); for (int i=0; i < SPEEDMETER_SLOTS; i++) { debug(" Bytes[%i]: %i, Time[%i]: %i", i, m_iSpeedBytes[i], i, m_iSpeedTime[i]); } debug(" QueueCoordinator"); debug(" ----------------"); DownloadQueue::Lock(); debug(" Active Downloads: %i", m_ActiveDownloads.size()); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pArticleDownloader = *it; pArticleDownloader->LogDebugInfo(); } DownloadQueue::Unlock(); debug(""); } void QueueCoordinator::ResetHangingDownloads() { if (g_pOptions->GetTerminateTimeout() == 0 && g_pOptions->GetConnectionTimeout() == 0) { return; } DownloadQueue::Lock(); time_t tm = ::time(NULL); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();) { ArticleDownloader* pArticleDownloader = *it; if (tm - pArticleDownloader->GetLastUpdateTime() > g_pOptions->GetConnectionTimeout() + 1 && pArticleDownloader->GetStatus() == ArticleDownloader::adRunning) { error("Cancelling hanging download %s", pArticleDownloader->GetInfoName()); pArticleDownloader->Stop(); } if (tm - pArticleDownloader->GetLastUpdateTime() > g_pOptions->GetTerminateTimeout() && pArticleDownloader->GetStatus() == ArticleDownloader::adRunning) { ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo(); debug("Terminating hanging download %s", pArticleDownloader->GetInfoName()); if (pArticleDownloader->Terminate()) { error("Terminated hanging download %s", pArticleDownloader->GetInfoName()); pArticleInfo->SetStatus(ArticleInfo::aiUndefined); } else { error("Could not terminate hanging download %s", Util::BaseFileName(pArticleInfo->GetResultFilename())); } m_ActiveDownloads.erase(it); pArticleDownloader->GetFileInfo()->SetActiveDownloads(pArticleDownloader->GetFileInfo()->GetActiveDownloads() - 1); pArticleDownloader->GetFileInfo()->GetNZBInfo()->SetActiveDownloads(pArticleDownloader->GetFileInfo()->GetNZBInfo()->GetActiveDownloads() - 1); // it's not safe to destroy pArticleDownloader, because the state of object is unknown delete pArticleDownloader; it = m_ActiveDownloads.begin(); continue; } it++; } DownloadQueue::Unlock(); } void QueueCoordinator::EnterLeaveStandBy(bool bEnter) { m_mutexStat.Lock(); m_bStandBy = bEnter; if (bEnter) { m_tPausedFrom = time(NULL); } else { if (m_tStartDownload == 0) { m_tStartDownload = time(NULL); } else { m_tStartDownload += time(NULL) - m_tPausedFrom; } m_tPausedFrom = 0; ResetSpeedStat(); } m_mutexStat.Unlock(); } void QueueCoordinator::CalcStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy) { m_mutexStat.Lock(); if (m_tStartServer > 0) { *iUpTimeSec = (int)(time(NULL) - m_tStartServer); } else { *iUpTimeSec = 0; } *bStandBy = m_bStandBy; if (m_bStandBy) { *iDnTimeSec = (int)(m_tPausedFrom - m_tStartDownload); } else { *iDnTimeSec = (int)(time(NULL) - m_tStartDownload); } *iAllBytes = m_iAllBytes; m_mutexStat.Unlock(); } /* * Detects large step changes of system time and adjust statistics. */ void QueueCoordinator::AdjustStartTime() { time_t m_tCurTime = time(NULL); time_t tDiff = m_tCurTime - m_tLastCheck; if (tDiff > 60 || tDiff < 0) { m_tStartServer += tDiff + 1; // "1" because the method is called once per second if (m_tStartDownload != 0 && !m_bStandBy) { m_tStartDownload += tDiff + 1; } } m_tLastCheck = m_tCurTime; } /* * Returns True if Entry was deleted from Queue or False if it was scheduled for Deletion. * NOTE: "False" does not mean unsuccess; the entry is (or will be) deleted in any case. */ bool QueueCoordinator::DeleteQueueEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo) { pFileInfo->SetDeleted(true); bool hasDownloads = false; for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pArticleDownloader = *it; if (pArticleDownloader->GetFileInfo() == pFileInfo) { hasDownloads = true; pArticleDownloader->Stop(); } } if (!hasDownloads) { DeleteFileInfo(pDownloadQueue, pFileInfo, false); } return hasDownloads; } bool QueueCoordinator::SetQueueEntryCategory(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szCategory) { if (pNZBInfo->GetPostInfo()) { error("Could not change category for %s. File in post-process-stage", pNZBInfo->GetName()); return false; } char szOldDestDir[1024]; strncpy(szOldDestDir, pNZBInfo->GetDestDir(), 1024); szOldDestDir[1024-1] = '\0'; pNZBInfo->SetCategory(szCategory); pNZBInfo->BuildDestDirName(); bool bDirUnchanged = !strcmp(pNZBInfo->GetDestDir(), szOldDestDir); bool bOK = bDirUnchanged || ArticleDownloader::MoveCompletedFiles(pNZBInfo, szOldDestDir); return bOK; } bool QueueCoordinator::SetQueueEntryName(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szName) { if (pNZBInfo->GetPostInfo()) { error("Could not rename %s. File in post-process-stage", pNZBInfo->GetName()); return false; } if (Util::EmptyStr(szName)) { error("Could not rename %s. The new name cannot be empty", pNZBInfo->GetName()); return false; } char szNZBNicename[1024]; NZBInfo::MakeNiceNZBName(szName, szNZBNicename, sizeof(szNZBNicename), false); pNZBInfo->SetName(szNZBNicename); if (pNZBInfo->GetKind() == NZBInfo::nkUrl) { char szFilename[1024]; snprintf(szFilename, 1024, "%s.nzb", szNZBNicename); szFilename[1024-1] = '\0'; pNZBInfo->SetFilename(szFilename); return true; } char szOldDestDir[1024]; strncpy(szOldDestDir, pNZBInfo->GetDestDir(), 1024); szOldDestDir[1024-1] = '\0'; pNZBInfo->BuildDestDirName(); bool bDirUnchanged = !strcmp(pNZBInfo->GetDestDir(), szOldDestDir); bool bOK = bDirUnchanged || ArticleDownloader::MoveCompletedFiles(pNZBInfo, szOldDestDir); return bOK; } bool QueueCoordinator::MergeQueueEntries(DownloadQueue* pDownloadQueue, NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZBInfo) { if (pDestNZBInfo->GetPostInfo() || pSrcNZBInfo->GetPostInfo()) { error("Could not merge %s and %s. File in post-process-stage", pDestNZBInfo->GetName(), pSrcNZBInfo->GetName()); return false; } if (pDestNZBInfo->GetKind() == NZBInfo::nkUrl || pSrcNZBInfo->GetKind() == NZBInfo::nkUrl) { error("Could not merge %s and %s. URLs cannot be merged", pDestNZBInfo->GetName(), pSrcNZBInfo->GetName()); return false; } // set new dest directory, new category and move downloaded files to new dest directory pSrcNZBInfo->SetFilename(pSrcNZBInfo->GetFilename()); SetQueueEntryCategory(pDownloadQueue, pSrcNZBInfo, pDestNZBInfo->GetCategory()); // reattach file items to new NZBInfo-object for (FileList::iterator it = pSrcNZBInfo->GetFileList()->begin(); it != pSrcNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetNZBInfo(pDestNZBInfo); pDestNZBInfo->GetFileList()->push_back(pFileInfo); } pSrcNZBInfo->GetFileList()->clear(); pDestNZBInfo->SetFileCount(pDestNZBInfo->GetFileCount() + pSrcNZBInfo->GetFileCount()); pDestNZBInfo->SetActiveDownloads(pDestNZBInfo->GetActiveDownloads() + pSrcNZBInfo->GetActiveDownloads()); pDestNZBInfo->SetFullContentHash(0); pDestNZBInfo->SetFilteredContentHash(0); pDestNZBInfo->SetSize(pDestNZBInfo->GetSize() + pSrcNZBInfo->GetSize()); pDestNZBInfo->SetRemainingSize(pDestNZBInfo->GetRemainingSize() + pSrcNZBInfo->GetRemainingSize()); pDestNZBInfo->SetPausedFileCount(pDestNZBInfo->GetPausedFileCount() + pSrcNZBInfo->GetPausedFileCount()); pDestNZBInfo->SetPausedSize(pDestNZBInfo->GetPausedSize() + pSrcNZBInfo->GetPausedSize()); pDestNZBInfo->SetSuccessSize(pDestNZBInfo->GetSuccessSize() + pSrcNZBInfo->GetSuccessSize()); pDestNZBInfo->SetCurrentSuccessSize(pDestNZBInfo->GetCurrentSuccessSize() + pSrcNZBInfo->GetCurrentSuccessSize()); pDestNZBInfo->SetFailedSize(pDestNZBInfo->GetFailedSize() + pSrcNZBInfo->GetFailedSize()); pDestNZBInfo->SetCurrentFailedSize(pDestNZBInfo->GetCurrentFailedSize() + pSrcNZBInfo->GetCurrentFailedSize()); pDestNZBInfo->SetParSize(pDestNZBInfo->GetParSize() + pSrcNZBInfo->GetParSize()); pDestNZBInfo->SetParSuccessSize(pDestNZBInfo->GetParSuccessSize() + pSrcNZBInfo->GetParSuccessSize()); pDestNZBInfo->SetParCurrentSuccessSize(pDestNZBInfo->GetParCurrentSuccessSize() + pSrcNZBInfo->GetParCurrentSuccessSize()); pDestNZBInfo->SetParFailedSize(pDestNZBInfo->GetParFailedSize() + pSrcNZBInfo->GetParFailedSize()); pDestNZBInfo->SetParCurrentFailedSize(pDestNZBInfo->GetParCurrentFailedSize() + pSrcNZBInfo->GetParCurrentFailedSize()); pDestNZBInfo->SetRemainingParCount(pDestNZBInfo->GetRemainingParCount() + pSrcNZBInfo->GetRemainingParCount()); pDestNZBInfo->SetMinTime(pSrcNZBInfo->GetMinTime() < pDestNZBInfo->GetMinTime() ? pSrcNZBInfo->GetMinTime() : pDestNZBInfo->GetMinTime()); pDestNZBInfo->SetMaxTime(pSrcNZBInfo->GetMaxTime() > pDestNZBInfo->GetMaxTime() ? pSrcNZBInfo->GetMaxTime() : pDestNZBInfo->GetMaxTime()); // reattach completed file items to new NZBInfo-object for (NZBInfo::Files::iterator it = pSrcNZBInfo->GetCompletedFiles()->begin(); it != pSrcNZBInfo->GetCompletedFiles()->end(); it++) { char* szFileName = *it; pDestNZBInfo->GetCompletedFiles()->push_back(szFileName); } pSrcNZBInfo->GetCompletedFiles()->clear(); // concatenate QueuedFilenames using character '|' as separator int iLen = strlen(pDestNZBInfo->GetQueuedFilename()) + strlen(pSrcNZBInfo->GetQueuedFilename()) + 1; char* szQueuedFilename = (char*)malloc(iLen); snprintf(szQueuedFilename, iLen, "%s|%s", pDestNZBInfo->GetQueuedFilename(), pSrcNZBInfo->GetQueuedFilename()); szQueuedFilename[iLen - 1] = '\0'; pDestNZBInfo->SetQueuedFilename(szQueuedFilename); free(szQueuedFilename); pDownloadQueue->GetQueue()->Remove(pSrcNZBInfo); delete pSrcNZBInfo; return true; } /* * Creates new nzb-item out of existing files from other nzb-items. * If any of file-items is being downloaded the command fail. * For each file-item an event "eaFileDeleted" is fired. */ bool QueueCoordinator::SplitQueueEntries(DownloadQueue* pDownloadQueue, FileList* pFileList, const char* szName, NZBInfo** pNewNZBInfo) { if (pFileList->empty()) { return false; } NZBInfo* pSrcNZBInfo = NULL; for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetActiveDownloads() > 0 || pFileInfo->GetCompletedArticles() > 0) { error("Could not split %s. File is already (partially) downloaded", pFileInfo->GetFilename()); return false; } if (pFileInfo->GetNZBInfo()->GetPostInfo()) { error("Could not split %s. File in post-process-stage", pFileInfo->GetFilename()); return false; } if (!pSrcNZBInfo) { pSrcNZBInfo = pFileInfo->GetNZBInfo(); } } NZBInfo* pNZBInfo = new NZBInfo(); pDownloadQueue->GetQueue()->push_back(pNZBInfo); pNZBInfo->SetFilename(pSrcNZBInfo->GetFilename()); pNZBInfo->SetName(szName); pNZBInfo->SetCategory(pSrcNZBInfo->GetCategory()); pNZBInfo->SetFullContentHash(0); pNZBInfo->SetFilteredContentHash(0); pNZBInfo->SetPriority(pSrcNZBInfo->GetPriority()); pNZBInfo->BuildDestDirName(); pNZBInfo->SetQueuedFilename(pSrcNZBInfo->GetQueuedFilename()); pNZBInfo->GetParameters()->CopyFrom(pSrcNZBInfo->GetParameters()); pSrcNZBInfo->SetFullContentHash(0); pSrcNZBInfo->SetFilteredContentHash(0); for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { FileInfo* pFileInfo = *it; DownloadQueue::Aspect aspect = { DownloadQueue::eaFileDeleted, pDownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo }; pDownloadQueue->Notify(&aspect); pFileInfo->SetNZBInfo(pNZBInfo); pNZBInfo->GetFileList()->push_back(pFileInfo); pSrcNZBInfo->GetFileList()->Remove(pFileInfo); pSrcNZBInfo->SetFileCount(pSrcNZBInfo->GetFileCount() - 1); pSrcNZBInfo->SetSize(pSrcNZBInfo->GetSize() - pFileInfo->GetSize()); pSrcNZBInfo->SetRemainingSize(pSrcNZBInfo->GetRemainingSize() - pFileInfo->GetRemainingSize()); pSrcNZBInfo->SetSuccessSize(pSrcNZBInfo->GetSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetCurrentSuccessSize(pSrcNZBInfo->GetCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetFailedSize(pSrcNZBInfo->GetFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pSrcNZBInfo->SetCurrentFailedSize(pSrcNZBInfo->GetCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetFileCount(pNZBInfo->GetFileCount() + 1); pNZBInfo->SetSize(pNZBInfo->GetSize() + pFileInfo->GetSize()); pNZBInfo->SetRemainingSize(pNZBInfo->GetRemainingSize() + pFileInfo->GetRemainingSize()); pNZBInfo->SetSuccessSize(pNZBInfo->GetSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); if (pFileInfo->GetParFile()) { pSrcNZBInfo->SetParSize(pSrcNZBInfo->GetParSize() - pFileInfo->GetSize()); pSrcNZBInfo->SetParSuccessSize(pSrcNZBInfo->GetParSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetParCurrentSuccessSize(pSrcNZBInfo->GetParCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetParFailedSize(pSrcNZBInfo->GetParFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pSrcNZBInfo->SetParCurrentFailedSize(pSrcNZBInfo->GetParCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pSrcNZBInfo->SetRemainingParCount(pSrcNZBInfo->GetRemainingParCount() - 1); pNZBInfo->SetParSize(pNZBInfo->GetParSize() + pFileInfo->GetSize()); pNZBInfo->SetParSuccessSize(pNZBInfo->GetParSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); pNZBInfo->SetRemainingParCount(pNZBInfo->GetRemainingParCount() + 1); } if (pFileInfo->GetPaused()) { pSrcNZBInfo->SetPausedFileCount(pSrcNZBInfo->GetPausedFileCount() - 1); pSrcNZBInfo->SetPausedSize(pSrcNZBInfo->GetPausedSize() - pFileInfo->GetRemainingSize()); pNZBInfo->SetPausedFileCount(pSrcNZBInfo->GetPausedFileCount() + 1); pNZBInfo->SetPausedSize(pNZBInfo->GetPausedSize() + pFileInfo->GetRemainingSize()); } } pNZBInfo->UpdateMinMaxTime(); if (pSrcNZBInfo->GetCompletedFiles()->empty()) { pSrcNZBInfo->UpdateMinMaxTime(); } if (pSrcNZBInfo->GetFileList()->empty()) { pDownloadQueue->GetQueue()->Remove(pSrcNZBInfo); delete pSrcNZBInfo; } *pNewNZBInfo = pNZBInfo; return true; }