mirror of
https://github.com/nzbget/nzbget.git
synced 2025-12-23 22:27:45 -05:00
from Unit “Options.cpp”. The latter now contains only program options (which cannot be changed without reload).
1476 lines
46 KiB
C++
1476 lines
46 KiB
C++
/*
|
|
* This file is part of nzbget. See <http://nzbget.net>.
|
|
*
|
|
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
|
|
* Copyright (C) 2007-2019 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "nzbget.h"
|
|
#include "QueueCoordinator.h"
|
|
#include "Options.h"
|
|
#include "WorkState.h"
|
|
#include "ServerPool.h"
|
|
#include "ArticleDownloader.h"
|
|
#include "ArticleWriter.h"
|
|
#include "DiskState.h"
|
|
#include "Util.h"
|
|
#include "FileSystem.h"
|
|
#include "Decoder.h"
|
|
#include "StatMeter.h"
|
|
|
|
bool QueueCoordinator::CoordinatorDownloadQueue::EditEntry(
|
|
int ID, EEditAction action, const char* args)
|
|
{
|
|
return m_owner->m_queueEditor.EditEntry(&m_owner->m_downloadQueue, ID, action, args);
|
|
}
|
|
|
|
bool QueueCoordinator::CoordinatorDownloadQueue::EditList(
|
|
IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, const char* args)
|
|
{
|
|
m_massEdit = true;
|
|
bool ret = m_owner->m_queueEditor.EditList(&m_owner->m_downloadQueue, idList, nameList, matchMode, action, args);
|
|
m_massEdit = false;
|
|
if (m_wantSave)
|
|
{
|
|
Save();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void QueueCoordinator::CoordinatorDownloadQueue::Save()
|
|
{
|
|
if (m_massEdit)
|
|
{
|
|
m_wantSave = true;
|
|
return;
|
|
}
|
|
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
g_DiskState->SaveDownloadQueue(this, m_historyChanged);
|
|
m_stateChanged = true;
|
|
}
|
|
|
|
for (NzbInfo* nzbInfo : GetQueue())
|
|
{
|
|
nzbInfo->SetChanged(false);
|
|
}
|
|
|
|
m_wantSave = false;
|
|
m_historyChanged = false;
|
|
|
|
// queue has changed, time to wake up if in standby
|
|
m_owner->WakeUp();
|
|
}
|
|
|
|
void QueueCoordinator::CoordinatorDownloadQueue::SaveChanged()
|
|
{
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
g_DiskState->SaveDownloadProgress(this);
|
|
m_stateChanged = true;
|
|
}
|
|
}
|
|
|
|
QueueCoordinator::QueueCoordinator()
|
|
{
|
|
debug("Creating QueueCoordinator");
|
|
|
|
CoordinatorDownloadQueue::Init(&m_downloadQueue);
|
|
}
|
|
|
|
QueueCoordinator::~QueueCoordinator()
|
|
{
|
|
debug("Destroying QueueCoordinator");
|
|
|
|
for (ArticleDownloader* articleDownloader : m_activeDownloads)
|
|
{
|
|
delete articleDownloader;
|
|
}
|
|
m_activeDownloads.clear();
|
|
|
|
CoordinatorDownloadQueue::Final();
|
|
|
|
debug("QueueCoordinator destroyed");
|
|
}
|
|
|
|
void QueueCoordinator::Load()
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
|
|
bool statLoaded = true;
|
|
bool perfectServerMatch = true;
|
|
bool queueLoaded = false;
|
|
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
statLoaded = g_StatMeter->Load(&perfectServerMatch);
|
|
|
|
if (g_DiskState->DownloadQueueExists())
|
|
{
|
|
queueLoaded = g_DiskState->LoadDownloadQueue(downloadQueue, g_ServerPool->GetServers());
|
|
}
|
|
else
|
|
{
|
|
g_DiskState->DiscardDownloadQueue();
|
|
}
|
|
}
|
|
|
|
if (queueLoaded && statLoaded)
|
|
{
|
|
g_DiskState->CleanupTempDir(downloadQueue);
|
|
}
|
|
|
|
if (queueLoaded && statLoaded && !perfectServerMatch)
|
|
{
|
|
debug("Changes in section <NEWS SERVERS> of config file detected, resaving queue");
|
|
|
|
// re-save current server list into diskstate to update server ids
|
|
g_StatMeter->Save();
|
|
|
|
// re-save queue into diskstate to update server ids
|
|
downloadQueue->HistoryChanged();
|
|
downloadQueue->Save();
|
|
|
|
// re-save file states into diskstate to update server ids
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
if (g_Options->GetContinuePartial())
|
|
{
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
if (!fileInfo->GetArticles()->empty())
|
|
{
|
|
g_DiskState->SaveFileState(fileInfo, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
|
|
{
|
|
if ((completedFile.GetStatus() == CompletedFile::cfPartial ||
|
|
completedFile.GetStatus() == CompletedFile::cfFailure) &&
|
|
completedFile.GetId() > 0)
|
|
{
|
|
FileInfo fileInfo(completedFile.GetId());
|
|
if (g_DiskState->LoadFileState(&fileInfo, g_ServerPool->GetServers(), true))
|
|
{
|
|
g_DiskState->SaveFileState(&fileInfo, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CoordinatorDownloadQueue::Loaded();
|
|
}
|
|
|
|
void QueueCoordinator::Run()
|
|
{
|
|
debug("Entering QueueCoordinator-loop");
|
|
|
|
Load();
|
|
AdjustDownloadsLimit();
|
|
bool wasStandBy = true;
|
|
bool articeDownloadsRunning = false;
|
|
time_t lastReset = 0;
|
|
g_StatMeter->IntervalCheck();
|
|
|
|
while (!IsStopped())
|
|
{
|
|
bool downloadsChecked = false;
|
|
bool downloadStarted = false;
|
|
NntpConnection* connection = g_ServerPool->GetConnection(0, nullptr, nullptr);
|
|
if (connection)
|
|
{
|
|
// start download for next article
|
|
FileInfo* fileInfo;
|
|
ArticleInfo* articleInfo;
|
|
bool freeConnection = false;
|
|
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
bool hasMoreArticles = GetNextArticle(downloadQueue, fileInfo, articleInfo);
|
|
articeDownloadsRunning = !m_activeDownloads.empty();
|
|
downloadsChecked = true;
|
|
m_hasMoreJobs = hasMoreArticles || articeDownloadsRunning;
|
|
if (hasMoreArticles && !IsStopped() && (int)m_activeDownloads.size() < m_downloadsLimit &&
|
|
(!g_WorkState->GetTempPauseDownload() || fileInfo->GetExtraPriority()))
|
|
{
|
|
StartArticleDownload(fileInfo, articleInfo, connection);
|
|
articeDownloadsRunning = true;
|
|
downloadStarted = true;
|
|
}
|
|
else
|
|
{
|
|
freeConnection = true;
|
|
}
|
|
}
|
|
|
|
if (freeConnection)
|
|
{
|
|
g_ServerPool->FreeConnection(connection, false);
|
|
}
|
|
}
|
|
|
|
if (!downloadsChecked)
|
|
{
|
|
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
|
articeDownloadsRunning = !m_activeDownloads.empty();
|
|
}
|
|
|
|
bool standBy = !articeDownloadsRunning;
|
|
if (standBy != wasStandBy)
|
|
{
|
|
g_StatMeter->EnterLeaveStandBy(standBy);
|
|
wasStandBy = standBy;
|
|
if (standBy)
|
|
{
|
|
SaveAllPartialState();
|
|
}
|
|
}
|
|
|
|
// sleep longer in StandBy
|
|
if (standBy)
|
|
{
|
|
Util::SetStandByMode(standBy);
|
|
Guard guard(m_waitMutex);
|
|
m_waitCond.WaitFor(m_waitMutex, 500, [&]{ return m_hasMoreJobs || IsStopped(); });
|
|
}
|
|
else
|
|
{
|
|
int sleepInterval = downloadStarted ? 0 : 5;
|
|
usleep(sleepInterval * 1000);
|
|
g_StatMeter->AddSpeedReading(0);
|
|
}
|
|
|
|
if (lastReset != Util::CurrentTime())
|
|
{
|
|
// this code should not be called too often, once per second is OK
|
|
g_ServerPool->CloseUnusedConnections();
|
|
ResetHangingDownloads();
|
|
if (!standBy)
|
|
{
|
|
SaveAllPartialState();
|
|
}
|
|
g_StatMeter->IntervalCheck();
|
|
g_Log->IntervalCheck();
|
|
AdjustDownloadsLimit();
|
|
Util::SetStandByMode(standBy);
|
|
lastReset = Util::CurrentTime();
|
|
}
|
|
}
|
|
|
|
WaitJobs();
|
|
SaveAllPartialState();
|
|
SaveQueueIfChanged();
|
|
SaveAllFileState();
|
|
|
|
debug("Exiting QueueCoordinator-loop");
|
|
}
|
|
|
|
void QueueCoordinator::WakeUp()
|
|
{
|
|
debug("Waking up QueueCoordinator");
|
|
// Resume Run()
|
|
Guard guard(m_waitMutex);
|
|
m_hasMoreJobs = true;
|
|
m_waitCond.NotifyAll();
|
|
}
|
|
|
|
void QueueCoordinator::WaitJobs()
|
|
{
|
|
// waiting for downloads
|
|
debug("QueueCoordinator: waiting for Downloads to complete");
|
|
|
|
while (true)
|
|
{
|
|
{
|
|
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
|
if (m_activeDownloads.empty())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
usleep(100 * 1000);
|
|
ResetHangingDownloads();
|
|
}
|
|
|
|
debug("QueueCoordinator: Downloads are completed");
|
|
}
|
|
|
|
/*
|
|
* Compute maximum number of allowed download threads
|
|
**/
|
|
void QueueCoordinator::AdjustDownloadsLimit()
|
|
{
|
|
if (m_serverConfigGeneration == g_ServerPool->GetGeneration())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// two extra threads for completing files (when connections are not needed)
|
|
int downloadsLimit = 2;
|
|
|
|
// allow one thread per 0-level (main) and 1-level (backup) server connection
|
|
for (NewsServer* newsServer : g_ServerPool->GetServers())
|
|
{
|
|
if ((newsServer->GetNormLevel() == 0 || newsServer->GetNormLevel() == 1) && newsServer->GetActive())
|
|
{
|
|
downloadsLimit += newsServer->GetMaxConnections();
|
|
}
|
|
}
|
|
|
|
m_downloadsLimit = downloadsLimit;
|
|
}
|
|
|
|
NzbInfo* QueueCoordinator::AddNzbFileToQueue(std::unique_ptr<NzbInfo> nzbInfo, NzbInfo* urlInfo, bool addFirst)
|
|
{
|
|
debug("Adding NZBFile to queue");
|
|
|
|
NzbInfo* addedNzb = nzbInfo.get();
|
|
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
|
|
DownloadQueue::Aspect foundAspect = { DownloadQueue::eaNzbFound, downloadQueue, nzbInfo.get(), nullptr };
|
|
downloadQueue->Notify(&foundAspect);
|
|
|
|
NzbInfo::EDeleteStatus deleteStatus = nzbInfo->GetDeleteStatus();
|
|
|
|
if (deleteStatus != NzbInfo::dsNone)
|
|
{
|
|
bool allPaused = !nzbInfo->GetFileList()->empty();
|
|
for (FileInfo* fileInfo: nzbInfo->GetFileList())
|
|
{
|
|
allPaused &= fileInfo->GetPaused();
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
g_DiskState->DiscardFile(fileInfo->GetId(), true, false, false);
|
|
}
|
|
}
|
|
nzbInfo->SetDeletePaused(allPaused);
|
|
}
|
|
|
|
if (deleteStatus == NzbInfo::dsNone)
|
|
{
|
|
if (g_Options->GetDupeCheck() && nzbInfo->GetDupeMode() != dmForce)
|
|
{
|
|
CheckDupeFileInfos(nzbInfo.get());
|
|
}
|
|
|
|
if (urlInfo)
|
|
{
|
|
// insert at the URL position
|
|
downloadQueue->GetQueue()->insert(downloadQueue->GetQueue()->Find(urlInfo), std::move(nzbInfo));
|
|
}
|
|
else
|
|
{
|
|
downloadQueue->GetQueue()->Add(std::move(nzbInfo), addFirst);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// temporary adding to queue in order for listeners to see it
|
|
downloadQueue->GetQueue()->Add(std::move(nzbInfo), true);
|
|
}
|
|
|
|
if (urlInfo)
|
|
{
|
|
addedNzb->SetId(urlInfo->GetId());
|
|
downloadQueue->GetQueue()->Remove(urlInfo);
|
|
}
|
|
|
|
if (deleteStatus == NzbInfo::dsNone)
|
|
{
|
|
addedNzb->PrintMessage(Message::mkInfo, "Collection %s added to queue", addedNzb->GetName());
|
|
}
|
|
|
|
if (deleteStatus != NzbInfo::dsManual)
|
|
{
|
|
DownloadQueue::Aspect addedAspect = { DownloadQueue::eaNzbAdded, downloadQueue, addedNzb, nullptr };
|
|
downloadQueue->Notify(&addedAspect);
|
|
}
|
|
|
|
if (deleteStatus != NzbInfo::dsNone)
|
|
{
|
|
// in a case if none of listeners did already delete the temporary object - we do it ourselves
|
|
downloadQueue->GetQueue()->Remove(addedNzb);
|
|
if (!downloadQueue->GetHistory()->Find(addedNzb->GetId()))
|
|
{
|
|
addedNzb = nullptr;
|
|
}
|
|
}
|
|
|
|
downloadQueue->Save();
|
|
|
|
return addedNzb;
|
|
}
|
|
|
|
void QueueCoordinator::CheckDupeFileInfos(NzbInfo* nzbInfo)
|
|
{
|
|
debug("CheckDupeFileInfos");
|
|
|
|
RawFileList dupeList;
|
|
|
|
int index1 = 0;
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
index1++;
|
|
bool dupe = false;
|
|
int index2 = 0;
|
|
for (FileInfo* fileInfo2 : nzbInfo->GetFileList())
|
|
{
|
|
index2++;
|
|
if (fileInfo != fileInfo2 &&
|
|
!strcmp(fileInfo->GetFilename(), fileInfo2->GetFilename()) &&
|
|
(fileInfo->GetSize() < fileInfo2->GetSize() ||
|
|
(fileInfo->GetSize() == fileInfo2->GetSize() && index2 < index1)))
|
|
{
|
|
// If more than two files have same filename we don't filter them out since that
|
|
// naming might be intentional and correct filenames must be read from article bodies.
|
|
int dupeCount = (int)std::count_if(nzbInfo->GetFileList()->begin(), nzbInfo->GetFileList()->end(),
|
|
[fileInfo2](std::unique_ptr<FileInfo>& fileInfo3)
|
|
{
|
|
return !strcmp(fileInfo3->GetFilename(), fileInfo2->GetFilename());
|
|
});
|
|
if (dupeCount == 2)
|
|
{
|
|
warn("File \"%s\" appears twice in collection, adding only the biggest file", fileInfo->GetFilename());
|
|
dupe = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (dupe)
|
|
{
|
|
dupeList.push_back(fileInfo);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (FileInfo* fileInfo : dupeList)
|
|
{
|
|
nzbInfo->UpdateDeletedStats(fileInfo);
|
|
nzbInfo->GetFileList()->Remove(fileInfo);
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
g_DiskState->DiscardFile(fileInfo->GetId(), true, false, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::Stop()
|
|
{
|
|
Thread::Stop();
|
|
|
|
debug("Stopping ArticleDownloads");
|
|
{
|
|
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
|
for (ArticleDownloader* articleDownloader : m_activeDownloads)
|
|
{
|
|
articleDownloader->Stop();
|
|
}
|
|
}
|
|
debug("ArticleDownloads are notified");
|
|
|
|
// Resume Run() to exit it
|
|
Guard guard(m_waitMutex);
|
|
m_waitCond.NotifyAll();
|
|
}
|
|
|
|
/*
|
|
* Returns next article for download.
|
|
*/
|
|
bool QueueCoordinator::GetNextArticle(DownloadQueue* downloadQueue, FileInfo* &fileInfo, ArticleInfo* &articleInfo)
|
|
{
|
|
// 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.
|
|
|
|
//debug("QueueCoordinator::GetNextArticle()");
|
|
|
|
bool ok = false;
|
|
|
|
RawFileList checkedFiles;
|
|
time_t curDate = Util::CurrentTime();
|
|
|
|
while (!ok)
|
|
{
|
|
fileInfo = nullptr;
|
|
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
bool nzbHigherPriority = fileInfo &&
|
|
((nzbInfo->HasExtraPriority() == fileInfo->GetNzbInfo()->HasExtraPriority() &&
|
|
nzbInfo->GetPriority() > fileInfo->GetNzbInfo()->GetPriority()) ||
|
|
(nzbInfo->HasExtraPriority() > fileInfo->GetNzbInfo()->HasExtraPriority()));
|
|
|
|
bool nzbPaused = nzbInfo->GetFileList()->size() - nzbInfo->GetPausedFileCount() <= 0;
|
|
|
|
if ((!fileInfo || nzbHigherPriority) && !nzbPaused &&
|
|
(!(g_WorkState->GetPauseDownload() || g_WorkState->GetQuotaReached()) || nzbInfo->GetForcePriority()))
|
|
{
|
|
for (FileInfo* fileInfo1 : nzbInfo->GetFileList())
|
|
{
|
|
bool alreadyChecked = !checkedFiles.empty() &&
|
|
std::find(checkedFiles.begin(), checkedFiles.end(), fileInfo1) != checkedFiles.end();
|
|
|
|
bool propagationWait = g_Options->GetPropagationDelay() > 0 &&
|
|
(int)fileInfo1->GetTime() + g_Options->GetPropagationDelay() >= (int)curDate;
|
|
|
|
bool higherPriority = fileInfo &&
|
|
((fileInfo1->GetExtraPriority() == fileInfo->GetExtraPriority() &&
|
|
fileInfo1->GetNzbInfo()->GetPriority() > fileInfo->GetNzbInfo()->GetPriority()) ||
|
|
(fileInfo1->GetExtraPriority() > fileInfo->GetExtraPriority()));
|
|
|
|
if (!alreadyChecked && !propagationWait && !fileInfo1->GetPaused() &&
|
|
!fileInfo1->GetDeleted() && (!fileInfo || higherPriority))
|
|
{
|
|
fileInfo = fileInfo1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fileInfo)
|
|
{
|
|
// there are no more files for download
|
|
break;
|
|
}
|
|
|
|
if (g_Options->GetDirectRename() &&
|
|
fileInfo->GetNzbInfo()->GetDirectRenameStatus() <= NzbInfo::tsRunning &&
|
|
!fileInfo->GetNzbInfo()->GetAllFirst() &&
|
|
GetNextFirstArticle(fileInfo->GetNzbInfo(), fileInfo, articleInfo))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (fileInfo->GetArticles()->empty() && g_Options->GetServerMode())
|
|
{
|
|
g_DiskState->LoadArticles(fileInfo);
|
|
LoadPartialState(fileInfo);
|
|
}
|
|
|
|
// check if the file has any articles left for download
|
|
for (ArticleInfo* article : fileInfo->GetArticles())
|
|
{
|
|
if (article->GetStatus() == ArticleInfo::aiUndefined)
|
|
{
|
|
articleInfo = article;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!ok)
|
|
{
|
|
// the file doesn't have any articles left for download
|
|
checkedFiles.reserve(100);
|
|
checkedFiles.push_back(fileInfo);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QueueCoordinator::GetNextFirstArticle(NzbInfo* nzbInfo, FileInfo* &fileInfo, ArticleInfo* &articleInfo)
|
|
{
|
|
// find a file not renamed yet
|
|
for (FileInfo* fileInfo1 : nzbInfo->GetFileList())
|
|
{
|
|
if (!fileInfo1->GetFilenameConfirmed())
|
|
{
|
|
if (fileInfo1->GetArticles()->empty() && g_Options->GetServerMode())
|
|
{
|
|
g_DiskState->LoadArticles(fileInfo1);
|
|
LoadPartialState(fileInfo1);
|
|
}
|
|
if (!fileInfo1->GetArticles()->empty())
|
|
{
|
|
ArticleInfo* article = fileInfo1->GetArticles()->at(0).get();
|
|
if (article->GetStatus() == ArticleInfo::aiUndefined)
|
|
{
|
|
fileInfo = fileInfo1;
|
|
articleInfo = article;
|
|
nzbInfo->SetDirectRenameStatus(NzbInfo::tsRunning);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// no more files for renaming remained
|
|
nzbInfo->SetAllFirst(true);
|
|
|
|
return false;
|
|
}
|
|
|
|
void QueueCoordinator::StartArticleDownload(FileInfo* fileInfo, ArticleInfo* articleInfo, NntpConnection* connection)
|
|
{
|
|
debug("Starting new ArticleDownloader");
|
|
|
|
ArticleDownloader* articleDownloader = new ArticleDownloader();
|
|
articleDownloader->SetAutoDestroy(true);
|
|
articleDownloader->Attach(this);
|
|
articleDownloader->SetFileInfo(fileInfo);
|
|
articleDownloader->SetArticleInfo(articleInfo);
|
|
articleDownloader->SetConnection(connection);
|
|
|
|
if (articleInfo->GetPartNumber() == 1 && g_Options->GetDirectRename() && !g_Options->GetRawArticle())
|
|
{
|
|
articleDownloader->SetContentAnalyzer(m_directRenamer.MakeArticleContentAnalyzer());
|
|
}
|
|
|
|
BString<1024> infoName("%s%c%s [%i/%i]", fileInfo->GetNzbInfo()->GetName(), PATH_SEPARATOR, fileInfo->GetFilename(), articleInfo->GetPartNumber(), (int)fileInfo->GetArticles()->size());
|
|
articleDownloader->SetInfoName(infoName);
|
|
|
|
articleInfo->SetStatus(ArticleInfo::aiRunning);
|
|
fileInfo->SetActiveDownloads(fileInfo->GetActiveDownloads() + 1);
|
|
fileInfo->GetNzbInfo()->SetActiveDownloads(fileInfo->GetNzbInfo()->GetActiveDownloads() + 1);
|
|
|
|
m_activeDownloads.push_back(articleDownloader);
|
|
articleDownloader->Start();
|
|
}
|
|
|
|
void QueueCoordinator::Update(Subject* Caller, void* Aspect)
|
|
{
|
|
debug("Notification from ArticleDownloader received");
|
|
|
|
ArticleDownloader* articleDownloader = (ArticleDownloader*)Caller;
|
|
if ((articleDownloader->GetStatus() == ArticleDownloader::adFinished) ||
|
|
(articleDownloader->GetStatus() == ArticleDownloader::adFailed) ||
|
|
(articleDownloader->GetStatus() == ArticleDownloader::adRetry))
|
|
{
|
|
ArticleCompleted(articleDownloader);
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::ArticleCompleted(ArticleDownloader* articleDownloader)
|
|
{
|
|
debug("Article downloaded");
|
|
|
|
FileInfo* fileInfo = articleDownloader->GetFileInfo();
|
|
bool completeFileParts = false;
|
|
|
|
{
|
|
NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
|
|
ArticleInfo* articleInfo = articleDownloader->GetArticleInfo();
|
|
bool retry = false;
|
|
bool fileCompleted = false;
|
|
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
|
|
if (articleDownloader->GetStatus() == ArticleDownloader::adFinished)
|
|
{
|
|
articleInfo->SetStatus(ArticleInfo::aiFinished);
|
|
fileInfo->SetSuccessSize(fileInfo->GetSuccessSize() + articleInfo->GetSize());
|
|
nzbInfo->SetCurrentSuccessSize(nzbInfo->GetCurrentSuccessSize() + articleInfo->GetSize());
|
|
nzbInfo->SetParCurrentSuccessSize(nzbInfo->GetParCurrentSuccessSize() + (fileInfo->GetParFile() ? articleInfo->GetSize() : 0));
|
|
fileInfo->SetSuccessArticles(fileInfo->GetSuccessArticles() + 1);
|
|
nzbInfo->SetCurrentSuccessArticles(nzbInfo->GetCurrentSuccessArticles() + 1);
|
|
}
|
|
else if (articleDownloader->GetStatus() == ArticleDownloader::adFailed)
|
|
{
|
|
articleInfo->SetStatus(ArticleInfo::aiFailed);
|
|
fileInfo->SetFailedSize(fileInfo->GetFailedSize() + articleInfo->GetSize());
|
|
nzbInfo->SetCurrentFailedSize(nzbInfo->GetCurrentFailedSize() + articleInfo->GetSize());
|
|
nzbInfo->SetParCurrentFailedSize(nzbInfo->GetParCurrentFailedSize() + (fileInfo->GetParFile() ? articleInfo->GetSize() : 0));
|
|
fileInfo->SetFailedArticles(fileInfo->GetFailedArticles() + 1);
|
|
nzbInfo->SetCurrentFailedArticles(nzbInfo->GetCurrentFailedArticles() + 1);
|
|
}
|
|
else if (articleDownloader->GetStatus() == ArticleDownloader::adRetry)
|
|
{
|
|
articleInfo->SetStatus(ArticleInfo::aiUndefined);
|
|
retry = true;
|
|
if (articleInfo->GetPartNumber() == 1)
|
|
{
|
|
nzbInfo->SetAllFirst(false);
|
|
}
|
|
}
|
|
|
|
if (!retry)
|
|
{
|
|
fileInfo->SetRemainingSize(fileInfo->GetRemainingSize() - articleInfo->GetSize());
|
|
nzbInfo->SetRemainingSize(nzbInfo->GetRemainingSize() - articleInfo->GetSize());
|
|
if (fileInfo->GetPaused())
|
|
{
|
|
nzbInfo->SetPausedSize(nzbInfo->GetPausedSize() - articleInfo->GetSize());
|
|
}
|
|
fileInfo->SetCompletedArticles(fileInfo->GetCompletedArticles() + 1);
|
|
fileCompleted = (int)fileInfo->GetArticles()->size() == fileInfo->GetCompletedArticles();
|
|
fileInfo->GetServerStats()->ListOp(articleDownloader->GetServerStats(), ServerStatList::soAdd);
|
|
nzbInfo->GetCurrentServerStats()->ListOp(articleDownloader->GetServerStats(), ServerStatList::soAdd);
|
|
fileInfo->SetPartialChanged(true);
|
|
}
|
|
|
|
if (!fileInfo->GetFilenameConfirmed() &&
|
|
articleDownloader->GetStatus() == ArticleDownloader::adFinished &&
|
|
articleDownloader->GetArticleFilename())
|
|
{
|
|
// in "FileNaming=auto"-mode prefer filename from nzb-file to filename read from article
|
|
// if the name from article seems to be obfuscated
|
|
bool useFilenameFromArticle = g_Options->GetFileNaming() == Options::nfArticle ||
|
|
(g_Options->GetFileNaming() == Options::nfAuto &&
|
|
!Util::AlphaNum(articleDownloader->GetArticleFilename()) &&
|
|
!nzbInfo->GetManyDupeFiles());
|
|
if (useFilenameFromArticle)
|
|
{
|
|
fileInfo->SetFilename(articleDownloader->GetArticleFilename());
|
|
fileInfo->MakeValidFilename();
|
|
}
|
|
fileInfo->SetFilenameConfirmed(true);
|
|
if (g_Options->GetDupeCheck() &&
|
|
nzbInfo->GetDupeMode() != dmForce &&
|
|
!nzbInfo->GetManyDupeFiles() &&
|
|
FileSystem::FileExists(BString<1024>("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, fileInfo->GetFilename())))
|
|
{
|
|
warn("File \"%s\" seems to be duplicate, cancelling download and deleting file from queue", fileInfo->GetFilename());
|
|
fileCompleted = false;
|
|
fileInfo->SetDupeDeleted(true);
|
|
DeleteQueueEntry(downloadQueue, fileInfo);
|
|
}
|
|
}
|
|
|
|
if (articleDownloader->GetContentAnalyzer() && articleDownloader->GetStatus() == ArticleDownloader::adFinished)
|
|
{
|
|
m_directRenamer.ArticleDownloaded(downloadQueue, fileInfo, articleInfo, articleDownloader->GetContentAnalyzer());
|
|
}
|
|
|
|
nzbInfo->SetDownloadedSize(nzbInfo->GetDownloadedSize() + articleDownloader->GetDownloadedSize());
|
|
|
|
CheckHealth(downloadQueue, fileInfo);
|
|
|
|
if (nzbInfo->GetParking() && fileInfo->GetActiveDownloads() == 1 && !fileInfo->GetDupeDeleted())
|
|
{
|
|
fileCompleted = true;
|
|
}
|
|
|
|
completeFileParts = fileCompleted && (!fileInfo->GetDeleted() || nzbInfo->GetParking());
|
|
|
|
if (!completeFileParts)
|
|
{
|
|
DeleteDownloader(downloadQueue, articleDownloader, false);
|
|
}
|
|
}
|
|
|
|
if (completeFileParts)
|
|
{
|
|
// all jobs done
|
|
articleDownloader->CompleteFileParts();
|
|
fileInfo->SetPartialChanged(false);
|
|
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
DeleteDownloader(downloadQueue, articleDownloader, true);
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::DeleteDownloader(DownloadQueue* downloadQueue,
|
|
ArticleDownloader* articleDownloader, bool fileCompleted)
|
|
{
|
|
FileInfo* fileInfo = articleDownloader->GetFileInfo();
|
|
NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
|
|
bool hasOtherDownloaders = fileInfo->GetActiveDownloads() > 1;
|
|
bool deleteFileObj = fileCompleted || (fileInfo->GetDeleted() && !hasOtherDownloaders);
|
|
|
|
// remove downloader from downloader list
|
|
m_activeDownloads.erase(std::find(m_activeDownloads.begin(), m_activeDownloads.end(), articleDownloader));
|
|
|
|
fileInfo->SetActiveDownloads(fileInfo->GetActiveDownloads() - 1);
|
|
nzbInfo->SetActiveDownloads(nzbInfo->GetActiveDownloads() - 1);
|
|
|
|
if (deleteFileObj)
|
|
{
|
|
DeleteFileInfo(downloadQueue, fileInfo, fileCompleted);
|
|
nzbInfo->SetChanged(true);
|
|
downloadQueue->SaveChanged();
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::DeleteFileInfo(DownloadQueue* downloadQueue, FileInfo* fileInfo, bool completed)
|
|
{
|
|
while (g_ArticleCache->FileBusy(fileInfo))
|
|
{
|
|
usleep(5*1000);
|
|
}
|
|
|
|
NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
|
|
bool parking = fileInfo->GetNzbInfo()->GetParking();
|
|
bool fileDeleted = fileInfo->GetDeleted();
|
|
|
|
fileInfo->SetDeleted(true);
|
|
|
|
if (completed || nzbInfo->GetDeleting())
|
|
{
|
|
nzbInfo->UpdateCompletedStats(fileInfo);
|
|
}
|
|
else
|
|
{
|
|
nzbInfo->UpdateDeletedStats(fileInfo);
|
|
}
|
|
|
|
CompletedFile::EStatus fileStatus =
|
|
fileInfo->GetTotalArticles() == fileInfo->GetSuccessArticles() ? CompletedFile::cfSuccess :
|
|
fileInfo->GetTotalArticles() == fileInfo->GetMissedArticles() + fileInfo->GetFailedArticles() ? CompletedFile::cfFailure :
|
|
fileInfo->GetSuccessArticles() > 0 || fileInfo->GetFailedArticles() > 0 ? CompletedFile::cfPartial :
|
|
CompletedFile::cfNone;
|
|
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
g_DiskState->DiscardFile(fileInfo->GetId(), fileStatus == CompletedFile::cfSuccess || (fileDeleted && !parking), true, false);
|
|
if (fileStatus == CompletedFile::cfPartial && (completed || parking))
|
|
{
|
|
g_DiskState->SaveFileState(fileInfo, true);
|
|
}
|
|
}
|
|
|
|
if (!completed)
|
|
{
|
|
DiscardTempFiles(fileInfo);
|
|
}
|
|
|
|
if (completed || parking)
|
|
{
|
|
fileInfo->GetNzbInfo()->GetCompletedFiles()->emplace_back(
|
|
fileInfo->GetId(),
|
|
completed && fileInfo->GetOutputFilename() ?
|
|
FileSystem::BaseFileName(fileInfo->GetOutputFilename()) : fileInfo->GetFilename(),
|
|
fileInfo->GetOrigname(), fileStatus,
|
|
fileStatus == CompletedFile::cfSuccess ? fileInfo->GetCrc() : 0,
|
|
fileInfo->GetParFile(), fileInfo->GetHash16k(), fileInfo->GetParSetId());
|
|
}
|
|
|
|
if (g_Options->GetDirectRename())
|
|
{
|
|
m_directRenamer.FileDownloaded(downloadQueue, fileInfo);
|
|
}
|
|
|
|
if (nzbInfo->GetDirectRenameStatus() == NzbInfo::tsRunning &&
|
|
!nzbInfo->GetDeleting() && nzbInfo->IsDownloadCompleted(true))
|
|
{
|
|
DiscardDirectRename(downloadQueue, nzbInfo);
|
|
}
|
|
|
|
std::unique_ptr<FileInfo> srcFileInfo = nzbInfo->GetFileList()->Remove(fileInfo);
|
|
|
|
DownloadQueue::Aspect aspect = { completed && !fileDeleted ?
|
|
DownloadQueue::eaFileCompleted : DownloadQueue::eaFileDeleted,
|
|
downloadQueue, nzbInfo, fileInfo };
|
|
downloadQueue->Notify(&aspect);
|
|
|
|
// now can destroy FileInfo
|
|
srcFileInfo.reset();
|
|
}
|
|
|
|
void QueueCoordinator::DiscardTempFiles(FileInfo* fileInfo)
|
|
{
|
|
if (!g_Options->GetDirectWrite() && !fileInfo->GetForceDirectWrite())
|
|
{
|
|
for (ArticleInfo* pa : fileInfo->GetArticles())
|
|
{
|
|
if (pa->GetResultFilename())
|
|
{
|
|
FileSystem::DeleteFile(pa->GetResultFilename());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_Options->GetDirectWrite() && fileInfo->GetOutputFilename() && !fileInfo->GetForceDirectWrite())
|
|
{
|
|
FileSystem::DeleteFile(fileInfo->GetOutputFilename());
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::SaveQueueIfChanged()
|
|
{
|
|
if (!g_Options->GetServerMode())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool hasChanges = false;
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
hasChanges |= nzbInfo->GetChanged();
|
|
}
|
|
|
|
if (hasChanges)
|
|
{
|
|
downloadQueue->Save();
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::SaveAllPartialState()
|
|
{
|
|
if (!g_Options->GetServerMode() || !g_Options->GetContinuePartial())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
SavePartialState(fileInfo);
|
|
}
|
|
}
|
|
|
|
downloadQueue->SaveChanged();
|
|
}
|
|
|
|
void QueueCoordinator::SavePartialState(FileInfo* fileInfo)
|
|
{
|
|
if (fileInfo->GetPartialChanged())
|
|
{
|
|
debug("Saving partial state for %s", fileInfo->GetFilename());
|
|
if (fileInfo->GetPartialState() == FileInfo::psCompleted)
|
|
{
|
|
g_DiskState->DiscardFile(fileInfo->GetId(), false, false, true);
|
|
}
|
|
g_DiskState->SaveFileState(fileInfo, false);
|
|
fileInfo->SetPartialChanged(false);
|
|
fileInfo->SetPartialState(FileInfo::psPartial);
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::LoadPartialState(FileInfo* fileInfo)
|
|
{
|
|
if (fileInfo->GetPartialState() == FileInfo::psPartial)
|
|
{
|
|
g_DiskState->LoadFileState(fileInfo, g_ServerPool->GetServers(), false);
|
|
}
|
|
else if (fileInfo->GetPartialState() == FileInfo::psCompleted)
|
|
{
|
|
g_DiskState->LoadFileState(fileInfo, g_ServerPool->GetServers(), true);
|
|
|
|
BString<1024> outputFilename("%s%c%s", fileInfo->GetNzbInfo()->GetDestDir(), PATH_SEPARATOR, fileInfo->GetFilename());
|
|
fileInfo->SetOutputFilename(outputFilename);
|
|
|
|
fileInfo->SetOutputInitialized(true);
|
|
fileInfo->SetForceDirectWrite(true);
|
|
fileInfo->SetFilenameConfirmed(true);
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::SaveAllFileState()
|
|
{
|
|
if (g_Options->GetServerMode() && m_downloadQueue.m_stateChanged)
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
g_DiskState->SaveAllFileInfos(downloadQueue);
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::CheckHealth(DownloadQueue* downloadQueue, FileInfo* fileInfo)
|
|
{
|
|
if (g_Options->GetHealthCheck() == Options::hcNone ||
|
|
fileInfo->GetNzbInfo()->GetHealthPaused() ||
|
|
fileInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsHealth ||
|
|
fileInfo->GetNzbInfo()->CalcHealth() >= fileInfo->GetNzbInfo()->CalcCriticalHealth(true) ||
|
|
(g_Options->GetParScan() == Options::psDupe && g_Options->GetHealthCheck() == Options::hcPark &&
|
|
fileInfo->GetNzbInfo()->GetSuccessArticles() * 100 / fileInfo->GetNzbInfo()->GetTotalArticles() > 10))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (g_Options->GetHealthCheck() == Options::hcPause)
|
|
{
|
|
warn("Pausing %s due to health %.1f%% below critical %.1f%%", fileInfo->GetNzbInfo()->GetName(),
|
|
fileInfo->GetNzbInfo()->CalcHealth() / 10.0, fileInfo->GetNzbInfo()->CalcCriticalHealth(true) / 10.0);
|
|
fileInfo->GetNzbInfo()->SetHealthPaused(true);
|
|
downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupPause, nullptr);
|
|
}
|
|
else if (g_Options->GetHealthCheck() == Options::hcDelete ||
|
|
g_Options->GetHealthCheck() == Options::hcPark)
|
|
{
|
|
fileInfo->GetNzbInfo()->PrintMessage(Message::mkWarning,
|
|
"Cancelling download and deleting %s due to health %.1f%% below critical %.1f%%",
|
|
fileInfo->GetNzbInfo()->GetName(), fileInfo->GetNzbInfo()->CalcHealth() / 10.0,
|
|
fileInfo->GetNzbInfo()->CalcCriticalHealth(true) / 10.0);
|
|
fileInfo->GetNzbInfo()->SetDeleteStatus(NzbInfo::dsHealth);
|
|
downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(),
|
|
g_Options->GetHealthCheck() == Options::hcPark ? DownloadQueue::eaGroupParkDelete : DownloadQueue::eaGroupDelete,
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::LogDebugInfo()
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
|
|
info(" ---------- Queue");
|
|
int64 remaining, remainingForced;
|
|
downloadQueue->CalcRemainingSize(&remaining, &remainingForced);
|
|
info(" Remaining: %.1f MB, Forced: %.1f MB", remaining / 1024.0 / 1024.0, remainingForced / 1024.0 / 1024.0);
|
|
info(" Download: %s, Post-process: %s, Scan: %s",
|
|
(g_WorkState->GetPauseDownload() ? "paused" : g_WorkState->GetTempPauseDownload() ? "temp-paused" : "active"),
|
|
(g_WorkState->GetPausePostProcess() ? "paused" : "active"),
|
|
(g_WorkState->GetPauseScan() ? "paused" : "active"));
|
|
|
|
info(" ---------- QueueCoordinator");
|
|
info(" Active Downloads: %i, Limit: %i", (int)m_activeDownloads.size(), m_downloadsLimit);
|
|
for (ArticleDownloader* articleDownloader : m_activeDownloads)
|
|
{
|
|
articleDownloader->LogDebugInfo();
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::ResetHangingDownloads()
|
|
{
|
|
if (g_Options->GetArticleTimeout() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
|
time_t tm = Util::CurrentTime();
|
|
|
|
for (ArticleDownloader* articleDownloader : m_activeDownloads)
|
|
{
|
|
if (tm - articleDownloader->GetLastUpdateTime() > g_Options->GetArticleTimeout() + 1 &&
|
|
articleDownloader->GetStatus() == ArticleDownloader::adRunning)
|
|
{
|
|
error("Cancelling hanging download %s @ %s", articleDownloader->GetInfoName(),
|
|
articleDownloader->GetConnectionName());
|
|
articleDownloader->Stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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* downloadQueue, FileInfo* fileInfo)
|
|
{
|
|
fileInfo->SetDeleted(true);
|
|
bool downloading = false;
|
|
for (ArticleDownloader* articleDownloader : m_activeDownloads)
|
|
{
|
|
if (articleDownloader->GetFileInfo() == fileInfo)
|
|
{
|
|
downloading = true;
|
|
articleDownloader->Stop();
|
|
}
|
|
}
|
|
|
|
if (!downloading)
|
|
{
|
|
DeleteFileInfo(downloadQueue, fileInfo, false);
|
|
}
|
|
return downloading;
|
|
}
|
|
|
|
bool QueueCoordinator::SetQueueEntryCategory(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* category)
|
|
{
|
|
if (nzbInfo->GetPostInfo())
|
|
{
|
|
error("Could not change category for %s. File in post-process-stage", nzbInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
BString<1024> oldDestDir = nzbInfo->GetDestDir();
|
|
|
|
nzbInfo->SetCategory(category);
|
|
nzbInfo->BuildDestDirName();
|
|
|
|
bool dirUnchanged = !strcmp(nzbInfo->GetDestDir(), oldDestDir);
|
|
bool ok = dirUnchanged || ArticleWriter::MoveCompletedFiles(nzbInfo, oldDestDir);
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool QueueCoordinator::SetQueueEntryName(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* name)
|
|
{
|
|
if (nzbInfo->GetPostInfo())
|
|
{
|
|
error("Could not rename %s. File in post-process-stage", nzbInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
if (Util::EmptyStr(name))
|
|
{
|
|
error("Could not rename %s. The new name cannot be empty", nzbInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
nzbInfo->SetName(NzbInfo::MakeNiceNzbName(name, false));
|
|
|
|
if (nzbInfo->GetKind() == NzbInfo::nkUrl)
|
|
{
|
|
nzbInfo->SetFilename(BString<1024>("%s.nzb", nzbInfo->GetName()));
|
|
return true;
|
|
}
|
|
|
|
BString<1024> oldDestDir = nzbInfo->GetDestDir();
|
|
|
|
nzbInfo->BuildDestDirName();
|
|
|
|
bool dirUnchanged = !strcmp(nzbInfo->GetDestDir(), oldDestDir);
|
|
bool ok = dirUnchanged || ArticleWriter::MoveCompletedFiles(nzbInfo, oldDestDir);
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool QueueCoordinator::MergeQueueEntries(DownloadQueue* downloadQueue, NzbInfo* destNzbInfo, NzbInfo* srcNzbInfo)
|
|
{
|
|
if (destNzbInfo->GetPostInfo() || srcNzbInfo->GetPostInfo())
|
|
{
|
|
error("Could not merge %s and %s. File in post-process-stage", destNzbInfo->GetName(), srcNzbInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
if (destNzbInfo->GetKind() == NzbInfo::nkUrl || srcNzbInfo->GetKind() == NzbInfo::nkUrl)
|
|
{
|
|
error("Could not merge %s and %s. URLs cannot be merged", destNzbInfo->GetName(), srcNzbInfo->GetName());
|
|
return false;
|
|
}
|
|
|
|
// set new dest directory, new category and move downloaded files to new dest directory
|
|
srcNzbInfo->SetFilename(srcNzbInfo->GetFilename());
|
|
SetQueueEntryCategory(downloadQueue, srcNzbInfo, destNzbInfo->GetCategory());
|
|
|
|
// reattach file items to new NZBInfo-object
|
|
for (std::unique_ptr<FileInfo>& fileInfo : *srcNzbInfo->GetFileList())
|
|
{
|
|
fileInfo->SetNzbInfo(destNzbInfo);
|
|
destNzbInfo->GetFileList()->Add(std::move(fileInfo));
|
|
}
|
|
srcNzbInfo->GetFileList()->clear();
|
|
|
|
destNzbInfo->SetFileCount(destNzbInfo->GetFileCount() + srcNzbInfo->GetFileCount());
|
|
destNzbInfo->SetActiveDownloads(destNzbInfo->GetActiveDownloads() + srcNzbInfo->GetActiveDownloads());
|
|
destNzbInfo->SetFullContentHash(0);
|
|
destNzbInfo->SetFilteredContentHash(0);
|
|
|
|
destNzbInfo->SetSize(destNzbInfo->GetSize() + srcNzbInfo->GetSize());
|
|
destNzbInfo->SetRemainingSize(destNzbInfo->GetRemainingSize() + srcNzbInfo->GetRemainingSize());
|
|
destNzbInfo->SetPausedFileCount(destNzbInfo->GetPausedFileCount() + srcNzbInfo->GetPausedFileCount());
|
|
destNzbInfo->SetPausedSize(destNzbInfo->GetPausedSize() + srcNzbInfo->GetPausedSize());
|
|
|
|
destNzbInfo->SetSuccessSize(destNzbInfo->GetSuccessSize() + srcNzbInfo->GetSuccessSize());
|
|
destNzbInfo->SetCurrentSuccessSize(destNzbInfo->GetCurrentSuccessSize() + srcNzbInfo->GetCurrentSuccessSize());
|
|
destNzbInfo->SetFailedSize(destNzbInfo->GetFailedSize() + srcNzbInfo->GetFailedSize());
|
|
destNzbInfo->SetCurrentFailedSize(destNzbInfo->GetCurrentFailedSize() + srcNzbInfo->GetCurrentFailedSize());
|
|
|
|
destNzbInfo->SetParSize(destNzbInfo->GetParSize() + srcNzbInfo->GetParSize());
|
|
destNzbInfo->SetParSuccessSize(destNzbInfo->GetParSuccessSize() + srcNzbInfo->GetParSuccessSize());
|
|
destNzbInfo->SetParCurrentSuccessSize(destNzbInfo->GetParCurrentSuccessSize() + srcNzbInfo->GetParCurrentSuccessSize());
|
|
destNzbInfo->SetParFailedSize(destNzbInfo->GetParFailedSize() + srcNzbInfo->GetParFailedSize());
|
|
destNzbInfo->SetParCurrentFailedSize(destNzbInfo->GetParCurrentFailedSize() + srcNzbInfo->GetParCurrentFailedSize());
|
|
destNzbInfo->SetRemainingParCount(destNzbInfo->GetRemainingParCount() + srcNzbInfo->GetRemainingParCount());
|
|
|
|
destNzbInfo->SetTotalArticles(destNzbInfo->GetTotalArticles() + srcNzbInfo->GetTotalArticles());
|
|
destNzbInfo->SetSuccessArticles(destNzbInfo->GetSuccessArticles() + srcNzbInfo->GetSuccessArticles());
|
|
destNzbInfo->SetFailedArticles(destNzbInfo->GetFailedArticles() + srcNzbInfo->GetFailedArticles());
|
|
destNzbInfo->SetCurrentSuccessArticles(destNzbInfo->GetCurrentSuccessArticles() + srcNzbInfo->GetCurrentSuccessArticles());
|
|
destNzbInfo->SetCurrentFailedArticles(destNzbInfo->GetCurrentFailedArticles() + srcNzbInfo->GetCurrentFailedArticles());
|
|
destNzbInfo->GetServerStats()->ListOp(srcNzbInfo->GetServerStats(), ServerStatList::soAdd);
|
|
destNzbInfo->GetCurrentServerStats()->ListOp(srcNzbInfo->GetCurrentServerStats(), ServerStatList::soAdd);
|
|
|
|
destNzbInfo->SetMinTime(srcNzbInfo->GetMinTime() < destNzbInfo->GetMinTime() ? srcNzbInfo->GetMinTime() : destNzbInfo->GetMinTime());
|
|
destNzbInfo->SetMaxTime(srcNzbInfo->GetMaxTime() > destNzbInfo->GetMaxTime() ? srcNzbInfo->GetMaxTime() : destNzbInfo->GetMaxTime());
|
|
|
|
destNzbInfo->SetDownloadedSize(destNzbInfo->GetDownloadedSize() + srcNzbInfo->GetDownloadedSize());
|
|
destNzbInfo->SetDownloadSec(destNzbInfo->GetDownloadSec() + srcNzbInfo->GetDownloadSec());
|
|
destNzbInfo->SetDownloadStartTime((destNzbInfo->GetDownloadStartTime() > 0 &&
|
|
destNzbInfo->GetDownloadStartTime() < srcNzbInfo->GetDownloadStartTime()) || srcNzbInfo->GetDownloadStartTime() == 0 ?
|
|
destNzbInfo->GetDownloadStartTime() : srcNzbInfo->GetDownloadStartTime());
|
|
|
|
// reattach completed file items to new NZBInfo-object
|
|
for (CompletedFile& completedFile : srcNzbInfo->GetCompletedFiles())
|
|
{
|
|
destNzbInfo->GetCompletedFiles()->push_back(std::move(completedFile));
|
|
}
|
|
srcNzbInfo->GetCompletedFiles()->clear();
|
|
|
|
// concatenate QueuedFilenames using character '|' as separator
|
|
CString queuedFilename;
|
|
queuedFilename.Format("%s|%s", destNzbInfo->GetQueuedFilename(), srcNzbInfo->GetQueuedFilename());
|
|
destNzbInfo->SetQueuedFilename(queuedFilename);
|
|
|
|
g_DiskState->DiscardFiles(srcNzbInfo);
|
|
downloadQueue->GetQueue()->Remove(srcNzbInfo);
|
|
|
|
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* downloadQueue, RawFileList* fileList, const char* name, NzbInfo** newNzbInfo)
|
|
{
|
|
if (fileList->empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
NzbInfo* srcNzbInfo = nullptr;
|
|
|
|
for (FileInfo* fileInfo : fileList)
|
|
{
|
|
if (fileInfo->GetActiveDownloads() > 0 || fileInfo->GetCompletedArticles() > 0)
|
|
{
|
|
error("Could not split %s. File is already (partially) downloaded", fileInfo->GetFilename());
|
|
return false;
|
|
}
|
|
if (fileInfo->GetNzbInfo()->GetPostInfo())
|
|
{
|
|
error("Could not split %s. File in post-process-stage", fileInfo->GetFilename());
|
|
return false;
|
|
}
|
|
if (!srcNzbInfo)
|
|
{
|
|
srcNzbInfo = fileInfo->GetNzbInfo();
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<NzbInfo> nzbInfo = std::make_unique<NzbInfo>();
|
|
|
|
nzbInfo->SetFilename(srcNzbInfo->GetFilename());
|
|
nzbInfo->SetName(name);
|
|
nzbInfo->SetCategory(srcNzbInfo->GetCategory());
|
|
nzbInfo->SetFullContentHash(0);
|
|
nzbInfo->SetFilteredContentHash(0);
|
|
nzbInfo->SetPriority(srcNzbInfo->GetPriority());
|
|
nzbInfo->BuildDestDirName();
|
|
nzbInfo->SetQueuedFilename(srcNzbInfo->GetQueuedFilename());
|
|
nzbInfo->GetParameters()->CopyFrom(srcNzbInfo->GetParameters());
|
|
|
|
srcNzbInfo->SetFullContentHash(0);
|
|
srcNzbInfo->SetFilteredContentHash(0);
|
|
|
|
for (FileInfo* fileInfo : fileList)
|
|
{
|
|
DownloadQueue::Aspect aspect = { DownloadQueue::eaFileDeleted, downloadQueue, fileInfo->GetNzbInfo(), fileInfo };
|
|
downloadQueue->Notify(&aspect);
|
|
|
|
nzbInfo->GetFileList()->Add(srcNzbInfo->GetFileList()->Remove(fileInfo));
|
|
fileInfo->SetNzbInfo(nzbInfo.get());
|
|
|
|
srcNzbInfo->SetFileCount(srcNzbInfo->GetFileCount() - 1);
|
|
srcNzbInfo->SetSize(srcNzbInfo->GetSize() - fileInfo->GetSize());
|
|
srcNzbInfo->SetRemainingSize(srcNzbInfo->GetRemainingSize() - fileInfo->GetRemainingSize());
|
|
srcNzbInfo->SetCurrentSuccessSize(srcNzbInfo->GetCurrentSuccessSize() - fileInfo->GetSuccessSize());
|
|
srcNzbInfo->SetCurrentFailedSize(srcNzbInfo->GetCurrentFailedSize() - fileInfo->GetFailedSize() - fileInfo->GetMissedSize());
|
|
srcNzbInfo->SetTotalArticles(srcNzbInfo->GetTotalArticles() - fileInfo->GetTotalArticles());
|
|
srcNzbInfo->SetFailedArticles(srcNzbInfo->GetFailedArticles() - fileInfo->GetMissedArticles());
|
|
srcNzbInfo->SetCurrentSuccessArticles(srcNzbInfo->GetCurrentSuccessArticles() - fileInfo->GetSuccessArticles());
|
|
srcNzbInfo->SetCurrentFailedArticles(srcNzbInfo->GetCurrentFailedArticles() - fileInfo->GetFailedArticles() - fileInfo->GetMissedArticles());
|
|
srcNzbInfo->GetCurrentServerStats()->ListOp(fileInfo->GetServerStats(), ServerStatList::soSubtract);
|
|
|
|
nzbInfo->SetFileCount(nzbInfo->GetFileCount() + 1);
|
|
nzbInfo->SetSize(nzbInfo->GetSize() + fileInfo->GetSize());
|
|
nzbInfo->SetRemainingSize(nzbInfo->GetRemainingSize() + fileInfo->GetRemainingSize());
|
|
nzbInfo->SetCurrentSuccessSize(nzbInfo->GetCurrentSuccessSize() + fileInfo->GetSuccessSize());
|
|
nzbInfo->SetCurrentFailedSize(nzbInfo->GetCurrentFailedSize() + fileInfo->GetFailedSize() + fileInfo->GetMissedSize());
|
|
nzbInfo->SetTotalArticles(nzbInfo->GetTotalArticles() + fileInfo->GetTotalArticles());
|
|
nzbInfo->SetFailedArticles(nzbInfo->GetFailedArticles() + fileInfo->GetMissedArticles());
|
|
nzbInfo->SetCurrentSuccessArticles(nzbInfo->GetCurrentSuccessArticles() + fileInfo->GetSuccessArticles());
|
|
nzbInfo->SetCurrentFailedArticles(nzbInfo->GetCurrentFailedArticles() + fileInfo->GetFailedArticles() + fileInfo->GetMissedArticles());
|
|
nzbInfo->GetCurrentServerStats()->ListOp(fileInfo->GetServerStats(), ServerStatList::soAdd);
|
|
|
|
if (fileInfo->GetParFile())
|
|
{
|
|
srcNzbInfo->SetParSize(srcNzbInfo->GetParSize() - fileInfo->GetSize());
|
|
srcNzbInfo->SetParCurrentSuccessSize(srcNzbInfo->GetParCurrentSuccessSize() - fileInfo->GetSuccessSize());
|
|
srcNzbInfo->SetParCurrentFailedSize(srcNzbInfo->GetParCurrentFailedSize() - fileInfo->GetFailedSize() - fileInfo->GetMissedSize());
|
|
srcNzbInfo->SetRemainingParCount(srcNzbInfo->GetRemainingParCount() - 1);
|
|
|
|
nzbInfo->SetParSize(nzbInfo->GetParSize() + fileInfo->GetSize());
|
|
nzbInfo->SetParCurrentSuccessSize(nzbInfo->GetParCurrentSuccessSize() + fileInfo->GetSuccessSize());
|
|
nzbInfo->SetParCurrentFailedSize(nzbInfo->GetParCurrentFailedSize() + fileInfo->GetFailedSize() + fileInfo->GetMissedSize());
|
|
nzbInfo->SetRemainingParCount(nzbInfo->GetRemainingParCount() + 1);
|
|
}
|
|
|
|
if (fileInfo->GetPaused())
|
|
{
|
|
srcNzbInfo->SetPausedFileCount(srcNzbInfo->GetPausedFileCount() - 1);
|
|
srcNzbInfo->SetPausedSize(srcNzbInfo->GetPausedSize() - fileInfo->GetRemainingSize());
|
|
|
|
nzbInfo->SetPausedFileCount(srcNzbInfo->GetPausedFileCount() + 1);
|
|
nzbInfo->SetPausedSize(nzbInfo->GetPausedSize() + fileInfo->GetRemainingSize());
|
|
}
|
|
}
|
|
|
|
nzbInfo->UpdateMinMaxTime();
|
|
if (srcNzbInfo->GetCompletedFiles()->empty())
|
|
{
|
|
srcNzbInfo->UpdateMinMaxTime();
|
|
}
|
|
|
|
if (srcNzbInfo->GetFileList()->empty())
|
|
{
|
|
g_DiskState->DiscardFiles(srcNzbInfo);
|
|
downloadQueue->GetQueue()->Remove(srcNzbInfo);
|
|
}
|
|
|
|
*newNzbInfo = nzbInfo.get();
|
|
downloadQueue->GetQueue()->Add(std::move(nzbInfo));
|
|
|
|
return true;
|
|
}
|
|
|
|
void QueueCoordinator::DirectRenameCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
|
|
{
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
if (g_Options->GetServerMode() && !fileInfo->GetArticles()->empty())
|
|
{
|
|
// save new file name into disk state file
|
|
g_DiskState->SaveFile(fileInfo);
|
|
}
|
|
}
|
|
|
|
DiscardDirectRename(downloadQueue, nzbInfo);
|
|
|
|
nzbInfo->SetDirectRenameStatus(NzbInfo::tsSuccess);
|
|
|
|
if (g_Options->GetParCheck() != Options::pcForce)
|
|
{
|
|
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupResume, nullptr);
|
|
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupPauseAllPars, nullptr);
|
|
}
|
|
|
|
if (g_Options->GetReorderFiles())
|
|
{
|
|
nzbInfo->PrintMessage(Message::mkInfo, "Reordering files for %s", nzbInfo->GetName());
|
|
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupSortFiles, nullptr);
|
|
}
|
|
|
|
nzbInfo->SetChanged(true);
|
|
downloadQueue->SaveChanged();
|
|
|
|
DownloadQueue::Aspect namedAspect = { DownloadQueue::eaNzbNamed, downloadQueue, nzbInfo, nullptr };
|
|
downloadQueue->Notify(&namedAspect);
|
|
}
|
|
|
|
void QueueCoordinator::DiscardDirectRename(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
|
|
{
|
|
int64 discardedSize = 0;
|
|
int discardedCount = 0;
|
|
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
if (fileInfo->GetParFile() && fileInfo->GetCompletedArticles() == 1 &&
|
|
fileInfo->GetActiveDownloads() == 0)
|
|
{
|
|
bool locked = false;
|
|
{
|
|
Guard contentGuard = g_ArticleCache->GuardContent();
|
|
locked = fileInfo->GetFlushLocked();
|
|
if (!locked)
|
|
{
|
|
fileInfo->SetFlushLocked(true);
|
|
}
|
|
}
|
|
|
|
if (!locked)
|
|
{
|
|
// discard downloaded articles from partially downloaded par-files
|
|
discardedSize += fileInfo->GetSuccessSize();
|
|
discardedCount++;
|
|
|
|
DiscardDownloadedArticles(nzbInfo, fileInfo);
|
|
}
|
|
|
|
if (!locked)
|
|
{
|
|
Guard contentGuard = g_ArticleCache->GuardContent();
|
|
fileInfo->SetFlushLocked(false);
|
|
}
|
|
}
|
|
|
|
if (g_Options->GetServerMode() &&
|
|
!fileInfo->GetArticles()->empty() && g_Options->GetContinuePartial() &&
|
|
fileInfo->GetActiveDownloads() == 0 && fileInfo->GetCachedArticles() == 0)
|
|
{
|
|
// discard article infos to free up memory if possible
|
|
debug("Discarding article infos for %s/%s", nzbInfo->GetName(), fileInfo->GetFilename());
|
|
fileInfo->SetPartialChanged(true);
|
|
SavePartialState(fileInfo);
|
|
fileInfo->GetArticles()->clear();
|
|
}
|
|
}
|
|
|
|
if (discardedSize > 0)
|
|
{
|
|
nzbInfo->PrintMessage(Message::mkDetail, "Discarded %s from %i files used for direct renaming",
|
|
*Util::FormatSize(discardedSize), discardedCount);
|
|
}
|
|
}
|
|
|
|
void QueueCoordinator::DiscardDownloadedArticles(NzbInfo* nzbInfo, FileInfo* fileInfo)
|
|
{
|
|
nzbInfo->SetRemainingSize(nzbInfo->GetRemainingSize() + fileInfo->GetSuccessSize() + fileInfo->GetFailedSize());
|
|
if (fileInfo->GetPaused())
|
|
{
|
|
nzbInfo->SetPausedSize(nzbInfo->GetPausedSize() + fileInfo->GetSuccessSize() + fileInfo->GetFailedSize());
|
|
}
|
|
nzbInfo->GetCurrentServerStats()->ListOp(fileInfo->GetServerStats(), ServerStatList::soSubtract);
|
|
fileInfo->GetServerStats()->clear();
|
|
|
|
nzbInfo->SetCurrentSuccessArticles(nzbInfo->GetCurrentSuccessArticles() - fileInfo->GetSuccessArticles());
|
|
nzbInfo->SetCurrentSuccessSize(nzbInfo->GetCurrentSuccessSize() - fileInfo->GetSuccessSize());
|
|
nzbInfo->SetParCurrentSuccessSize(nzbInfo->GetParCurrentSuccessSize() - fileInfo->GetSuccessSize());
|
|
fileInfo->SetSuccessSize(0);
|
|
fileInfo->SetSuccessArticles(0);
|
|
|
|
nzbInfo->SetCurrentFailedArticles(nzbInfo->GetCurrentFailedArticles() - fileInfo->GetFailedArticles());
|
|
nzbInfo->SetCurrentFailedSize(nzbInfo->GetCurrentFailedSize() - fileInfo->GetFailedSize());
|
|
nzbInfo->SetParCurrentFailedSize(nzbInfo->GetParCurrentFailedSize() - fileInfo->GetFailedSize());
|
|
fileInfo->SetFailedSize(0);
|
|
fileInfo->SetFailedArticles(0);
|
|
|
|
fileInfo->SetCompletedArticles(0);
|
|
fileInfo->SetRemainingSize(fileInfo->GetSize() - fileInfo->GetMissedSize());
|
|
|
|
// discard temporary files
|
|
DiscardTempFiles(fileInfo);
|
|
g_DiskState->DiscardFile(fileInfo->GetId(), false, true, false);
|
|
|
|
fileInfo->SetOutputFilename(nullptr);
|
|
fileInfo->SetOutputInitialized(false);
|
|
fileInfo->SetCachedArticles(0);
|
|
fileInfo->SetPartialChanged(false);
|
|
fileInfo->SetPartialState(FileInfo::psNone);
|
|
|
|
if (g_Options->GetServerMode())
|
|
{
|
|
// free up memory used by articles if possible
|
|
fileInfo->GetArticles()->clear();
|
|
}
|
|
else
|
|
{
|
|
// reset article states if discarding isn't possible
|
|
for (ArticleInfo* articleInfo : fileInfo->GetArticles())
|
|
{
|
|
articleInfo->SetStatus(ArticleInfo::aiUndefined);
|
|
articleInfo->SetResultFilename(nullptr);
|
|
articleInfo->DiscardSegment();
|
|
}
|
|
}
|
|
}
|