diff --git a/Options.cpp b/Options.cpp index 3f62efd2..1896fc62 100644 --- a/Options.cpp +++ b/Options.cpp @@ -1,5 +1,5 @@ /* - * This file if part of nzbget + * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrei Prygounkov @@ -132,6 +132,7 @@ static const char* OPTION_DIRECTWRITE = "DirectWrite"; static const char* OPTION_WRITEBUFFERSIZE = "WriteBufferSize"; static const char* OPTION_NZBDIRINTERVAL = "NzbDirInterval"; static const char* OPTION_NZBDIRFILEAGE = "NzbDirFileAge"; +static const char* OPTION_PARCLEANUPQUEUE = "ParCleanupQueue"; #ifndef WIN32 const char* PossibleConfigLocations[] = @@ -212,6 +213,7 @@ Options::Options(int argc, char* argv[]) m_iWriteBufferSize = 0; m_iNzbDirInterval = 0; m_iNzbDirFileAge = 0; + m_bParCleanupQueue = false; char szFilename[MAX_PATH + 1]; #ifdef WIN32 @@ -403,6 +405,7 @@ void Options::InitDefault() SetOption(OPTION_WRITEBUFFERSIZE, "0"); SetOption(OPTION_NZBDIRINTERVAL, "5"); SetOption(OPTION_NZBDIRFILEAGE, "60"); + SetOption(OPTION_PARCLEANUPQUEUE, "no"); } void Options::InitOptFile() @@ -543,6 +546,7 @@ void Options::InitOptions() m_bCrcCheck = (bool)ParseOptionValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues); m_bRetryOnCrcError = (bool)ParseOptionValue(OPTION_RETRYONCRCERROR, BoolCount, BoolNames, BoolValues); m_bDirectWrite = (bool)ParseOptionValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues); + m_bParCleanupQueue = (bool)ParseOptionValue(OPTION_PARCLEANUPQUEUE, BoolCount, BoolNames, BoolValues); const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" }; const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses }; diff --git a/Options.h b/Options.h index ffa6028f..4a878728 100644 --- a/Options.h +++ b/Options.h @@ -1,5 +1,5 @@ /* - * This file if part of nzbget + * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrei Prygounkov @@ -133,6 +133,7 @@ private: int m_iWriteBufferSize; int m_iNzbDirInterval; int m_iNzbDirFileAge; + bool m_bParCleanupQueue; // Parsed command-line parameters bool m_bServerMode; @@ -224,6 +225,7 @@ public: int GetWriteBufferSize() { return m_iWriteBufferSize; } int GetNzbDirInterval() { return m_iNzbDirInterval; } int GetNzbDirFileAge() { return m_iNzbDirFileAge; } + bool GetParCleanupQueue() { return m_bParCleanupQueue; } // Parsed command-line parameters bool GetServerMode() { return m_bServerMode; } diff --git a/PrePostProcessor.cpp b/PrePostProcessor.cpp index 019939e3..6e5169de 100644 --- a/PrePostProcessor.cpp +++ b/PrePostProcessor.cpp @@ -89,6 +89,7 @@ PrePostProcessor::PrePostProcessor() g_pQueueCoordinator->Attach(&m_QueueCoordinatorObserver); m_ParQueue.clear(); + m_FailedParJobs.clear(); #ifndef DISABLE_PARCHECK m_ParCheckerObserver.owner = this; @@ -104,6 +105,11 @@ PrePostProcessor::~PrePostProcessor() { delete *it; } + + for (FileList::iterator it = m_FailedParJobs.begin(); it != m_FailedParJobs.end(); it++) + { + free(*it); + } } void PrePostProcessor::Run() @@ -484,7 +490,16 @@ void PrePostProcessor::ParCheckerUpdate(Subject * Caller, void * Aspect) fclose(file); } } - + + bool bCollectionCompleted = IsCollectionCompleted(m_ParChecker.GetNZBFilename()); + bool bHasFailedParJobs = HasFailedParJobs(m_ParChecker.GetNZBFilename()); + + if (g_pOptions->GetParCleanupQueue() && m_ParChecker.GetStatus() == ParChecker::psFinished && + bCollectionCompleted && !bHasFailedParJobs) + { + ParCleanupQueue(m_ParChecker.GetNZBFilename()); + } + int iParStatus = 0; if (m_ParChecker.GetStatus() == ParChecker::psFailed) { @@ -502,16 +517,142 @@ void PrePostProcessor::ParCheckerUpdate(Subject * Caller, void * Aspect) ExecPostScript(szPath, m_ParChecker.GetNZBFilename(), m_ParChecker.GetParFilename(), iParStatus); m_mutexParChecker.Lock(); + ParJob* pParJob = m_ParQueue.front(); m_ParQueue.pop_front(); delete pParJob; m_bHasMoreJobs = !m_ParQueue.empty(); + + if (m_ParChecker.GetStatus() == ParChecker::psFailed && !bCollectionCompleted) + { + m_FailedParJobs.push_back(strdup(m_ParChecker.GetNZBFilename())); + } + if (bCollectionCompleted) + { + ClearFailedParJobs(m_ParChecker.GetNZBFilename()); + } + m_mutexParChecker.Unlock(); } } +/** + * Delete unneeded (paused) par-files from download queue after successful par-check. + * If the collection has paused non-par-files, none files will be deleted (even pars). + */ +void PrePostProcessor::ParCleanupQueue(const char* szNZBFilename) +{ + // check if nzb-file has only pars paused + int ID = 0; + bool bOnlyPars = true; + DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); + for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) + { + FileInfo* pFileInfo = *it; + if (!strcmp(pFileInfo->GetNZBInfo()->GetFilename(), szNZBFilename)) + { + ID = pFileInfo->GetID(); + if (!pFileInfo->GetPaused() && !pFileInfo->GetDeleted() && + !ParChecker::ParseParFilename(pFileInfo->GetFilename(), NULL, NULL)) + { + bOnlyPars = false; + break; + } + } + } + g_pQueueCoordinator->UnlockQueue(); + + if (bOnlyPars && ID > 0) + { + char szNZBNiceName[1024]; + NZBInfo::MakeNiceNZBName(szNZBFilename, szNZBNiceName, sizeof(szNZBNiceName)); + info("Cleaning up download queue for %s", szNZBNiceName); + g_pQueueCoordinator->GetQueueEditor()->EditEntry(ID, false, QueueEditor::eaGroupDelete, 0); + } +} + +/** + * Check if nzb-file has failures from other par-jobs + * (if nzb-file has more than one collections) + */ +bool PrePostProcessor::HasFailedParJobs(const char* szNZBFilename) +{ + bool bHasFailedJobs = false; + + m_mutexParChecker.Lock(); + for (FileList::iterator it = m_FailedParJobs.begin(); it != m_FailedParJobs.end(); it++) + { + char* szNZBFilename = *it; + if (!strcmp(szNZBFilename, m_ParChecker.GetNZBFilename())) + { + bHasFailedJobs = true; + break; + } + } + m_mutexParChecker.Unlock(); + + return bHasFailedJobs; +} + +/** + * Delete info about failed par-jobs for nzb-collection after the collection is completely downloaded. + * Mutex "m_mutexParChecker" must be locked prior to call of this funtion. + */ +void PrePostProcessor::ClearFailedParJobs(const char* szNZBFilename) +{ + for (FileList::iterator it = m_FailedParJobs.begin(); it != m_FailedParJobs.end();) + { + char* szFailedNZBFilename = *it; + if (!strcmp(szNZBFilename, szFailedNZBFilename)) + { + m_FailedParJobs.erase(it); + free(szFailedNZBFilename); + it = m_FailedParJobs.begin(); + continue; + } + it++; + } +} + #endif +bool PrePostProcessor::IsCollectionCompleted(const char* szNZBFilename) +{ + bool bCollectionCompleted = true; + + DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); + for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) + { + FileInfo* pFileInfo = *it; + if (!pFileInfo->GetPaused() && + !strcmp(pFileInfo->GetNZBInfo()->GetFilename(), szNZBFilename)) + { + bCollectionCompleted = false; + break; + } + } + g_pQueueCoordinator->UnlockQueue(); + +#ifndef DISABLE_PARCHECK + if (bCollectionCompleted) + { + m_mutexParChecker.Lock(); + for (ParQueue::iterator it = m_ParQueue.begin() + 1; it != m_ParQueue.end(); it++) + { + ParJob* pParJob = *it; + if (!strcmp(pParJob->GetNZBFilename(), szNZBFilename)) + { + bCollectionCompleted = false; + break; + } + } + m_mutexParChecker.Unlock(); + } +#endif + + return bCollectionCompleted; +} + void PrePostProcessor::ExecPostScript(const char * szPath, const char * szNZBFilename, const char * szParFilename, int iParStatus) { const char* szScript = g_pOptions->GetPostProcess(); @@ -529,36 +670,7 @@ void PrePostProcessor::ExecPostScript(const char * szPath, const char * szNZBFil return; } - bool bCollectionCompleted = true; - DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); - for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++) - { - FileInfo* pFileInfo2 = *it; - if (!pFileInfo2->GetPaused() && - !strcmp(pFileInfo2->GetNZBInfo()->GetFilename(), szNZBFilename)) - { - bCollectionCompleted = false; - break; - } - } - g_pQueueCoordinator->UnlockQueue(); - -#ifndef DISABLE_PARCHECK - if (bCollectionCompleted) - { - m_mutexParChecker.Lock(); - for (ParQueue::iterator it = m_ParQueue.begin(); it != m_ParQueue.end(); it++) - { - ParJob* pParJob = *it; - if (!strcmp(pParJob->GetNZBFilename(), szNZBFilename)) - { - bCollectionCompleted = false; - break; - } - } - m_mutexParChecker.Unlock(); - } -#endif + bool bCollectionCompleted = IsCollectionCompleted(szNZBFilename); char szParStatus[10]; snprintf(szParStatus, 10, "%i", iParStatus); diff --git a/PrePostProcessor.h b/PrePostProcessor.h index ee53865b..09ca2e63 100644 --- a/PrePostProcessor.h +++ b/PrePostProcessor.h @@ -1,5 +1,5 @@ /* - * This file if part of nzbget + * This file is part of nzbget * * Copyright (C) 2007 Andrei Prygounkov * @@ -83,10 +83,11 @@ private: void CheckIncomingNZBs(); bool WasLastInCollection(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, bool bIgnorePaused); void ExecPostScript(const char* szPath, const char* szNZBFilename, const char * szParFilename, int iParStatus); - + bool IsCollectionCompleted(const char* szNZBFilename); Mutex m_mutexParChecker; ParQueue m_ParQueue; + FileList m_FailedParJobs; #ifndef DISABLE_PARCHECK ParChecker m_ParChecker; @@ -98,6 +99,9 @@ private: bool AddPar(FileInfo* pFileInfo, bool bDeleted); bool SameParCollection(const char* szFilename1, const char* szFilename2); bool FindMainPars(const char* szPath, FileList* pFileList); + void ParCleanupQueue(const char* szNZBFilename); + bool HasFailedParJobs(const char* szNZBFilename); + void ClearFailedParJobs(const char* szNZBFilename); #endif public: diff --git a/nzbget.conf.example b/nzbget.conf.example index 0c3b9411..4da23dd9 100644 --- a/nzbget.conf.example +++ b/nzbget.conf.example @@ -379,6 +379,14 @@ ParRepair=yes # and the option "strictparname" does not change this behavior StrictParName=yes +# Cleanup download queue after successful check/repair (yes, no) +# Enable this option for automatic deletion of unneeded (paused) par-files +# from download queue after successful check/repair. +# NOTE: before cleaning up the program checks if all paused files are par-files. +# If there are paused non-par-files (this means that you have paused them +# manually), the cleanup will be skipped for this collection. +ParCleanupQueue=yes + ############################################################################## ### POSTPROCESSING ###