mirror of
https://github.com/nzbget/nzbget.git
synced 2026-02-23 09:44:12 -05:00
1023 lines
26 KiB
C++
1023 lines
26 KiB
C++
/*
|
|
* This file is part of nzbget
|
|
*
|
|
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
|
|
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <cstdio>
|
|
#include <sys/stat.h>
|
|
#ifndef WIN32
|
|
#include <unistd.h>
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#include "nzbget.h"
|
|
#include "QueueCoordinator.h"
|
|
#include "Options.h"
|
|
#include "ServerPool.h"
|
|
#include "ArticleDownloader.h"
|
|
#include "DiskState.h"
|
|
#include "Log.h"
|
|
#include "Util.h"
|
|
#include "Decoder.h"
|
|
|
|
extern Options* g_pOptions;
|
|
extern ServerPool* g_pServerPool;
|
|
extern DiskState* g_pDiskState;
|
|
|
|
QueueCoordinator::QueueCoordinator()
|
|
{
|
|
debug("Creating QueueCoordinator");
|
|
|
|
m_bHasMoreJobs = true;
|
|
ResetSpeedStat();
|
|
|
|
m_iAllBytes = 0;
|
|
m_tStartServer = 0;
|
|
m_tStartDownload = 0;
|
|
m_tPausedFrom = 0;
|
|
m_bStandBy = true;
|
|
|
|
YDecoder::Init();
|
|
}
|
|
|
|
QueueCoordinator::~QueueCoordinator()
|
|
{
|
|
debug("Destroying QueueCoordinator");
|
|
// Cleanup
|
|
|
|
debug("Deleting DownloadQueue");
|
|
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
|
|
{
|
|
delete *it;
|
|
}
|
|
m_DownloadQueue.GetFileQueue()->clear();
|
|
|
|
debug("Deleting ArticleDownloaders");
|
|
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
|
|
{
|
|
delete *it;
|
|
}
|
|
m_ActiveDownloads.clear();
|
|
|
|
YDecoder::Final();
|
|
|
|
debug("QueueCoordinator destroyed");
|
|
}
|
|
|
|
void QueueCoordinator::Run()
|
|
{
|
|
debug("Entering QueueCoordinator-loop");
|
|
|
|
m_mutexDownloadQueue.Lock();
|
|
|
|
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);
|
|
|
|
m_mutexDownloadQueue.Unlock();
|
|
|
|
m_tStartServer = time(NULL);
|
|
m_tLastCheck = m_tStartServer;
|
|
bool bWasStandBy = true;
|
|
bool bArticeDownloadsRunning = false;
|
|
int iResetCounter = 0;
|
|
|
|
while (!IsStopped())
|
|
{
|
|
if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))
|
|
{
|
|
NNTPConnection* pConnection = g_pServerPool->GetConnection(0);
|
|
if (pConnection)
|
|
{
|
|
// start download for next article
|
|
FileInfo* pFileInfo;
|
|
ArticleInfo* pArticleInfo;
|
|
|
|
m_mutexDownloadQueue.Lock();
|
|
bool bHasMoreArticles = GetNextArticle(pFileInfo, pArticleInfo);
|
|
bArticeDownloadsRunning = !m_ActiveDownloads.empty();
|
|
m_bHasMoreJobs = bHasMoreArticles || bArticeDownloadsRunning;
|
|
if (bHasMoreArticles && !IsStopped() && Thread::GetThreadCount() < g_pOptions->GetThreadLimit())
|
|
{
|
|
StartArticleDownload(pFileInfo, pArticleInfo, pConnection);
|
|
bArticeDownloadsRunning = true;
|
|
}
|
|
else
|
|
{
|
|
g_pServerPool->FreeConnection(pConnection, false);
|
|
}
|
|
m_mutexDownloadQueue.Unlock();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_mutexDownloadQueue.Lock();
|
|
bArticeDownloadsRunning = !m_ActiveDownloads.empty();
|
|
m_mutexDownloadQueue.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();
|
|
}
|
|
}
|
|
|
|
// waiting for downloads
|
|
debug("QueueCoordinator: waiting for Downloads to complete");
|
|
bool completed = false;
|
|
while (!completed)
|
|
{
|
|
m_mutexDownloadQueue.Lock();
|
|
completed = m_ActiveDownloads.size() == 0;
|
|
m_mutexDownloadQueue.Unlock();
|
|
usleep(100 * 1000);
|
|
ResetHangingDownloads();
|
|
}
|
|
debug("QueueCoordinator: Downloads are completed");
|
|
|
|
debug("Exiting QueueCoordinator-loop");
|
|
}
|
|
|
|
void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
|
|
{
|
|
debug("Adding NZBFile to queue");
|
|
|
|
m_mutexDownloadQueue.Lock();
|
|
|
|
FileQueue tmpFileQueue;
|
|
tmpFileQueue.clear();
|
|
FileQueue DupeList;
|
|
DupeList.clear();
|
|
|
|
int index1 = 0;
|
|
for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++)
|
|
{
|
|
index1++;
|
|
FileInfo* pFileInfo = *it;
|
|
|
|
if (g_pOptions->GetDupeCheck())
|
|
{
|
|
bool dupe = false;
|
|
if (IsDupe(pFileInfo))
|
|
{
|
|
warn("File \"%s\" seems to be duplicate, skipping", pFileInfo->GetFilename());
|
|
dupe = true;
|
|
}
|
|
int index2 = 0;
|
|
for (NZBFile::FileInfos::iterator it2 = pNZBFile->GetFileInfos()->begin(); it2 != pNZBFile->GetFileInfos()->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 nzb-request, adding only the biggest file", pFileInfo->GetFilename());
|
|
dupe = true;
|
|
break;
|
|
}
|
|
}
|
|
if (dupe)
|
|
{
|
|
DupeList.push_back(pFileInfo);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bAddFirst)
|
|
{
|
|
tmpFileQueue.push_front(pFileInfo);
|
|
}
|
|
else
|
|
{
|
|
tmpFileQueue.push_back(pFileInfo);
|
|
}
|
|
}
|
|
|
|
for (FileQueue::iterator it = tmpFileQueue.begin(); it != tmpFileQueue.end(); it++)
|
|
{
|
|
if (bAddFirst)
|
|
{
|
|
m_DownloadQueue.GetFileQueue()->push_front(*it);
|
|
}
|
|
else
|
|
{
|
|
m_DownloadQueue.GetFileQueue()->push_back(*it);
|
|
}
|
|
}
|
|
|
|
for (FileQueue::iterator it = DupeList.begin(); it != DupeList.end(); it++)
|
|
{
|
|
FileInfo* pFileInfo = *it;
|
|
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
|
|
{
|
|
g_pDiskState->DiscardFile(pFileInfo);
|
|
}
|
|
delete pFileInfo;
|
|
}
|
|
|
|
m_DownloadQueue.GetNZBInfoList()->Add(pNZBFile->GetNZBInfo());
|
|
|
|
pNZBFile->DetachFileInfos();
|
|
|
|
Aspect aspect = { eaNZBFileAdded, &m_DownloadQueue, pNZBFile->GetNZBInfo(), NULL };
|
|
Notify(&aspect);
|
|
|
|
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
|
|
{
|
|
g_pDiskState->SaveDownloadQueue(&m_DownloadQueue);
|
|
}
|
|
|
|
m_mutexDownloadQueue.Unlock();
|
|
}
|
|
|
|
/*
|
|
* NOTE: see note to "AddSpeedReading"
|
|
*/
|
|
float QueueCoordinator::CalcCurrentDownloadSpeed()
|
|
{
|
|
if (m_bStandBy)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE;
|
|
if (iTimeDiff == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
float fSpeed = m_iSpeedTotalBytes / 1024.0f / iTimeDiff;
|
|
return fSpeed;
|
|
}
|
|
|
|
void QueueCoordinator::AddSpeedReading(int iBytes)
|
|
{
|
|
int iNowSlot = (int)time(NULL) / 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 outging 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;
|
|
}
|
|
|
|
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()
|
|
{
|
|
m_iSpeedStartTime = (int)time(NULL) / 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;
|
|
}
|
|
|
|
long long QueueCoordinator::CalcRemainingSize()
|
|
{
|
|
long long lRemainingSize = 0;
|
|
|
|
m_mutexDownloadQueue.Lock();
|
|
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
|
|
{
|
|
FileInfo* pFileInfo = *it;
|
|
if (!pFileInfo->GetPaused() && !pFileInfo->GetDeleted())
|
|
{
|
|
lRemainingSize += pFileInfo->GetRemainingSize();
|
|
}
|
|
}
|
|
m_mutexDownloadQueue.Unlock();
|
|
|
|
return lRemainingSize;
|
|
}
|
|
|
|
/*
|
|
* NOTE: DownloadQueue must be locked prior to call of this function
|
|
* 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(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)
|
|
{
|
|
Aspect aspect = { eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo };
|
|
Notify(&aspect);
|
|
|
|
DeleteFileInfo(pFileInfo, false);
|
|
}
|
|
return hasDownloads;
|
|
}
|
|
|
|
void QueueCoordinator::Stop()
|
|
{
|
|
Thread::Stop();
|
|
|
|
debug("Stopping ArticleDownloads");
|
|
m_mutexDownloadQueue.Lock();
|
|
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
|
|
{
|
|
(*it)->Stop();
|
|
}
|
|
m_mutexDownloadQueue.Unlock();
|
|
debug("ArticleDownloads are notified");
|
|
}
|
|
|
|
/*
|
|
* Returns next article for download.
|
|
*/
|
|
bool QueueCoordinator::GetNextArticle(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.
|
|
|
|
//debug("QueueCoordinator::GetNextArticle()");
|
|
|
|
bool bOK = false;
|
|
|
|
// pCheckedFiles stores
|
|
bool* pCheckedFiles = NULL;
|
|
|
|
while (!bOK)
|
|
{
|
|
//debug("QueueCoordinator::GetNextArticle() - in loop");
|
|
|
|
pFileInfo = NULL;
|
|
int iNum = 0;
|
|
int iFileNum = 0;
|
|
|
|
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
|
|
{
|
|
FileInfo* pFileInfo1 = *it;
|
|
if ((!pCheckedFiles || !pCheckedFiles[iNum]) &&
|
|
!pFileInfo1->GetPaused() && !pFileInfo1->GetDeleted() &&
|
|
(!pFileInfo || (pFileInfo1->GetPriority() > pFileInfo->GetPriority())))
|
|
{
|
|
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 iArrSize = sizeof(bool) * m_DownloadQueue.GetFileQueue()->size();
|
|
pCheckedFiles = (bool*)malloc(iArrSize);
|
|
memset(pCheckedFiles, false, iArrSize);
|
|
}
|
|
pCheckedFiles[iFileNum] = true;
|
|
}
|
|
}
|
|
|
|
if (pCheckedFiles)
|
|
{
|
|
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);
|
|
BuildArticleFilename(pArticleDownloader, pFileInfo, pArticleInfo);
|
|
|
|
pArticleInfo->SetStatus(ArticleInfo::aiRunning);
|
|
pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() + 1);
|
|
|
|
m_ActiveDownloads.push_back(pArticleDownloader);
|
|
pArticleDownloader->Start();
|
|
}
|
|
|
|
void QueueCoordinator::BuildArticleFilename(ArticleDownloader* pArticleDownloader, FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
|
|
{
|
|
char name[1024];
|
|
|
|
snprintf(name, 1024, "%s%i.%03i", g_pOptions->GetTempDir(), pFileInfo->GetID(), pArticleInfo->GetPartNumber());
|
|
name[1024-1] = '\0';
|
|
pArticleInfo->SetResultFilename(name);
|
|
|
|
char tmpname[1024];
|
|
snprintf(tmpname, 1024, "%s.tmp", name);
|
|
tmpname[1024-1] = '\0';
|
|
pArticleDownloader->SetTempFilename(tmpname);
|
|
|
|
snprintf(name, 1024, "%s%c%s [%i/%i]", pFileInfo->GetNZBInfo()->GetName(), (int)PATH_SEPARATOR, pFileInfo->GetFilename(), pArticleInfo->GetPartNumber(), pFileInfo->GetArticles()->size());
|
|
name[1024-1] = '\0';
|
|
pArticleDownloader->SetInfoName(name);
|
|
|
|
if (g_pOptions->GetDirectWrite())
|
|
{
|
|
pFileInfo->LockOutputFile();
|
|
|
|
if (pFileInfo->GetOutputFilename())
|
|
{
|
|
strncpy(name, pFileInfo->GetOutputFilename(), 1024);
|
|
name[1024-1] = '\0';
|
|
}
|
|
else
|
|
{
|
|
snprintf(name, 1024, "%s%c%i.out.tmp", pFileInfo->GetNZBInfo()->GetDestDir(), (int)PATH_SEPARATOR, pFileInfo->GetID());
|
|
name[1024-1] = '\0';
|
|
pFileInfo->SetOutputFilename(name);
|
|
}
|
|
|
|
pFileInfo->UnlockOutputFile();
|
|
|
|
pArticleDownloader->SetOutputFilename(name);
|
|
}
|
|
}
|
|
|
|
DownloadQueue* QueueCoordinator::LockQueue()
|
|
{
|
|
m_mutexDownloadQueue.Lock();
|
|
return &m_DownloadQueue;
|
|
}
|
|
|
|
void QueueCoordinator::UnlockQueue()
|
|
{
|
|
m_mutexDownloadQueue.Unlock();
|
|
}
|
|
|
|
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();
|
|
ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo();
|
|
bool bPaused = false;
|
|
bool fileCompleted = false;
|
|
|
|
m_mutexDownloadQueue.Lock();
|
|
|
|
if (pArticleDownloader->GetStatus() == ArticleDownloader::adFinished)
|
|
{
|
|
pArticleInfo->SetStatus(ArticleInfo::aiFinished);
|
|
}
|
|
else if (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed)
|
|
{
|
|
pArticleInfo->SetStatus(ArticleInfo::aiFailed);
|
|
}
|
|
else if (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry)
|
|
{
|
|
pArticleInfo->SetStatus(ArticleInfo::aiUndefined);
|
|
bPaused = true;
|
|
}
|
|
|
|
if (!bPaused)
|
|
{
|
|
pFileInfo->SetRemainingSize(pFileInfo->GetRemainingSize() - pArticleInfo->GetSize());
|
|
pFileInfo->SetCompleted(pFileInfo->GetCompleted() + 1);
|
|
fileCompleted = (int)pFileInfo->GetArticles()->size() == pFileInfo->GetCompleted();
|
|
}
|
|
|
|
if (!pFileInfo->GetFilenameConfirmed() &&
|
|
pArticleDownloader->GetStatus() == ArticleDownloader::adFinished &&
|
|
pArticleDownloader->GetArticleFilename())
|
|
{
|
|
pFileInfo->SetFilename(pArticleDownloader->GetArticleFilename());
|
|
pFileInfo->SetFilenameConfirmed(true);
|
|
if (g_pOptions->GetDupeCheck() && pFileInfo->IsDupe(pFileInfo->GetFilename()))
|
|
{
|
|
warn("File \"%s\" seems to be duplicate, cancelling download and deleting file from queue", pFileInfo->GetFilename());
|
|
fileCompleted = false;
|
|
DeleteQueueEntry(pFileInfo);
|
|
}
|
|
}
|
|
|
|
bool deleteFileObj = false;
|
|
|
|
if (pFileInfo->GetDeleted())
|
|
{
|
|
int cnt = 0;
|
|
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
|
|
{
|
|
if ((*it)->GetFileInfo() == pFileInfo)
|
|
{
|
|
cnt++;
|
|
}
|
|
}
|
|
if (cnt == 1)
|
|
{
|
|
// this was the last Download for a file deleted from queue
|
|
deleteFileObj = true;
|
|
}
|
|
}
|
|
|
|
if (fileCompleted && !IsStopped() && !pFileInfo->GetDeleted())
|
|
{
|
|
// all jobs done
|
|
m_mutexDownloadQueue.Unlock();
|
|
pArticleDownloader->CompleteFileParts();
|
|
m_mutexDownloadQueue.Lock();
|
|
deleteFileObj = true;
|
|
}
|
|
|
|
// delete Download from Queue
|
|
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
|
|
{
|
|
ArticleDownloader* pa = *it;
|
|
if (pa == pArticleDownloader)
|
|
{
|
|
m_ActiveDownloads.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() - 1);
|
|
|
|
if (deleteFileObj)
|
|
{
|
|
bool fileDeleted = pFileInfo->GetDeleted();
|
|
// delete File from Queue
|
|
pFileInfo->SetDeleted(true);
|
|
|
|
Aspect aspect = { fileCompleted && !fileDeleted ? eaFileCompleted : eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo };
|
|
Notify(&aspect);
|
|
|
|
DeleteFileInfo(pFileInfo, fileCompleted);
|
|
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
|
|
{
|
|
g_pDiskState->SaveDownloadQueue(&m_DownloadQueue);
|
|
}
|
|
}
|
|
|
|
m_mutexDownloadQueue.Unlock();
|
|
}
|
|
|
|
void QueueCoordinator::DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted)
|
|
{
|
|
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
|
|
{
|
|
FileInfo* pa = *it;
|
|
if (pa == pFileInfo)
|
|
{
|
|
m_DownloadQueue.GetFileQueue()->erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
|
|
{
|
|
g_pDiskState->DiscardFile(pFileInfo);
|
|
}
|
|
|
|
if (!bCompleted)
|
|
{
|
|
DiscardDiskFile(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());
|
|
}
|
|
}
|
|
|
|
bool QueueCoordinator::IsDupe(FileInfo* pFileInfo)
|
|
{
|
|
debug("Checking if the file is already queued");
|
|
|
|
// checking on disk
|
|
if (pFileInfo->IsDupe(pFileInfo->GetFilename()))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// checking in queue
|
|
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
|
|
{
|
|
FileInfo* pQueueEntry = *it;
|
|
if (!strcmp(pFileInfo->GetNZBInfo()->GetDestDir(), pQueueEntry->GetNZBInfo()->GetDestDir()) &&
|
|
!strcmp(pFileInfo->GetFilename(), pQueueEntry->GetFilename()) &&
|
|
pFileInfo != pQueueEntry)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void QueueCoordinator::LogDebugInfo()
|
|
{
|
|
debug("--------------------------------------------");
|
|
debug("Dumping debug debug to log");
|
|
debug("--------------------------------------------");
|
|
|
|
debug(" SpeedMeter");
|
|
debug(" ----------");
|
|
float fSpeed = CalcCurrentDownloadSpeed();
|
|
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(" ----------------");
|
|
|
|
m_mutexDownloadQueue.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();
|
|
}
|
|
m_mutexDownloadQueue.Unlock();
|
|
|
|
debug("");
|
|
|
|
g_pServerPool->LogDebugInfo();
|
|
}
|
|
|
|
void QueueCoordinator::ResetHangingDownloads()
|
|
{
|
|
const int TimeOut = g_pOptions->GetTerminateTimeout();
|
|
if (TimeOut == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_mutexDownloadQueue.Lock();
|
|
time_t tm = ::time(NULL);
|
|
|
|
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();)
|
|
{
|
|
ArticleDownloader* pArticleDownloader = *it;
|
|
if (tm - pArticleDownloader->GetLastUpdateTime() > TimeOut &&
|
|
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);
|
|
// it's not safe to destroy pArticleDownloader, because the state of object is unknown
|
|
delete pArticleDownloader;
|
|
it = m_ActiveDownloads.begin();
|
|
continue;
|
|
}
|
|
it++;
|
|
}
|
|
|
|
m_mutexDownloadQueue.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_tStartDownload += tDiff + 1;
|
|
}
|
|
}
|
|
m_tLastCheck = m_tCurTime;
|
|
}
|
|
|
|
bool QueueCoordinator::SetQueueEntryNZBCategory(NZBInfo* pNZBInfo, const char* szCategory)
|
|
{
|
|
if (pNZBInfo->GetPostProcess())
|
|
{
|
|
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::SetQueueEntryNZBName(NZBInfo* pNZBInfo, const char* szName)
|
|
{
|
|
if (pNZBInfo->GetPostProcess())
|
|
{
|
|
error("Could not rename %s. File in post-process-stage", pNZBInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
if (strlen(szName) == 0)
|
|
{
|
|
error("Could not rename %s. The new name cannot be empty", pNZBInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
char szOldDestDir[1024];
|
|
strncpy(szOldDestDir, pNZBInfo->GetDestDir(), 1024);
|
|
szOldDestDir[1024-1] = '\0';
|
|
|
|
char szNZBNicename[1024];
|
|
NZBInfo::MakeNiceNZBName(szName, szNZBNicename, sizeof(szNZBNicename), false);
|
|
pNZBInfo->SetName(szNZBNicename);
|
|
|
|
pNZBInfo->BuildDestDirName();
|
|
|
|
bool bDirUnchanged = !strcmp(pNZBInfo->GetDestDir(), szOldDestDir);
|
|
bool bOK = bDirUnchanged || ArticleDownloader::MoveCompletedFiles(pNZBInfo, szOldDestDir);
|
|
|
|
return bOK;
|
|
}
|
|
|
|
/*
|
|
* NOTE: DownloadQueue must be locked prior to call of this function
|
|
*/
|
|
bool QueueCoordinator::MergeQueueEntries(NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZBInfo)
|
|
{
|
|
if (pDestNZBInfo->GetPostProcess() || pSrcNZBInfo->GetPostProcess())
|
|
{
|
|
error("Could not merge %s and %s. File in post-process-stage", pDestNZBInfo->GetName(), pSrcNZBInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
// set new dest directory, new category and move downloaded files to new dest directory
|
|
pSrcNZBInfo->SetFilename(pSrcNZBInfo->GetFilename());
|
|
SetQueueEntryNZBCategory(pSrcNZBInfo, pDestNZBInfo->GetCategory());
|
|
|
|
// reattach file items to new NZBInfo-object
|
|
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
|
|
{
|
|
FileInfo* pFileInfo = *it;
|
|
if (pFileInfo->GetNZBInfo() == pSrcNZBInfo)
|
|
{
|
|
pFileInfo->SetNZBInfo(pDestNZBInfo);
|
|
}
|
|
}
|
|
|
|
pDestNZBInfo->SetFileCount(pDestNZBInfo->GetFileCount() + pSrcNZBInfo->GetFileCount());
|
|
pDestNZBInfo->SetSize(pDestNZBInfo->GetSize() + pSrcNZBInfo->GetSize());
|
|
|
|
// 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);
|
|
|
|
return true;
|
|
}
|